こんにちは、何者でもなくなりました。

さて、今はOCamlとLaTeXしか書いてないという状況に陥りました。人生にありがちな時期ですね。

1. OCamlでもevalがしたい!

ではこちらをどうぞ。

I’m trying to “eval” a string representing an OCaml expression in OCaml. I’m looking to do something equivalent to Python’s eval.So far I’ve not been able to find much. The Parsing module looks l…

以下引用(upop上で動くよ)。

eval
#require "compiler-libs" (* Assuming you're using utop, if compiling then this is the package you need *)
let eval code =
  let as_buf = Lexing.from_string code in
  let parsed = !Toploop.parse_toplevel_phrase as_buf in
  ignore (Toploop.execute_phrase true Format.std_formatter parsed)

compiler-libsというOCamlに同梱されているライブラリーを使いますと、このようにevaれるわけですね。

2. evalの結果を文字列で取りたい!

evaってるのはToploop.execute_phraseで、戻り値はランタイムエラーしたかそうでないかのboolになります。 上記ではignoreで捨ててunitにしてますね。

例えば、eval "int_of_char 'a';;"なんてしたときに戻り値が"97"というstringだと嬉しい人がいるかもしれない。

俺なら実装やってくだけだな

3. 出力先

format型の話をしよう。

OCaml 標準ライブラリ探訪 #3.0: Printf: 便利だけどいろいろ謎のある奴 - Oh, you `re no (fun _ → more)

OCamlのformat (型安全なprintf/scanf) の仕組み - 簡潔なQ

話おわり

4. 解

evalToploop.execute_phraseに渡している引数を観察すると、 trueFormat.std_formatterを渡していることが分かる。

trueは後述。第2引数は件のformatじゃねえかい。 このformatの出力先をstring refみたいなところに出してその中身を返せば行けそうだ。 でもどうやって? まずはFormat.std_formatterがどうなってるかを見てみよう。

stdlib/format.ml#L1038で定義されている。 formatter_of_out_channelが何者か辿ってみると、

formatter_of_out_channel(stdlib/format.ml#L1018)
let formatter_of_out_channel oc =
  make_formatter (output_substring oc) (fun () -> flush oc)

了解!

このmake_formatterって使えそうだなと思って定義を見てみるが全くわからない。 幸運にも、formatter_of_out_channelの真下にわかりやすい例がある。

formatter_of_buffer(stdlib/format.ml#L1023)
let formatter_of_buffer b =
  make_formatter (Buffer.add_substring b) ignore

Buffer.add_substringignoreという、わかりやすい関数で構成されている。 これなら型がたちどころに分かるな。

Module Buffer - OCaml.jp を見るとsubstringしてバッファーにくっつける感じですね。 そしてバッファーbを部分適用しているので、なるほどmake_formatterに渡しているのはstring -> int -> int -> unitunit -> unitか。

もう見えてきましたね。substringをstring refなどに書いて参照すればOK。 あとはやるだけ。

eval returning string
let records = ref ""  (* 書き出す string ref *)
let ref_b rs = fun s i j ->
  let subs = String.sub s i j in
  rs := !rs ^ subs
let fmt = Format.make_formatter (ref_b records) ignore (* 新しいフォーマッター *)

let eval code =
  let as_buf = Lexing.from_string code in
  let parsed = !Toploop.parse_toplevel_phrase as_buf in
  let () = Toploop.execute_phrase true fmt parsed |> ignore in
  let ret = !records in 
  records := ""; ret

はい実行

utop
utop # eval "int_of_char 'a';;";;
- : string = "- : int = 97\n"

ありがたい! 型名まで付いている!!! いらない!!!!!

おわりだよ〜

その前にToploop.execute_phraseに渡しているboolは、実行結果をフォーマッターに渡すか否かですはい解散

追記20171118

こんなことしなくてもFormat.str_formatterFormat.flush_str_formatterという便利なものがあってじゃな…。


『ブレードランナー 2049』観に行きましたが全部最高でした。

『ブレードランナー』の続編としての立ち位置、ストーリー、BGM、ハリソン・フォード…全て…。