これは、解析ライブラリを使用すると、コードが驚くほど短くなり、非常に表現力豊かになる状況です。(これに答えるために実験していたとき、それがとてもきちんとしていたことに驚きました!)
私は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指定された文字リストにないものはすべて解析します。
between3 つのパーサーを取り、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 行のコードです。