All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.1.0 - 2026-10-03
Changed
The evaluator is substantially faster: roughly 3-7x on call-heavy code. Core forms are now analysed once into an internal representation, variable references are resolved to lexical slots ahead of time, and the result is compiled into closures, so evaluation no longer re-parses forms or looks variables up by name. Each global binding has its own cell, read directly at run time. Two-argument integer
+ - * = < > <= >=run inline while those names still mean the standard procedures. A namedlet, or anyletrec*whose lambdas are only ever called directly, runs as direct calls with no closures.workload 1.0.0 this release fib(20) 20.4 ms 3.6 ms named-let loop 100k 120.7 ms 16.2 ms closures 10k 23.6 ms 4.0 ms list build+map+fold 10k 25.3 ms 5.9 ms string build 2k 2.6 ms 0.8 ms (Medians from
bench/eval_bench.exson OTP 28.5 with JIT.)%Schooner.Compiled{}now holds the analysed program rather than the expanded source forms. It is still plain data, safe to cache or persist, and is still opaque. A%Compiled{}produced by 1.0.x cannot be run by this release: running one raises. Recompile cached or persisted programs withSchooner.compile/2after upgrading.Imports and library loading are stricter. An
only,except, orrenameimport that names an identifier the library doesn't export, or arenamewhose new name collides with another binding, now fails withSchooner.Eval.Errorinstead of being silently ignored or overwriting a binding.Schooner.Library.Loader.load_string/2,3without:base_dirnow rejects absolute include paths. Scripts and libraries that relied on the old behaviour need updating; the corresponding entries under Fixed have the details.
Added
Schooner.Host.raise_error/2andSchooner.Host.raise_value/1let host functions raise errors that scripts can catch withguardorwith-exception-handler.bench/eval_bench.exs, an evaluator throughput benchmark with no extra dependencies (MIX_ENV=prod mix run bench/eval_bench.exs).
Fixed
Macros defined inside top-level
beginforms now remain visible to later forms even when thebeginalso contains ordinary forms, including nestedbeginforms and compiled programs.Import
renameclauses now apply simultaneously, preserving bindings in swaps and rotations. Duplicate destinations and renames that would overwrite an export retained under its original name now return script errors.Import modifiers
only,except, andrenamenow reject identifiers absent from the inner import set. Scripts that previously imported an unknown name silently now fail with a script error.Loading a library with
base_dir: "/"no longer rejects includes beneath the filesystem root.digit-valuenow returns the correct decimal value for adjacent Unicode digit sets, including mathematical styled digits.Library sources loaded without a base directory now reject absolute include paths as well as relative ones. Load from a file or pass
:base_dirto confine includes to the library root directory.Tail loops with internal definitions, including bodies that create callbacks, no longer grow the stack or allocate recursive process-dictionary slots when binding targets are distinct and initializer references only target earlier bindings. Closures in these forms retain their captured lexical values, including when saved by host primitives. Self and forward references retain recursive binding behavior.
Rational
asin/acosoutside the real domain and negative bases raised to fractional powers now return complex values. Inexact realexptandexphandle overflow and underflow, and numeric domain or range failures return script errors instead of leaking Elixir arithmetic exceptions.The error for a
define-syntaxoutside the top level now says that only top-level definitions are supported and suggestslet-syntaxorletrec-syntax; it previously described only macro-introduced definitions. The expired-continuation error no longer refers to an internal planning document.Module documentation and guides no longer contradict the implementation in several places, including the representation of promises and the empty list,
equal?on foreign values, how out-of-rangeasin/acosand negativesqrtbehave, and how include paths are confined when loading libraries.The sandboxing examples in the "Running Untrusted Scheme" guide now work. They built the environment in one process and evaluated it in another, which fails. The first example also passed
:max_heap_sizetoTask.Supervisor.async_nolink/3, which ignores it, so no heap limit applied. The examples now set the limit inside the task, give it in words rather than bytes, and setinclude_shared_binaries: trueso that large strings and bytevectors count towards it.The "Host Functions" guide told host code to raise
Schooner.Errorfor errors a script can catch. That exception bypasses Scheme handlers. The guide now usesSchooner.Host.raise_error/2, which reachesguardandwith-exception-handler.The "Special-form names" deviation now describes the actual behaviour: rebinding is accepted, but core special forms keep their meaning at the head of a form.
1.0.0 - 2026-05-01
Added
- Initial release.