nfl/repl

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.