open Printf
open Eval

let version = "%%VERSION%%"
let debug = ref false

let error_to_string e =
  try raise e with
  | Lex.Invalid_character c -> sprintf "invalid character %c" c
  | Lex.Expected c -> sprintf "expected %c" c
  | Parser.Expected t -> sprintf "expected %s" t
  | Parser.Unexpected_token t -> sprintf "unexpected token \"%s\"" t
  | Type.Invalid t -> sprintf "invalid type %s" (Type.to_string t)
  | Eval.Unbound v -> sprintf "unbound value %s" v
  | Eval.Too_many_arguments -> "applied too many arguments"
  | Failure f -> sprintf "error on %s" f
  | Division_by_zero -> "cannot divide by zero"
  | _ -> raise e

let print_error e =
  printf "error: %s\n" @@ error_to_string e

let stdlib = [
  "sin"; "cos"; "tan";
  "deg"; "rad";
]
  |> List.to_seq
  |> Seq.map (fun v -> v, External v)

let g =
  let g = Env.init_global () in
  Env.add_seq g stdlib;
  g

(* read-eval-print *) 
let rep env : unit =
  printf "> ";
  let line = read_line () in
  if line = "quit" then raise Exit;
  let ast = line |> Lex.tokenize |> Parser.parse in
  if !debug then Ast.print ast;
  let var, v = Eval.eval env ast in
  match v with
  | Nop -> ()
  | _ ->
    Env.set env "ans" v;
    printf "%s: %s = %s\n"
      var (Type.to_string @@ Value.typeof v) (Value.to_string v)

exception Reset_line (* used to indicate ^C is pressed *)

let init_repl () =
  Env.set g "ans" (Int 0);
  (* treat Ctrl-C as to reset line *)
  let reset_line _ = raise Reset_line in
  Sys.(set_signal sigint (Signal_handle reset_line))

(* simple REPL with error handling *)
let rec repl env : unit =
  try rep env; repl env with
  | Exit | End_of_file (* Ctrl-D *) -> ()
  | Reset_line -> printf "\n"; repl env
  | e -> print_error e; repl env

let () =
  init_repl ();
  printf "Configurable Evaluator %s\n" version; (* banner *)
  repl g