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

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

1. OCamlでもevalがしたい!

ではこちらをどうぞ。

以下引用(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、ハリソン・フォード…全て…。