open Ast
open Ast.Value

exception No_operation
exception No_such_variable of string

let rec binop op l r =
  let tl = typeof l and tr = typeof r in
  let ty = Type.merge tl tr in
  let rec promote_until t x =
    if typeof x = t
    then x
    else promote_until t (promote x)
  in
  let l = promote_until ty l
  and r = promote_until ty r in
  match Binop.get op ty with
  | None -> begin
      try binop op (promote l) (promote r)
      with _ -> raise No_operation
    end
  | Some f -> f l r

let eval vars ast =
  let rec aux = function
    | Value v -> v
    | Var v -> begin match Hashtbl.find_opt vars v with
        | None -> raise @@ No_such_variable v
        | Some v -> v
      end
    | Binop (l, op, r) ->
      let l = aux l and r = aux r in
      binop op l r
    | Set_binop_pre (op, l) ->
      let l =
        match aux l with
        | Int n -> n
        | v -> raise @@ Invalid_type (typeof v)
      in
      Hashtbl.replace Parser.precedence op l;
      Nop
    | Get_binop_pre op ->
      Int (Hashtbl.find Parser.precedence op)
    | Set_binop_aso (op, a) ->
      Hashtbl.replace Parser.oper_assoc op @@ Parser.assoc_of_string a;
      Nop
    | Get_binop_aso op ->
      match Hashtbl.find_opt Parser.oper_assoc op with
      | None -> String "left"
      | Some a -> String (Parser.assoc_to_string a)
  in
  aux ast