js_of_ocamlでocamllex/menhir製パーサをJSにコンパイルする
私は趣味の言語処理系実装でocamllex/menhirというパーサジェネレータにお世話になることが多い。(このブログの過去記事を参照)
js_of_ocamlは基本的にFFIを使わないライブラリだったらJavaScriptにコンパイルしてくれるはずなので、ブラウザでパーサが使えるのではないか?と気になって試してみた。
例によってなるべく最小構成で試してみたら非常に簡単にできた。
パーサを試すだけ、ということで評価器部分はなし。
構文は楽なS式。しかもリテラルなし、変数とカッコのみ。
こんな感じのミニマルなAST:
type t = | Var of string | Call of t list
文字列変換も楽:
let rec to_string = function | Var s -> "Var(" ^ s ^ ")" | Call xs -> let s = List.map to_string xs |> String.concat " " in "Call(" ^ s ^ ")"
lexer.mll:
let whitespace = [' ' '\t' '\n'] let char = ['a'-'z' 'A'-'Z' '+' '-' '*' '/' '<' '>' '=' '!' '?' '_'] let variable = char+ rule f = parse | whitespace* { f lexbuf } | "(" { Parser.LPAREN } | ")" { Parser.RPAREN } | variable as s { Parser.VAR s } | eof { EOF }
以前書いたS式パーサの簡略版。思い切って数字はまったくパースできなくしてある。でも見直したらvariable
定義は冗長だし削れるな・・・(元々はlet variable = char (char|digit)*
という定義だった)
parser.mly:
%token <string> VAR %token LPAREN %token RPAREN %token EOF %start <Exp.t> f %% f : e = expr; EOF { e } expr : | s = VAR; { Exp.Var s } | LPAREN; es = list (expr); RPAREN { Exp.Call es }
カッコの間に何もなかったら?など、いつもならいろいろ考えることを全部飛ばしているので実にスッキリ。
main.mlで定義されるブラウザとのインタフェースも、一番簡単なJSコンソールへの出力:
let () = "(test a b c)" |> Lexing.from_string |> Parser.f Lexer.f |> Exp.to_string |> print_endline
あとはduneファイルを定義:
(menhir (modules parser)) (ocamllex lexer) (executable (name main) (modes js))
menhirでparser.mlyを、ocamllexでlexer.mllをOCamlにコンパイルした上でmainをJSにコンパイル。ocamllex/menhirどちらに関してもJSへのコンパイルのために何か特別な設定をする必要もなし。
これでdune build ./main.bc.js
とビルドしてmain.htmlを開くとちゃんとJSコンソールに"Call(Var(test) Var(a) Var(b) Var(c))"と出力される。
自作言語のインタプリタなどはこれで簡単にウェブ提供できそう。