
(* Modify only interpret and typecheck *)

open Ast

exception ListError
exception TypeError
exception RunTimeError
exception Unimplemented (* shows you what to change *)

(******* environment, context, and heap implementation *******)
(* note: using mutation and a "fresh" function would make some things
         more efficient, but that is not our focus *)

let empty = []

let extend lst x e = (x,e)::lst

let rec lookup lst y =
  match lst with
    [] -> raise ListError
  | (x,v)::tl -> if x=y then v else lookup tl y

let rec update lst y v =
  match lst with
    [] -> raise ListError
  | (x,v2)::tl -> if x=y then (x,v)::tl else (x,v2)::(update tl y v)

(* assumes lst was built only by previous calls to extend_fresh and update *)
let extend_fresh lst e = 
  let newlab = "l" ^ (string_of_int ((List.length lst) + 1)) in
  (newlab, (newlab,e)::lst)

(* Do not change anything above here. *)

(************* interpreter ***************)
(* note: remember we are large-step -- return a heap and a value *)
(* note: we did two cases for you *)

let rec interpret heap env e =
  match e with
    Const i -> heap,e
  | If(e1,e2,e3) ->
      let h1,v1 = interpret heap env e1 in
      (match v1 with
	Const(0) -> interpret h1 env e3
      |	Const _  -> interpret h1 env e2
      |	_ -> raise RunTimeError)
  | _ -> raise Unimplemented

(************** type-checker ***********)
(* note: t1=t2 will do what you want to compare types structurally, 
   e.g., Arrow(Int,Int)=Arrow(Int,Int)
   But t1==t2 is roughly pointer-equality -- do not accidentally use it or
   you will be very sorry *)
(* note: we did two cases for you *)

let rec typecheck ctxt e = (* return a type *)
  match e with
    Const i -> Int
  | If(e1,e2,e3) ->
      let t1 = typecheck ctxt e1 in
      let t2 = typecheck ctxt e2 in
      let t3 = typecheck ctxt e3 in
      if t1=Int && t2=t3
      then t2
      else raise TypeError
  | _ -> raise Unimplemented

(* Do not change anything below here *)

(* expect one command-line argument, a file to parse *)
(* you do not need to understand this interaction with the system *)
let parse_file () =
  let argv = Sys.argv in
  let _ = 
    if Array.length argv != 2
    then (prerr_string ("usage: " ^ argv.(0) ^ " [file-to-parse]\n");
	  exit 1) in
  let ch = open_in argv.(1) in
  Parse.program Lex.lexer (Lexing.from_channel ch)

let _ =
  let prog = parse_file() in
  (try
    (ignore(typecheck empty prog);
(*     print_string (exp2string prog); *) (* useful for debugging *)
     print_string "\ntypechecking complete";
     flush stdout;
     print_string ("\ninterpreted answer = ");
     print_string (exp2string ((fun (_,x)->x) (interpret empty empty prog)));
     print_newline ())
  with TypeError -> print_string "did not typecheck\n")
