# 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 `) 2. Try as statement (with continuation for incomplete input) 3. **Try as shell command (`!`)** 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 `, 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()` |