Schooner.Host (schooner v1.0.0)

Copy Markdown View Source

Helpers for authoring Elixir-side host functions exposed to Scheme.

Schooner does not auto-marshal across the host boundary. A host function has the same shape as a built-in primitive — it consumes a list of Schooner.Value.t/0 arguments and returns a Schooner.Value.t/0 — and uses the helpers in this module to move between Scheme values and idiomatic Elixir terms. The named constructors and accessors form the seam that future representation changes pivot on; even when the underlying impl is the identity (Schooner strings are bare Elixir binaries today), host code stays representation-agnostic by going through the helpers.

Naming convention

Asserting accessors (to_*!/2) raise Schooner.Host.TypeError when the input does not match the expected shape. They take a keyword argument with :op set to a host-supplied label for the call site so the error points at the wrapper, not the bare predicate.

Total accessors (to_*/1) return {:ok, term()} | :error for the "branch on shape" case where a type mismatch is not an error.

Several accessor names — Schooner.Host.to_string/1 and Schooner.Host.to_string!/2 — collide with Kernel.to_string/1. Don't import Schooner.Host. Use alias Schooner.Host and call the accessors as Host.to_string! etc.; the alias is one line and the call sites stay explicit.

Worked example

defmodule MyApp.SchemeLib do
  alias Schooner.Host

  @spec specs() :: [{binary(), Schooner.Value.arity_spec(), fun()}]
  def specs do
    [
      {"info", 1, &info/1},
      {"now-ms", 0, &now_ms/1}
    ]
  end

  defp info([msg]) do
    text = Host.to_string!(msg, op: "myapp/info")
    IO.puts(text)
    :unspecified
  end

  defp now_ms([]) do
    System.system_time(:millisecond)
  end
end

Summary

Functions

Build a Schooner.Library from host-supplied primitive specs and arbitrary value bindings.

Assert value is a Scheme boolean and return the Elixir true | false. Note: this differs from "truthy" — only false is Scheme-falsy, but every other value is not a Scheme boolean. Use Schooner.Value.truthy?/1 for the truthiness test.

Assert value is a Scheme bytevector and return the underlying Elixir binary.

Assert value is a Scheme character and return the codepoint integer.

Assert value is a bare inexact real and return the Elixir float. Rejects integers, rationals, the float specials (+inf.0, -inf.0, +nan.0), and complex. Hosts that want "any real number, coerced" should call to_real!/2 instead.

Assert value is a foreign-wrapped host term and return the wrapped term. Mirrors Schooner.Value.foreign_ref/1 but raises the host-facing TypeError instead of ArgumentError.

Assert value is an exact integer and return the bare Elixir integer. Rejects rationals, floats, the float specials, and complex.

Assert value is a proper Scheme list and return the underlying Elixir list. Raises on improper lists and non-list values.

Assert value is callable from Elixir — a closure, a primitive, or a parameter — and return it unchanged. Use this to validate the shape of a callback argument before passing it to Schooner.apply/2.

Assert value is any real number and return it as an Elixir number. Integers and floats pass through; rationals are coerced to their float approximation; the float specials are rejected (no plain BEAM-float representation).

Assert value is a Scheme string and return the underlying Elixir binary. Rejects symbols, bytevectors, and any non-string.

Assert value is a Scheme symbol and return its name as an Elixir binary.

Assert value is a Scheme vector and return its elements as an Elixir list. The list shape is more idiomatic for Elixir iteration; hosts that want a tuple can call Tuple.to_list/1's inverse, but most use cases want Enum.

Functions

bool(b)

@spec bool(boolean()) :: Schooner.Value.bool_v()

See Schooner.Value.bool/1.

bytevector(bs)

@spec bytevector([byte()] | binary()) :: Schooner.Value.bytevector_v()

See Schooner.Value.bytevector/1.

char(cp)

See Schooner.Value.char/1.

foreign(term)

@spec foreign(term()) :: Schooner.Value.foreign_v()

See Schooner.Value.foreign/1.

library(opts)

@spec library(keyword()) :: Schooner.Library.t()

Build a Schooner.Library from host-supplied primitive specs and arbitrary value bindings.

Options

  • :name — canonical library name as a list of binary or non-negative-integer segments. Defaults to [], which marks the library as anonymous: it cannot be reached via Scheme (import ...), and its bindings are applied directly to the runtime environment when the embedder lists it. Named libraries (e.g. ["myapp", "log"]) are sandbox-safe — the script must explicitly (import (myapp log)) to see them.

  • :primitives — list of {name, arity, fun} triples registered as Schooner.Value.primitive/3 bindings. arity may be an integer, {:at_least, n}, or {:between, lo, hi}. fun must be (list() -> Schooner.Value.t()) — the underlying primitive ABI.

  • :values — list of {name, value} pairs for arbitrary Schooner.Value.t/0 bindings (constants, foreign-wrapped service handles, pre-built closures). Useful when the host wants to expose data, not just procedures.

Names within a library must be unique across :primitives and :values combined; a collision raises ArgumentError.

Examples

Named library — script must import:

Schooner.Host.library(
  name: ["myapp", "log"],
  primitives: [
    {"info", 1, &MyApp.SchemeLib.info/1}
  ]
)

Anonymous library — bindings auto-applied, no import required. Sandbox-loosening; list deliberately:

Schooner.Host.library(
  primitives: [
    {"now-ms", 0, fn [] -> System.system_time(:millisecond) end}
  ]
)

list(items)

@spec list([Schooner.Value.t()]) :: Schooner.Value.t()

See Schooner.Value.list/1.

pair(car, cdr)

See Schooner.Value.pair/2.

primitive(name, arity, fun)

See Schooner.Value.primitive/3.

string(s)

@spec string(binary()) :: Schooner.Value.string_v()

See Schooner.Value.string/1.

symbol(name)

@spec symbol(binary()) :: Schooner.Value.sym_v()

See Schooner.Value.symbol/1.

to_bool(value)

@spec to_bool(Schooner.Value.t()) :: {:ok, boolean()} | :error

to_bool!(value, opts)

@spec to_bool!(
  Schooner.Value.t(),
  keyword()
) :: boolean()

Assert value is a Scheme boolean and return the Elixir true | false. Note: this differs from "truthy" — only false is Scheme-falsy, but every other value is not a Scheme boolean. Use Schooner.Value.truthy?/1 for the truthiness test.

to_bytevector(arg1)

@spec to_bytevector(Schooner.Value.t()) :: {:ok, binary()} | :error

to_bytevector!(value, opts)

@spec to_bytevector!(
  Schooner.Value.t(),
  keyword()
) :: binary()

Assert value is a Scheme bytevector and return the underlying Elixir binary.

to_char(arg1)

@spec to_char(Schooner.Value.t()) :: {:ok, non_neg_integer()} | :error

to_char!(value, opts)

@spec to_char!(
  Schooner.Value.t(),
  keyword()
) :: non_neg_integer()

Assert value is a Scheme character and return the codepoint integer.

to_float(value)

@spec to_float(Schooner.Value.t()) :: {:ok, float()} | :error

to_float!(value, opts)

@spec to_float!(
  Schooner.Value.t(),
  keyword()
) :: float()

Assert value is a bare inexact real and return the Elixir float. Rejects integers, rationals, the float specials (+inf.0, -inf.0, +nan.0), and complex. Hosts that want "any real number, coerced" should call to_real!/2 instead.

to_foreign_ref(arg1)

@spec to_foreign_ref(Schooner.Value.t()) :: {:ok, term()} | :error

to_foreign_ref!(value, opts)

@spec to_foreign_ref!(
  Schooner.Value.t(),
  keyword()
) :: term()

Assert value is a foreign-wrapped host term and return the wrapped term. Mirrors Schooner.Value.foreign_ref/1 but raises the host-facing TypeError instead of ArgumentError.

to_integer(value)

@spec to_integer(Schooner.Value.t()) :: {:ok, integer()} | :error

to_integer!(value, opts)

@spec to_integer!(
  Schooner.Value.t(),
  keyword()
) :: integer()

Assert value is an exact integer and return the bare Elixir integer. Rejects rationals, floats, the float specials, and complex.

to_list(list)

@spec to_list(Schooner.Value.t()) :: {:ok, [Schooner.Value.t()]} | :error

to_list!(list, opts)

@spec to_list!(
  Schooner.Value.t(),
  keyword()
) :: [Schooner.Value.t()]

Assert value is a proper Scheme list and return the underlying Elixir list. Raises on improper lists and non-list values.

to_proc(value)

@spec to_proc(Schooner.Value.t()) :: {:ok, Schooner.Value.t()} | :error

to_proc!(value, opts)

@spec to_proc!(
  Schooner.Value.t(),
  keyword()
) :: Schooner.Value.t()

Assert value is callable from Elixir — a closure, a primitive, or a parameter — and return it unchanged. Use this to validate the shape of a callback argument before passing it to Schooner.apply/2.

to_real(value)

@spec to_real(Schooner.Value.t()) :: {:ok, number()} | :error

to_real!(value, opts)

@spec to_real!(
  Schooner.Value.t(),
  keyword()
) :: number()

Assert value is any real number and return it as an Elixir number. Integers and floats pass through; rationals are coerced to their float approximation; the float specials are rejected (no plain BEAM-float representation).

to_string(value)

@spec to_string(Schooner.Value.t()) :: {:ok, binary()} | :error

to_string!(value, opts)

@spec to_string!(
  Schooner.Value.t(),
  keyword()
) :: binary()

Assert value is a Scheme string and return the underlying Elixir binary. Rejects symbols, bytevectors, and any non-string.

to_symbol_name(arg1)

@spec to_symbol_name(Schooner.Value.t()) :: {:ok, binary()} | :error

to_symbol_name!(value, opts)

@spec to_symbol_name!(
  Schooner.Value.t(),
  keyword()
) :: binary()

Assert value is a Scheme symbol and return its name as an Elixir binary.

to_vector(arg1)

@spec to_vector(Schooner.Value.t()) :: {:ok, [Schooner.Value.t()]} | :error

to_vector!(value, opts)

@spec to_vector!(
  Schooner.Value.t(),
  keyword()
) :: [Schooner.Value.t()]

Assert value is a Scheme vector and return its elements as an Elixir list. The list shape is more idiomatic for Elixir iteration; hosts that want a tuple can call Tuple.to_list/1's inverse, but most use cases want Enum.

vector(items)

@spec vector([Schooner.Value.t()]) :: Schooner.Value.vector_v()

See Schooner.Value.vector/1.