let rec string_of_expression expr =
  "(" ^ (string_of_type expr.expr_type) ^ ") " ^
  match expr.expr_val with
    | Lt(e1, e2) -> string_of_expression e1 ^ " < " ^ string_of_expression e2
    | Le(e1, e2) -> string_of_expression e1 ^ " <= " ^ string_of_expression e2
    | Eq(e1, e2) -> string_of_expression e1 ^ " = " ^ string_of_expression e2
    | Neq(e1, e2) -> string_of_expression e1 ^ " <> " ^ string_of_expression e2
    | Plus(e1, e2) -> string_of_expression e1 ^ " + " ^ string_of_expression e2
    | Minus(e1, e2) -> string_of_expression e1 ^ " - " ^ string_of_expression e2
    | Mult(e1, e2) -> string_of_expression e1 ^ " * " ^ string_of_expression e2
    | Div(e1, e2) -> string_of_expression e1 ^ " / " ^ string_of_expression e2
    | Mod(e1, e2) -> string_of_expression e1 ^ " mod " ^ string_of_expression e2
    | And(e1, e2) -> string_of_expression e1 ^ " and " ^ string_of_expression e2
    | Or(e1, e2) -> string_of_expression e1 ^ " or " ^ string_of_expression e2
    | Not e -> "not " ^ string_of_expression e
    | Uminus e -> "-" ^ string_of_expression e
    | Int i -> string_of_int i
    | Bool b -> if b then "true" else "false"
    | Var(v, []) -> v.var_name
    | Var(v, el) -> v.var_name ^ "(" ^ String.concat " " (List.map string_of_expression el) ^ ")"
    | Arr(e1, e2) -> string_of_expression e1 ^ "[" ^ (string_of_expression e2) ^ "]"
    | Rec(e, f) -> string_of_expression e ^ "." ^ f
    | Address_of(e) -> "&" ^ string_of_expression e
    | Bang(e) -> string_of_expression e ^ "^"
    | New(t) -> "new " ^ string_of_type !t