# `Hyperlang`
[🔗](https://github.com/hyperbob-labs/hyperlang/blob/v0.1.0/lib/hyperlang.ex#L1)

Compile and run untrusted Erlang-shaped guest programs.

The whole public API is four functions:

  * `program/1` compiles an immutable source value into a
    `Hyperlang.Program`. The manifest, the binding MFA set, the export set
    and the atom universe are fixed here and enter the program hash.
  * `program/2` returns a program cached under a caller-owned key and calls
    the loader only on a miss.
  * `run/4` runs one declared export with exactly the authority in its
    spec.
  * `module/1` returns the program's confined OTP callback module. Every
    callback goes through `run/4`.

Errors are sanitized maps whose `:category` comes from a closed taxonomy;
see `doc/SECURITY_SPECIFICATION.md`.

# `errors`

```elixir
@type errors() :: [map()]
```

# `manifest`

```elixir
@type manifest() :: map()
```

# `source_bundle`

```elixir
@type source_bundle() :: binary() | %{optional(atom() | binary()) =&gt; term()}
```

# `module`

```elixir
@spec module(Hyperlang.Program.t()) :: module()
```

Return the loaded confined module for an immutable program value.

The module is a `GenServer` callback module. Start it with
`GenServer.start(Hyperlang.module(program), {spec, arg})`; `init/1`
receives `arg`, and every callback runs under `spec`. Raises
`ArgumentError` when the program cannot be loaded.

From `init/1` until it exits, an actor pins the program's code slot, so
the module stays loaded under it even after `release` or code-pool
pressure. The pin is charged to the actor's scope (`spec.policy.scope`,
else the actor's own default scope), which may pin at most `max_code_pins`
distinct slots. `init/1` stops with a `code_pool` error when that limit is
reached (`pin_limit`) or when the slot was reclaimed before the actor
started (`not_loaded`); call `module/1` again and restart.

# `program`

```elixir
@spec program(binary() | map()) :: {:ok, Hyperlang.Program.t()} | {:error, errors()}
```

Compile an immutable, closed-world source value.

Source maps use `%{entry: "name.erl", files: %{"name.erl" => source}}` and
may carry a compiler manifest under `:manifest`. A binary is the compact
single-source form.

# `program`

```elixir
@spec program(term(), (-&gt; binary()
                    | map()
                    | {:ok, binary() | map()}
                    | {:error, term()})) ::
  {:ok, Hyperlang.Program.t()} | {:error, errors()}
```

Return a program cached under a caller-owned key, compiling the loader's
source value on a miss.

# `run`

```elixir
@spec run(map(), Hyperlang.Program.t(), atom() | binary(), list()) ::
  {:ok, term(),
   %{
     stdout: [binary()],
     reductions: non_neg_integer(),
     wall_us: non_neg_integer()
   }}
  | {:error, term()}
```

Run a declared export using only the authority present in `spec`.

`spec` is `%{bindings: %{name => {module, state}}, policy: map}` plus an
optional `syscall` host door. `bindings` must name exactly the bindings the
program's manifest declares, with the same modules. `export` is `:entry`
(the manifest result root, called with `hd(args)`), an atom, or an
`"name/arity"` export id.

Success returns `{:ok, value, meta}` where `meta` is
`%{stdout: [binary], reductions: non_neg_integer, wall_us: non_neg_integer}`:
the sanitized guest stdout chunks in order, the reductions the guest call
consumed in its own process, and the wall time of the call in
microseconds. Failure returns `{:error, error}` with a sanitized error map.

---

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