以下のプログラムは Tiny C のパーサをParsec ライブラリを使って書きかけたものである。
Tiny Cの構文を適切な代数的データ型として定義し、Tiny Cのパーサーを完成させよ。 (Tiny CのBNFはこのページの最後に示す。)
tinyCStyleとlexerは、 C言語のトークンの定義(実際にはJava言語のものを流用)に合わせて、後述の whiteSpace〜reservedOpを用意するための補助的定義である。
whiteSpace〜reservedOpは、C言語のトークンをパースするための パーサ関数である。また、runLexはパーサをテストするための関数である。
これらは組み合わせて、例えば次のように使用する。
Parser> runLex (do {parseId; parseAddSub; parseId; return ()}) "x+y" () ← エラーがない場合は、パーサを実行した結果をそのまま返す。 Parser> runLex (do {parseId; parseAddSub; parseId; return ()}) "x*y" parse error at (line 1, column 2): unexpected "*" expecting letter or digit, "+" or "-" ← 構文エラーがある場合は、エラーメッセージを返す。個々のパーサ関数の意味と使用例は以下の通りである。
Parser> runLex whiteSpace "" () Parser> runLex whiteSpace " " () Parser> runLex whiteSpace " x " parse error at (line 1, column 2): unexpected "x" expecting end of input
Parser> runLex (symbol "+") "+" "+" Parser> runLex (symbol "+") "*" parse error at (line 1, column 1): unexpected "*" expecting "+"
Parser> runLex natural "112" 112 Parser> runLex natural "-112" parse error at (line 1, column 1): unexpected "-" expecting natural Parser> runLex natural "x1" parse error at (line 1, column 1): unexpected "x" expecting natural
Parser> runLex identifier "x1" "x1" Parser> runLex identifier "1" parse error at (line 1, column 1): unexpected "1" expecting identifier Parser> runLex identifier "while" parse error at (line 1, column 1): unexpected reserved word "while" expecting letter or digit
Parser> runLex (do { reserved "int"; identifier }) "int x" "x" Parser> runLex (do { reserved "int"; identifier }) "intx" parse error at (line 1, column 1): unexpected "x" expecting end of "int" Parser> runLex (do { symbol "int"; identifier }) "intx" "x"
Parser> runLex operator "->" "->" Parser> runLex operator "&&" "&&"
Parser> runLex (do { identifier; reservedOp "++"; operator }) "x++ +" "+" Parser> runLex (do { identifier; reservedOp "++"; operator }) "x+++" parse error at (line 1, column 2): unexpected "+" expecting letter or digit or end of "++"
Parser> runLex (parens natural) "(123)" 123 Parser> runLex (parens natural) "(123" parse error at (line 1, column 5): unexpected end of input expecting digit or ")" Parser> runLex (parens natural) "123" parse error at (line 1, column 1): unexpected "1" expecting "("
詳しくは、Parsecのドキュメント内の The members of TokenParserを参照すること。
例として次のようなBNF
S | → | “(” L “)” | |
| | Id | ||
L | → | S L' | |
L' | → | “,” S L' | |
| | ε |
data S = List [S] | Id String -- Sに対応するデータ型 deriving Show parseS = (do { l <- parens parseL; return (List l) }) <|> (do { x <- identifier; return (Id x) }) parseL = do { s <- parseS; l' <- parseL'; return (s:l') } parseL' = (do { comma; s <- parseS; l' <- parseL'; return (s:l') }) <|> return []実行結果は以下のようになる。
Parser> runLex parseS "(x, y, z)" List [Id "x",Id "y",Id "z"] Parser> runLex parseS "(x, (y, z), (w))" List [Id "x",List [Id "y",Id "z"],List [Id "w"]]
上記のプリミティブパーサ関数を使用した Tiny C用のパーサの書きかけの parse〜関数の意味は以下の通りである。
Parser> runLex parseTypeName "void " "void" Parser> runLex parseTypeName "int " "int" Parser> runLex parseTypeName "foo" parse error at (line 1, column 1): unexpected "f" expecting "int" or "void"
Parser> runLex (do { s <- parseSignPart ; n <- parseIntConstant ; return (s, n) }) "-123" ("-",123) Parser> runLex (do { s <- parseSignPart ; n <- parseIntConstant ; return (s, n) }) "+456" ("+",456) Parser> runLex (do { s <- parseSignPart ; n <- parseIntConstant ; return (s, n) }) "789" ("+",789)
Parser> runLex (do { n1 <- parseIntConstant ; op <- parseMultDiv ; n2 <- parseIntConstant ; return (n1, op, n2) }) "1*2" (1,"*",2) Parser> runLex (do { n1 <- parseIntConstant ; op <- parseMultDiv ; n2 <- parseIntConstant ; return (n1, op, n2) }) "1+2" parse error at (line 1, column 2): unexpected "+" expecting digit, "*" or "/"
Tiny CのBNFを示す。(上記 Tiny Cコンパイラ解説から引用)
Program → ExternalDeclaration | ExternalDeclaration Program
ExternalDeclaration → FunctionDef | FunctionDecl | VariableDecl
FunctionDef → FunctionDeclarator Block
FunctionDecl → FunctionDeclarator ";"
FunctionDeclarator → TypeName FuncName "(" ParamList ")"
ParamList → TypeName NameSpec ParamListTail | ε
ParamListTail→ "," TypeName NameSpec ParamListTail | ε
VariableDecl → TypeName NameSpec NameListTail ";"
NameListTail → "," NameSpec NameListTail | ε
NameSpec → Id | Id "[" IntConstant "]"
Block → "{" StatementSeq "}"
StatementSeq → Statement | Statement StatementSeq | ε
Statement →
Variable "=" Expression ";"
| "if" "(" ConditionExp ")" Statement ElsePart
| "while" "(" ConditionExp ")" Statement
| FuncName "(" ExpressionList ")" ";"
| Block | "return" ";"
ElsePart → "else" Statement | ε
ExpressionList → Expression ExpListTail | ε
ExpListTail → "," Expression ExpListTail | ε
Expression → SignPart Term ExpTail
ExpTail → AddSub Term ExpTail | ε
Term → Factor TermTail
TermTail → MultDiv Factor TermTail | ε
Factor → IntConstant | Variable | "(" Expression ")"
Variable → Id SubscriptPart
SubscriptPart→ "[" Expression "]" | ε
ConditionExp → Expression CompOperator Expression
CompOperator → "==" | "!=" | ">" | ">=" | "<=" | "<"
AddSub → "+" | "-"
MultDiv → "*" | "/"
SignPart → "+" | "-" | ε
FuncName → Id
TypeName → "int" | "void"
Id → Alpha IdTail
IntConstant → Digit ConstTail
IdTail → Alpha IdTail | Digit IdTail | ε
Alpha → "a"|"b"|"c"|"d"|"e"|"f"|"g"|"h"|"i"|"j"|"k"|"l"|"m"|
"n"|"o"|"p"|"q"|"r"|"s"|"t"|"u"|"v"|"w"|"x"|"y"|"z"|
"A"|"B"|"C"|"D"|"E"|"F"|"G"|"H"|"I"|"J"|"K"|"L"|"M"|
"N"|"O"|"P"|"Q"|"R"|"S"|"T"|"U"|"V"|"W"|"X"|"Y"|"Z"
Digit → "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
ConstTail → Digit ConstTail | ε
記法: α→β : 記号列βにαという名前を付ける(αはβで置き換えられる)。
(BNF) b : 記号列 a の後ろに記号列 b を置いた記号列
a | b : 記号列 a または記号列 b
"〜" で囲まれた記号は終端記号("xx"は xx そのもの)、
εは空記号。
本例での用法: 英大文字で始まる語は非終端記号(構文要素名)。
parseSomething
という名前の関数を定義する。
BNF中の灰色の部分は既に定義済みである。
Factor → IntConstant | Variable Variable → IdVariable, Factor, TermTail, Term, ExpTail, Expression, SubscriptPartの順に定義し、 ここで、Factor, Variableの定義を元に戻す。) 次に ExpListTail, ExpressionList, Block〜ElsePartの順に定義し、最後に全体のパーサを定義する。
type Expression = (SignPart, Term, ExpTail) type ExpTail = [(AddSub, Term)] type Term = (Factor, TermTail) type TermTail = [(MultDiv, Factor)] data Factor = MkIntConst Integer | MkVar Variable | MkExpr Expression deriving Show type Variable = (Id, SubscriptPart) type SubscriptPart = Maybe Expression type ConditionExp = (Expression, CompOperator, Expression) type CompOperator = String type AddSub = String type MultDiv = String type SignPart = String type Id = String同様にしてStatementやProgramも対応するデータ型を 適宜定義する。