# `Schooner`
[🔗](https://github.com/ausimian/schooner/blob/1.0.0/lib/schooner.ex#L1)

An embeddable, sandboxed Scheme interpreter for the BEAM, targeting
the r7rs-small language minus its mutable operations.

The pipeline is Lexer → Reader → Expander → Eval. The
`Schooner.Value` module provides the tagged-term value model and
`Schooner.Env` the immutable-with-mutable-globals runtime
environment. Macro bindings live in a separate expansion-time
syntax env threaded through expansion only.

## Choosing an entry point

Schooner's entry points come in pairs following the Elixir
convention: a tagged-tuple form (`run/1`, `eval/2,3`) and a bang
form (`run!/1`, `eval!/2,3`) that raises on failure. Picking
between `run*` and `eval*` is a **trust-posture decision** — the
naming is the opposite of what reflex suggests.

| Family        | Auto-imports                                                                              | Trust posture                                       | Use for                                                            |
| ---           | ---                                                                                       | ---                                                 | --- |
| `run/1`/`run!/1`     | injects `(import ...)` of every shipped standard library when the script declares none | **Not sandbox-safe.** Every shipped primitive is in scope by default. | tests, REPL-style use, your own scripts where you control the source |
| `eval/2,3`/`eval!/2,3` | none — bindings come exclusively from the `env` argument and the script's own `(import ...)` declarations | **Sandbox-safe.** The embedder controls the surface. | embedding untrusted or semi-trusted scripts |

The implicit-import behaviour of `run`/`run!` is opt-in via the
`implicit_imports: :all` option on the 3-arity `eval/3` and
`eval!/3`. Embedders who want the convenience without the rename
can call `eval!/3` (or `eval/3`) with the option themselves.

Within each family, the bang form raises one of:

  * `Schooner.Error` — uncaught Scheme `(raise ...)` in the script
  * `Schooner.Eval.Error` — runtime evaluation failure
  * `Schooner.Primitive.Error` — primitive type / arity / domain error
  * `Schooner.Library.NotFoundError` — `(import ...)` of a missing library
  * `Schooner.Lexer.Error`, `Schooner.Reader.Error`,
    `Schooner.Expander.Error` — source-level failures

The non-bang form returns `{:ok, value}` on success or
`{:error, exception}` for any of the above. `ArgumentError`
raised by malformed options is **not** caught — that is an
embedder bug, not a script-level failure.

### Embedding untrusted code

Use `eval/2` (or `eval!/2`) and require the script to declare
exactly which libraries it needs. A script that omits
`(import ...)` cannot reach any primitive — even `+` is unbound:

    iex> Schooner.eval!("(import (only (scheme base) +)) (+ 1 2)", Schooner.Env.new())
    3

Pair this with BEAM-level resource limits — run `eval!/2` inside
a spawned process with `:max_heap_size` and a `Task.shutdown/2`
timeout so a runaway script cannot exhaust the host.

For richer sandbox composition (registering host libraries,
pre-imports, etc.), construct a `Schooner.Environment` via
`Schooner.Environment.new/1` and pass it to `eval/2`.

# `apply`

```elixir
@spec apply(Schooner.Value.t(), [Schooner.Value.t()]) ::
  {:ok, Schooner.Value.t()} | {:error, Exception.t()}
```

Invoke a Scheme procedure value from Elixir. Returns
`{:ok, value}` on success, `{:error, exception}` for any
script-level failure. Use `apply!/2` for the raising variant.

`proc` may be any procedure value — a closure (returned by
evaluating a `lambda` or a `define` form), a primitive, or a
parameter. `args` is a list of `t:Schooner.Value.t/0` arguments.

This is the host-side hook for callback patterns: pass a Scheme
procedure into a host function, capture it, and invoke it later
via `apply/2`.

# `apply!`

```elixir
@spec apply!(Schooner.Value.t(), [Schooner.Value.t()]) :: Schooner.Value.t()
```

Bang form of `apply/2` — raises on script-level failure. See
`apply/2` for arguments.

# `compile`

```elixir
@spec compile(binary()) :: {:ok, Schooner.Compiled.t()} | {:error, Exception.t()}
```

# `compile`

```elixir
@spec compile(binary(), Schooner.Environment.t()) ::
  {:ok, Schooner.Compiled.t()} | {:error, Exception.t()}
```

Read, expand, and pre-resolve `source` against `env_struct`'s
registry. Returns `{:ok, %Schooner.Compiled{}}` on success,
`{:error, exception}` on any source-level failure. Use
`compile!/1,2` for the raising variant.

The compiled artifact can be passed to `run_compiled/2`
repeatedly against any compatible environment. Macros are
expanded at compile time; variable bindings from `(import ...)`
declarations are pre-resolved and baked into the artifact.

When called without an environment, defaults to a fresh
`Schooner.Environment.new/0` (every shipped standard library
available).

# `compile!`

```elixir
@spec compile!(binary()) :: Schooner.Compiled.t()
```

# `compile!`

```elixir
@spec compile!(binary(), Schooner.Environment.t()) :: Schooner.Compiled.t()
```

Bang form of `compile/2` — raises on source-level failure.

# `eval`

```elixir
@spec eval(binary(), Schooner.Env.t() | Schooner.Environment.t()) ::
  {:ok, Schooner.Value.t()} | {:error, Exception.t()}
```

Read and evaluate `source` against `env`, threading top-level
definitions and any explicit `(import ...)` bindings into it.

Returns `{:ok, value}` on success, `{:error, exception}` for
any script-level failure. Use `eval!/2` for the raising variant.

`env` may be either a `Schooner.Env` (low-level) or a
`Schooner.Environment` (the embedding-friendly bundle of env +
registry + syntax env produced by `Schooner.Environment.new/1`).
When given an `Env`, no implicit imports are added — bindings
come exclusively from `env` and the script's own `(import ...)`
declarations.

This is the strict path: it is the right entry point for
embedding scripts whose source the host does not control. See
"Choosing an entry point" in the moduledoc.

# `eval`

```elixir
@spec eval(binary(), Schooner.Env.t(), keyword()) ::
  {:ok, Schooner.Value.t()} | {:error, Exception.t()}
```

Read and evaluate `source` against `env` with `opts`. Returns
`{:ok, value}` on success, `{:error, exception}` for any
script-level failure. Use `eval!/3` for the raising variant.

Options match `eval!/3`. The `Environment` overload of `eval/2`
does not accept options — pre-imports and library composition
are baked into the `Environment` at construction time.

# `eval!`

```elixir
@spec eval!(binary(), Schooner.Env.t() | Schooner.Environment.t()) ::
  Schooner.Value.t()
```

Bang form of `eval/2` — raises on script-level failure.

# `eval!`

```elixir
@spec eval!(binary(), Schooner.Env.t(), keyword()) :: Schooner.Value.t()
```

Bang form of `eval/3` — raises on script-level failure.

Options:

  * `:implicit_imports` — controls implicit imports prepended
    to a script that declares none of its own.
      * `:none` (default) — no implicit imports. Bindings come
        exclusively from `env` and the script's own
        `(import ...)` declarations. Use this for untrusted
        input.
      * `:all` — implicitly import every shipped standard
        library. Skipped if the script already declares any
        `(import ...)`, on the principle that an explicit
        import means the user has opted in to a tighter
        surface.

# `run`

```elixir
@spec run(binary()) :: {:ok, Schooner.Value.t()} | {:error, Exception.t()}
```

Read and evaluate `source` in a fresh empty env, with every shipped
standard library implicitly imported when the script declares no
imports of its own.

Returns `{:ok, value}` on success, `{:error, exception}` for
any script-level failure. Use `run!/1` for the raising variant.

**Not for untrusted input.** A script with no `(import ...)`
form reaches every primitive Schooner ships with. Use `eval/2`
for embedding scripts you do not control — see "Choosing an
entry point" in the moduledoc.

# `run!`

```elixir
@spec run!(binary()) :: Schooner.Value.t()
```

Bang form of `run/1` — raises on script-level failure.

# `run_compiled`

```elixir
@spec run_compiled(Schooner.Compiled.t(), Schooner.Environment.t()) ::
  {:ok, Schooner.Value.t()} | {:error, Exception.t()}
```

Evaluate a `%Schooner.Compiled{}` against `env_struct`. Returns
`{:ok, value}` on success, `{:error, exception}` for any
script-level failure. Use `run_compiled!/2` for the raising
variant.

The compiled program's pre-resolved variable bindings are
re-applied to `env_struct.env` on every call, so the
`(import ...)` surface the program was compiled against is
always in scope regardless of `env_struct`'s registry. Macros
are not re-expanded — the program's macro shape is frozen at
compile time.

# `run_compiled!`

```elixir
@spec run_compiled!(Schooner.Compiled.t(), Schooner.Environment.t()) ::
  Schooner.Value.t()
```

Bang form of `run_compiled/2` — raises on script-level failure.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
