(* Baseline: untyped lambda calculus with closures over a Hashtbl env *)
type term =
  | Var of string
  | Abs of string * term
  | App of term * term
  | Num of int
;;

type value =
  | VNum of int
  | VClos of string * term * (string * value) list
;;

let rec lookup name env =
  match env with
  | [] -> failwith "unbound"
  | (n, v) :: t -> if n = name then v else lookup name t
;;

let rec eval env t =
  match t with
  | Num n -> VNum n
  | Var x -> lookup x env
  | Abs (x, body) -> VClos (x, body, env)
  | App (f, a) ->
    let fv = eval env f in
    let av = eval env a in
    (match fv with
     | VClos (param, body, captured) ->
       eval ((param, av) :: captured) body
     | _ -> failwith "not a function")
;;

let unwrap v = match v with VNum n -> n | _ -> failwith "not a number" ;;

(* (\x. \y. x) 7 99 = 7 *)
let term =
  App (App (Abs ("x", Abs ("y", Var "x")), Num 7), Num 99)
;;

unwrap (eval [] term)
