Haskellの勉強がてらParsecを使ってみました。
コードを眺めただけだと、再帰的下向き構文解析を手で書いているのとあまり変わらないように見えますが、実際に書いてみると非常に簡単ですね。モナド使ってるっていうのが、気に入りませんが。構文解析のコードしか書いてないのに、字句解析器もやってくれます。
次の文法をS式に書き直すパーサを書いてみました。中置記法を前置記法に変換します。LLパーサなので、左再帰しないように気を使います。
Infix → Exp
Exp → Term "+" Exp | Term "-" Exp | Term
Term → Factor "*" Term | Factor "/" Term | Factor
Factor → ident | number | "(" Exp ")"
さらに、これを変換して、
Infix → Exp
Exp → Term Exp*
Exp* → "+" Term Exp* | "-" Term Exp* | ε
Term → Factor Term*
Term* → "*" Factor Term* | "/" Factor Term* | ε
Factor → ident | number | "(" Exp ")"
を構文解析させます。変換したのは、演算子の適用順序を正しくするためです。εは、空を表します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Parser where | |
import Text.ParserCombinators.Parsec | |
import qualified Text.ParserCombinators.Parsec.Token as TokenSymbols | |
import Text.ParserCombinators.Parsec.Language | |
lexer :: TokenSymbols.TokenParser () | |
lexer = TokenSymbols.makeTokenParser(emptyDef) | |
symbol = TokenSymbols.symbol lexer | |
number = TokenSymbols.natural lexer | |
ident = TokenSymbols.identifier lexer | |
generateS_exp :: String -> String -> String -> String | |
generateS_exp op left right = "(" ++ op ++ " " ++ left ++ " " ++ right ++ ")" | |
parseInfix = (do {exp <- psExp; return exp}) | |
psExp = (do {left<- psTerm; exp <- (psExpStar left); return exp}) | |
psExpStar left = (do {symbol "+"; | |
right <- psTerm; | |
append <- psExpStar (generateS_exp "+" left right); | |
return append}) | |
<|> (do {symbol "-"; | |
right <- psTerm; | |
append <- psExpStar (generateS_exp "-" left right); | |
return append}) | |
<|> return left | |
psTerm = (do {left<- psFactor; exp <- (psTermStar left); return exp}) | |
psTermStar left = (do {symbol "*"; | |
right <- psFactor; | |
append <- psTermStar (generateS_exp "*" left right); | |
return append}) | |
<|> (do {symbol "/"; | |
right <- psFactor; | |
append <- psTermStar (generateS_exp "/" left right); | |
return append}) | |
<|> return left | |
psFactor = (do {obj <- number; return (show obj)}) | |
<|> (do {obj <- ident; return obj}) | |
<|> (do {symbol "("; | |
obj <- psExp; | |
symbol ")"; | |
return obj}) | |
convertI2S s = parse parseInfix "i2s" s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Prelude> :load "parsecSample.hs" | |
[1 of 1] Compiling Parser ( parsecSample.hs, interpreted ) | |
Ok, modules loaded: Parser. | |
*Parser> convertI2S "1+2 * 3" | |
Right "(+ 1 (* 2 3))" | |
*Parser> | |
*Parser> convertI2S "1+2 * (3 + 4)" | |
Right "(+ 1 (* 2 (+ 3 4)))" | |
*Parser> convertI2S "1/2 * (3 - 4 - 5)" | |
Right "(* (/ 1 2) (- (- 3 4) 5))" | |
*Parser> convertI2S "1/2 * (3 - 4 - 5) @gmail.com" | |
Right "(* (/ 1 2) (- (- 3 4) 5))" | |
*Parser> convertI2S "http://1/2 * (3 - 4 - 5)" | |
Right "http" | |
*Parser> convertI2S "+http://1/2 * (3 - 4 - 5)" | |
Left "i2s" (line 1, column 1): | |
unexpected "+" | |
expecting natural, identifier or "(" | |
*Parser> |
Parsecは、ひたすら読めるまで読み続けて、パターンにマッチしない文字列が来たら、エラーを出さずに、マッチした部分の文字列の実行結果を返して、終了するようです。文字列を最後まで読み込み、エラーを出す為には、文法にEOF記号を混ぜる必要がありそうですね。Infix → Exp '$'、みたいな書き方にすればよかったってことでしょうか。
参考文献
- Parsec練習問題 : http://guppy.eng.kagawa-u.ac.jp/Seminar/Haskell/parsec.html
- tinyCの文法をParsecを使って書くというお話。大変勉強になりました。
- [Haskell]Parsecというパーサーライブラリで電卓を作る簡単な方法 : http://bugrammer.g.hatena.ne.jp/nisemono_san/20111117/1321555610
- parsec-3.0.1: Monadic parser combinators ,Text.Parsec : http://hackage.haskell.org/packages/archive/parsec/3.0.1/doc/html/Text-Parsec.html
- オフィシャルのドキュメント
0 件のコメント :
コメントを投稿