(*
 * calc.ml
 *
 * Calculator for simple expressions
 *
 * The expression grammar is as follows:
 *
 *     E ::= T {+ T}
 *     T ::= F {* F}
 *     F ::= ( E ) | Int
 *
 * Expressions are parsed and evaluated on the fly.
 *
 * You can compile this file with the following command:
 *
 *     ocamlc -I +camlp4 -pp camlp4o calc.ml -o calc
 *)

(* Lex *)

module Token =
  struct
    type token =
      | LPar
      | RPar
      | Plus
      | Star
      | Number of float
  end

let rec lex = parser
  | [< ' (' ' | '\n' | '\r' | '\t'); stream >] -> lex stream
  | [< ' ('('); stream >] -> [< 'Token.LPar; lex stream >]
  | [< ' (')'); stream >] -> [< 'Token.RPar; lex stream >]
  | [< ' ('+'); stream >] -> [< 'Token.Plus; lex stream >]
  | [< ' ('*'); stream >] -> [< 'Token.Star; lex stream >]
  | [< ' ('0' .. '9' as c); stream >] ->
    let buffer = Buffer.create 1 in
    Buffer.add_char buffer c;
    lex_number buffer stream
  | [< >] -> [< >]

and lex_number buffer = parser
  | [< ' ('0' .. '9' | '.' as c); stream >] ->
    Buffer.add_char buffer c;
    lex_number buffer stream
  | [< stream >] ->
    let contents = Buffer.contents buffer in
    let number = float_of_string contents in
    [< 'Token.Number number; lex stream >]

let print_token token = match token with
  | Token.LPar -> print_endline "LPar"
  | Token.RPar -> print_endline "RPar"
  | Token.Plus -> print_endline "Plus"
  | Token.Star -> print_endline "Star"
  | Token.Number value -> print_endline @@ "Number: " ^ string_of_float value

(* Parse *)

module Expr =
  struct
    type expr =
      | Add of expr * expr
      | Mul of expr * expr
      | Num of float
  end

(* E -> T E' *)
let rec parse_expr = parser
  | [< lhs=parse_term; expr=parse_expr_rhs lhs >] -> expr

(* E' -> + T E' | eps *)
and parse_expr_rhs lhs = parser
  | [< 'Token.Plus;
       rhs=parse_term;
       expr=parse_expr_rhs (Expr.Add (lhs, rhs)) >] -> expr
  | [< >] -> lhs

(* T -> F T' *)
and parse_term = parser
  | [< lhs=parse_factor; expr=parse_term_rhs lhs >] -> expr

(* T' -> * F T' | eps *)
and parse_term_rhs lhs = parser
  | [< 'Token.Star;
       rhs=parse_factor;
       term=parse_term_rhs (Expr.Mul (lhs, rhs)) >] -> term
  | [< >] -> lhs

(* F -> ( E )  | Num *)
and parse_factor = parser
  | [< 'Token.LPar; expr=parse_expr; 'Token.RPar >] -> expr
  | [< 'Token.Number value >] -> Expr.Num value

let parse = parse_expr

let rec print_expr = function
  | Expr.Add (lhs, rhs) ->
    print_string "Add(";
    print_expr lhs;
    print_string ", ";
    print_expr rhs;
    print_string ")"
  | Expr.Mul (lhs, rhs) ->
    print_string "Mul(";
    print_expr lhs;
    print_string ", ";
    print_expr rhs;
    print_string ")"
  | Expr.Num value ->
    print_string "Num(";
    print_float value;
    print_string ")"

(* Eval *)

let rec eval = function
  | Expr.Add (lhs, rhs) -> eval lhs +. eval rhs
  | Expr.Mul (lhs, rhs) -> eval lhs *. eval rhs
  | Expr.Num value -> value

(* Main *)

let print_result result =
  print_string "= ";
  print_float result;
  print_endline "";
  flush stdout

let () =
  try
    while true do
      try
        print_string "> "; flush stdout;
        let line = input_line stdin in
        (*Stream.of_string line |> lex |> Stream.iter print_token*)
        (*Stream.of_string line |> lex |> parse |> print_expr; print_endline ""*)
        Stream.of_string line |> lex |> parse |> eval |> print_result
      with
      | End_of_file -> raise Exit
      | exc -> print_endline @@ Printexc.to_string exc
    done
  with Exit -> ()