Using ulex and menhir with OMake

OMake で ulex と menhir を使う方法についてメモ。

ocamllex ではなく ulex を使うモチベーションですが、ocamllex では utf8 なファイルを扱えないので他の lexer が必要だからです。

TLDR

lexer.ml

pos は Lexing.position で後で Lexing を受け取る関数を Ulesing を扱えるようにする変換で必要になります。

exception SyntaxError of string

let regexp white = [' ' '\t']

let eat pos n =
  let open Lexing in
  {
    pos with pos_cnum = pos.pos_cnum + n;
  }

let next_line pos =
  let open Lexing in
  {
    pos with pos_lnum = pos.pos_lnum + 1;
  }

let rec read pos =
  lexer
  | white   -> read (eat pos 1) lexbuf
  | integer -> eat pos (Ulexing.lexeme_length lexbuf), TINT (int_of_string(Ulexing.utf8_lexeme lexbuf))
  | eof     -> TEOF
  | _       -> raise (SyntaxError ("Illegal string character: " ^ (U.utf8_lexeme lexbuf)))

parser.mly

%{
type expr = | Eint of int
%}

%token <int> TINT
%token TEOF

%start <expr> expression

%%

expression:
    | e = TINT { Eint e }

main.ml

Parser.expression の型は (Lexing.lexbuf -> Parser.token) -> Lexing.lexbuf -> Parser.tree で、 ocamllex のLexing に依存しています。なので、MenhirLib.Convert.Simplified.traditional2revised を使ってその型を ulex を使えるように変換します。

MenhirLib.Convert.Simplified.traditional2revised の型は以下のコードの場合は ((Lexing.lexbuf -> Parser.token) -> Lexing.lexbuf -> Parser.tree) -> 'a になっていますが、なぜか emacs で MenhirLib モジュールが見付けらないのでここだけ本来の型は分かりませんでした。本筋とは関係ないのでまぁいいとしましょう。

let parse_string fname doc =
  let lexbuf = Ulexing.from_utf8_string doc in
  let parser = MenhirLib.Convert.Simplified.traditional2revised Parser.expression in
  let lexer () =
    let pos_ref = ref (Lexer.new_pos fname) in
    let pre_pos = !pos_ref in
    let (post_pos, token) = Lexer.read !pos_ref lexbuf in
    (token, pre_pos, post_pos)
  in
  parser lexer

let () = begin
  print_int @@ parse_string "example.js" "1234";
  print_newline ();
end

OMakefile

さて、ようやく OMakefile です。

まず、ulex のドキュメントによると、ulex を使ったコードをコンパイルするには ocamlfind に -syntax camlp4o を渡す必要があります。しかし、OCAMLFINDFLAGS += -syntax camlp4o をベタに設定してしまうと、グローバルに設定が有効になってしまい以下のようなエラー(内容は他のプロジェクトのものです)に遭遇することになります。

File "typeSafeConfigFactory.ml", line 7, characters 2-5:
Parse error: "module" or "open" or [opt_rec] expected after "let" (in [expr])
File "typeSafeConfigFactory.ml", line 1:
Error: Error while running external preprocessor

ですので、なんとかして OCAMLFINDFLAGS を lexer.ml にだけ適用する必要があります。 私も綺麗な方法は分からなかったのですが、以下の OMakefile では section でスコープを切った上で、lexer.ml だけ別のルールを適用することで回避しています。 OMakefile の section に記述したルールで、foo.o: のように target だけ記述し dependencies と body を書かないことで implicit rule を適用することができるため、この仕組みにより実行されるべき body を自動決定させます。

OCAMLFLAGS    += -bin-annot -warn-error A -g

USE_OCAMLFIND  = true

MENHIR_ENABLED = true

NATIVE_ENABLED = true

BYTE_ENABLED = true

MENHIR_FLAGS += --infer --explain

OCAMLPACKS[] +=
        ulex
        menhirLib

section
        OCAMLFINDFLAGS += -syntax camlp4o
        lexer.o lexer.cmt lexer.cmx lexer.cmo lexer.cmi:

OCamlGeneratedFiles(parser.ml)

FILES[] =
        parser
        main

.DEFAULT: $(OCamlProgram main, $(FILES) lexer)

あとは omake でコンパイルするだけです。

Comments

comments powered by Disqus