///|
/// Evaluate variable lookup
fn eval_variable(name : String, env : Env) -> Iter[Json] raise InterpreterError {
  guard env.get(name) is Some(v) else {
    raise InvalidOperation("Undefined variable: $\{name}")
  }
  Iter::singleton(v)
}

///|
/// Evaluate function call
fn eval_function_call(
  name : String,
  args : Array[Expr],
  input : Json,
  env : Env,
) -> Iter[Json] raise InterpreterError {
  match env.get_function(name) {
    Some((body, params)) =>
      match params {
        [] => eval_with_env(body, input, env)
        _ =>
          if args.length() != params.length() {
            raise EvalError(
              "Function \{name} expects \{params.length()} arguments, got \{args.length()}",
            )
          } else {
            let arg_values = args.map(fn(arg) raise InterpreterError {
              match eval_with_env(arg, input, env).collect() {
                [] => null
                [first, ..] => first
              }
            })
            let new_env = bind_params(env, params, arg_values)
            eval_with_env(body, input, new_env)
          }
      }
    None => raise EvalError("Undefined function: \{name}")
  }
}

///|
fn bind_params(
  env : Env,
  params : ArrayView[String],
  values : ArrayView[Json],
) -> Env {
  match (params, values) {
    ([], []) => env
    ([name, .. rest_params], [value, .. rest_values]) =>
      bind_params(env.set(name, value), rest_params, rest_values)
    _ => env
  }
}

///|
/// Evaluate As binding
fn eval_as(
  expr : Expr,
  var_name : String,
  body : Expr,
  input : Json,
  env : Env,
) -> Iter[Json] raise InterpreterError {
  let results = eval_with_env(expr, input, env).collect()
  let all_results : Array[Json] = []
  for result in results {
    let new_env = env.set(var_name, result)
    for val in eval_with_env(body, input, new_env) {
      all_results.push(val)
    }
  }
  all_results.iter()
}