Anvil REPL
Anvil is an interactive Steel REPL — the SLIME-style way to develop Steel. It runs your code in a second, fully independent Steel interpreter, separate from the editor's own config/scripting engine. Forms you evaluate in Anvil live in their own image: experiments never leak into the running editor, the image can be restarted from scratch, and a runaway loop can be interrupted instead of freezing the editor.
The name: you hammer Steel into shape on an anvil.
Anvil vs. eval-buffer
Macros already has in-editor evaluation: M-: (eval-expression), C-c C-e (eval-last-sexp), and C-c C-b (eval-buffer). Those run against the editor's own engine — that's exactly what you want for live-editing your config, because a define immediately changes the running editor.
Anvil is the opposite trade-off, for when you want a clean room:
| In-editor eval | Anvil REPL | |
|---|---|---|
| Engine | The editor's config engine | A separate, dedicated interpreter |
| State | Shared with the live editor | Isolated; restartable |
| Runaway loop | Wedges editor scripting | Interruptible (C-c C-c) |
| Editor primitives | Available (insert, define-key, …) |
Not registered — pure values |
| Printed output | Goes to the editor | Captured and shown with the result |
| Introspection | Static (LSP, compile-only) | Live image (apropos, doc, macroexpand) |
Use in-editor eval to hack on your config; use Anvil to test pure Steel code in isolation without risking the running editor.
Opening the REPL
M-x anvil-repl opens the *anvil-repl* buffer. Type a form on a line and press RET to evaluate it; the result is appended below as a ;; => … comment line.
(+ 1 2)
;; => 3
(map (lambda (x) (* x x)) '(1 2 3 4))
;; => (1 4 9 16)
From a Steel source buffer you can pop straight to the REPL with C-c C-z.
Interrupting a runaway evaluation
If you evaluate something that never returns — an infinite loop or unbounded recursion — press C-c C-c to stop it. The evaluation unwinds with an error and the REPL stays alive for the next form:
(define (spin) (spin))
(spin) ; ... hangs ... press C-c C-c
;; error: Interrupted by user
This works for any Steel loop, including raw tail recursion like the above — Steel's VM checks an interrupt flag on every instruction, so there's nowhere for a loop to hide. (The one thing it can't interrupt is code blocked inside a native call, but Anvil registers no such primitives.)
Restarting the image
C-c C-k (anvil-repl-reset) throws away the interpreter and builds a fresh one — every define you made is gone, as if you'd just started Anvil. Handy when the image has accumulated state you want to clear, or to confirm code works from a clean slate.
Evaluating from a source buffer
While editing a .scm file (steel or macros-steel mode), you can send code into the Anvil image and see the result echoed in the minibuffer, without leaving your buffer:
- C-c C-n —
anvil-eval-region: evaluate the active region. - C-M-x —
anvil-eval-defun: evaluate the top-level form at point (no need to select it). - C-c C-l —
anvil-load-file:(load …)the file you're visiting into the image — the SLIME "compile/load file" feel. Because it loads by path, relativeload/requireinside the file resolve against the file's directory. - M-x
anvil-eval-buffer— evaluate the whole buffer's text (works even on an unsaved buffer).
;; Point anywhere inside this form, then C-M-x:
(define (square x) (* x x))
;; minibuffer: anvil => #<function:square>
Captured output
(display …), (displayln …), and friends are captured and shown with the result, instead of vanishing to the process's stdout. So print-debugging works as you'd expect:
(begin (displayln "computing…") (+ 2 3))
;; anvil:
;; computing…
;; 5
Each evaluation starts with a fresh output buffer, so you only ever see this form's output.
Introspecting the live image
These commands answer against the actual running image — its real bindings, docstrings, and macros — not a static analysis of your source. (That distinction matters: the LSP completion/diagnostics you get while editing come from a separate compile-only engine; these come from Anvil itself.)
- C-c C-d —
anvil-doc: show the docstring for the symbol at point. - C-c C-x —
anvil-macroexpand: macroexpand the region (or the form at point) and show the expansion. Uses the un-optimized expansion, so you see the real macro shape —(when #t 1)reveals itsif, rather than being folded to1. - C-c C-a —
anvil-apropos: prompt for a pattern and list every live binding whose name contains it. Because it queries the image, anything you've justdefined shows up immediately.
Results that don't fit the minibuffer (doc, macroexpand, apropos) are rendered into an *anvil* buffer.
(define my-helper 42)
;; C-c C-a, type "my-hel" → *anvil* lists: my-helper
Completion
Inside the *anvil-repl* buffer, M-i (completion-at-point) completes the symbol at point against the live image — not a static analysis. So it offers every built-in and everything you've defined this session, the moment you define it:
(define (frobnicate x) (* x 2))
;; later, type (frob then M-i → frobnicate
This is the SLIME model: completion asks the running interpreter, the same way anvil-apropos does, rather than the editor's compile-only LSP. Completion is symbol-aware, so hyphenated/punctuated names like string->number and set! complete as one token. The popup narrows as you keep typing.
Not here: an interactive debugger with restarts. Steel unwinds on error rather than offering a condition/restart system, so a runaway or failing form surfaces as an error string (and stays interruptible), not a debugger stack.
Command & key reference
| Key | Command | Action |
|---|---|---|
M-x anvil-repl |
anvil-repl |
Open / switch to the *anvil-repl* buffer |
| RET (in REPL) | anvil-send-input |
Evaluate the current line |
| C-c C-c (in REPL) | anvil-repl-interrupt |
Interrupt the current evaluation |
| C-c C-k (in REPL) | anvil-repl-reset |
Restart the image (fresh interpreter) |
| M-i (in REPL) | completion-at-point |
Complete the symbol at point against the live image |
C-c C-z (in steel) |
anvil-repl |
Pop to the REPL from a source buffer |
C-c C-n (in steel) |
anvil-eval-region |
Evaluate the region in the Anvil image |
C-M-x (in steel) |
anvil-eval-defun |
Evaluate the top-level form at point |
C-c C-l (in steel) |
anvil-load-file |
(load …) the visited file into the image |
C-c C-d (in steel) |
anvil-doc |
Docstring for the symbol at point |
C-c C-x (in steel) |
anvil-macroexpand |
Macroexpand the region / form at point |
C-c C-a (in steel) |
anvil-apropos |
List live image bindings matching a pattern |
M-x anvil-eval-buffer |
anvil-eval-buffer |
Evaluate the whole buffer in the Anvil image |
The source-buffer commands are bound in both the steel and macros-steel modes. All of these are ordinary Steel commands defined in the bundled runtime — see Scripting with Steel to rebind them or build on anvil-eval.