私。
ADT のパーサーは、ADT の形状を反映する傾向があります。ADT は 2 つのバラバラな部分で構成されているため、パーサーにも 2 つのバラバラな部分がある可能性があります。
atom = _ <|> _
Ⅱ.
1 桁を解析する方法を知っていると仮定すると (基本パーサーと呼びましょうdigit
)、(非負の) 整数を繰り返すだけで解析します。
natural = let loop = digit >> loop in loop
これにより、数字の無限ストリームが正常に解析され、破棄されます。もっとうまくやれるでしょうか?残念ながら、モナドインスタンスだけではなく、別の基本的なコンビネータ が必要ですmany
。これは、他のパーサーを変更して入力を 0 回以上消費し、結果をリストに蓄積します。空の解析は有効な数値ではないため、実際にはこれを少し調整します
many1 p = do x <- p
xs <- many p
return (x:xs)
natural' = many1 digit
III.
原子はどうですか?テスト ケースに合格するには、アトムが 1 対多の英数字またはバックスラッシュである必要があるようです。繰り返しになりますが、このバラバラな構造は、パーサーですぐに表現できます。
sym = many1 (_ <|> _)
ここでも組み込みの単純なパーサー コンビネータを使用して、必要なものを構築します。たとえば、satisfy :: (Char -> Bool) -> Parser Char
述語を満たす任意の文字に一致するものです。すぐに別の便利なコンビネータを構築でき、char c = satisfy (==c) :: Char -> Parser Char
それで完了です。
sym = many1 (char '/' <|> satisfy isAlpha)
whereisAlpha
は正規表現によく似た述語[a-zA-Z]
です。
IV.
これで、パーサーのコアができました
natural <|> sym :: Parser String
コンビネータはmany1
、文字パーサーを文字のリスト( String
s!) のパーサーに持ち上げます。このリフティング アクションは、ADT パーサーを構築するための基本的な考え方でもあります。Parser String
に持ち上げたいと思いParser Atom
ます。それを行う1つの方法は、関数を使用することtoAtom :: String -> Atom
ですfmap
。Parser
atom' :: Parser Atom
atom' = fmap toAtom (natural <|> sym)
しかし、型を持つ関数はString -> Atom
、そもそもパーサーを構築するという目的を無効にします。
I. で述べたように、重要な部分は、ADT の形状がatom
パーサーの形状に反映されることです。これを利用して、最終的なパーサーを構築する必要があります。
V.
atom
パーサーの構造で情報を利用する必要があります。代わりに 2 つの関数を作成しましょう
liftInt :: String -> Atom -- creates `AInt`s
liftSym :: String -> Atom -- creates `ASym`s
liftInt = AInt . read
liftSym = ASym
String
それぞれがs を s に変換する方法を述べているだけでなく、どのようなものを扱っているAtom
かを宣言しています。に解析できない文字列を渡すと、実行時エラーがスローされることに注意してください。幸いなことに、それはまさに私たちが知っていることです。Atom
liftInt
Int
atomInt :: Parser Atom
atomInt = liftInt <$> natural
atomSym :: Parser Sym
atomSym = liftSym <$> sym
atom'' = atomInt <|> atomSym
ここで、私たちのパーサーは、自然に有効な解析である文字列のみを返すatom''
という保証を利用します--- への呼び出しは失敗しません!--- そして、両方を順番に構築しようとします。私たちの ADT の構造のような構造。natural
read
AInt
ASym
Ⅵ.
したがって、シバン全体は
atom''' = AInt . read <$> many1 digit
<|> ASym <$> many1 ( char '/'
<|> satisfy isAlpha)
これは、パーサー コンビネータの面白さを示しています。全体は、小さくて構成可能なシンプルな部品を使用して、地面から構築されます。それぞれが非常に小さな仕事をしますが、全体としてパーサーの大きなスペースにまたがっています。
また、この文法は、ADT のブランチを増やしたり、より完全に指定されたシンボル タイプ パーサーを使用したり、失敗の装飾を<?>
使用して簡単に拡張したりできます。これにより、失敗した解析で優れたエラー メッセージが表示されます。