だから、最初のヒントは見てみることですIndentParser
type IndentParser s u a = ParsecT s u (State SourcePos) a
つまり、現在の列番号などにアクセスするために使用できる抽象的なコンテナである、ParsecT
非常に注意深い監視を続けています。したがって、おそらく現在の「インデントのレベル」をに格納しています。それが、「参照レベル」が何を意味するかについての私の最初の推測です。SourcePos
SourcePos
つまり、コンテキストに依存する新しい種類、特に現在のインデントに依存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
(これは、変更するためのバックドアを提供したことを除いて、ほとんど です。)runIndent
initialPos
これで、を見ることができますindented
。ソースを調べることで、2つのことを実行していることがわかります。まず、現在の列番号が、に格納されているに格納されている「参照レベル」以下であるfail
かどうかを確認します。次に、(列カウンターではなく)の行カウンターを不思議なことに最新のものに更新します。 SourcePos
SourcePos
State
State
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
を、「参照列」(の)がと呼ばれたときとまったく同じであると考えるものSourcePos
にState
変換しますwithPos
。
これは私たちに別のヒントを与えます、ところで。これにより、参照列を変更する権限があることがわかります。
(1)では、新しい低レベルの参照列演算子を使用して、どのようblock
に機能するかを見てみましょう。はの観点から実装されているため、から始めます。withBlock
withBlock
block
block
-- 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
block
p
f
withPos
次に、を取得したらwithBlock
、withBlock' = withBlock (\_ bs -> bs)
。
(5)したがって、indented
友人はまさにこれを行うためのツールです。によって選択された「参照位置」に対して誤ってインデントされた場合、解析はすぐに失敗しwithPos
ます。
Applicative
(4)はい、ベースでスタイル解析を使用する方法を学ぶまで、これらの人について心配する必要はありませんParsec
。多くの場合、解析を指定する方法は、はるかにクリーンで、高速で、簡単です。時にはそれらはさらに強力ですが、あなたがMonad
sを理解していれば、それらはほとんど常に完全に同等です。
(6)そしてこれが核心です。これまでに説明したツールは、を使用して目的のインデントを記述できる場合にのみ、インデントの失敗を実行できますwithPos
。withPos
すぐに、他の解析の成功または失敗に基づいて指定することは不可能だと思います...したがって、さらに深いレベルに進む必要があります。幸いなことに、sを機能させるメカニズムIndentParser
は明らかです。これは、を含む単なる内部State
モナドですSourcePos
。lift :: MonadTrans t => m a -> t m a
この内部状態を操作し、「参照列」を好きなように設定するために使用できます。
乾杯!