- Scheme 100%
sigil-nrepl
Network REPL server for
Sigil — provides a JSON-RPC-style
nREPL endpoint that editors and live-development tools connect to.
Built on (sigil repl) for prompt rendering and command parsing, and
on (sigil socket) for transport.
Long-running Sigil apps (live-crafter, cinder-cantata, game servers, etc.) embed an nREPL server so the developer can evaluate code, switch modules, inspect state, and iterate on running code without restarting the process.
Modules
| Module | Purpose |
|---|---|
(sigil nrepl) |
nREPL server: connection handling, session state, op dispatch (eval/doc/...) |
(sigil nrepl client) |
nREPL client: connect, send requests, named-connection registry |
Wire protocol
Messages are length-prefixed S-expressions:
<length:u32-be><sexp>
Request format: (request :id <string> :op <symbol> [:module <string>] [...params])
Response format: (response :id <string> :status <symbol> [...results])
Ops supported: eval, abort (alias interrupt), complete, doc,
describe, macroexpand, switch-module, modules, plus the debug-protocol
ops (debug-policy, debug-state, debug-frames, debug-restarts,
debug-quit).
Interrupt / abort
Expression evals run cooperatively — one preemptive-yield slice per
nrepl-process-pending — so a CPU-bound eval (e.g. (let loop () (loop))) does
not freeze the host's loop. Interrupt one from a second connection:
(request :id <abort-id> :op abort :target-id <eval-request-id>)
The abort responder gets :status ok :aborted <eval-request-id>; the
interrupted eval receives :status error :code "interrupted"; the session
survives and evaluates normally afterward. (A single top-level form that mixes
a top-level define with a long loop runs on the immediate, non-abortable path
— define at top level, then run, as separate requests.)
Output streaming
Output written during an eval is streamed back incrementally as it happens, before the final response, carrying the original eval request id:
(response :id <eval-id> :status out :out <string>) ; stdout
(response :id <eval-id> :status err :err <string>) ; stderr
followed by the normal terminal (response :id <eval-id> :status ok :value ...).
The bundled (sigil nrepl client) transparently drains these frames and returns
the terminal response from nrepl-eval.
Embedding an nREPL server
(import (sigil nrepl))
(define server (nrepl-start port: 7888))
;; In your app's main loop, drain pending requests:
(nrepl-process-pending server)
;; On shutdown:
(nrepl-stop server)
Connecting from a client
(import (sigil nrepl client))
(define conn (nrepl-connect "127.0.0.1" 7888))
(nrepl-eval conn "(+ 1 2)") ; => ((status . ok) (value . "3"))
(nrepl-switch-module conn "(my app)")
(nrepl-disconnect conn)
Build and test
sigil deps install
sigil build
sigil test --report
Local development against an unreleased sigil monorepo or against a local sigil-repl checkout:
sigil build --redirects ./dev-redirects.sgl
License
BSD-3-Clause. See LICENSE in the sigil monorepo for the canonical copy.