Parsecにちょっと触ってみる(4):式

import Text.ParserCombinators.Parsec hiding (spaces)
import Text.ParserCombinators.Parsec.Expr

run :: Show a => Parser a -> String -> IO ()
run p input = case (parse p "" input) of
            Left err -> putStr "parse error at " >> print err
            Right x  -> print x

expr    :: Parser Integer
expr    = buildExpressionParser table factor
        <?> "expression"

table   = [[op "*" (*) AssocLeft, op "/" div AssocLeft]
          ,[op "+" (+) AssocLeft, op "-" (-) AssocLeft]
          ]
        where
          op s f assoc
             = Infix (do{ string s; return f}) assoc

factor  = do{ char '('
            ; x <- expr
            ; char ')'
            ; return x
            }
        <|> number
        <?> "simple expression"

number  :: Parser Integer
number  = do{ ds <- many1 digit
            ; return (read ds)
            }
        <?> "number"
 *Main> run expr "1+2*3"         --=> 7
 *Main> run expr "(1+2)*3"       --=> 9
 *Main> run expr "8/4/2"         --=> 1
 *Main> run expr "8/(4/2)"       --=> 4
 *Main> run expr "1 + 2"         --=> 1
 *Main> run expr "1+ 2"
parse error at (line 1, column 3):
unexpected " "
expecting simple expression
 *Main> run expr " 1+2"
parse error at (line 1, column 1):
unexpected " "
expecting expression

テーブルの順序を変更すると計算の優先順位も変る。

table   = [[op "+" (+) AssocLeft, op "-" (-) AssocLeft]
          ,[op "*" (*) AssocLeft, op "/" div AssocLeft]]
 *Main> run expr "4+2*3"   --=> 18
 *Main> run expr "4+2/2"   --=> 3

スペースを読みとばす。

spaces :: Parser ()
spaces = skipMany space

number  :: Parser Integer
number  = do{ spaces
            ; ds <- many1 digit
            ; spaces
            ; return (read ds)
            }
        <?> "number"
 *Main> run expr "1 + 2"            --=> 3
 *Main> run expr "1 + 2 "           --=> 3
buildExpressionParser 関数は二つの引数を取る。一つは演算の規則を定義した
テーブルのリスト。もう一つは factor(要素) を定義したパーサ関数だ。演算の左結合、
右結合はテーブルに定義し、演算の優先度はテーブルのリスト内での前後関係で定義する。