The nfl repl session engine (#14) — an interactive read/expand/compile/ run loop backed by an actual nim c per accepted input, rather than any kind of interpreter. See man/repl.md for the user-facing model this implements: full-session replay (every accepted input is kept in a transcript and the whole transcript is recompiled and re-run on each new input, with only the newly produced output printed), name-keyed redefinition (a new var/proc/type/defmacro/… replaces an earlier one of the same name in place), and CL-style defvar (idempotent: a re-entered defvar for an already-bound name is skipped outright).
Types
AddOutcome = enum aoAdded, ## Compiled, ran, and committed; `message` is any new ## program output to print (may be empty). aoSkippedDefvar, ## A `defvar` re-entered for an already-bound name — ## intentionally a no-op; `message` is empty. aoBlank, ## The input was empty / comments only; nothing to do. aoError ## Reader, macro-expansion, compile, or runtime error; ## `message` is the diagnostic to show. The session is ## unchanged (transactional — see the module doc).
AddResult = object outcome*: AddOutcome message*: string
ReadOutcome = enum roForm, ## `source`/`forms` hold one or more complete top-level forms. roBlank, ## Input was empty or comments-only; nothing to evaluate. roError, ## A genuine (non-`incomplete`) reader error; `message` is set. roEof ## End of input (Ctrl-D) with no pending partial entry.
- What readEntry produced for one prompt/continuation cycle.
ReadResult = object outcome*: ReadOutcome source*: string forms*: seq[Syntax] message*: string
- The result of readEntry. For a :command line (roForm with forms.len == 0), source is the trimmed command text; otherwise source is the raw, possibly multi-line, entry text.
ReplSession = object ## One temp dir for the whole session — holds ## `session.nfl`, `wrapper.nim`, and the compiled ## binary; reused across inputs (unlike ## `cli.nim`'s per-call `tempBuildDir`) so a ## shared nimcache actually speeds up successive ## compiles. ## `getCurrentDir()` at session start — passed as ## `expandModule`'s `currentDir` so a relative ## `(import ./helpers.nfl)` resolves against ## where `nfl repl` was launched, not against the ## session's temp dir (see module doc). ## Captured stdout+stderr of the last successful ## run — a fresh run's output is diffed against ## this so only the new suffix gets printed.
Procs
proc closeSession(session: ReplSession) {....raises: [OSError], tags: [ReadDirEffect, WriteDirEffect], forbids: [].}
- Removes the session's per-session temp dir. Never removes the shared nimcache dir (sharedNimcacheDir) — that's meant to outlive any one session.
proc initSession(autoloadCore = true): ReplSession {....raises: [OSError, IOError], tags: [ReadEnvEffect, ReadIOEffect, WriteDirEffect, ReadDirEffect], forbids: [].}
- Starts a new session: one temp dir for its session.nfl/wrapper.nim/ binary (removed by closeSession), plus a stable, shared nimcache dir reused across every nfl repl process (not per-session) so nfl/compiler and the preamble aren't recompiled from scratch on every launch — only session.nfl's own accumulated content forces a recompile each input.
proc readEntry(readLine: proc (prompt: string; line: var string): bool {.closure.}; prompt, continuePrompt: string): ReadResult {. ...raises: [Exception, Exception], tags: [RootEffect], forbids: [].}
- Reads lines (via readLine — std/rdstdin's readLineFromStdin in cli.nim, a plain stub in tests) until they form a complete top-level input, prompting prompt for the first line and continuePrompt for every line after. Incompleteness is decided by the reader itself (ReaderError.incomplete, diagnostics.nim) rather than by counting delimiters here — that would duplicate string/|sym|/#| |# lexing.
proc transcriptText(session: ReplSession): string {....raises: [], tags: [], forbids: [].}
- The whole accepted session, as NFL source — what :transcript prints.
proc tryAddInput(session: var ReplSession; source: string; forms: seq[Syntax]): AddResult {. ...raises: [ValueError, Exception, KeyError, IOError, OSError], tags: [ RootEffect, ReadDirEffect, ReadIOEffect, WriteIOEffect, ReadEnvEffect, ExecIOEffect, TimeEffect], forbids: [].}
- Attempts to add one already-read entry (readEntry's source/forms) to the session. On success (aoAdded) the whole candidate transcript has been compiled and run, and the session is updated to match; on any failure the session is left exactly as it was (transactional — see the module doc), and message carries a diagnostic already rewritten to point at <repl:N> REPL input, never at generated Nim or the on-disk session.nfl.