Arantium Maestum

プログラミング、囲碁、読書の話題

js_of_ocamlでocamllex/menhir製パーサをJSにコンパイルする

私は趣味の言語処理系実装でocamllex/menhirというパーサジェネレータにお世話になることが多い。(このブログの過去記事を参照)

js_of_ocamlは基本的にFFIを使わないライブラリだったらJavaScriptコンパイルしてくれるはずなので、ブラウザでパーサが使えるのではないか?と気になって試してみた。

例によってなるべく最小構成で試してみたら非常に簡単にできた。

Minimal example using ocamllex/menhir with js_of_ocaml : compile to JS with the command `dune build ./main.bc.js` · GitHub

パーサを試すだけ、ということで評価器部分はなし。

構文は楽な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))"と出力される。

自作言語のインタプリタなどはこれで簡単にウェブ提供できそう。