Update project issues: resolve #02-#04, add #08-#10

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).
This commit is contained in:
Cormac Shannon
2026-02-28 18:27:55 +00:00
parent 39d6cc95cb
commit 4b49907ce7
6 changed files with 204 additions and 3 deletions

View File

@@ -0,0 +1,114 @@
# 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:
```lua
`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):
```lua
!bash
!vim foo.lua
!ssh user@host
```
`!` doesn't conflict with Lua syntax (`not` is the logical-not keyword). Supports `${}` interpolation like backticks:
```lua
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
```lua
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:
```lua
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), then `waitpid()` and return exit code as integer
- Child: restore `SIG_DFL` for `SIGINT`/`SIGQUIT` before `execvp()`
- Reuses existing `parse_argv()` for command string tokenization
## Lexer changes (if using `!` prefix)
- Add `TK_ICMD` and `TK_ICMD_INTERP` tokens to enum in `llex.h`
- Add `in_icmd` flag to `LexState` (mirrors `in_command` for backticks)
- `read_icmd_body()`: like `read_command_body()` but terminates on newline/EOF instead of closing backtick
- `read_icmd()`: skips leading whitespace after `!`, then calls `read_icmd_body()`
- `luaX_readicmdcont()`: continuation reader after `${}` interpolation (mirrors `luaX_readcommandcont()`)
- Handle `!` case in `llex()` switch: when `!` is followed by a non-`=` character at statement position, read as interactive command
## Parser changes (if using `!` prefix)
- Add `interactiveexp()` in `lparser.c` — mirrors `commandexp()` but compiles to `__interactive(cmdstring)` instead of `__command(cmdstring)`
- Add `TK_ICMD` / `TK_ICMD_INTERP` cases in `simpleexp()`
- 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-Z` suspend the child and return to lush? Requires `tcsetpgrp()` / process group work. Probably defer to a later issue.
- Should bare-word REPL fallback (Option B) be a separate issue?
- `PATH` lookup: `execvp` already searches `PATH`, 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()` |