Skip to content

feat(repl): interactive line editor — history, cursor editing, tab completion (#392)#456

Merged
InauguralPhysicist merged 3 commits into
mainfrom
392-repl-line-editor
Jul 6, 2026
Merged

feat(repl): interactive line editor — history, cursor editing, tab completion (#392)#456
InauguralPhysicist merged 3 commits into
mainfrom
392-repl-line-editor

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

What

The last item in the #419 "Now" tier: the CLI REPL on a tty gets a zero-dependency raw-termios line editor, ported from the EigenOS console editor (the downstream-gap-surfaces-upstream rule applied to our own port). Pressing up-arrow now recalls history instead of printing ^[[A.

  • History — in-memory ring + EIGS_HISTORY file (default ~/.eigenscript_history, created 0600), draft parking on up-arrow, consecutive dedupe, persists across sessions.
  • Editing — arrows, Home/End/Delete, Ctrl-A/E/U/K/W/L; Ctrl-C cancels the line and an open : block; Ctrl-D is EOF on an empty line.
  • Tab completion — over builtins + session top-level bindings (both are env bindings, so one env-chain walk covers them).
  • Observer touch — interactive sessions arm history recording from the start, so prev of x works on session bindings; per-line compilation otherwise records nothing to answer from.

Invariants held

  • Piped/non-tty byte-identical: the old fgets loop is kept verbatim for non-tty stdin — verified against a pre-change binary across 9 transcript+rc scenarios. EIGS_REPL_PLAIN=1 forces it on a tty.
  • Zero new link deps: no readline/libedit. repl.c is CLI-only like main.c (Makefile CLI_ONLY filter for LSP/embed/fuzz builds; amalgamate.sh drops it), so termios never reaches the embed/freestanding profiles.
  • The parse-error path now frees the partial AST (cf. builtins: free partial AST when eval hits a parse error #214) — the old inline loop leaked it.
  • TCSADRAIN, not TCSAFLUSH, around eval — flush discarded pasted type-ahead (found by the pty tests).

Acceptance (from #392)

  • Interactive session supports history/arrows/editing (+ tab completion)
  • Suite-gated REPL transcript test — section [42c]: 3 byte-exact piped goldens + 13 pty-driven editor tests (tests/test_repl.py)
  • make output has zero new link dependencies
  • Piped/non-tty behavior unchanged (byte-exact goldens)

Gates

  • Release suite 2552/2552; ASan + detect_leaks=1 suite 2552/2552 ([42c] re-run standalone against the ASan binary: 16/16)
  • make lsp / make embed-smoke / make freestanding-check green

Closes #392

🤖 Generated with Claude Code

…mpletion (#392)

Port the EigenOS console editor (EigenOS src/hal.c) to the CLI REPL as a
zero-dependency raw-termios line editor in the new CLI-only src/repl.c:

- history: in-memory ring + EIGS_HISTORY file (default
  ~/.eigenscript_history, created 0600), draft parking on up-arrow,
  consecutive dedupe, persistence across sessions
- editing: arrows, Home/End/Delete, Ctrl-A/E/U/K/W/L, Ctrl-C cancels the
  line AND an open ':' block, Ctrl-D is EOF on an empty line
- tab completion over builtins + session bindings (both are env bindings,
  so one env-chain walk covers them)
- observer touch: interactive sessions arm g_trace_hist/g_trace_obs_hist
  from the start, so 'prev of x' works on session bindings (per-line
  compilation otherwise records no history to answer from)

Piped/non-tty stdin keeps the old fgets loop byte-for-byte (verified
against a pre-change binary across 9 transcript+rc scenarios);
EIGS_REPL_PLAIN=1 forces it on a tty. TCSADRAIN (not TCSAFLUSH) around
eval so pasted type-ahead survives. The parse-error path now frees the
partial AST (cf. #214) — the old inline loop leaked it.

repl.c is CLI-only like main.c (Makefile CLI_ONLY filter for
LSP/embed/fuzz; amalgamate.sh drops it) so termios never reaches the
embed/freestanding profiles.

Suite section [42c]: 3 byte-exact piped-transcript goldens +
13 pty-driven editor tests (tests/test_repl.py). Release and ASan
(detect_leaks=1) suites both 2552/2552; make lsp / embed-smoke /
freestanding-check green.

Closes #392

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread src/repl.c Dismissed
Comment thread src/repl.c Dismissed
Comment thread src/repl.c Dismissed
Comment thread src/repl.c Dismissed
InauguralPhysicist and others added 2 commits July 6, 2026 13:12
build.sh's hardcoded SOURCES didn't know about the new CLI-only repl.c —
undefined reference to eigenscript_repl on every CI job that builds via
build.sh (linux/macos matrix, bench, install.sh). The lsp branch strips
repl.c alongside main.c.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
openpty()+subprocess left the child with no controlling tty; the
raw-termios calls sat in undefined job-control territory — Linux
tolerated it, macOS wedged the child and all 13 editor tests hung.
pty.fork() gives the child the pty as its controlling terminal, which
is what a real terminal session is anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread tests/test_repl.py
if r:
try:
self.buf += os.read(self.master, 4096)
except OSError:
@InauguralPhysicist InauguralPhysicist merged commit 42ef8b0 into main Jul 6, 2026
17 checks passed
@InauguralPhysicist InauguralPhysicist deleted the 392-repl-line-editor branch July 6, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

REPL: line editing, history, and observer-native display (port the EigenOS editor)

2 participants