Skip to content

Commit

Permalink
Use continuation passing style to make parse_expr tail recursive
Browse files Browse the repository at this point in the history
  • Loading branch information
feihong committed Sep 11, 2021
1 parent 4bc1ef0 commit 80a7e2e
Showing 1 changed file with 29 additions and 23 deletions.
52 changes: 29 additions & 23 deletions eva.ml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ let is_symbol c =
| c when c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' -> true
| _ -> false

let is_symbol_body c =
match c with
| c when is_symbol c -> true
|'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' -> true
| _ -> false

let parse_number first_char stream =
let rec loop ~has_dot acc =
match Stream.peek stream with
Expand All @@ -43,7 +49,7 @@ let parse_symbol first_char stream =
let make_symbol chars = Ok (Symbol (reverse_implode chars)) in
let rec loop acc =
match Stream.peek stream with
| Some c when is_symbol c ->
| Some c when is_symbol_body c ->
Stream.junk stream;
loop (c :: acc)
| None | Some _ -> make_symbol acc
Expand All @@ -66,34 +72,34 @@ let parse_string stream =
| c -> loop (c :: acc)
in loop []

let rec parse_expr stream =
let rec parse_expr stream fn =
match Stream.next stream with
| exception Stream.Failure -> errorf "EOF"
| c when is_space c -> parse_expr stream
| exception Stream.Failure -> fn (errorf "EOF")
| c when is_space c -> parse_expr stream fn
| '-' | '.' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' as c ->
parse_number c stream
fn (parse_number c stream)
| '"' -> fn (parse_string stream)
| c when is_symbol c -> parse_symbol c stream
| '"' -> parse_string stream
| '(' -> parse_list stream
| c -> errorf "Unexpected character %c" c
| '(' -> parse_list stream [] fn
| c -> fn (errorf "Unexpected character %c" c)

and parse_list stream =
let rec loop acc =
match Stream.peek stream with
| None -> errorf "Unterminated list"
| Some ')' ->
Stream.junk stream;
Ok (EList (List.rev acc))
| Some _ ->
(match parse_expr stream with
| Error _ as err -> err
| Ok x -> loop (x :: acc))
in loop []
and parse_list stream acc fn =
match Stream.peek stream with
| None -> fn (errorf "Unterminated list")
| Some c when is_space c ->
Stream.junk stream;
parse_list stream acc fn
| Some ')' ->
Stream.junk stream;
fn (Ok (EList (List.rev acc)))
| Some _ -> parse_expr stream (function
| Error _ as err -> fn err
| Ok x -> parse_list stream (x :: acc) fn)

let parse s =
let stream = Stream.of_string s in
let rec loop acc =
match parse_expr stream with
match parse_expr stream (fun x -> x) with
| Error "EOF" -> Ok (List.rev acc)
| Error _ as err -> err
| Ok thing -> loop (thing :: acc)
Expand All @@ -109,8 +115,8 @@ let cases = [
{|"It's not a \"bear\"\nit's a doggie"|}, [String "It's not a \"bear\"\nit's a doggie"];
"foo-bar-baz", [Symbol "foo-bar-baz"];
"*", [Symbol "*"];
{|(a 1 "batarang")|}, [EList [Symbol "a"; Number 1.; String "batarang"]];
{|foo (bar) (baz 22)|}, [Symbol "foo"; EList [Symbol "bar"]; EList [Symbol "baz"; Number 22.]];
(* {|(a 1 "batarang")|}, [EList [Symbol "a"; Number 1.; String "batarang"]];
{|foo (bar) (baz 22)|}, [Symbol "foo"; EList [Symbol "bar"]; EList [Symbol "baz"; Number 22.]]; *)
]

let () = cases |> List.iter (fun (s, value) -> assert (parse s = Ok value))

0 comments on commit 80a7e2e

Please sign in to comment.