ocamlyaccとocamllex
実はyaccをまともに使ったことが無かったりする私ですが、ドキュメントの例にエラー処理を追加してみました。
/* File parser.mly */ %{ let parse_error (z : string) = print_string z; print_string " at ";; let print_pos p = print_int p.Lexing.pos_lnum; print_string ":"; print_int (p.Lexing.pos_bol + 1);; %} %token <int> INT %token PLUS MINUS TIMES DIV %token LPAREN RPAREN %token EOL %left PLUS MINUS /* lowest precedence */ %left TIMES DIV /* medium precedence */ %nonassoc UMINUS /* highest precedence */ %start main /* the entry point */ %type <int> main %% main: | expr EOL { $1 } | expr error EOL { print_pos (rhs_start_pos 2); print_string "\n"; $1 } | error EOL { print_pos (rhs_start_pos 1); print_string "\n"; 0 } ; expr: | INT { $1 } | LPAREN expr RPAREN { $2 } | expr PLUS expr { $1 + $3 } | expr MINUS expr { $1 - $3 } | expr TIMES expr { $1 * $3 } | expr DIV expr { $1 / $3 } | MINUS expr %prec UMINUS { - $2 } ;
(* File lexer.mll *) { open Parser (* The type token is defined in parser.mli *) exception Eof } rule token = parse | [' ' '\t'] { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; token lexbuf } | ['\n' ] { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_lnum = lexbuf.Lexing.lex_curr_p.Lexing.pos_lnum + 1; Lexing.pos_bol = 0 }; EOL } | ['0'-'9']+ as lxm { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + (String.length lxm) }; INT(int_of_string lxm) } | '+' { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; PLUS } | '-' { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; MINUS } | '*' { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; TIMES } | '/' { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; DIV } | '(' { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; LPAREN } | ')' { lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; RPAREN } | eof { raise Eof } | ['\x00'-'\xff'] as c { print_string ("lexical error (" ^ (String.make 1 c) ^ ") at "); print_int lexbuf.Lexing.lex_curr_p.Lexing.pos_lnum; print_string ":"; print_int (lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1); print_string "\n"; lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with Lexing.pos_bol = lexbuf.Lexing.lex_curr_p.Lexing.pos_bol + 1 }; token lexbuf }
(* File calc.ml *) try let lexbuf = Lexing.from_channel stdin in while true do let result = Parser.main Lexer.token lexbuf in print_int result; print_newline (); flush stdout done with Lexer.Eof -> exit 0
- Parsingがlexbufを要求するためocamlyaccを使おうと思えばocamllexの使用が必須。
- つまりスキャナレスパーサーとして使えない。
- つまり文字列以外をパースできない。
- ocamllexでは現在位置を自前で更新する必要かある。何か間違ってると思います。
- エラーはerrorにマッチする。
- %prec UMINUSがなんのためにあるのかわかんない。
- http://docs.sun.com/app/docs/doc/805-5827/6j5gfran3?l=ja&a=view
- 単項"-"の優先順位を変更しているらしい。
- ユーザーデータを渡せるところが欲しい。lexbufにでも未使用フィールド用意しておいてくれてたらObj.magicで突っ込むんですが。
折角OCamlのStreamはn要素先読みができるのですし、token Stream.tを取るパーサジェネレータが欲しいなー。