最近また趣味でコンパイラを書いていて、初めてちゃんとOCaml使って書いている。 ocamlyaccとか使わずに書くことで関数型ネイティブになっていくことが目的なので、パーサも自作。
以下のようなパーサがゴリゴリかけて、本当に最高。 ここ数年はとにかくGoでプログラムを書いていたのでOCamlの感覚になれるのに時間がかかったけど、 スピードが出てからどんどんコードかけて非常に気持ちがいい。
let char_list_to_int (chars : char list) : int =
let digit_of_char c = int_of_char c - int_of_char '0' in
List.fold_left (fun acc c -> (acc * 10) + digit_of_char c) 0 chars
(* satisfy 関数の定義 *)
let satisfy (show : 'a -> string) (predicate : 'a -> bool) (lst : 'a list) :
('b * 'a list, Err.parse_error) result =
match lst with
| [] -> Error (Err.NotSatisfied "Unexpected end of input")
| x :: xs ->
if predicate x then Ok ([ x ], xs)
else
Error
(Err.NotSatisfied
(Format.asprintf "Predicate not satisfied for: %s" (show x)))
let rec takewhile0 (predicate : 'a -> bool) (lst : 'a list) : 'b * 'a list =
match lst with
| [] -> ([], lst)
| x :: xs ->
if predicate x then
let yes, no = takewhile0 predicate xs in
(x :: yes, no)
else ([], lst)
let rec alt (parsers : ('a list -> ('b * 'a list, Err.parse_error) result) list)
(input : 'a list) : ('b * 'a list, Err.parse_error) result =
match parsers with
| [] -> Error (Err.NoApplicableRule "all parsers didn't fit")
| parser :: rest -> (
match parser input with
| Ok result -> Ok result
| Error _ -> alt rest input)
let map parser f input =
match parser input with
| Ok (result, rest) -> Ok (f result, rest)
| Error err -> Error err
let fold_left f init parser input =
let rec aux acc input =
match parser input with
| Ok (result, rest) -> aux (f acc result) rest
| Error _ -> Ok (acc, input)
in
aux init input
let bind parser f input =
match parser input with
| Ok (result, rest) -> f result rest
| Error err -> Error err
let check_then parser next_parser input =
match parser input with
| Ok (_, _) -> next_parser input
| Error err -> Error err
let opt parser input =
match parser input with
| Ok (result, rest) -> Ok (Some result, rest)
| Error _ -> Ok (None, input)
こんな感じで素朴なパーサライブラリを作っている OCamlといえば自作コンパイラを含む言語処理系のイメージがあるけど、他に何が作れるかなあ、もっとOCamlを書きたい。