- #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
3.1 KiB
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:
- Try as expression (
return <line>) - Try as statement (with continuation for incomplete input)
- 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 printshows the function,return xshows 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():
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 xcompiles and returns nil. This is correct: the user defined it, Lua should handle it. - Lua keywords (
if,for,while) are not valid identifiers inisbareidterms — they failaddreturn, entermultilinefor continuation, and behave normally. !cmdstill works — it compiles as a valid expression in the first path.
Files
| File | Changes |
|---|---|
lua.c |
isbareid(), addshellcmd(), modified loadline() |