open Combinator (* ENABLE DEBUG OUTPUT? *) let DEBUG = false (* AST *) type Expr = | Num of int | Plus of Expr list (* GRAMMAR *) let expr, exprImpl = recparser() let number = pmany1 pdigit |>> (fun ds -> stringify ds |> int |> Num) "number" let inPlus p = pbetween (pstr "(plus ") p (pchar ')') let pmany2sep p sep = pseq p (pmany1 (pright sep p)) (fun (x,xs) -> x::xs) "pmany2sep" (* The following was Danny's good alternative suggestion. If we defined a `pmany2` parser, and changed inPlus to omit the space character, then it would work exactly the same as the above, and it would be simpler too! let pmany2sep p sep = pmany1 (pright sep p) *) let plusExpr = inPlus (pmany2sep expr pws1) |>> Plus "plusExpr" exprImpl := number <|> plusExpr "expr" let grammar = pleft expr peof "grammar" (* PARSER HELPER *) let parse input : Expr option = let i = if DEBUG then debug input else prepare input match grammar i with | Success(ast,_) -> Some ast | Failure(_,_) -> None (* EVALUATOR *) let rec eval e : int = match e with | Num n -> n | Plus es -> // alternatively: es |> List.fold (fun acc e -> eval e + acc) 0 es |> List.map eval |> List.sum (* MAIN *) [] let main args = let input = args[0] let ast_opt = parse input match ast_opt with | Some ast -> let i = eval ast printfn "%d" i | None -> printfn "Invalid expression." 0