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にマッチする。
    • yaccグローバル変数使いまくりでしたがocamlyaccはそうではないのでparse_errorは書いても意味が無い……と思われます。
  • %prec UMINUSがなんのためにあるのかわかんない。
  • ユーザーデータを渡せるところが欲しい。lexbufにでも未使用フィールド用意しておいてくれてたらObj.magicで突っ込むんですが。

折角OCamlのStreamはn要素先読みができるのですし、token Stream.tを取るパーサジェネレータが欲しいなー。