これは、解析ライブラリを使用すると、コードが驚くほど短くなり、非常に表現力豊かになる状況です。(これに答えるために実験していたとき、それがとてもきちんとしていたことに驚きました!)
私はParsecを使用し (この記事には詳細情報へのリンクがいくつかあります)、それを (モナドではなく) 「アプリケーション モード」で使用します。
コード
まず、さまざまなインポートと定義:
import Text.Parsec
import Control.Applicative ((<*), (<$>))
data Tree = Branch Tree Tree | Leaf Char deriving (Eq, Show)
paren, tree, unit :: Parsec String st Tree
現在、ツリーの基本単位は、単一の文字 (かっこではない) またはかっこで囲まれたツリーのいずれかです。(
括弧で囲まれたツリーは、との間の通常のツリー)
です。通常のツリーは、左に関連付けられたブランチに配置されたユニットにすぎません (非常に自己再帰的です)。Haskell と Parsec の場合:
-- parenthesised tree or `Leaf <character>`
unit = paren <|> (Leaf <$> noneOf "()") <?> "group or literal"
-- normal tree between ( and )
paren = between (char '(') (char ')') tree
-- all the units connected up left-associatedly
tree = foldl1 Branch <$> many1 unit
-- attempt to parse the whole input (don't short-circuit on the first error)
onlyTree = tree <* eof
(はい、それがパーサー全体です!)
paren
必要に応じて、なくても構いませんunit
が、上記のコードは非常に表現力豊かなので、そのままにしておくことができます。
簡単な説明として(ドキュメントへのリンクを提供しました):
(<|>)
基本的に「左パーサーまたは右パーサー」を意味します。
(<?>)
より適切なエラー メッセージを作成できます。
noneOf
指定された文字リストにないものはすべて解析します。
between
3 つのパーサーを取り、1 番目と 2 番目のパーサーで区切られている限り、3 番目のパーサーの値を返します。
char
その引数を文字どおりに解析します。
many1
その引数の 1 つ以上を解析してリストに入れます (ゼロ以上を解析するmany1
のではなく、空の文字列は無効であるように見えます)。many
eof
入力の末尾に一致します。
関数を使用しparse
てパーサーを実行できます (これは を返しますEither ParseError Tree
。これLeft
はエラーでRight
あり、正しい解析です)。
としてread
これを like 関数として使用すると、次のread
ようになります。
read' str = case parse onlyTree "" str of
Right tr -> tr
Left er -> error (show er)
(以前はread'
との競合を避けてきましPrelude.read
た。Read
インスタンスが必要な場合は、実装するためにもう少し作業を行う必要がありますreadPrec
(または必要なものは何でも) が、実際の解析が既に完了しているため、それほど難しくはありません。)
例
いくつかの基本的な例:
*Tree> read' "A"
Leaf 'A'
*Tree> read' "AB"
Branch (Leaf 'A') (Leaf 'B')
*Tree> read' "ABC"
Branch (Branch (Leaf 'A') (Leaf 'B')) (Leaf 'C')
*Tree> read' "A(BC)"
Branch (Leaf 'A') (Branch (Leaf 'B') (Leaf 'C'))
*Tree> read' "ABC(DE)F" == example
True
*Tree> read' "ABC(DEF)" == example
False
*Tree> read' "ABCDEF" == example
False
エラーのデモンストレーション:
*Tree> read' ""
***Exception: (line 1, column 1):
unexpected end of input
expecting group or literal
*Tree> read' "A(B"
***Exception: (line 1, column 4):
unexpected end of input
expecting group or literal or ")"
tree
最後に、との違いonlyTree
:
*Tree> parse tree "" "AB)CD" -- success: ignores ")CD"
Right (Branch (Leaf 'A') (Leaf 'B'))
*Tree> parse onlyTree "" "AB)CD" -- fail: can't parse the ")"
Left (line 1, column 3):
unexpected ')'
expecting group or literal or end of input
結論
パーセクすごい!この答えは長くなるかもしれませんが、その核心はすべての作業を行うわずか 5 ~ 6 行のコードです。