- #25: $VAR expansion in commands - #26: Shell abbreviations and user-extensible builtins - #27: $(cmd) subcommand syntax - #22: Updated with implementation details (in progress) - #7: Standardize status label
75 lines
3.1 KiB
Markdown
75 lines
3.1 KiB
Markdown
# 22 — Implicit interactive commands (drop `!` prefix)
|
|
|
|
**Status:** in progress
|
|
|
|
## Goal
|
|
|
|
In the REPL, treat unrecognized input as shell commands so users can type `git status` or `ls -la` without the `!` prefix.
|
|
|
|
## Implementation
|
|
|
|
### REPL fallback chain (`loadline()` in `lua.c`)
|
|
|
|
The standard Lua REPL tries two compilations. We add a third:
|
|
|
|
1. Try as expression (`return <line>`)
|
|
2. Try as statement (with continuation for incomplete input)
|
|
3. **Try as shell command (`!<line>`)**
|
|
|
|
This is done by adding `addshellcmd()` which prepends `!` to the line and compiles it, triggering the existing lexer/parser path for interactive commands.
|
|
|
|
### The bare identifier problem
|
|
|
|
Multi-word input like `git status` fails both Lua paths and correctly falls through to shell. But single-word commands like `ls` compile as `return ls;` — a valid Lua expression that returns `nil` — so they never reach the shell fallback.
|
|
|
|
Worse: if `addreturn` is bypassed, `ls` as a statement is *incomplete* Lua (the parser expects `ls(...)` or `ls = ...`), so `multiline()` enters continuation mode and the REPL hangs waiting for more input.
|
|
|
|
### Fix: check `_G` before the expression path
|
|
|
|
Before trying `return <line>`, check if the line is a bare identifier (single word matching `[a-zA-Z_][a-zA-Z0-9_]*`). If it is, look it up in `_G`:
|
|
|
|
- **In `_G`** → proceed normally (`return print` shows the function, `return x` shows its value)
|
|
- **Not in `_G`** → skip expression and statement paths entirely, go straight to shell
|
|
|
|
This is a compile-time check — no runtime error interception, no flags, no special subroutines. ~10 lines of C in `loadline()`:
|
|
|
|
```c
|
|
line = lua_tostring(L, 1);
|
|
bare = isbareid(line);
|
|
if (bare && lua_getglobal(L, line) == LUA_TNIL) {
|
|
lua_pop(L, 1);
|
|
status = addshellcmd(L); /* straight to shell */
|
|
}
|
|
else {
|
|
if (bare) lua_pop(L, 1);
|
|
/* normal expression → statement → shell fallback chain */
|
|
}
|
|
```
|
|
|
|
### Summary of behavior
|
|
|
|
| Input | Path taken | Result |
|
|
|-------|-----------|--------|
|
|
| `print("hi")` | expression | Lua expression |
|
|
| `x = 42` | statement | Lua statement |
|
|
| `if true then print("x") end` | statement | Lua statement |
|
|
| `print` | expression (in `_G`) | shows function value |
|
|
| `x` (after `x=42`) | expression (in `_G`) | shows 42 |
|
|
| `git status` | expression fails → statement fails → shell | runs git |
|
|
| `ls -la` | expression fails → statement fails → shell | runs ls |
|
|
| `echo hello` | expression fails → statement fails → shell | runs echo |
|
|
| `ls` | bare id, not in `_G` → shell | runs ls |
|
|
| `!pwd` | expression (already valid `!` syntax) | runs pwd |
|
|
|
|
### Edge cases
|
|
|
|
- A global explicitly set to `nil` (`x = nil`) would still try the expression path — `return x` compiles and returns nil. This is correct: the user defined it, Lua should handle it.
|
|
- Lua keywords (`if`, `for`, `while`) are not valid identifiers in `isbareid` terms — they fail `addreturn`, enter `multiline` for continuation, and behave normally.
|
|
- `!cmd` still works — it compiles as a valid expression in the first path.
|
|
|
|
## Files
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `lua.c` | `isbareid()`, `addshellcmd()`, modified `loadline()` |
|