repl

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package repl is the REPL driver of design/03-analyzer-eval.md §7b: a loop of Read (pkg/reader) → Analyze+Eval (pkg/eval) → bind *1 *2 *3 (and *e on error) → print via pr-str, over an injected reader/writer pair. The terminal frontend (cmd/cljgo) and the future nREPL server are both thin frontends of this one driver.

REPL ergonomics (ADR 0018): graceful exit words, help, and did-you-mean suggestions. All affordances are fallback-only — a user-defined var always wins (the precedence principle) — and the exit/help words additionally gate on interactive use (d.Prompts), so piped scripts see exactly the historical semantics.

Session is the shared eval-session helper of ADR 0031: the binding frame every interactive session pushes (*ns* plus the *1 *2 *3 *e result/error vars) and the per-eval bookkeeping on it. The terminal Driver and pkg/nrepl are both thin frontends of this one helper — the smallest export that keeps their session semantics identical (spike S15 found Driver itself is not reusable: Run is wired to line-oriented stdin and EvalString deliberately skips *1).

Session journals (ADR 0016): every successful top-level form is appended — with its namespace context and a timestamp — to ~/.config/cljgo/sessions/<id>.journal, plain readable Clojure. Failed forms are journaled as comments (visible history, never replayed). :resume <id> replays a journal through the normal read→analyze→eval path and continues journaling to that id. Journaling is off when stdin is not a tty unless CLJGO_SESSION=1, so scripts, pipes and tests stay clean.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Driver

type Driver struct {
	// Prompts controls whether Run writes a prompt to Out before each
	// line of input (on for a terminal, off for piped input).
	Prompts bool

	// Interactive gates the ADR 0018 exit/quit/help affordances: they
	// only fire at an interactive prompt, so piped scripts keep the
	// historical unresolved-symbol semantics. Frontends set it for a tty;
	// tests inject it directly (no real tty needed).
	Interactive bool
	// contains filtered or unexported fields
}

Driver owns one evaluator session. *1 *2 *3 *e are proper dynamic vars in clojure.core (design/03 §7b): Run pushes a session frame binding them (plus *ns*) and set!s them after each eval; they revert to their nil roots when the session ends, as on JVM Clojure.

func New

func New(in io.Reader, out, errOut io.Writer) *Driver

New returns a driver with a fresh evaluator. in may be nil when only EvalReader/EvalString will be used (e.g. `cljgo run`).

func (*Driver) EvalReader

func (d *Driver) EvalReader(r io.Reader, filename string) (any, error)

EvalReader reads and evaluates every form from r (e.g. a .clj file), returning the value of the last form. No REPL affordances: results are not printed and *1/*e are not bound; errors return immediately with position. *ns* and *file* are bound for the load, as Clojure's load does (design/03 §7a) — an in-ns inside the file is undone when the load finishes. This is the `cljgo run` and conformance path.

func (*Driver) EvalString

func (d *Driver) EvalString(src, filename string) (any, error)

EvalString is EvalReader over a string (tests, future nREPL eval op).

func (*Driver) Evaluator

func (d *Driver) Evaluator() *eval.Evaluator

Evaluator exposes the session's evaluator (tests, future nREPL ops).

func (*Driver) Interrupt

func (d *Driver) Interrupt()

Interrupt aborts the input continuation in progress: the pending unfinished form is discarded and a fresh primary prompt is drawn. The session itself keeps running — at an empty prompt an interrupt is just a newline + new prompt. Safe to call from any goroutine (Run's SIGINT listener, a future nREPL interrupt op).

func (*Driver) Run

func (d *Driver) Run() error

Run is the interactive loop. Input is accumulated line by line; when the buffer ends mid-form (reader.ErrIncomplete) more input is read before the unfinished form is evaluated, so a form may span lines and one line may hold many forms. Each form is evaluated and printed AS IT COMPLETES: a syntax error later in the buffer never discards the result (or error) of a form that already closed. Reader syntax errors and eval errors are printed (with position when available) and the loop continues; only input exhaustion or an I/O error ends it.

Interrupts: Run listens for SIGINT for its whole lifetime. Ctrl-C discards the pending unfinished input (the " #_=> " continuation) and redraws a fresh prompt; it NEVER exits the session — like JVM Clojure's REPL under rlwrap (`clj`). The session ends on Ctrl-D (EOF at the prompt), exactly as clojure.main does.

func (*Driver) SessionID

func (d *Driver) SessionID() string

SessionID is this session's journal id ("" when journaling is off or Run has not started). Frontends may print it at startup.

type Session added in v0.2.0

type Session struct {
	// contains filtered or unexported fields
}

Session holds the vars of one eval session's binding frame. It is NOT goroutine-safe: dynamic bindings are goroutine-keyed in pkg/lang, so a session lives on exactly one goroutine — push Bindings() there and call RecordResult/RecordError only from it.

func NewSession added in v0.2.0

func NewSession(ev *eval.Evaluator) *Session

NewSession returns a session over ev. The *1 *2 *3 *e vars are the interned clojure.core ones; their nil roots are untouched — per-session values live only in the pushed frame.

func (*Session) Bindings added in v0.2.0

func (s *Session) Bindings() lang.IPersistentMap

Bindings is the session frame (design/03 §7b): *ns* seeded from the current namespace, *1 *2 *3 *e starting nil. Push it with lang.PushThreadBindings on the session's goroutine (and pop on exit); callers may Assoc extra vars (pkg/nrepl adds *out*) before pushing.

func (*Session) Evaluator added in v0.2.0

func (s *Session) Evaluator() *eval.Evaluator

Evaluator exposes the session's evaluator.

func (*Session) RecordError added in v0.2.0

func (s *Session) RecordError(err error)

RecordError binds *e to the eval error. Only valid under the session frame.

func (*Session) RecordResult added in v0.2.0

func (s *Session) RecordResult(res any)

RecordResult shifts the result history after a successful eval: *3 ← *2 ← *1 ← res. Only valid under the session frame.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL