///|
pub(all) struct Environment {
  loader : ((String) -> String raise Error)? // host-provided: module name -> source
  module_cache : Map[String, Map[String, @value.Value]] // cached module globals
  predeclared : Map[String, @value.Value] // built-in functions
}

///|
pub fn Environment::new(
  loader? : ((String) -> String raise Error)? = None,
) -> Environment {
  { loader, module_cache: {}, predeclared: predeclared_builtins() }
}

///|
pub(all) struct Scope {
  bindings : Map[String, @value.Value]
  parent : Scope?
  env : Environment? // only set at root scope
}

///|
pub fn Scope::new(parent? : Scope, env? : Environment) -> Scope {
  { bindings: {}, parent, env }
}

///|
pub fn Scope::get(self : Scope, name : String) -> @value.Value? {
  match self.bindings.get(name) {
    Some(v) => Some(v)
    None =>
      match self.parent {
        Some(parent) => parent.get(name)
        None => None
      }
  }
}

///|
pub fn Scope::set(self : Scope, name : String, value : @value.Value) -> Unit {
  self.bindings.set(name, value)
}

///|
/// Walk up the scope chain to find the Environment.
pub fn Scope::get_env(self : Scope) -> Environment? {
  match self.env {
    Some(e) => Some(e)
    None =>
      match self.parent {
        Some(parent) => parent.get_env()
        None => None
      }
  }
}

///|
pub fn Scope::with_builtins(
  loader? : ((String) -> String raise Error)? = None,
) -> Scope {
  let env = Environment::new(loader~)
  let scope = Scope::new(env~)
  for name, value in env.predeclared {
    scope.set(name, value)
  }
  scope
}