///|
/// What the host supplies: everything about a PurePy run that is not pure.
///
/// PurePy is a pure language, but a RUN is not a pure thing: it prints, it
/// reads its arguments, it loads modules, and -- when it is embedded -- it
/// calls out to whatever the application wants to expose. Those four are the
/// whole of it, and they are all here, so an embedder can see the surface at
/// once instead of discovering it a call at a time.
///
///   * **Output.** `write` receives what `print` produced, one call per
///     `print`, while the run is still going. A host that streams to a
///     terminal writes it out; a host that wants a transcript accumulates.
///   * **Arguments.** `argv` is what `sys.argv` reads. `argv[0]` is the
///     program's own name, as in Python.
///   * **Modules.** `modules` are importable modules the host defines, beyond
///     the five the specification predefines. Their members are values, and a
///     member may be a function the host implements.
///   * **Foreign calls.** `call` answers a call to one of those functions. It
///     is given the name the host registered and the arguments the guest
///     passed, and returns an `Outcome`: a value, an abort, or -- for
///     anything it does not recognise or cannot do -- an operation the
///     semantics does not cover.
///
///     It is `async`, so it may also answer LATER: a host that has to read a
///     socket, await a promise or ask a person suspends, and the whole run
///     parks where it stood -- mid-expression, inside a call, anywhere -- and
///     resumes on the value the host eventually supplies. Nothing about the
///     guest changes: PurePy has no `await`, and a program cannot tell a call
///     that answered late from one that answered at once. See `run_with` for
///     how a synchronous embedder drives a run that can park.
///
/// Everything else about a run is already a value: the program is a
/// `SourceTree`, so a host that keeps its guest code in a database, a zip file
/// or a string never touches the filesystem.
///
/// Note what is NOT here. There is no way for the host to mutate a guest
/// value, because there is no mutation; no way to install a callback the guest
/// invokes implicitly, because there are no hooks; and no ambient authority at
/// all -- a guest can reach exactly the modules the host handed it. A host
/// that gives no modules gives a program that can only compute and print.
///
/// Suspension adds nothing to that list. A parked run is not a concurrent
/// one: there is one guest, it is at exactly one point, and the host holds
/// the only continuation. Resuming it twice is the host resuming it twice,
/// which is the host's bug and not an escape from the semantics.
pub struct Host {
  write : (String) -> Unit
  argv : Array[String]
  modules : Map[String, @value.Env]
  call : async (String, Array[@value.Value]) -> @value.Outcome noraise
}

///|
/// A module the host defines: a name the guest can import, and its members.
///
/// A member is any `Value`. `@value.host_fn("name")` makes one the guest can
/// call and the host answers by name.
pub(all) struct HostModule {
  name : String
  members : Array[(String, @value.Value)]
}

///|
/// A host.
///
/// Every part has a default that does nothing observable: output is dropped,
/// `sys.argv` is empty, no modules are defined, and a foreign call is an
/// operation the semantics does not cover. A host supplies the parts it wants.
///
/// A `call` that answers on the spot is written exactly as it was before it
/// could do otherwise: a plain function is a valid `async` one.
pub fn Host::new(
  write? : (String) -> Unit = fn(_) {  },
  argv? : Array[String] = [],
  modules? : Array[HostModule] = [],
  call? : async (String, Array[@value.Value]) -> @value.Outcome noraise = fn(
    name,
    _,
  ) {
    Stuck(
      "a call to the host function '" + name + "', which it does not answer",
    )
  },
) -> Host {
  let table : Map[String, @value.Env] = Map([])
  for m in modules {
    // Every module has a `__name__`, as the predefined ones do.
    let entries : Array[(String, @value.Value)] = [("__name__", Str(m.name))]
    for e in m.members {
      entries.push(e)
    }
    table[m.name] = @value.env_of(entries)
  }
  { write, argv, modules: table, call, }
}

///|
/// The names of each host module's members.
///
/// The CHECKER needs these and not the values: a `from hostmod import f` has
/// to type-check before it can run, and the checker has no business knowing
/// what `f` is. This is the one place the two sides of a host module meet.
pub fn Host::member_names(self : Host) -> Map[String, Array[String]] {
  let out : Map[String, Array[String]] = Map([])
  for name, env in self.modules {
    let members : Array[String] = []
    env.each(fn(k, _) { if k != "__name__" { members.push(k) } })
    @basic.sort_names(members)
    out[name] = members
  }
  out
}

///|
/// The modules a program may import that have no source: the five the
/// specification predefines, and the host's own.
pub fn Host::module_names(self : Host) -> Array[String] {
  let out : Array[String] = []
  for p in @context.predefined_modules {
    out.push(p)
  }
  for name, _ in self.modules {
    if !out.contains(name) {
      out.push(name)
    }
  }
  @basic.sort_names(out)
  out
}

// ---------------------------------------------------------------------------

///|
/// Somewhere to put output when the caller just wants the transcript.
///
/// `run_program` uses one of these; `run_with` does not, because a host that
/// supplies its own `write` has somewhere better to put it.
pub struct Sink {
  buf : StringBuilder
}

///|
pub fn Sink::new() -> Sink {
  { buf: StringBuilder(), }
}

///|
pub fn Sink::write(self : Sink, text : String) -> Unit {
  self.buf.write_string(text)
}

///|
pub fn Sink::text(self : Sink) -> String {
  self.buf.to_string()
}