The opt-in (schooner debug) library: tracing, printing and
assertions for script authors.
display, write and newline return their rendered text rather
than writing it to a port (see Deviations), so a
(display x) dropped into a procedure body shows nothing. This
library sends output to a sink the embedder chooses instead. Like
Schooner.Time, it is not in the standard registry: a script can
only import it from an environment built with it, so the default
sandbox stays free of side effects.
env =
Schooner.Environment.new(
pre_imports: [["scheme", "base"]],
libraries: [Schooner.Debug.library(sink: {:logger, :debug})]
)
Schooner.eval!(~s|(import (schooner debug)) (trace "sum" (+ 1 2))|, env)
# logs "sum: 3" and returns 3Syntax
All three exports are syntax, not procedures, so each one knows where it was used:
(trace label expr)evaluatesexpr, sends"<label>: <value>"to the sink and returns the value unchanged. The label is rendered as bydisplayand the value as bywrite; multiple values are written space-separated. Wrap any expression in place without restructuring the code around it.tracehas to see the value, soexpris not in tail position, but tail calls withinexprstill are: wrapping a call to a long-running loop does not make the loop grow the stack.(print obj ...)sends thedisplayrendering of its arguments, separated by spaces, to the sink. Its value is unspecified.(assert expr)and(assert expr message)return the value ofexprwhen it is true. When it is#fthey raise an error object, as(error ...)does, whose message is"assertion failed: <expr>"with the source text ofexpr, followed by": <message>"when a message is given.messageis only evaluated when the assertion fails. Aguardcatches the error like any other; if nothing does, it reaches the host as aSchooner.Error.
Because they are syntax, the names cannot be passed around as
values: write (lambda (x) (print x)) rather than print.
Sinks
library/1 takes a required :sink:
{:logger, level}logs each message throughLoggeratlevel, with the script location in the:schooner_locationmetadata and the kind of message (:traceor:print) in:schooner_debug.- a pid is sent
{:schooner_debug, kind, text, location}. - a 1-arity function is called with
%{kind: kind, text: text, location: location}; its return value is ignored.
location is the Schooner.Location of the trace or print
form. Like the locations of errors, it is only recorded when the
script is evaluated with :file, locations: true or
debug: true; otherwise it is nil. The same holds for the
location of a failed assertion.
The sink runs in the evaluating process. If it raises, exits or
throws, evaluation stops with a Schooner.Primitive.Error, which
Schooner.eval/3 returns as {:error, exception}. A script's
guard does not catch it.
Compiled scripts
Schooner.compile/3 expands macros when it compiles, so a compiled
script keeps the sink of the environment it was compiled against.
Compile against an environment with the sink you want to run with.
Summary
Functions
Build the (schooner debug) library. Pass it to
Schooner.Environment.new/1 in :libraries to let scripts evaluated
against the environment (import (schooner debug)).
Types
@type message() :: %{ kind: :trace | :print, text: binary(), location: Schooner.Location.t() | nil }
What a function sink is called with.
@type sink() :: {:logger, Logger.level()} | pid() | (message() -> any())
Where trace and print send their output.
Functions
@spec library(keyword()) :: Schooner.Library.t()
Build the (schooner debug) library. Pass it to
Schooner.Environment.new/1 in :libraries to let scripts evaluated
against the environment (import (schooner debug)).
Options
:sink(required) — wheretraceandprintsend their output:{:logger, level}, a pid, or a 1-arity function. See "Sinks" above.