(* Baseline: recursive-descent calculator for "+", "*", parens, ints. *)
type expr =
  | Lit of int
  | Add of expr * expr
  | Mul of expr * expr
;;

let parse_input src =
  let pos = ref 0 in
  let peek () = if !pos < String.length src then String.get src !pos else "" in
  let advance () = pos := !pos + 1 in
  let skip_ws () =
    while !pos < String.length src && peek () = " " do advance () done
  in

  let rec parse_atom () =
    skip_ws () ;
    if peek () = "(" then begin
      advance () ;
      let e = parse_expr () in
      skip_ws () ;
      advance () ; (* consume ')' *)
      e
    end
    else
      let start = !pos in
      let rec digits () =
        if !pos < String.length src then
          let c = peek () in
          if c >= "0" && c <= "9" then begin advance () ; digits () end
          else ()
      in
      digits () ;
      let n = Int.of_string (String.sub src start (!pos - start)) in
      Lit n

  and parse_term () =
    skip_ws () ;
    let lhs = ref (parse_atom ()) in
    let rec loop () =
      skip_ws () ;
      if peek () = "*" then begin
        advance () ;
        lhs := Mul (!lhs, parse_atom ()) ;
        loop ()
      end
    in
    loop () ;
    !lhs

  and parse_expr () =
    skip_ws () ;
    let lhs = ref (parse_term ()) in
    let rec loop () =
      skip_ws () ;
      if peek () = "+" then begin
        advance () ;
        lhs := Add (!lhs, parse_term ()) ;
        loop ()
      end
    in
    loop () ;
    !lhs
  in
  parse_expr ()
;;

let rec eval e =
  match e with
  | Lit n -> n
  | Add (a, b) -> eval a + eval b
  | Mul (a, b) -> eval a * eval b
;;

(* (1 + 2) * 3 + 4 = 9 + 4 = 13 *)
eval (parse_input "(1 + 2) * 3 + 4")
