Mark issues #02 (backtick lexing/parsing), #03 (command execution runtime), and #04 (argv parsing) as resolved. Add new issues for configuration (#08), programmable prompt (#09), and interactive command execution (#10).
4.5 KiB
Issue #10 — Interactive command execution
Problem
All backtick commands redirect stdout/stderr to pipes and capture output into a table {code, stdout, stderr}. Interactive programs lose their tty connection:
`bash` -- launches bash but it can't read/write the terminal
`vim foo` -- screen is blank, no input works
A real shell needs to hand the terminal to a child process and wait for it to finish.
What interactive execution means
The child process inherits all three file descriptors (stdin, stdout, stderr) directly from the parent. The parent waitpid()s until the child exits. The terminal behaves as if lush isn't there — the child owns it.
Syntax
Option A: ! prefix (recommended starting point)
A ! token marks a command as interactive. The command string extends to end-of-line (no closing delimiter needed):
!bash
!vim foo.lua
!ssh user@host
! doesn't conflict with Lua syntax (not is the logical-not keyword). Supports ${} interpolation like backticks:
local f = "foo.lua"
!vim ${f}
Option B: Bare-word REPL fallback (separate issue?)
If a line fails to parse as Lua, interpret it as an interactive command:
> vim foo.lua -- not valid Lua, runs interactively
> ls -la -- same
> local x = 1 -- valid Lua, runs as Lua
Only applies in the REPL, not scripts. Could be combined with Option A. Touches lua.c (REPL loop) rather than the lexer/parser, so may warrant its own issue.
Option C: Built-in function
exec("bash")
exec("vim", "foo.lua")
No new syntax. Could serve as the low-level primitive that ! compiles down to.
Return value
Interactive commands return the exit code as a plain integer:
local code = !bash
assert(type(code) == "number")
No stdout/stderr to capture — they go to the terminal. Returning an integer (not a table) clearly distinguishes interactive from captured commands.
When used as a statement (no assignment), the return value is discarded.
Runtime implementation
Add luaB_interactive to lcmd.c, registered as __interactive global:
fork()without creating any pipes- Child:
execvp()directly (inherits parent's stdin/stdout/stderr) - Parent: ignore
SIGINT/SIGQUIT(so Ctrl-C goes to child, not lush), thenwaitpid()and return exit code as integer - Child: restore
SIG_DFLforSIGINT/SIGQUITbeforeexecvp() - Reuses existing
parse_argv()for command string tokenization
Lexer changes (if using ! prefix)
- Add
TK_ICMDandTK_ICMD_INTERPtokens to enum inllex.h - Add
in_icmdflag toLexState(mirrorsin_commandfor backticks) read_icmd_body(): likeread_command_body()but terminates on newline/EOF instead of closing backtickread_icmd(): skips leading whitespace after!, then callsread_icmd_body()luaX_readicmdcont(): continuation reader after${}interpolation (mirrorsluaX_readcommandcont())- Handle
!case inllex()switch: when!is followed by a non-=character at statement position, read as interactive command
Parser changes (if using ! prefix)
- Add
interactiveexp()inlparser.c— mirrorscommandexp()but compiles to__interactive(cmdstring)instead of__command(cmdstring) - Add
TK_ICMD/TK_ICMD_INTERPcases insimpleexp() - Both simple and interpolated forms follow the same pattern as backtick commands
Open questions
- Which syntax option(s) to pursue? Could start with
!prefix and add others later. - Job control: should
Ctrl-Zsuspend the child and return to lush? Requirestcsetpgrp()/ process group work. Probably defer to a later issue. - Should bare-word REPL fallback (Option B) be a separate issue?
PATHlookup:execvpalready searchesPATH, so this should just work.- Should
!support multi-line commands (e.g. with\continuation)? Probably not — keep it simple.
Files touched
| File | Description |
|---|---|
lcmd.c |
Add luaB_interactive — fork/exec without pipes, return exit code |
lcmd.h |
Declare luaB_interactive |
linit.c |
Register __interactive global in opencommand() |
llex.h |
Add TK_ICMD, TK_ICMD_INTERP tokens; in_icmd flag on LexState; declare luaX_readicmdcont() |
llex.c |
Add read_icmd_body(), read_icmd(), luaX_readicmdcont(); handle ! in llex() |
lparser.c |
Add interactiveexp(); handle TK_ICMD/TK_ICMD_INTERP in simpleexp() |
lua.c |
(Only if doing Option B) REPL fallback logic in loadline() |