(*  Predefined set of functions. *)

include Omake_pos.Make (struct let name = "Omake_eval" end);;


let debug_eval =
  Lm_debug.create_debug (**)
    { debug_name = "debug-eval";
      debug_description = "Debug the evaluator";
      debug_value = false
    }

let print_ast =
  Lm_debug.create_debug (**)
    { debug_name = "print-ast";
      debug_description = "Print the AST after parsing";
      debug_value = false
    }

let print_ir =
  Lm_debug.create_debug (**)
    { debug_name = "print-ir";
      debug_description = "Print the IR after parsing";
      debug_value = false
    }

let print_rules =
  Lm_debug.create_debug (**)
    { debug_name = "print-rules";
      debug_description = "Print the rules after evaluation";
      debug_value = false
    }

let print_files =
  Lm_debug.create_debug (**)
    { debug_name = "print-files";
      debug_description = "Print the files as they are read";
      debug_value = false
    }

let bool_of_string s =
  match String.lowercase_ascii s with
  | ""
  | "0"
  | "no"
  | "nil"
  | "false"
  | "undefined" ->
    false
  | _ ->
    true

(*
 * For now, use a bogu location for parameters.
 *)
(* let param_loc = Lm_location.bogus_loc "Omake_eval.param" *)

(*
 * Including files.
 *)

(************************************************************************
 * Utilities.
*)
let raise_uncaught_exception pos = function
  | Sys.Break
  | Omake_value_type.OmakeException _
  | Omake_value_type.OmakeFatal _
  | Omake_value_type.OmakeFatalErr _
  | Omake_value_type.UncaughtException _ as exn ->
    raise exn
  | exn ->
    raise (Omake_value_type.UncaughtException (pos, exn))

(*
 * Add an optional quote.
 *)
let buffer_add_quote buf = function
    Some c -> Buffer.add_char buf c
  | None -> ()

(*
 * The various forms of empty values.
 *)
let rec is_empty_value ( v : Omake_value_type.t) =
  match v with
  | ValNone
  | ValWhite _
  | ValString ""
  | ValData ""
  | ValQuote []
  | ValArray []
  | ValRules [] ->
    true
  | ValSequence vl ->
    List.for_all is_empty_value vl
  | ValObject obj ->
    (try is_empty_value 
        (Omake_env.venv_find_field_internal_exn obj Omake_symbol.builtin_sym) with
      Not_found ->
      false)
  | ValInt _
  | ValFloat _
  | ValData _
  | ValQuote _
  | ValQuoteString _
  | ValString _
  | ValArray _
  | ValMaybeApply _
  | ValFun _
  | ValFunCurry _
  | ValPrim _
  | ValPrimCurry _
  | ValRules _
  | ValNode _
  | ValDir _
  | ValStringExp _
  | ValBody _
  | ValMap _
  | ValChannel _
  | ValClass _
  | ValCases _
  | ValOther _
  | ValDelayed _
  | ValVar _ ->
    false

(*
 * Check whether a value has an embedded array.
 *)
let rec is_array_value (v : Omake_value_type.t) =
  match v with
  | ValArray _ ->
    true
  | ValSequence [v]
  | ValQuote [v] ->
    is_array_value v
  | ValObject obj ->
    (try
      match Omake_env.venv_find_field_internal_exn obj Omake_symbol.builtin_sym with
        ValArray _ -> true
      | _ -> false
    with
      Not_found ->
      false)
  | ValNone
  | ValInt _
  | ValFloat _
  | ValData _
  | ValQuote _
  | ValQuoteString _
  | ValWhite _
  | ValString _
  | ValMaybeApply _
  | ValSequence _
  | ValFun _
  | ValFunCurry _
  | ValPrim _
  | ValPrimCurry _
  | ValRules _
  | ValNode _
  | ValDir _
  | ValStringExp _
  | ValBody _
  | ValMap _
  | ValChannel _
  | ValClass _
  | ValCases _
  | ValOther _
  | ValVar _
  | ValDelayed _ ->
    false

(*
 * Determine when an application is ready from its arity.
 *)
type partial_arity =
  | FullArity    of Omake_value_type.t list * Omake_value_type.t list
  | PartialArity of Omake_ir.arity * Omake_value_type.t list

let rec concat_n_args args1 args2 n =
  if n = 0 then
    FullArity (List.rev args1, args2)
  else
    match args2 with
      arg :: args2 ->
      concat_n_args (arg :: args1) args2 (n - 1)
    | [] ->
      raise (Invalid_argument "concat_n_args")

let arity_apply_args ( arity : Omake_ir.arity ) args1 args2 =
  let len = List.length args2 in
  match arity with
  | ArityRange (min, max) ->
    if len < min then
      let args = List.rev_append args2 args1 in
      PartialArity (ArityRange (min - len, max - len), args)
    else if len < max then
      let args = List.rev_append args1 args2 in
      FullArity (args, [])
    else
      concat_n_args args1 args2 max
  | ArityExact i ->
    if len < i then
      let args = List.rev_append args2 args1 in
      PartialArity ( ArityExact (i - len), args)
    else
      concat_n_args args1 args2 i
  | ArityNone ->
    FullArity ([], List.rev_append args1 args2)
  | ArityAny | ArityConstructor ->
    FullArity (List.rev_append args1 args2, [])

(************************************************************************
 * Compiling utilities.
*)
let postprocess_ir venv ( ir : Omake_ir.t) =
  let () =
    if Lm_debug.debug print_ir then
      Format.eprintf "@[<v 3>IR1:@ %a@]@." 
        Omake_ir_print.pp_print_exp ir.ir_exp
  in
  let ir = { ir with ir_exp = Omake_ir_semant.build_prog venv ir.ir_exp } in
  let () =
    if Lm_debug.debug print_ir then
      Format.eprintf "@[<v 3>IR2:@ %a@]@." Omake_ir_print.pp_print_exp ir.ir_exp
  in
  ir

(**  Parse and evaluate a file. *)
let rec parse_ir 
    (venv : Omake_env.t) 
    (scope : Omake_env.include_scope) 
    (node : Omake_node.Node.t) : Omake_ir.t =
  let filename = Omake_node.Node.fullname node in
  let ast = Omake_ast_lex.parse_ast filename in
  let () =
    if Lm_debug.debug print_ast then
      Format.eprintf "@[<v 3>AST (initial):@ %a@]@." Omake_ast_print.pp_print_prog ast
  in
  let ast = Omake_exp_lex.compile_prog ast in
  let () =
    if Lm_debug.debug print_ast then
      Format.eprintf "@[<v 3>AST %a:@ %a@]@." Omake_node.pp_print_node node Omake_ast_print.pp_print_prog ast
  in
  let vars = Omake_env.venv_include_scope venv scope in
  let _senv, ir = Omake_ir_ast.compile_prog (Omake_ir_ast.penv_of_vars (open_ir venv) venv node vars) ast in
  postprocess_ir venv ir

(*
 * When constructing a path, the relative filenames
 * should be auto-rehash.
 *
 *    values  : the path
 *    dirname : the subdirectory to search (often ".")
 *)
and path_of_values_select venv pos (values : Omake_value_type.t list) dirname =
  let rec collect groups auto_rehash items (values : Omake_value_type.t list) =
    match values with
    | v :: values ->
      let rehash_flag, dir =
        match v with
        | ValDir dir ->
          false, dir
        | ValNode _ ->
          let dir = Omake_env.venv_intern_dir venv (string_of_value venv pos v) in
          false, dir
        | _ ->
          let s = string_of_value venv pos v in
          let rehash_flag = not (Lm_filename_util.is_absolute s) in
          let dir = Omake_env.venv_intern_dir venv s in
          rehash_flag, dir
      in
      let dir = Omake_node.Dir.chdir dir dirname in
      let groups, items =
        if rehash_flag <> auto_rehash && items <> [] then
          (auto_rehash, List.rev items) :: groups, [dir]
        else
          groups, dir :: items
      in
      collect groups rehash_flag items values
    | [] ->
      if items <> [] then
        (auto_rehash, List.rev items) :: groups
      else
        groups
  in
  List.rev (collect [] false [] values)

and path_of_values_rehash venv pos values dirname =
  let dir_of_value (v : Omake_value_type.t) =
    let dir =
      match v with
      | ValDir dir ->
        dir
      | _ ->
        Omake_env.venv_intern_dir venv (string_of_value venv pos v)
    in
    Omake_node.Dir.chdir dir dirname
  in
  [true, List.map dir_of_value values]

and path_of_values venv pos values dirname =
  let auto_rehash =
    try bool_of_value venv pos 
        (Omake_env.venv_find_var_exn venv Omake_var.auto_rehash_var) with
      Not_found ->
      false
  in
  let f =
    if auto_rehash then
      path_of_values_rehash
    else
      path_of_values_select
  in
  f venv pos values dirname

(*
 * Open the file.
 * Get the IR and return the vars.
 *)
and find_include_file venv pos loc filename =
  let pos = string_pos "find_include_file" pos in
  let cache = Omake_env.venv_cache venv in
  if not (Filename.is_relative filename) || not (Filename.is_implicit filename) then
    let fullname = filename ^ Omake_state.omake_file_suffix in
    let node1 = Omake_env.venv_intern venv PhonyProhibited fullname in
    if Omake_cache.exists cache node1 then
      node1
    else
      let node2 = Omake_env.venv_intern venv PhonyProhibited filename in
      if Omake_cache.exists cache node2 then
        node2
      else
        let print_error buf =
          Format.fprintf buf "@[<hv 3>include file not found, neither file exists:@ %a@ %a@]" (**)
            Omake_node.pp_print_node node1
            Omake_node.pp_print_node node2
        in
        raise (Omake_value_type.OmakeException (loc_pos loc pos, LazyError print_error))
  else
    let dirname = Filename.dirname filename in
    let basename = Filename.basename filename in
    let fullname = basename ^ Omake_state.omake_file_suffix in
    let path = Omake_env.venv_find_var venv pos loc Omake_var.omakepath_var in
    let full_path = values_of_value venv pos path in
    let path = path_of_values venv pos full_path dirname in
    let cache = Omake_env.venv_cache venv in
    let listing = Omake_cache.ls_path cache path in
    try
      match Omake_cache.listing_find cache listing fullname with
        DirEntry dir ->
        raise (Omake_value_type.OmakeException (loc_pos loc pos, StringDirError ("is a directory", dir)))
      | NodeEntry node ->
        node
    with
      Not_found ->
      try
        match Omake_cache.listing_find cache listing basename with
          DirEntry dir ->
          raise (Omake_value_type.OmakeException (loc_pos loc pos, StringDirError ("is a directory", dir)))
        | NodeEntry node ->
          node
      with
        Not_found ->
        let print_error buf =
          Format.fprintf buf "@[<hv 3>include file %s not found in OMAKEPATH@ (@[<hv3>OMAKEPATH[] =%a@])@]" (**)
            filename
            Omake_value_print.pp_print_value_list full_path
        in
        raise (Omake_value_type.OmakeException (loc_pos loc pos, LazyError print_error))

and open_ir venv filename pos loc =
  let pos = string_pos "open_ir" pos in
  let source = find_include_file venv pos loc filename in
  let ir  : Omake_ir.t = compile_ir venv Omake_env.IncludePervasives pos loc source in
  if !print_ir then begin
    Format.eprintf "@[<v 3>Vars: %a" Omake_node.pp_print_node source;
    Lm_symbol.SymbolTable.iter (fun v info ->
      Format.eprintf "@ %a = %a" Lm_symbol.pp_print_symbol v Omake_ir_print.pp_print_var_info info) ir.ir_vars;
    Format.eprintf "@]@."
  end;
  source, ir.ir_vars

(*
 * The include file contains the IR for the file.
 * Try to load the old entry.
 * If it fails, compile the file and save the new entry.
 *)
and compile_add_ir_info venv scope pos _ source info =
  let _pos = string_pos "compile_add_ir_info" pos in
  try Omake_env.Static.get_ir info with
    Not_found ->
    let ir = parse_ir venv scope source in
    Omake_env.Static.add_ir info ir;
    ir

and compile_ir_info venv scope pos loc source info =
  let _pos = string_pos "compile_ir_info" pos in
  try Omake_env.Static.find_ir info with
    Not_found ->
    Omake_env.Static.rewrite info (compile_add_ir_info venv scope pos loc source)

and compile_ir venv scope pos loc source =
  let pos = string_pos "compile_ir" pos in
  (*
       * Try to get a cached copy.
       *)
  try Omake_env.venv_find_ir_file_exn venv source with
    Not_found ->
    let ir =
      (*
                * Open the database.
                *)
      try Omake_env.Static.read venv source (compile_ir_info venv scope pos loc source)
      with Not_found ->
        raise (Omake_value_type.OmakeException (loc_pos loc pos, StringNodeError ("can't open IR", source)))
    in
    Omake_env.venv_add_ir_file venv source ir;
    ir

(*
 * The object file contains the evaluated file.
 *)
and compile_add_object_info compile _ pos source info =
  let _pos = string_pos "compile_add_object_info_info" pos in
  try Omake_env.Static.get_object info with
    Not_found ->
    let obj = compile info source in
    Omake_env.Static.add_object info obj;
    obj

(*
 * Try to load the old entry.
 * If it fails, compile the file and save the new entry.
 *)
and compile_object_info compile venv pos source info =
  let _pos = string_pos "compile_object_info" pos in
  try Omake_env.Static.find_object info with
    Not_found ->
    Omake_env.Static.rewrite info (compile_add_object_info compile venv pos source)

and compile_object compile venv pos loc source =
  let pos = string_pos "compile_ast" pos in

  (*
       * Try to get a cached copy.
       *)
  try Omake_env.venv_find_object_file_exn venv source with
    Not_found ->
    let obj =
      (*
                * Open the database.
                *)
      try Omake_env.Static.read venv source (compile_object_info compile venv pos source)
      with Not_found ->
        raise (Omake_value_type.OmakeException (loc_pos loc pos, StringNodeError ("can't open object", source)))
    in
    Omake_env.venv_add_object_file venv source obj;
    obj

(************************************************************************
 * Value operations.
*)

(*
 * Get the string representation of a value.
 * It not legal to convert an array to a string.
 *)
and string_of_value venv pos (v : Omake_value_type.t) =
  let pos = string_pos "string_of_value" pos in
  let scratch_buf = Buffer.create 32 in
  let rec collect (v : Omake_value_type.t) =
    match eval_prim_value venv pos v with
    (* Values that expand to nothing *)
    | ValNone
    | ValFun _
    | ValFunCurry _
    | ValPrim _
    | ValPrimCurry _
    | ValRules _
    | ValBody _
    | ValMap _
    | ValObject _
    | ValChannel _
    | ValClass _
    | ValCases _
    | ValOther _
    | ValArray []
    | ValVar _ ->
      ()
    | ValSequence vl ->
      List.iter collect vl
    | ValQuote vl ->
      string_of_quote_buf scratch_buf venv pos vl
    | ValQuoteString (c, vl) ->
      Buffer.add_char scratch_buf c;
      string_of_quote_buf scratch_buf venv pos vl;
      Buffer.add_char scratch_buf c
    | ValArray [v] ->
      collect v
    | ValArray vl ->
      let print_error buf =
        Format.fprintf buf "@[<v 3>Array value where string expected:";
        Format.fprintf buf "@ Use the $(string ...) function if you really want to do this";
        Format.fprintf buf "@ @[<v 3>The array has length %d:" (List.length vl);
        ignore (List.fold_left (fun index v ->
            Format.fprintf buf "@ @[<hv 3>[%d] =@ %a@]" index Omake_value_print.pp_print_value v;
            succ index) 0 vl);
        Format.fprintf buf "@]@]@."
      in
      raise (Omake_value_type.OmakeException (pos, LazyError print_error))
    | ValInt i ->
      Buffer.add_string scratch_buf (string_of_int i)
    | ValFloat x ->
      Buffer.add_string scratch_buf (string_of_float x)
    | ValData s
    | ValWhite s
    | ValString s ->
      Buffer.add_string scratch_buf s
    | ValDir dir2 ->
      Buffer.add_string scratch_buf (Omake_env.venv_dirname venv dir2)
    | ValNode node ->
      Buffer.add_string scratch_buf (Omake_env.venv_nodename venv node)
    | ValStringExp _
    | ValMaybeApply _
    | ValDelayed _ ->
      raise (Invalid_argument "string_of_value")  in
  collect v;
  Buffer.contents scratch_buf

(*
 * Collect the values in a quotation into a string.
 * Even array values are flattened without warning.
 *)
and string_of_quote venv pos c vl =
  let pos = string_pos "string_of_quote" pos in
  let scratch_buf = Buffer.create 32 in
  buffer_add_quote scratch_buf c;
  string_of_quote_buf scratch_buf venv pos vl;
  buffer_add_quote scratch_buf c;
  Buffer.contents scratch_buf

and string_of_quote_buf scratch_buf venv pos vl =
  let pos = string_pos "string_of_quote_buf" pos in
  let rec collect v =
    match (eval_value venv pos v : Omake_value_type.t) with
      (* Values that expand to nothing *)
    | ValNone
    | ValFun _
    | ValFunCurry _
    | ValPrim _
    | ValPrimCurry _
    | ValRules _
    | ValBody _
    | ValMap _
    | ValObject _
    | ValChannel _
    | ValClass _
    | ValCases _
    | ValOther _
    | ValArray []
    | ValVar _ ->
      ()
    | ValSequence vl
    | ValQuote vl ->
      List.iter collect vl
    | ValQuoteString (c, vl) ->
      Buffer.add_char scratch_buf c;
      List.iter collect vl;
      Buffer.add_char scratch_buf c
    | ValArray [v] ->
      collect v
    | ValArray vl ->
      collect_array vl
    | ValInt i ->
      Buffer.add_string scratch_buf (string_of_int i)
    | ValFloat x ->
      Buffer.add_string scratch_buf (string_of_float x)
    | ValData s
    | ValWhite s
    | ValString s ->
      Buffer.add_string scratch_buf s
    | ValDir dir2 ->
      Buffer.add_string scratch_buf (Omake_env.venv_dirname venv dir2)
    | ValNode node ->
      Buffer.add_string scratch_buf (Omake_env.venv_nodename venv node)
    | ValStringExp _
    | ValMaybeApply _
    | ValDelayed _ ->
      raise (Invalid_argument "string_of_value")
  and collect_array vl =
    match vl with
      [v] ->
      collect v
    | v :: vl ->
      collect v;
      Buffer.add_char scratch_buf ' ';
      collect_array vl
    | [] ->
      ()
  in
  List.iter collect vl

(*
 * Get a list of values from the value.
 * Array elements are always special, and returned as an element.
 * We divide values into two classes:
 *    The "catenable" values are the values that can be concatenated to
 *    form a string.  These include: string, node, dir, int, float.
 *
 *    Nothing else can be concatenated with a string, and is always preserved
 *    in the value list.
 *)
and values_of_value venv pos v =
  let pos = string_pos "values_of_value" pos in

  (*
    * Convert a catenable value to a string
    *)
  let group tokens : Omake_value_type.t  = ValSequence tokens in
  let wrap_string s : Omake_value_type.t = ValString s in
  let wrap_data s   : Omake_value_type.t = ValData s in
  let wrap_token s  : Omake_value_type.t = ValData s in
  let lexer _ _ _   = None in
  let tokens = Lm_string_util.tokens_create_lexer ~lexer ~wrap_string ~wrap_data ~wrap_token ~group in

  (*
    * Array elements are always separate values.
    * The arrays are flattened.
    *)
  let rec collect_array tokens (vl : Omake_value_type.t list) vll =
    match vl, vll with
    | v :: vl, _ ->
      begin match eval_value venv pos v with
        ValArray el ->
        collect_array tokens el (vl :: vll)
      | ValSequence [v] ->
        collect_array tokens (v :: vl) vll
      | v ->
        collect_array (Lm_string_util.tokens_atomic tokens v) vl vll
      end
    | [], vl :: vll ->
      collect_array tokens vl vll
    | [], [] ->
      tokens
  in

  (*
    * Collect_string is used when we have seen whitespace
    * in a sequence.  Collect the values into the string buffer,
    * then parse the string into separate tokens.
    *)
  let rec collect tokens vl vll =
    match vl, vll with
    | v :: vl, _ ->
      let v : Omake_value_type.t = eval_catenable_value venv pos v in
      begin match v with
      | ValNone ->
        collect tokens vl vll

      (* Strings *)
      | ValWhite s
      | ValString s ->
        collect (Lm_string_util.tokens_string tokens s) vl vll
      | ValSequence el ->
        collect tokens el (vl :: vll)

      (* Other catenable values *)
      | ValData _
      | ValInt _
      | ValFloat _
      | ValDir _
      | ValNode _
      | ValQuote _
      | ValQuoteString _ ->
        collect (Lm_string_util.tokens_add tokens v) vl vll

      (* Atomic values *)
      | ValArray el ->
        collect (collect_array (Lm_string_util.tokens_break tokens) el []) vl vll
      | ValFun _
      | ValFunCurry _
      | ValPrim _
      | ValPrimCurry _
      | ValRules _
      | ValBody _
      | ValMap _
      | ValObject _
      | ValChannel _
      | ValClass _
      | ValCases _
      | ValOther _
      | ValVar _ ->
        collect (Lm_string_util.tokens_atomic tokens v) vl vll
      | ValStringExp _
      | ValMaybeApply _
      | ValDelayed _ ->
        raise (Omake_value_type.OmakeException (pos, StringValueError ("illegal application", v)))
      end
    | [], vl :: vll ->
      collect tokens vl vll
    | [], [] ->
      Lm_string_util.tokens_flush tokens
  in
  collect tokens [v] []

(*
 * Get a string list from the value.
 * This is always legal because arrays have been flattened.
 *)
and strings_of_value venv pos v =
  let values = values_of_value venv pos v in
  List.map (string_of_value venv pos) values

(*
 * Get a list of tokens from the value.
 * This is a lot like the previous function, but we use a lexer
 * for parsing special character sequences.
 *)
and tokens_of_value venv pos lexer v =
  let pos = string_pos "tokens_of_value" pos in

  (*
    * Convert a catenable value to a string
    *)
  let group tokens  = Omake_env.TokGroup tokens in
  let wrap_string s = Omake_env.TokString (ValString s) in
  let wrap_data s   = Omake_env.TokString (ValData s) in
  let wrap_token s  = Omake_env.TokToken s in
  let tokens = Lm_string_util.tokens_create_lexer ~lexer ~wrap_string ~wrap_data ~wrap_token ~group in

  (*
    * Array elements are always separate values.
    * The arrays are flattened.
    *)
  let rec collect_array (tokens : Omake_env.tok Lm_string_util.tokens) vl vll =
    match vl, vll with
      v :: vl, _ ->
      (match eval_value venv pos v with
        ValArray el ->
        collect_array tokens el (vl :: vll)
      | ValSequence [v] ->
        collect_array tokens (v :: vl) vll
      | v ->
        collect_array (Lm_string_util.tokens_atomic tokens (TokString v)) vl vll)
    | [], vl :: vll ->
      collect_array tokens vl vll
    | [], [] ->
      tokens
  in

  (*
    * Collect_string is used when we have seen whitespace
    * in a sequence.  Collect the values into the string buffer,
    * then parse the string into separate tokens.
    *)
  let rec collect (tokens : Omake_env.tok Lm_string_util.tokens) vl vll =
    match vl, vll with
      v :: vl, _ ->
      let v = eval_catenable_value venv pos v in
      (match v with
        ValNone ->
        collect tokens vl vll

      (* Strings *)
      | ValWhite s
      | ValString s ->
        collect (Lm_string_util.tokens_lex tokens s) vl vll
      | ValSequence el ->
        collect tokens el (vl :: vll)

      (* Other catenable values *)
      | ValData _
      | ValInt _
      | ValFloat _
      | ValDir _
      | ValNode _
      | ValQuote _ ->
        collect (Lm_string_util.tokens_add tokens (TokString v)) vl vll
      | ValQuoteString (_, v) ->
        collect (Lm_string_util.tokens_add tokens (TokString (ValQuote v))) vl vll

      (* Atomic values *)
      | ValArray el ->
        collect (collect_array (Lm_string_util.tokens_break tokens) el []) vl vll
      | ValFun _
      | ValFunCurry _
      | ValPrim _
      | ValPrimCurry _
      | ValRules _
      | ValBody _
      | ValMap _
      | ValObject _
      | ValChannel _
      | ValClass _
      | ValCases _
      | ValOther _
      | ValVar _ ->
        collect (Lm_string_util.tokens_atomic tokens (TokString v)) vl vll
      | ValStringExp _
      | ValMaybeApply _
      | ValDelayed _ ->
        raise (Omake_value_type.OmakeException (pos, StringValueError ("illegal application", v))))
    | [], vl :: vll ->
      collect tokens vl vll
    | [], [] ->
      Lm_string_util.tokens_flush tokens
  in
  collect tokens [v] []

(*
 * Flatten the value list into a arg_string list.
 * Basically just concatenate all the values, being
 * careful to preserve quoting.  In addition, we want to
 * concatenate adjacent strings of the same type.
 *)
and arg_of_values venv pos vl =
  let pos = string_pos "arg_of_values" pos in

  (*
    * Flatten all sequences.
    *)
  let rec collect is_quoted tokens vl vll =
    match vl, vll with
      v :: vl, _ ->
      let v = eval_value venv pos v in
      (match v with
        ValNone ->
        collect is_quoted tokens vl vll

      (* Strings *)
      | ValWhite s
      | ValString s ->
        let tokens =
          if is_quoted then
            Omake_command.arg_buffer_add_data tokens s
          else
            Omake_command.arg_buffer_add_string tokens s
        in
        collect is_quoted tokens vl vll
      | ValData s ->
        collect is_quoted (Omake_command.arg_buffer_add_data tokens s) vl vll
      | ValSequence el ->
        collect is_quoted tokens el (vl :: vll)
      | ValArray el ->
        collect true tokens el (vl :: vll)

      (* Other quoted values *)
      | ValInt _
      | ValFloat _
      | ValDir _
      | ValNode _
      | ValQuote _
      | ValQuoteString _
      | ValFun _
      | ValFunCurry _
      | ValPrim _
      | ValPrimCurry _
      | ValRules _
      | ValBody _
      | ValMap _
      | ValObject _
      | ValChannel _
      | ValClass _
      | ValCases _
      | ValOther _
      | ValVar _ ->
        let tokens = Omake_command.arg_buffer_add_data tokens (string_of_value venv pos v) in
        collect is_quoted tokens vl vll

      (* Illegal values *)
      | ValStringExp _
      | ValMaybeApply _
      | ValDelayed _ ->
        raise (Omake_value_type.OmakeException (pos, StringValueError ("illegal application", v))))
    | [], vl :: vll ->
      collect is_quoted tokens vl vll
    | [], [] ->
      Omake_command.arg_buffer_contents tokens
  in
  collect false Omake_command.arg_buffer_empty vl []

and argv_of_values venv pos vll =
  List.map (arg_of_values venv pos) vll

(*
 * Boolean test.
 * Arrays are always true.
 *)
and bool_of_value venv pos v =
  let values = values_of_value venv pos v in
  match values with
    []
  | [ValNone]
  | [ValWhite _] ->
    false
  | [ValInt i] ->
    i <> 0
  | [ValFloat x] ->
    x <> 0.0
  | [ValData s]
  | [ValString s] ->
    bool_of_string s
  | [ValQuote vl] ->
    bool_of_string (string_of_quote venv pos None vl)
  | _ ->
    true

(*
 * The value should be a directory.
 *)
and file_of_value venv pos file =
  let pos = string_pos "file_of_value" pos in
  let file = eval_prim_value venv pos file in
  match file with
    ValNode node ->
    node
  | ValDir dir ->
    Omake_node.Node.node_of_dir dir
  | ValData _
  | ValString _
  | ValSequence _
  | ValQuote _
  | ValQuoteString _
  | ValInt _
  | ValFloat _ ->
    Omake_env.venv_intern venv PhonyExplicit (string_of_value venv pos file)
  | ValArray _
  | ValNone
  | ValWhite _
  | ValMaybeApply _
  | ValFun _
  | ValFunCurry _
  | ValPrim _
  | ValPrimCurry _
  | ValRules _
  | ValStringExp _
  | ValBody _
  | ValMap _
  | ValObject _
  | ValChannel _
  | ValClass _
  | ValCases _
  | ValVar _
  | ValDelayed _
  | ValOther _ ->
    raise (Omake_value_type.OmakeException (pos, StringError "illegal value"))

(*
 * Be lazy about concatenating arrays, to
 * avoid quadratic behavior.
 *)
and append_arrays venv pos a1 a2 : Omake_value_type.t  =
  if is_array_value a1 then
    if is_array_value a2 then
      ValArray [a1; a2]
    else
      let al = values_of_value venv pos a2 in
      ValArray (a1 :: al)
  else if is_array_value a2 then
    let al = values_of_value venv pos a1 in
    ValArray [ValArray al; a2]
  else if is_empty_value a1 then
    a2
  else if is_empty_value a2 then
    a1
  else
    ValSequence [a1; ValWhite " "; a2]

(************************************************************************
 * Evaluation.
*)

(*
 * Eval a static value.
 *)
and eval_value_static venv pos key v =
  let pos = string_pos "eval_value_static" pos in
  let obj =
    match Omake_env.venv_find_static_info venv pos key with
      StaticValue obj ->
      obj
    | StaticRule 
        { srule_env  = venv;
          srule_deps = deps;
          srule_vals = values;
          srule_exp  = e;
          srule_static ; 
          _
        } -> 
      let values = List.flatten (List.map (values_of_value venv pos) values) in
      let values = List.map (eval_prim_value venv pos) values in
      let digest = Omake_command_digest.digest_of_exp pos values e in
      let cache = Omake_env.venv_cache venv in
      let obj =
        (* Try to fetch the value from the memo *)
        try Omake_cache.find_value cache key srule_static deps digest with
          Not_found ->
          (* Finally, if we don't have a value, evaluate the rule.
           * Prevent recursive calls *)
          let () = Omake_env.venv_set_static_info venv key (StaticValue Omake_value_util.empty_obj) in
          let venv, v = eval_exp venv Omake_value_type.ValNone e in
          let obj = eval_object venv pos v in
          Omake_cache.add_value cache key srule_static deps digest (MemoSuccess obj);
          obj
      in
      Omake_env.venv_set_static_info venv key (StaticValue obj);
      obj
  in
  Omake_env.venv_find_field_internal obj pos v

and eval_value_delayed venv pos (p : Omake_value_type.value_delayed ref) =
  match !p with
  | ValValue v ->
    eval_value_core venv pos v
  | ValStaticApply (key, v) ->
    let v = eval_value_static venv pos key v in
    p := ValValue v;
    eval_value_core venv pos v

(*
 * Unfold the outermost application to get a real value.
 *)
and eval_value_core venv pos v : Omake_value_type.t =
  match v with
  | ValMaybeApply (loc, v) ->
    let v =
      try Some (Omake_env.venv_find_var_exn venv v) with
        Not_found ->
        None
    in
    begin match v with
    | Some v -> ValArray [eval_value_core venv pos (eval_var venv pos loc v)]
    | None -> ValNone
    end
  | ValDelayed p ->
    eval_value_delayed venv pos p
  | ValSequence [v] ->
    eval_value_core venv pos v
  | ValStringExp (env, e) ->
    let v = eval_string_exp (Omake_env.venv_with_env venv env) pos e in
    eval_value_core venv pos v
  | _ ->
    v

and eval_value venv pos v =
  let pos = string_pos "eval_value" pos in
  eval_value_core venv pos v

and eval_single_value venv pos v =
  let pos = string_pos "eval_single_value" pos in
  match eval_value venv pos v with
    ValArray [v] ->
    eval_single_value venv pos v
  | _ ->
    v

and eval_prim_value venv pos v : Omake_value_type.t =
  let pos = string_pos "eval_prim_value" pos in
  let v = eval_value venv pos v in
  match v with
    ValArray [v] ->
    eval_prim_value venv pos v
  | ValObject obj ->
    (try Omake_env.venv_find_field_internal_exn obj Omake_symbol.builtin_sym with
      Not_found ->
      v)
  | _ ->
    v

(*
 * The values are being flattened, so expand all sequences.
 *)
and eval_catenable_value venv pos v =
  let pos = string_pos "eval_catenable_value" pos in
  let v = eval_value venv pos v in
  match v with
    ValObject obj ->
    (try
      match Omake_env.venv_find_field_internal_exn obj Omake_symbol.builtin_sym with
        ValNone
      | ValWhite _
      | ValString _
      | ValSequence _
      | ValData _
      | ValInt _
      | ValFloat _
      | ValDir _
      | ValNode _
      | ValArray _
      | ValRules _ as v ->
        v
      | _ ->
        v
    with
      Not_found ->
      v)
  | _ ->
    v

(*
 * Evaluate the value in a function body.
 * Expand all applications.
 *)
and eval_body_value venv pos v : Omake_value_type.t =
  match (eval_value venv pos v : Omake_value_type.t) with
  | ValSequence sl ->
    ValSequence (List.map (eval_body_value venv pos) sl)
  | ValArray sl ->
    ValArray (List.map (eval_body_value venv pos) sl)
  | ValBody (_, [], [], body, _) ->
    snd (eval_sequence_exp venv pos body)
  | ValNone
  | ValInt _
  | ValFloat _
  | ValData _
  | ValWhite _
  | ValString _
  | ValQuote _
  | ValQuoteString _
  | ValDir _
  | ValNode _
  | ValFun _
  | ValFunCurry _
  | ValPrim _
  | ValPrimCurry _
  | ValRules _
  | ValMap _
  | ValObject _
  | ValChannel _
  | ValClass _
  | ValCases _
  | ValVar _
  | ValOther _ as result ->
    result
  | ValBody _    (* it is an error when keyword/params <> [] *)
  | ValStringExp _
  | ValMaybeApply _
  | ValDelayed _ ->
    raise (Invalid_argument "eval_body_value")

and eval_body_exp venv pos x v : (Omake_env.t * Omake_value_type.t) =
  match (eval_value venv pos v : Omake_value_type.t) with
  | ValSequence sl ->
    venv, ValSequence (List.map (eval_body_value venv pos) sl)
  | ValArray sl ->
    venv, ValArray (List.map (eval_body_value venv pos) sl)
  | ValBody (_, [], [], body, export) ->
    eval_sequence_export venv pos x body export
  | ValNone
  | ValInt _
  | ValFloat _
  | ValData _
  | ValQuote _
  | ValQuoteString _
  | ValWhite _
  | ValString _
  | ValDir _
  | ValNode _
  | ValFun _
  | ValFunCurry _
  | ValPrim _
  | ValPrimCurry _
  | ValRules _
  | ValMap _
  | ValObject _
  | ValChannel _
  | ValClass _
  | ValCases _
  | ValVar _
  | ValOther _ as result ->
    venv, result
  | ValBody _    (* it is an error when keyword/params <> [] *)
  | ValStringExp _
  | ValMaybeApply _
  | ValDelayed _ ->
    raise (Invalid_argument "eval_body_exp")

(*
 * Evaluate a variable.
 * It is fine for the variable to evaluate to a function.
 * But if the function has arity 0, then evaluate it.
 *)
and eval_var venv pos loc v =
  match v with
  | ValFun (env, _, [], body, _) ->
    let venv = Omake_env.venv_with_env venv env in
    let _, result = eval_sequence venv pos Omake_value_type.ValNone body in
    result
  | ValFunCurry (env, args, _, [], body, _, []) ->
    let venv = Omake_env.venv_with_partial_args venv env args in
    let _, result = eval_sequence venv pos ValNone body in
    result
  | ValFunCurry (env, args, _, [], body, export, kargs) ->
    (* XXX: verify that we should pass forward the exports *)
    let venv_new = Omake_env.venv_with_partial_args venv env args in
    let venv_new, v = eval_sequence venv_new pos ValNone body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    eval_apply venv pos loc v [] kargs
  | ValPrim (_, _, ApplyEmpty, f) ->
    snd (Omake_env.venv_apply_prim_fun f venv pos loc [] [])
  | _ ->
    v

(*
 * Evaluate a key.
 *)
and eval_key venv pos loc v =
  try
    let map = eval_map venv pos (Omake_env.venv_find_var_exn venv Omake_var.map_field_var) in
    Omake_env.venv_map_find map pos (ValData v)
  with
    Not_found ->
    raise (Omake_value_type.OmakeException (loc_pos loc pos, UnboundKey v))

(*
 * Evaluate an application.
 *)
and eval_apply venv pos loc v args kargs =
  let pos = string_pos "eval_apply" pos in
  match eval_value venv pos v with
    ValFun (env, keywords, params, body, _) ->
    let venv = Omake_env.venv_add_args venv pos loc env params args keywords kargs in
    let _, result = eval_sequence_exp venv pos body in
    result
  | ValFunCurry (env, pargs, keywords, params, body, export, kargs1) ->
    let venv_new, args, kargs = Omake_env.venv_add_curry_args venv pos loc env pargs params args keywords kargs1 kargs in
    let venv_new, v = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    eval_apply venv pos loc v args kargs
  | ValPrim (_, _, _, f) ->
    snd (Omake_env.venv_apply_prim_fun f venv pos loc args kargs)
  | ValPrimCurry (_, _, f, args1, kargs1) ->
    snd (Omake_env.venv_apply_prim_fun f venv pos loc (List.rev_append args1 args) (List.rev_append kargs1 kargs))
  | ValBody (env, keywords, params, body, exports) when keywords <> [] || params <> [] ->
      let v = Omake_value_type.ValFun(env, keywords, params, body, exports) in
      eval_apply venv pos loc v args kargs
  | v ->
    if args = [] && kargs = [] then
      v
    else
      let print_error buf =
        Format.fprintf buf "@[<v 3>illegal function application:@ @[<hv 3>function:@ %a@]" Omake_value_print.pp_print_value v;
        List.iter (fun arg ->
          Format.fprintf buf "@ @[<hv 3>arg = %a@]" Omake_value_print.pp_print_value arg) args;
        List.iter (fun (v, arg) ->
          Format.fprintf buf "@ @[<hv 3>%a = %a@]" Lm_symbol.pp_print_symbol v Omake_value_print.pp_print_value arg) kargs;
        Format.fprintf buf "@]"
      in
      raise (Omake_value_type.OmakeException (pos, LazyError print_error))

(*
 * Evaluate an application with string arguments.
 *)
and eval_apply_string_exp venv venv_obj pos loc v args kargs =
  let pos = string_pos "eval_apply_string_exp" pos in
  match eval_value venv pos v with
    ValFun (env, keywords, params, body, _) ->
    let args = List.map (eval_string_exp venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_string_exp venv pos s) kargs in
    let venv_new = Omake_env.venv_add_args venv_obj pos loc env params args keywords kargs in
    let _, result = eval_sequence_exp venv_new pos body in
    result
  | ValFunCurry (env, pargs, keywords, params, body, export, kargs1) ->
    let args = List.map (eval_string_exp venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_string_exp venv pos s) kargs in
    let venv_new, args, kargs = Omake_env.venv_add_curry_args venv_obj pos loc env pargs params args keywords kargs1 kargs in
    let venv_new, v = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    eval_apply venv pos loc v args kargs
  | ValPrim (_, be_eager, _, f) ->
    let args = List.map (eval_prim_arg_exp be_eager venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_prim_arg_exp true venv pos s) kargs in
    snd (Omake_env.venv_apply_prim_fun f venv_obj pos loc args kargs)
  | ValPrimCurry (_, be_eager, f, args1, kargs1) ->
    let args = List.map (eval_prim_arg_exp be_eager venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_prim_arg_exp true venv pos s) kargs in
    snd (Omake_env.venv_apply_prim_fun f venv_obj pos loc (List.rev_append args1 args) (List.rev_append kargs1 kargs))
  | ValBody (env, keywords, params, body, exports) when keywords <> [] || params <> [] ->
      let v = Omake_value_type.ValFun(env, keywords, params, body, exports) in
      eval_apply_string_exp venv venv_obj pos loc v args kargs
  | v ->
    if args = [] && kargs = [] then
      v
    else
      let print_error buf =
        Format.fprintf buf "@[<v 3>illegal function application:@ @[<hv 3>function:@ %a@]" Omake_value_print.pp_print_value v;
        List.iter (fun arg ->
          Format.fprintf buf "@ @[<hv 3>arg = %a@]" Omake_ir_print.pp_print_string_exp arg) args;
        List.iter (fun (v, arg) ->
          Format.fprintf buf "@ @[<hv 3>%a = %a@]" Lm_symbol.pp_print_symbol v Omake_ir_print.pp_print_string_exp arg) kargs;
        Format.fprintf buf "@]"
      in
      raise (Omake_value_type.OmakeException (pos, LazyError print_error))

(*
 * Get a function from a value.
 *)
and eval_fun ?(caller_env=false) venv pos v =
  match eval_value venv pos v with
    ValFun (env, keywords, params, body, export) ->
    let f venv pos loc args kargs =
      let venv_new = Omake_env.venv_add_args venv pos loc env params args keywords kargs in
      let venv_new, result = eval_sequence_exp venv_new pos body in
      let venv = Omake_env.add_exports venv venv_new pos export in
      venv, result
    in
    true, f
  | ValFunCurry (env, pargs, keywords, params, body, export, kargs1) ->
    let f venv pos loc args kargs =
      let venv_new, args, kargs = Omake_env.venv_add_curry_args venv pos loc env pargs params args keywords kargs1 kargs in
      let venv_new, v = eval_sequence_exp venv_new pos body in
      let venv = Omake_env.add_exports venv venv_new pos export in
      eval_apply_export venv pos loc v args kargs
    in
    true, f
  | ValPrim (_, be_eager, _, f) ->
    be_eager, Omake_env.venv_apply_prim_fun f
  | ValPrimCurry (_, be_eager, f, args1, kargs1) ->
    let f venv pos loc args2 kargs2 =
      Omake_env.venv_apply_prim_fun f venv pos loc (List.rev_append args1 args2) (List.rev_append kargs1 kargs2)
    in
    be_eager, f
  | ValBody (defenv, keywords, params, body, export) ->
    let f venv pos loc args kargs =
      let env =  (* diff to ValFun! *)
        if caller_env then
          Omake_env.venv_get_env venv
        else
          defenv in
      let venv_new = Omake_env.venv_add_args venv pos loc env params args keywords kargs in
      let venv_new, result = eval_sequence_exp venv_new pos body in
      let venv = Omake_env.add_exports venv venv_new pos export in
      venv, result
    in
    true, f
  | _ ->
    raise (Omake_value_type.OmakeException (pos, StringError "not a function"))

and definition_env_of_fun venv pos v =
  match eval_value venv pos v with
    | ValFun (env, _, _, _, _) -> env
    | ValFunCurry (env, _, _, _, _, _, _) -> env
    | ValBody (env, _, _, _, _) -> env
    | ValPrim _
    | ValPrimCurry _ -> Omake_env.venv_get_env venv
    | _ ->
        raise (Omake_value_type.OmakeException (pos, StringError "not a function"))
  
          
(*
 * Get an object from a variable.
 *)
and eval_map venv pos x =
  match eval_value venv pos x with
    ValMap map ->
    map
  | _ ->
    raise (Omake_value_type.OmakeException (pos, StringError "not a map"))

and eval_object venv pos x =
  try eval_object_exn venv pos x with
    Not_found ->
    raise (Omake_value_type.OmakeException (pos, StringError "not an object"))

and eval_object_exn venv pos x =
  let x = eval_value venv pos x in
  match x with
    ValObject env ->
    env
  | ValInt _
  | ValOther (ValExitCode _) ->
    create_object venv x Omake_var.int_object_var
  | ValFloat _ ->
    create_object venv x Omake_var.float_object_var
  | ValData _
  | ValQuote _
  | ValQuoteString _ ->
    create_object venv x Omake_var.string_object_var
  | ValSequence _
  | ValWhite _
  | ValString _
  | ValNone ->
    create_object venv x Omake_var.sequence_object_var
  | ValArray _ ->
    create_object venv x Omake_var.array_object_var
  | ValFun _
  | ValFunCurry _
  | ValPrim _
  | ValPrimCurry _ ->
    create_object venv x Omake_var.fun_object_var
  | ValRules _ ->
    create_object venv x Omake_var.rule_object_var
  | ValNode _ ->
    create_object venv x Omake_var.file_object_var
  | ValDir _ ->
    create_object venv x Omake_var.dir_object_var
  | ValBody (env, keywords, params, body, exports) when keywords <> [] || params <> [] ->
      let x = Omake_value_type.ValFun(env, keywords, params, body, exports) in
      eval_object_exn venv pos x
  | ValBody _ ->
    create_object venv x Omake_var.body_object_var
  | ValChannel (InChannel, _) ->
    create_object venv x Omake_var.in_channel_object_var
  | ValChannel (OutChannel, _) ->
    create_object venv x Omake_var.out_channel_object_var
  | ValChannel (InOutChannel, _) ->
    create_object venv x Omake_var.in_out_channel_object_var
  | ValOther (ValLexer _) ->
    create_object venv x Omake_var.lexer_object_var
  | ValOther (ValParser _) ->
    create_object venv x Omake_var.parser_object_var
  | ValOther (ValLocation _) ->
    create_object venv x Omake_var.location_object_var
  | ValOther (ValEnv _) ->
    raise (Omake_value_type.OmakeException (pos, StringError "dereferenced <env>"))
  | ValClass _ ->
    raise (Invalid_argument "internal error: dereferenced $class")
  | ValCases _ ->
    raise (Invalid_argument "internal error: dereferenced cases")
  | ValMap _ ->
    create_map venv x Omake_var.map_object_var
  | ValVar _ ->
    create_object venv x Omake_var.var_object_var
  | ValStringExp _
  | ValMaybeApply _
  | ValDelayed _ ->
    raise (Invalid_argument "find_object")

and create_object venv x v =
  let obj = Omake_env.venv_find_var_exn venv v in
  match obj with
    ValObject env ->
    Omake_env.venv_add_field_internal env Omake_symbol.builtin_sym x
  | _ ->
    raise Not_found

and create_map venv x v =
  let obj = Omake_env.venv_find_var_exn venv v in
  match obj with
    ValObject env ->
    Omake_env.venv_add_field_internal env Omake_symbol.map_sym x
  | _ ->
    raise Not_found

(*
 * Field operations.
 *)
and eval_find_field_exn venv path obj pos vl =
  match vl with
    [v] ->
    path, obj, v
  | v :: vl ->
    let path, v = Omake_env.venv_find_field_path_exn venv path obj pos v in
    let obj = eval_object_exn venv pos v in
    eval_find_field_exn venv path obj pos vl
  | [] ->
    raise (Omake_value_type.OmakeException (pos, StringError "empty method name"))

and eval_find_field_aux venv envl pos v vl =
  match envl with
  | [env] ->
    let env = eval_object_exn venv pos env in
    let path : Omake_value_type.path = PathVar v in
    eval_find_field_exn venv path env pos vl
  | env :: envl ->
    let env = eval_object_exn venv pos env in
    (try eval_find_field_exn venv (PathVar v) env pos vl with
      Not_found ->
      eval_find_field_aux venv envl pos v vl)
  | [] ->
    raise Not_found

and eval_find_field venv pos _ v vl =
  let envl = Omake_env.venv_current_objects venv pos v in
  try eval_find_field_aux venv envl pos v vl with
    Not_found ->
    let pos = string_pos "eval_find_field" pos in
    raise (Omake_value_type.OmakeException (pos, UnboundMethod vl))

(*
 * Method paths.
 *)
and eval_with_method_exn venv path obj pos vl =
  match vl with
    [v] ->
    let v = Omake_env.venv_find_field_exn venv obj pos v in
    let venv = Omake_env.venv_with_object venv obj in
    venv, path, v
  | v :: vl ->
    let path, v = Omake_env.venv_find_field_path_exn venv path obj pos v in
    let obj = eval_object_exn venv pos v in
    eval_with_method_exn venv path obj pos vl
  | [] ->
    raise (Omake_value_type.OmakeException (pos, StringError "empty method name"))

and eval_with_method_aux venv envl pos v vl =
  match envl with
  | [env] ->
    let env = eval_object_exn venv pos env in
    eval_with_method_exn venv (PathVar v) env pos vl
  | env :: envl ->
    let env = eval_object_exn venv pos env in
    (try eval_with_method_exn venv (PathVar v) env pos vl with
      Not_found ->
      eval_with_method_aux venv envl pos v vl)
  | [] ->
    raise Not_found

and eval_with_method venv pos loc v vl =
  let envl = Omake_env.venv_current_objects venv pos v in
  try eval_with_method_aux venv envl pos v vl with
    Not_found ->
    let pos = string_pos "eval_with_method" (loc_pos loc pos) in
    raise (Omake_value_type.OmakeException (pos, UnboundMethod vl))

(*
 * Method paths.
 *)
and eval_find_method_exn venv obj pos vl =
  match vl with
    [v] ->
    let v = Omake_env.venv_find_field_exn venv obj pos v in
    let venv = Omake_env.venv_with_object venv obj in
    venv, v
  | v :: vl ->
    let v = Omake_env.venv_find_field_exn venv obj pos v in
    let obj = eval_object_exn venv pos v in
    eval_find_method_exn venv obj pos vl
  | [] ->
    raise (Omake_value_type.OmakeException (pos, StringError "empty method name"))

and eval_find_method_aux venv envl pos vl =
  match envl with
    [env] ->
    let env = eval_object_exn venv pos env in
    eval_find_method_exn venv env pos vl
  | env :: envl ->
    let env = eval_object_exn venv pos env in
    (try eval_find_method_exn venv env pos vl with
      Not_found ->
      eval_find_method_aux venv envl pos vl)
  | [] ->
    raise Not_found

and eval_find_method venv pos loc v vl =
  let envl = Omake_env.venv_current_objects venv pos v in
  try eval_find_method_aux venv envl pos vl with
    Not_found ->
    let pos = string_pos "eval_find_method" (loc_pos loc pos) in
    raise (Omake_value_type.OmakeException (pos, UnboundMethod vl))

(*
 * Check whether a field is defined.
 *)
and eval_defined_field_exn venv env pos vl =
  match vl with
    [v] ->
    Omake_env.venv_defined_field venv env v
  | v :: vl ->
    let v = Omake_env.venv_find_field_exn venv env pos v in
    let obj = eval_object_exn venv pos v in
    eval_defined_field_exn venv obj pos vl
  | [] ->
    raise (Omake_value_type.OmakeException (pos, StringError "empty method name"))

and eval_defined_field_aux venv envl pos vl =
  match envl with
    [env] ->
    let env = eval_object_exn venv pos env in
    eval_defined_field_exn venv env pos vl
  | env :: envl ->
    let env = eval_object_exn venv pos env in
    (try eval_defined_field_exn venv env pos vl with
      Not_found ->
      eval_defined_field_aux venv envl pos vl)
  | [] ->
    raise Not_found

and eval_defined_field venv pos _ v vl =
  let envl = Omake_env.venv_current_objects venv pos v in
  try eval_defined_field_aux venv envl pos vl with
    Not_found ->
    false

(*
 * Simplify a quoted value if possible.
 * Strings are concatenated.
 *)
and simplify_quote_val venv pos c (el : Omake_value_type.t list) : Omake_value_type.t  =
  let buf = Buffer.create 32 in
  let flush vl : Omake_value_type.t list =
    if Buffer.length buf = 0 then
      vl
    else
      let s = Buffer.contents buf in
      Buffer.clear buf;
      ValData s :: vl in
  let rec collect vl el =
    match el with
      | e :: el ->
          ( match eval_value venv pos e with
              | ValWhite s
              | ValString s
              | ValData s ->
                  Buffer.add_string buf s;
                  collect vl el
              | v ->
                  collect (v :: flush vl) el
          )
      | [] ->
          List.rev (flush vl) in
  let el = collect [] el in
  match c with
    | None ->
        (* GS: ValQuote just concatenates the inner elements without caring
           about sequences. Think about renaming to ValConcat.
         *)
        ( match el with
            | [ValData _ as e] -> e
            | _ -> ValQuote el
        )
    | Some c ->
        ValQuoteString (c, el)

(*
 * Evaluate a string expression.
 *)
and eval_string_exp venv pos s =
  let pos = string_pos "eval_string_exp" pos in
  match s with
    NoneString _ ->
    ValNone
  | IntString (_, i) ->
    ValInt i
  | FloatString (_, x) ->
    ValFloat x
  | WhiteString (_, s) ->
    ValWhite s
  | ConstString (_, s) ->
    ValString s
  | KeyApplyString (loc, v) ->
    eval_key venv pos loc v
  | FunString (_, opt_params, params, body, export) ->
    let opt_params = eval_keyword_param_value_list_exp venv pos opt_params in
    let env = Omake_env.venv_get_env venv in
    (* We use now ValBody with parameters instead of ValFun for translating
       "=>..." blocks. ValFun has the disadvantage that it resets the
       static (private) variables every time the function is invoked. This
       doesn't play nice with foreach and potentially other imperative loop
       constructs.
     *)
    ValBody (env, opt_params, params, body, export)
  | ApplyString (loc, v, [], []) ->
    eval_var venv pos loc (Omake_env.venv_find_var venv pos loc v)
  | ApplyString (loc, v, args, kargs) ->
    eval_apply_string_exp venv venv pos loc (Omake_env.venv_find_var venv pos loc v) args kargs
  | SuperApplyString (loc, super, v, args, kargs) ->
    let v = Omake_env.venv_find_super_field venv pos loc super v in
    eval_apply_string_exp venv venv pos loc v args kargs
  | MethodApplyString (loc, v, vl, args, kargs) ->
    let venv_obj, v = eval_find_method venv pos loc v vl in
    eval_apply_string_exp venv venv_obj pos loc v args kargs
  | SequenceString (_, sl) ->
    ValSequence (List.map (eval_string_exp venv pos) sl)
  | ObjectString (_, e, export)
  | BodyString (_, e, export) ->
    let env = Omake_env.venv_get_env venv in
    ValBody (env, [], [], e, export)
  | ArrayString (_, el) ->
    ValArray (List.map (eval_string_exp venv pos) el)
  | ArrayOfString (_, e) ->
    let v = eval_string_exp venv pos e in
    ValArray (values_of_value venv pos v)
  | ExpString (_, e, _) ->
    let _, result = eval_sequence_exp venv pos e in
    result
  | CasesString (_, cases) ->
    let cases =
      List.map (fun (v, e1, e2, export) ->
        v, eval_string_exp venv pos e1, e2, export) cases
    in
    ValCases cases
  | QuoteString (_, el) ->
    simplify_quote_val venv pos None (List.map (eval_string_exp venv pos) el)
  | QuoteStringString (_, c, el) ->
    simplify_quote_val venv pos (Some c) (List.map (eval_string_exp venv pos) el)
  | VarString (loc, v) ->
    ValVar (loc, v)
  | ThisString _ ->
    ValObject (Omake_env.venv_this venv)
  | LazyString (_, s) ->
    ValStringExp (Omake_env.venv_get_env venv, s)
  | LetVarString (_, v, s1, s2) ->
    let x = eval_string_exp venv pos s1 in
    let venv = Omake_env.venv_add_var venv v x in
    eval_string_exp venv pos s2

(* and eval_keyword_string_exp venv pos (v, s) = *)
(*   v, eval_string_exp venv pos s *)

and eval_keyword_param_value_list_exp venv pos opt_params =
  List.map (eval_keyword_param_value_exp venv pos) opt_params

and eval_keyword_param_value_exp venv pos = function
    v, v_info, Some s ->
    v, v_info, Some (eval_string_exp venv pos s)
  | _, _, None as param ->
    param

and eval_prim_arg_exp be_eager venv pos s =
  if be_eager then
    eval_string_exp venv pos s
  else
    ValStringExp (Omake_env.venv_get_env venv, s)

(************************************************************************
 * Export versions.
 *
 * These functions with the _export suffix also allow modifications
 * to the environment.
*)
and eval_var_export venv pos loc (v : Omake_value_type.t) =
  let pos = string_pos "eval_var_export" pos in

  (* Do not use eval_value; we don't want to force evaluation *)
  match v with
  | ValFun (env, _, [], body, export) ->
    let venv_new = Omake_env.venv_with_env venv env in
    let venv_new, result = eval_sequence venv_new pos ValNone body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    venv, result
  | ValFunCurry (env, pargs, _, [], body, export, []) ->
    let venv_new = Omake_env.venv_with_partial_args venv env pargs in
    let venv_new, result = eval_sequence venv_new pos ValNone body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    venv, result
  | ValFunCurry (env, pargs, _, [], body, export, kargs) ->
    let venv_new = Omake_env.venv_with_partial_args venv env pargs in
    let venv_new, v = eval_sequence venv_new pos ValNone body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    eval_apply_export venv pos loc v [] kargs
  | ValPrim (_, _, ApplyEmpty, f) ->
    Omake_env.venv_apply_prim_fun f venv pos loc [] []
  | _ ->
    venv, v

(*
 * Evaluate an application.
 *)
and eval_apply_export venv pos loc v args kargs =
  let pos = string_pos "eval_apply_export" pos in
  match (eval_value venv pos v : Omake_value_type.t) with
  | ValFun (env, keywords, params, body, export) ->
    let venv_new = Omake_env.venv_add_args venv pos loc env params args keywords kargs in
    let venv_new, result = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    venv, result
  | ValFunCurry (env, pargs, keywords, params, body, export, kargs1) ->
    let venv_new, args, kargs = Omake_env.venv_add_curry_args venv pos loc env pargs params args keywords kargs1 kargs in
    let venv_new, v = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    eval_apply_export venv pos loc v args kargs
  | ValPrim (_, _, _, f) ->
    Omake_env.venv_apply_prim_fun f venv pos loc args kargs
  | ValPrimCurry (_, _, f, args1, kargs1) ->
    Omake_env.venv_apply_prim_fun f venv pos loc (List.rev_append args1 args) (List.rev_append kargs1 kargs)
  | ValBody (env, keywords, params, body, exports) when keywords <> [] || params <> [] ->
      let v = Omake_value_type.ValFun(env, keywords, params, body, exports) in
      eval_apply_export venv pos loc v args kargs
  | v ->
    if args = [] && kargs = [] then
      venv, v
    else
      let print_error buf =
        Format.fprintf buf "@[<v 3>illegal function application:@ @[<hv 3>function:@ %a@]" Omake_value_print.pp_print_value v;
        List.iter (fun arg ->
          Format.fprintf buf "@ @[<hv 3>arg = %a@]" Omake_value_print.pp_print_value arg) args;
        List.iter (fun (v, arg) ->
          Format.fprintf buf "@ @[<hv 3>%a = %a@]" Lm_symbol.pp_print_symbol v Omake_value_print.pp_print_value arg) kargs;
        Format.fprintf buf "@]"
      in
      raise (Omake_value_type.OmakeException (pos, LazyError print_error))

and eval_partial_apply venv pos loc v args kargs :  (Omake_env.t * Omake_value_type.t )=
  match eval_value venv pos v with
  | ValFun (env, keywords, params, body, export) ->
    begin match 
      (Omake_env.venv_add_partial_args venv pos loc env [] params args keywords [] kargs
      ) with
    | PartialApply (env, pargs, keywords, params, kargs) ->
      venv, ValFunCurry (env, pargs, keywords, params, body, export, kargs)
    | FullApply (venv, args, kargs) ->
      let venv_new, v = eval_sequence_exp venv pos body in
      let venv = Omake_env.add_exports venv venv_new pos export in
      eval_partial_apply venv pos loc v args kargs
    end
  | ValFunCurry (env, pargs, keywords, params, body, export, kargs1) ->
    (match Omake_env.venv_add_partial_args venv pos loc env pargs params args keywords kargs1 kargs with
      PartialApply (env, pargs, keywords, params, kargs) ->
      venv, ValFunCurry (env, pargs, keywords, params, body, export, kargs)
    | FullApply (venv, args, kargs) ->
      let venv_new, v = eval_sequence_exp venv pos body in
      let venv = Omake_env.add_exports venv venv_new pos export in
      eval_partial_apply venv pos loc v args kargs)
  | ValPrim (arity, eager, _, f) ->
    (match arity_apply_args arity [] args with
      FullArity (current_args, rest_args) ->
      (* We assume the primitive takes all the keyword args *)
      let venv, v = Omake_env.venv_apply_prim_fun f venv pos loc current_args kargs in
      eval_partial_apply venv pos loc v rest_args []
    | PartialArity (arity, args) ->
      venv, ValPrimCurry (arity, eager, f, args, List.rev kargs))
  | ValPrimCurry (arity, eager, f, args1, kargs1) ->
    (match arity_apply_args arity args1 args with
      FullArity (current_args, rest_args) ->
      (* We assume the primitive takes all the keyword args *)
      let venv, v = Omake_env.venv_apply_prim_fun f venv pos loc current_args kargs in
      eval_partial_apply venv pos loc v rest_args []
    | PartialArity (arity, args) ->
      venv, ValPrimCurry (arity, eager, f, args, List.rev_append kargs kargs1))
  | ValBody (env, keywords, params, body, exports) when keywords <> [] || params <> [] ->
      let v = Omake_value_type.ValFun(env, keywords, params, body, exports) in
      eval_partial_apply venv pos loc v args kargs
  | v ->
    if args = [] && kargs = [] then
      venv, v
    else
      let print_error buf =
        Format.fprintf buf "@[<v 3>illegal function application:@ @[<hv 3>function:@ %a@]" Omake_value_print.pp_print_value v;
        List.iter (fun arg ->
          Format.fprintf buf "@ @[<hv 3>arg = %a@]" Omake_value_print.pp_print_value arg) args;
        List.iter (fun (v, arg) ->
          Format.fprintf buf "@ @[<hv 3>%a = %a@]" Lm_symbol.pp_print_symbol v Omake_value_print.pp_print_value arg) kargs;
        Format.fprintf buf "@]"
      in
      raise (Omake_value_type.OmakeException (pos, LazyError print_error))

and eval_apply_string_export_exp venv venv_new pos loc v args kargs =
  let pos = string_pos "eval_apply_string_export_exp" pos in
  match eval_value venv pos v with
    ValFun (env, keywords, params, body, export) ->
    let args = List.map (eval_string_exp venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_string_exp venv pos s) kargs in
    let venv_new = Omake_env.venv_add_args venv_new pos loc env params args keywords kargs in
    let venv_new, result = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    venv, result
  | ValFunCurry (env, pargs, keywords, params, body, export, kargs1) ->
    let args = List.map (eval_string_exp venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_string_exp venv pos s) kargs in
    let venv_new, args, kargs = Omake_env.venv_add_curry_args venv_new pos loc env pargs params args keywords kargs1 kargs in
    let venv_new, v = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_exports venv venv_new pos export in
    eval_apply_export venv pos loc v args kargs
  | ValPrim (_, be_eager, _, f) ->
    let args = List.map (eval_prim_arg_exp be_eager venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_prim_arg_exp be_eager venv pos s) kargs in
    Omake_env.venv_apply_prim_fun f venv_new pos loc args kargs
  | ValPrimCurry (_, be_eager, f, args1, kargs1) ->
    let args = List.map (eval_prim_arg_exp be_eager venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_prim_arg_exp be_eager venv pos s) kargs in
    Omake_env.venv_apply_prim_fun f venv_new pos loc (List.rev_append args1 args) (List.rev_append kargs1 kargs)
  | ValBody (env, keywords, params, body, exports) when keywords <> [] || params <> [] ->
      let v = Omake_value_type.ValFun(env, keywords, params, body, exports) in
      eval_apply_string_export_exp venv venv_new pos loc v args kargs
  | v ->
    if args = [] && kargs = [] then
      venv, v
    else
      let print_error buf =
        Format.fprintf buf "@[<v 3>illegal function application:@ @[<hv 3>function:@ %a@]" Omake_value_print.pp_print_value v;
        List.iter (fun arg ->
          Format.fprintf buf "@ @[<hv 3>arg = %a@]" Omake_ir_print.pp_print_string_exp arg) args;
        List.iter (fun (v, arg) ->
          Format.fprintf buf "@ @[<hv 3>%a = %a@]" Lm_symbol.pp_print_symbol v Omake_ir_print.pp_print_string_exp arg) kargs;
        Format.fprintf buf "@]"
      in
      raise (Omake_value_type.OmakeException (pos, LazyError print_error))

and eval_apply_method_export_exp venv venv_obj pos loc path v args kargs =
  let pos = string_pos "eval_apply_method_export_exp" pos in
  match eval_value venv pos v with
    ValFun (env, keywords, params, body, export) ->
    let args = List.map (eval_string_exp venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_string_exp venv pos s) kargs in
    let venv_new = Omake_env.venv_add_args venv_obj pos loc env params args keywords kargs in
    let venv_new, result = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_path_exports venv venv_obj venv_new pos path export in
    venv, result
  | ValFunCurry (env, pargs, keywords, params, body, export, kargs1) ->
    (* XXX: JYH: this, need to think about *)
    let args = List.map (eval_string_exp venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_string_exp venv pos s) kargs in
    let venv_new, args, kargs = Omake_env.venv_add_curry_args venv_obj pos loc env pargs params args keywords kargs1 kargs in
    let venv_new, v = eval_sequence_exp venv_new pos body in
    let venv = Omake_env.add_path_exports venv venv_obj venv_new pos path export in
    eval_apply_export venv pos loc v args kargs
  | ValPrim (_, be_eager, _, f) ->
    let args = List.map (eval_prim_arg_exp be_eager venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_prim_arg_exp be_eager venv pos s) kargs in
    let venv_new, result = Omake_env.venv_apply_prim_fun f venv_obj pos loc args kargs in
    let venv = Omake_env.hoist_this venv venv_new path in
    venv, result
  | ValPrimCurry (_, be_eager, f, args1, kargs1) ->
    let args = List.map (eval_prim_arg_exp be_eager venv pos) args in
    let kargs = List.map (fun (v, s) -> v, eval_prim_arg_exp be_eager venv pos s) kargs in
    let venv_new, result = Omake_env.venv_apply_prim_fun f venv_obj pos loc (List.rev_append args1 args) (List.rev_append kargs1 kargs) in
    let venv = Omake_env.hoist_this venv venv_new path in
    venv, result
  | ValBody (env, keywords, params, body, exports) when keywords <> [] || params <> [] ->
      let v = Omake_value_type.ValFun(env, keywords, params, body, exports) in
      eval_apply_method_export_exp venv venv_obj pos loc path v args kargs
  | v ->
    if args = [] && kargs = [] then
      venv, v
    else
      let print_error buf =
        Format.fprintf buf "@[<v 3>illegal function application:@ @[<hv 3>function:@ %a@]" Omake_value_print.pp_print_value v;
        List.iter (fun arg ->
          Format.fprintf buf "@ @[<hv 3>arg = %a@]" Omake_ir_print.pp_print_string_exp arg) args;
        List.iter (fun (v, arg) ->
          Format.fprintf buf "@ @[<hv 3>%a = %a@]" Lm_symbol.pp_print_symbol v Omake_ir_print.pp_print_string_exp arg) kargs;
        Format.fprintf buf "@]"
      in
      raise (Omake_value_type.OmakeException (pos, LazyError print_error))

(*
 * Evaluate a string expression, and allow exports.
 *)
and eval_string_export_exp venv pos ( s : Omake_ir.string_exp)
  : (Omake_env.t * Omake_value_type.t)=
  let pos = string_pos "eval_string_export_exp" pos in
  match s with
  | NoneString _ ->
    venv, ValNone
  | IntString (_, i) ->
    venv, ValInt i
  | FloatString (_, x) ->
    venv, ValFloat x
  | WhiteString (_, s) ->
    venv, ValWhite s
  | ConstString (_, s) ->
    venv, ValString s
  | KeyApplyString (loc, v) ->
    venv, eval_key venv pos loc v
  | FunString (_, opt_params, params, body, export) ->
    let opt_params = eval_keyword_param_value_list_exp venv pos opt_params in
    let env = Omake_env.venv_get_env venv in
    venv, ValFun (env, opt_params, params, body, export)
  | ApplyString (loc, v, [], []) ->
    eval_var_export venv pos loc (Omake_env.venv_find_var venv pos loc v)
  | ApplyString (loc, v, args, kargs) ->
    eval_apply_string_export_exp venv venv pos loc (Omake_env.venv_find_var venv pos loc v) args kargs
  | SuperApplyString (loc, super, v, args, kargs) ->
    let v = Omake_env.venv_find_super_field venv pos loc super v in
    eval_apply_string_export_exp venv venv pos loc v args kargs
  | MethodApplyString (loc, v, vl, args, kargs) ->
    let venv_obj, path, v = eval_with_method venv pos loc v vl in
    eval_apply_method_export_exp venv venv_obj pos loc path v args kargs
  | SequenceString (_, sl) ->
    venv, ValSequence (List.map (eval_string_exp venv pos) sl)
  | ObjectString (_, e, export)
  | BodyString (_, e, export) ->
    let env = Omake_env.venv_get_env venv in
    venv, ValBody (env, [], [], e, export)
  | ArrayString (_, el) ->
    venv, ValArray (List.map (eval_string_exp venv pos) el)
  | ArrayOfString (_, e) ->
    let v = eval_string_exp venv pos e in
    venv, ValArray (values_of_value venv pos v)
  | ExpString (_, e, export) ->
    eval_sequence_export_exp venv pos e export
  | CasesString (_, cases) ->
    let cases =
      List.map (fun (v, e1, e2, export) ->
        v, eval_string_exp venv pos e1, e2, export) cases
    in
    venv, ValCases cases
  | QuoteString (_, el) ->
    venv, simplify_quote_val venv pos None (List.map (eval_string_exp venv pos) el)
  | QuoteStringString (_, c, el) ->
    venv, simplify_quote_val venv pos (Some c) (List.map (eval_string_exp venv pos) el)
  | VarString (loc, v) ->
    venv, ValVar (loc, v)
  | ThisString _ ->
    venv, ValObject (Omake_env.venv_this venv)
  | LazyString (_, s) ->
    venv, ValStringExp (Omake_env.venv_get_env venv, s)
  | LetVarString (_, v, s1, s2) ->
    let venv, x = eval_string_export_exp venv pos s1 in
    let venv = Omake_env.venv_add_var venv v x in
    eval_string_export_exp venv pos s2

(************************************************************************
 * Evaluate an expression.
*)
and eval_exp venv _ e =
  let pos = string_pos "eval_exp" (ir_exp_pos e) in
  match e with
    LetVarExp (_, v, [], flag, s) ->
    eval_let_var_exp venv pos v flag s
  | LetVarExp (loc, v, vl, flag, s) ->
    eval_let_var_field_exp venv pos loc v vl flag s
  | LetKeyExp (_, v, flag, s) ->
    eval_let_key_exp venv pos v flag s
  | LetFunExp (loc, v, [], curry, opt_params, params, body, export) ->
    eval_let_fun_exp venv pos loc v curry opt_params params body export
  | LetFunExp (loc, v, vl, curry, opt_params, params, body, export) ->
    eval_let_fun_field_exp venv pos loc v vl curry opt_params params body export
  | LetObjectExp (_, v, [], s, e, export) ->
    eval_let_object_exp venv pos v s e export
  | LetObjectExp (loc, v, vl, s, e, export) ->
    eval_let_object_field_exp venv pos loc v vl s e export
  | LetThisExp (_, e) ->
    eval_let_this_exp venv pos e
  | ShellExp (loc, e) ->
    eval_shell_exp venv pos loc e
  | IfExp (_, cases) ->
    eval_if_exp venv pos cases
  | SequenceExp (_, e) ->
    eval_sequence_exp venv pos e
  | SectionExp (_, _, e, export) ->
    eval_section_exp venv pos e export
  | OpenExp (loc, s) ->
    eval_open_exp venv pos loc s
  | IncludeExp (loc, s, e) ->
    eval_include_exp venv pos loc s e
  | ApplyExp (loc, f, args, kargs) ->
    eval_apply_exp venv pos loc f args kargs
  | SuperApplyExp (loc, super, v, args, kargs) ->
    eval_super_apply_exp venv pos loc super v args kargs
  | MethodApplyExp (loc, v, vl, args, kargs) ->
    eval_method_apply_exp venv pos loc v vl args kargs
  | ReturnBodyExp (_, e, id) ->
    eval_return_body_exp venv pos e id
  | StringExp (_, s) ->
    eval_string_value_exp venv pos s
  | ReturnExp (loc, s, id) ->
    eval_return_exp venv pos loc s id
  | ReturnSaveExp _ ->
    eval_return_save_exp venv pos
  | ReturnObjectExp (_, names) ->
    eval_return_object_exp venv pos names
  | KeyExp (loc, v) ->
    eval_key_exp venv pos loc v
  | StaticExp (_, node, key, e) ->
    eval_static_exp venv pos node key e

(*
 * Variable definitions.
 *)
and eval_let_var_exp venv pos v flag s =
  let pos = string_pos "eval_var_exp" pos in
  let venv, s = eval_string_export_exp venv pos s in
  let s =
    match flag with
      VarDefNormal ->
      s
    | VarDefAppend ->
      append_arrays venv pos (Omake_env.venv_get_var venv pos v) s
  in
  let venv = Omake_env.venv_add_var venv v s in
  venv, s

and eval_let_var_field_exp venv pos loc v vl flag s =
  let pos = string_pos "eval_var_field_exp" pos in
  let venv, e = eval_string_export_exp venv pos s in
  let path, obj, v = eval_find_field venv pos loc v vl in
  let e =
    match flag with
      VarDefNormal ->
      e
    | VarDefAppend ->
      append_arrays venv pos (Omake_env.venv_find_field venv obj pos v) e
  in
  let venv, obj = Omake_env.venv_add_field venv obj pos v e in
  let venv = Omake_env.hoist_path venv path obj in
  venv, e

(*
 * Key (property) definitions.
 *)
and eval_let_key_exp venv pos v flag s =
  let pos = string_pos "eval_let_key_exp" pos in

  let venv, s = eval_string_export_exp venv pos s in

  (* Get the current property list *)
  let map =
    try Omake_env.venv_find_var_exn venv Omake_var.map_field_var with
      Not_found ->
      raise (Omake_value_type.OmakeException (pos, StringError "current object is not a Map"))
  in
  let map = eval_map venv pos map in
  let v : Omake_value_type.t = ValData v in
  (* Add the new definition *)
  let s =
    match flag with
    | VarDefNormal ->
      s
    | VarDefAppend ->
      append_arrays venv pos (Omake_env.venv_map_find map pos v) s
  in
  let map = Omake_env.venv_map_add map pos v s in
  let venv = Omake_env.venv_add_var venv Omake_var.map_field_var (ValMap map) in
  venv, s

(*
 * Function definitions.
 *)
and eval_let_fun_exp venv pos _ v curry opt_params params body export =
  let opt_params = eval_keyword_param_value_list_exp venv pos opt_params in
  let env = Omake_env.venv_get_env venv in
  let e : Omake_value_type.t =
    if curry then
      ValFunCurry (env, [], opt_params, params, body, export, [])
    else
      ValFun (env, opt_params, params, body, export)
  in
  let venv = Omake_env.venv_add_var venv v e in
  venv, e

and eval_let_fun_field_exp venv pos loc v vl curry opt_params params body export =
  let opt_params = eval_keyword_param_value_list_exp venv pos opt_params in
  let env = Omake_env.venv_get_env venv in
  let e : Omake_value_type.t =
    if curry then
      ValFunCurry (env, [], opt_params, params, body, export, [])
    else
      ValFun (env, opt_params, params, body, export)
  in
  let path, obj, v = eval_find_field venv pos loc v vl in
  let venv, obj = Omake_env.venv_add_field venv obj pos v e in
  let venv = Omake_env.hoist_path venv path obj in
  venv, e

(*
 * Shell expression.
 *)
and eval_shell_exp venv pos loc e =
  let pos = string_pos "eval_shell_exp" pos in
  let () =
    if !Omake_shell_type.debug_shell then
      Format.eprintf "@[<v 3>eval_shell_exp (pid = %i):@ %a@]@." (**)
        (Unix.getpid()) Omake_ir_print.pp_print_string_exp e
  in
  let v = Omake_env.venv_find_var venv pos loc Omake_var.system_var in
  let venv, s = eval_string_export_exp venv pos e in
  eval_apply_export venv pos loc v [s] []

(*
 * Conditionals.
 * The test should expand to a Boolean of some form.
 *)
and eval_if_cases venv pos cases =
  match cases with
    (s, el, export) :: cases ->
    let s = eval_string_exp venv pos s in
    let b = bool_of_value venv pos s in
    if b then
      eval_sequence_export_exp venv pos el export
    else
      eval_if_cases venv pos cases
  | [] ->
    venv, ValNone

and eval_if_exp venv pos cases =
  let pos = string_pos "eval_if_exp" pos in
  eval_if_cases venv pos cases

(*
 * Sequence.
 *)
and eval_sequence venv pos result el =
  match el with
    e :: el ->
    let venv, result = eval_exp venv result e in
    eval_sequence venv pos result el
  | [] ->
    venv, result

and eval_sequence_export venv pos result el export =
  let venv_new, result = eval_sequence venv pos result el in
  let venv = Omake_env.add_exports venv venv_new pos export in
  venv, result

and eval_sequence_exp venv pos el =
  let pos = string_pos "eval_sequence_exp" pos in
  eval_sequence venv pos ValNone el

and eval_sequence_export_exp venv pos el export =
  let pos = string_pos "eval_sequence_export_exp" pos in
  eval_sequence_export venv pos ValNone el export

and eval_section_exp venv pos el export =
  let pos = string_pos "eval_section_exp" pos in
  eval_sequence_export venv pos ValNone el export

(*
 * Look for a cached object.  If it does not exist,
 * then evaluate the body to create the object.
 * Inline all the fields.
 *)
and eval_static_exp venv pos node key el =
  let pos = string_pos "eval_static_exp" pos in
  let obj =
    try Omake_env.venv_find_static_object venv node key with
      Not_found ->
      (* Evaluate the object, and save it *)
      let _, result = eval_sequence (Omake_env.venv_define_object venv) pos ValNone el in
      let obj = eval_object venv pos result in
      Omake_env.venv_add_static_object venv node key obj;
      obj
  in
  let venv = Omake_env.venv_include_static_object venv obj in
  venv, ValNone

(*
 * Object.
 * The argument string is ignored.
 * Push a new object.
 *)
and eval_let_object_exp venv pos v s el export =
  let pos = string_pos "eval_let_object_exp" pos in
  let parent = eval_string_exp venv pos s in
  let obj = eval_object venv pos parent in
  let venv_obj = Omake_env.venv_define_object venv in
  let venv_obj = Omake_env.venv_include_object venv_obj obj in
  let venv_obj, result = eval_sequence venv_obj pos ValNone el in
  let venv = Omake_env.venv_add_var venv v result in
  let venv = Omake_env.add_exports venv venv_obj pos export in
  venv, result

and eval_let_object_field_exp venv pos loc v vl s el export =
  let pos = string_pos "eval_let_object_field_exp" pos in
  let parent = eval_string_exp venv pos s in
  let obj = eval_object venv pos parent in
  let venv_obj = Omake_env.venv_define_object venv in
  let venv_obj = Omake_env.venv_include_object venv_obj obj in
  let venv_obj, e = eval_sequence venv_obj pos ValNone el in
  let path, obj, v = eval_find_field venv pos loc v vl in
  let venv, obj = Omake_env.venv_add_field venv obj pos v e in
  let venv = Omake_env.hoist_path venv path obj in
  let venv = Omake_env.add_exports venv venv_obj pos export in
  venv, e

(*
 * This.
 * Set the current object to the given object.
 *)
and eval_let_this_exp venv pos s =
  let pos = string_pos "eval_this_exp" pos in
  let venv, obj = eval_string_export_exp venv pos s in
  let obj = eval_object venv pos obj in
  let venv = Omake_env.venv_with_object venv obj in
  venv, ValObject obj

(*
 * Include a file.
 * The environment after the file is evaluated is used in the rest
 * of this file.
 *)
and eval_include_exp venv pos loc s _ =
  let pos = string_pos "eval_include" pos in
  let name =
    match eval_string_exp venv pos s with
      ValNode node ->
      (* Use an absolute name, preventing path lookup *)
      Omake_node.Node.absname node
    | name ->
      string_of_value venv pos name
  in
  let node = find_include_file venv pos loc name in
  let venv = Omake_env.venv_add_file venv node in
  let venv = include_file venv Omake_env.IncludePervasives pos loc node in
  venv, ValNone

(*
 * Open a file.
 * Include it if it is not already included.
 *)
and eval_open_exp venv pos loc nodes =
  let pos = string_pos "eval_open" pos in
  let venv =
    List.fold_left (fun venv node ->
      if Omake_env.venv_is_included_file venv node then
        venv
      else
        let venv = Omake_env.venv_add_file venv node in
        include_file venv Omake_env.IncludePervasives pos loc node) venv nodes
  in
  venv, ValNone

(*
 * Key lookup.
 *)
and eval_key_exp venv pos loc v =
  let pos = string_pos "eval_key_exp" pos in
  let result = eval_key venv pos loc v in
  venv, result

(*
 * Function application.
 *)
and eval_apply_exp venv pos loc f args kargs =
  let pos = string_pos "eval_apply_exp" pos in
  eval_apply_string_export_exp venv venv pos loc (Omake_env.venv_find_var venv pos loc f) args kargs

and eval_super_apply_exp venv pos loc super v args kargs =
  let pos = string_pos "eval_super_apply_exp" pos in
  let v = Omake_env.venv_find_super_field venv pos loc super v in
  eval_apply_string_export_exp venv venv pos loc v args kargs

and eval_method_apply_exp venv pos loc v vl args kargs =
  let pos = string_pos "eval_method_apply_exp" pos in
  let venv_obj, path, v = eval_with_method venv pos loc v vl in
  eval_apply_method_export_exp venv venv_obj pos loc path v args kargs

(*
 * Return a value.  This is just the identity.
 *)
and eval_return_body_exp venv pos e id =
  let _pos = string_pos "eval_return_body_exp" pos in
  try eval_sequence_exp venv pos e with
    Omake_value_type.Return (_, v, id') when id' == id ->
    venv, v

and eval_return_exp venv pos loc s id =
  let pos = string_pos "eval_return_exp" pos in
  let result = eval_string_exp venv pos s in
  raise (Omake_value_type.Return (loc, result, id))

and eval_string_value_exp venv pos s =
  let pos = string_pos "eval_string_value_exp" pos in
  let result = eval_string_exp venv pos s in
  venv, result

and eval_return_save_exp venv pos =
  let _pos = string_pos "eval_return_save_exp" pos in
  venv, ValNone

and eval_return_object_exp venv _ names =
  let result = Omake_env.venv_current_object venv names in
  venv, ValObject result

(*
 * Include a file.
 *)
and eval_include_file venv scope pos loc node =
  let ir = compile_ir venv scope pos loc node in
  let venv_new = Omake_env.venv_add_var venv 
      Omake_var.file_var (ValNode node) in
  let venv_new, result = eval_exp venv_new ValNone ir.ir_exp in
  let venv = Omake_env.add_exports venv venv_new pos ExportAll in
  venv, result

and include_file venv scope pos loc target =
  let pos = string_pos "include_file" pos in
  let venv = Omake_env.venv_add_included_file venv target in
  let venv, _ = eval_include_file venv scope pos loc target in
  venv

(*
 * Parse and evaluate a file as if it were an object.
 *)
and eval_object_file venv pos loc node =
  let parse_obj info node =
    let ir = compile_add_ir_info venv IncludePervasives pos loc node info in
    match ir with
      { ir_classnames = names;
        ir_exp = e;
        _}      ->

      let venv = Omake_env.venv_get_pervasives venv node in
      let venv = Omake_env.venv_define_object venv in
      let venv, _ = eval_exp venv ValNone e in
      Omake_env.venv_current_object venv names in
  compile_object parse_obj venv pos loc node

(************************************************************************
 * Evaluator.
*)
and eval venv e =
  let _, result = eval_exp venv ValNone e in
  result

let eval_open_file = open_ir

let eval_apply = eval_apply_export

(************************************************************************
 * Project compiler.
*)
let compile venv =
  let rootname =
    if Sys.file_exists Omake_state.makeroot_name then
      Omake_state.makeroot_name
    else
      Omake_state.makeroot_short_name
  in
  let node = Omake_env.venv_intern venv PhonyProhibited rootname in
  let venv = Omake_env.venv_add_file venv node in
  let loc = Lm_location.bogus_loc (Omake_node.Node.fullname node) in
  let pos = string_pos "compile" (loc_exp_pos loc) in
  let _ = eval_include_file venv IncludePervasives pos loc node in
  if Lm_debug.debug print_rules then
    Format.eprintf "@[<hv 3>Rules:%a@]@." Omake_env.pp_print_explicit_rules venv

(************************************************************************
 * Dependencies.
*)
let compile_deps venv node buf =
  let deps = Omake_ast_lex.parse_deps buf in
  let vars = Omake_env.venv_include_scope venv IncludePervasives in
  let senv_empty = Omake_ir_ast.penv_of_vars (open_ir venv) venv node vars in
  List.map 
    (fun (target, source, loc) ->
      let pos = string_pos "compile_deps" (loc_exp_pos loc) in
      let _, target = Omake_ir_ast.build_string senv_empty target pos in
      let _, source = Omake_ir_ast.build_string senv_empty source pos in
      let target = eval_string_exp venv pos target in
      let source = eval_string_exp venv pos source in
      let targets = strings_of_value venv pos target in
      let sources = strings_of_value venv pos source in
      targets, sources) deps
