11

Parsec の一種のアドオンである Haskell 用Text.Parsec.Indentのパッケージが提供するモジュール内の関数の使用方法を理解するのに苦労しています。indents

これらすべての機能は何をしますか? それらはどのように使用されますか?

Haddock の簡単な説明は理解できます。withBlock使用方法の例と、ここここwithBlock、およびここで型を見つけました。4 つのパーサーとフレンドのドキュメントも理解できます。しかし、まだ多くのことが私を混乱させています。runIndentIndentParserindentBrackets

特に:

  1. withBlock f a pとはどう違いますか

    do aa <- a
       pp <- block p
       return f aa pp
    

    withBlock' a p同様に、との違いは何ですかdo {a; block p}

  2. indented関数と友達の家族で、「参照のレベル」とは何ですか? つまり、「参照」とは何ですか?

  3. 繰り返しになりますが、関数indentedとフレンドはどのように使用されますか? を除いてwithPos、それらは引数をとらず、すべてタイプIParser ()(IParser はthisまたはthisのように定義されている) であるように見えるので、エラーを生成するか生成しないかだけで、doブロックに表示する必要があると推測しています。とありますが、詳細がわかりません。

    少なくともソース コードwithPosで の使用例をいくつか見つけたので、十分に長い間それを見つめていれば、おそらくそれを理解できるでしょう。

  4. <+/>「<code><+/> はインデントに敏感なパーサーapにとって、モナドとは何か」という役立つ説明が付属しています。これは、いくつかのセッションを費やして頭を包み込み、apそれがパーサーにどのように類似しているかを理解したい場合に最適です。次に、他の3 つのコンビネータが を参照して定義され<+/>、グループ全体が初心者には近づきにくくなります。

    これらを使用する必要がありますか? それらを無視してdo代わりに使用できますか?

  5. Parsecの通常のlexemeコンビネーターおよびwhiteSpaceパーサーは、文句を言わずに複数トークン構成の途中で喜んで改行を消費します。しかし、インデント スタイルの言語では、字句構造の解析を停止したり、行が壊れていて次の行のインデントが本来よりも少ない場合にエラーをスローしたりしたい場合があります。Parsecでこれを行うにはどうすればよいですか?

  6. 私が解析しようとしている言語では、理想的には、語彙構造が次の行に続くことが許可されるタイミングのルールは、最初の行の終わりまたは次の行の先頭に表示されるトークンに依存する必要があります。Parsecでこれを達成する簡単な方法はありますか? (難しい場合は、現時点では気にする必要はありません。)

4

1 に答える 1

11

だから、最初のヒントは見てみることですIndentParser

type IndentParser s u a = ParsecT s u (State SourcePos) a

つまり、現在の番号などにアクセスするために使用できる抽象的なコンテナである、ParsecT非常に注意深い監視を続けています。したがって、おそらく現在の「インデントのレベル」をに格納しています。それが、「参照レベル」が何を意味するかについての私の最初の推測です。SourcePosSourcePos

つまり、コンテキストに依存する新しい種類、特に現在のインデントに依存indentsする新しい種類を提供します。Parsec私はあなたの質問に順不同で答えます。


(2)「参照レベル」は、このインデントレベルが開始する現在のパーサーコンテキスト状態で参照される「信念」です。より明確にするために、(3)のいくつかのテストケースを挙げましょう。

(3)これらの関数の実験を開始するために、小さなテストランナーを作成します。与えた文字列を使用してパーサーを実行し、変更した文字列をState使用して内部をアンラップしinitialPosます。コード内

import Text.Parsec
import Text.Parsec.Pos
import Text.Parsec.Indent
import Control.Monad.State

testParse :: (SourcePos -> SourcePos) 
          -> IndentParser String () a 
          -> String -> Either ParseError a
testParse f p src = fst $ flip runState (f $ initialPos "") $ runParserT p () "" src

(これは、変更するためのバックドアを提供したことを除いて、ほとんど です。)runIndentinitialPos

これで、を見ることができますindented。ソースを調べることで、2つのことを実行していることがわかります。まず、現在の列番号が、に格納されているに格納されている「参照レベル」以下であるfailかどうかを確認します。次に、(列カウンターではなく)の行カウンターを不思議なことに最新のものに更新します。 SourcePosSourcePos StateState SourcePos

私の理解では、最初の振る舞いだけが重要です。ここで違いがわかります。

>>> testParse id indented ""
Left (line 1, column 1): not indented

>>> testParse id (spaces >> indented) "   "
Right ()

>>> testParse id (many (char 'x') >> indented) "xxxx"
Right ()

したがって、indented成功するには、列の位置を「参照」列の位置を超えて押し出すのに十分な空白(または他の何か!)を消費する必要があります。そうしないと、「インデントされていません」と表示されません。次の3つの関数についても、同様の動作が存在します。same現在の位置と参照位置が同じ行にsameOrIndentedない場合は失敗し、現在の列が参照列よりも厳密に小さい場合は同じ行にない限り失敗し、現在の列checkIndentと参照位置が同じ行にない場合は失敗します。参照列が一致します。

withPos少し違います。これは単なる、IndentParserではなく、IndentParser-combinatorです。これは、入力IndentParserを、「参照列」(の)がと呼ばれたときとまったく同じであると考えるものSourcePosState変換しますwithPos

これは私たちに別のヒントを与えます、ところで。これにより、参照列を変更する権限があることがわかります。

(1)では、新しい低レベルの参照列演算子を使用して、どのようblockに機能するかを見てみましょう。はの観点から実装されているため、から始めます。withBlockwithBlockblockblock

-- simplified from the actual source
block p = withPos $ many1 (checkIndent >> p)

したがって、 「参照列」を現在の列にリセットし、それぞれがこの新しく設定された「参照列」と同じようにインデントされている限りblock、少なくとも1つの解析を消費します。p今、私たちは見ることができますwithBlock

withBlock f a p = withPos $ do
  r1 <- a
  r2 <- option [] (indented >> block p)
  return (f r1 r2)

したがって、「参照列」を現在の列にリセットし、単一の解析を解析し、 sのa解析を試みてから、を使用して結果を結合します。正しい「参照列」を選択するために使用する必要があることを除いて、実装はほぼ正しいです。indented blockpfwithPos

次に、を取得したらwithBlockwithBlock' = withBlock (\_ bs -> bs)

(5)したがって、indented友人はまさにこれを行うためのツールです。によって選択された「参照位置」に対して誤ってインデントされた場合、解析はすぐに失敗しwithPosます。

Applicative(4)はい、ベースでスタイル解析を使用する方法を学ぶまで、これらの人について心配する必要はありませんParsec。多くの場合、解析を指定する方法は、はるかにクリーンで、高速で、簡単です。時にはそれらはさらに強力ですが、あなたがMonadsを理解していれば、それらはほとんど常に完全に同等です。

(6)そしてこれが核心です。これまでに説明したツールは、を使用して目的のインデントを記述できる場合にのみ、インデントの失敗を実行できますwithPoswithPosすぐに、他の解析の成功または失敗に基づいて指定することは不可能だと思います...したがって、さらに深いレベルに進む必要があります。幸いなことに、sを機能させるメカニズムIndentParserは明らかです。これは、を含む単なる内部StateモナドですSourcePoslift :: MonadTrans t => m a -> t m aこの内部状態を操作し、「参照列」を好きなように設定するために使用できます。

乾杯!

于 2013-03-24T01:09:13.327 に答える