# `Schooner.Debug`
[🔗](https://github.com/ausimian/schooner/blob/1.2.0/lib/schooner/debug.ex#L1)

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](deviations.md)), 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 3

## Syntax

All three exports are syntax, not procedures, so each one knows
where it was used:

  * `(trace label expr)` evaluates `expr`, sends
    `"<label>: <value>"` to the sink and returns the value unchanged.
    The label is rendered as by `display` and the value as by
    `write`; multiple values are written space-separated. Wrap any
    expression in place without restructuring the code around it.
    `trace` has to see the value, so `expr` is not in tail position,
    but tail calls within `expr` still are: wrapping a call to a
    long-running loop does not make the loop grow the stack.

  * `(print obj ...)` sends the `display` rendering of its arguments,
    separated by spaces, to the sink. Its value is unspecified.

  * `(assert expr)` and `(assert expr message)` return the value of
    `expr` when it is true. When it is `#f` they raise an error
    object, as `(error ...)` does, whose message is
    `"assertion failed: <expr>"` with the source text of `expr`,
    followed by `": <message>"` when a message is given. `message`
    is only evaluated when the assertion fails. A `guard` catches
    the error like any other; if nothing does, it reaches the host
    as a `Schooner.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 through `Logger` at
    `level`, with the script location in the `:schooner_location`
    metadata and the kind of message (`:trace` or `: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.

# `message`

```elixir
@type message() :: %{
  kind: :trace | :print,
  text: binary(),
  location: Schooner.Location.t() | nil
}
```

What a function sink is called with.

# `sink`

```elixir
@type sink() :: {:logger, Logger.level()} | pid() | (message() -&gt; any())
```

Where `trace` and `print` send their output.

# `library`

```elixir
@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) — where `trace` and `print` send their output:
    `{:logger, level}`, a pid, or a 1-arity function. See "Sinks"
    above.

---

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