It is indeed less tiresome that i had imagined.
I attach the corresponding full code (with original camlp4 formulation in comment).
Handling of the minus operator is here handled before parsing; this is not a problem since such a parser is supposed to be called on very small strings in practice.
ps : i’m still wondering whether some library and/or ppx-based generic (afap) implementation of Camlp4 stream parsers is possible..
8<—— Stream-based parser for simple arithmetic expressions (with non-negative integers)
type ident = string
type value = int
type t =
EConst of value (** Constants *)
| EVar of ident (** Input, output or local variable *)
| EBinop of string * t * t (** Binary operation *)
let keywords = ["+"; "-"; "*"; "/"; "("; ")"]
let mk_binary_minus s = s |> String.split_on_char '-' |> String.concat " - "
let lexer s = s |> mk_binary_minus |> Stream.of_string |> Genlex.make_lexer keywords
open Genlex
(* let rec p_exp0 = parser
* | [< 'Int n >] -> EConst n
* | [< 'Ident i >] -> EVar i
* | [< 'Kwd "("; e=p_exp ; 'Kwd ")" >] -> e *)
let rec p_exp0 s =
match Stream.next s with
| Int n -> EConst n
| Ident i -> EVar i
| Kwd "(" ->
let e = p_exp s in
begin match Stream.peek s with
| Some (Kwd ")") -> Stream.junk s; e
| _ -> raise Stream.Failure
end
| _ -> raise Stream.Failure
(* and p_exp1 = parser
* | [< e1=p_exp0 ; rest >] -> p_exp2 e1 rest *)
and p_exp1 s =
let e1 = p_exp0 s in
p_exp2 e1 s
(* and p_exp2 e1 = parser
* | [< 'Kwd "*"; e2=p_exp1 >] -> EBinop("*", e1, e2)
* | [< 'Kwd "/"; e2=p_exp1 >] -> EBinop("/", e1, e2)
* | [< >] -> e1 *)
and p_exp2 e1 s =
match Stream.peek s with
| Some (Kwd "*") -> Stream.junk s; let e2 = p_exp1 s in EBinop("*", e1, e2)
| Some (Kwd "/") -> Stream.junk s; let e2 = p_exp1 s in EBinop("/", e1, e2)
| _ -> e1
(* and p_exp = parser
* | [< e1=p_exp1 ; rest >] -> p_exp3 e1 rest *)
and p_exp s =
let e1 = p_exp1 s in p_exp3 e1 s
(* and p_exp3 e1 = parser
* | [< 'Kwd "+"; e2=p_exp >] -> EBinop("+", e1, e2)
* | [< 'Kwd "-"; e2=p_exp >] -> EBinop("-", e1, e2)
* | [< >] -> e1 *)
and p_exp3 e1 s =
match Stream.peek s with
| Some (Kwd "+") -> Stream.junk s; let e2 = p_exp s in EBinop("+", e1, e2)
| Some (Kwd "-") -> Stream.junk s; let e2 = p_exp s in EBinop("-", e1, e2)
| _ -> e1
let parse s = s |> lexer |> p_exp