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. Going through the
named constructors and accessors keeps host code independent of the
value representation, even where a helper is currently the
identity (Schooner strings are bare Elixir binaries today).
Use raise_error/2 or raise_value/1 to raise failures that a
script can catch with guard or with-exception-handler.
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.
Recommended use pattern
Schooner.Host.to_string/1 collides with Kernel.to_string/1.
Don't import Schooner.Host. Use alias Schooner.Host and
call the accessors as Host.to_string! etc.
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.
Raise a Scheme error object of kind :user with message and irritants.
Raise any Scheme value, including a symbol or a host-built error object.
Assert value is a Scheme boolean and return the Elixir
true | false. This is a type check, not a truthiness test: every
value other than #f is truthy in Scheme, but only #t and #f
are booleans. Use Schooner.Value.truthy?/1 for truthiness.
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, which suits Enum-based host code. Use
List.to_tuple/1 on the result if a tuple is needed.
Functions
@spec bool(boolean()) :: Schooner.Value.bool_v()
@spec bytevector([byte()] | binary()) :: Schooner.Value.bytevector_v()
@spec char(non_neg_integer()) :: Schooner.Value.char_v()
@spec foreign(term()) :: Schooner.Value.foreign_v()
@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 asSchooner.Value.primitive/3bindings.aritymay be an integer,{:at_least, n}, or{:between, lo, hi}.funmust be(list() -> Schooner.Value.t())— the underlying primitive ABI.:values— list of{name, value}pairs for arbitrarySchooner.Value.t/0bindings (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}
]
)
@spec list([Schooner.Value.t()]) :: Schooner.Value.t()
@spec pair(Schooner.Value.t(), Schooner.Value.t()) :: Schooner.Value.pair_v()
@spec primitive(binary(), Schooner.Value.arity_spec(), (list() -> Schooner.Value.t())) :: Schooner.Value.primitive_v()
@spec raise_error(binary(), [Schooner.Value.t()]) :: no_return()
Raise a Scheme error object of kind :user with message and irritants.
A script's guard or with-exception-handler catches the error.
With no handler, it surfaces to the embedder as Schooner.Error:
Schooner.eval/2 returns {:error, %Schooner.Error{}}.
Raising Schooner.Error directly with Kernel.raise/2 bypasses
Scheme handlers.
message must be an Elixir string and irritants a list of Scheme
values. A non-binary message or non-list irritants raises
ArgumentError, indicating a host programming error.
alias Schooner.Host
Host.raise_error("db query failed", [Host.foreign(:timeout)])
@spec raise_value(Schooner.Value.t()) :: no_return()
Raise any Scheme value, including a symbol or a host-built error object.
A script's guard or with-exception-handler catches the value.
With no handler, it surfaces to the embedder as Schooner.Error:
Schooner.eval/2 returns {:error, %Schooner.Error{}}.
Raising Schooner.Error directly with Kernel.raise/2 bypasses
Scheme handlers.
alias Schooner.Host
Host.raise_value(Host.symbol("service-unavailable"))
@spec string(binary()) :: Schooner.Value.string_v()
@spec symbol(binary()) :: Schooner.Value.sym_v()
@spec to_bool(Schooner.Value.t()) :: {:ok, boolean()} | :error
@spec to_bool!( Schooner.Value.t(), keyword() ) :: boolean()
Assert value is a Scheme boolean and return the Elixir
true | false. This is a type check, not a truthiness test: every
value other than #f is truthy in Scheme, but only #t and #f
are booleans. Use Schooner.Value.truthy?/1 for truthiness.
@spec to_bytevector(Schooner.Value.t()) :: {:ok, binary()} | :error
@spec to_bytevector!( Schooner.Value.t(), keyword() ) :: binary()
Assert value is a Scheme bytevector and return the underlying
Elixir binary.
@spec to_char(Schooner.Value.t()) :: {:ok, non_neg_integer()} | :error
@spec to_char!( Schooner.Value.t(), keyword() ) :: non_neg_integer()
Assert value is a Scheme character and return the codepoint
integer.
@spec to_float(Schooner.Value.t()) :: {:ok, float()} | :error
@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.
@spec to_foreign_ref(Schooner.Value.t()) :: {:ok, term()} | :error
@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.
@spec to_integer(Schooner.Value.t()) :: {:ok, integer()} | :error
@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.
@spec to_list(Schooner.Value.t()) :: {:ok, [Schooner.Value.t()]} | :error
@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.
@spec to_proc(Schooner.Value.t()) :: {:ok, Schooner.Value.t()} | :error
@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.
@spec to_real(Schooner.Value.t()) :: {:ok, number()} | :error
@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).
@spec to_string(Schooner.Value.t()) :: {:ok, binary()} | :error
@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.
@spec to_symbol_name(Schooner.Value.t()) :: {:ok, binary()} | :error
@spec to_symbol_name!( Schooner.Value.t(), keyword() ) :: binary()
Assert value is a Scheme symbol and return its name as an Elixir
binary.
@spec to_vector(Schooner.Value.t()) :: {:ok, [Schooner.Value.t()]} | :error
@spec to_vector!( Schooner.Value.t(), keyword() ) :: [Schooner.Value.t()]
Assert value is a Scheme vector and return its elements as an
Elixir list, which suits Enum-based host code. Use
List.to_tuple/1 on the result if a tuple is needed.
@spec vector([Schooner.Value.t()]) :: Schooner.Value.vector_v()