Implement prefix-based interactive commands via ! (issue #14)

Add !command syntax that runs commands with inherited terminal (no
capture). The lexer treats ! as a statement-starting token, reading
to end-of-line with the same interpolation/escape/pipe support as
backtick commands. The C function sets _ = {code=N, stdout="", stderr=""}.
This commit is contained in:
Cormac Shannon
2026-03-02 22:06:29 +00:00
parent 75098da240
commit db858b3f68
9 changed files with 399 additions and 47 deletions

View File

@@ -124,9 +124,12 @@ lexerror([['alo \98]], "<eof>")
-- valid characters in variable names
for i = 0, 255 do
local s = string.char(i)
-- skip '!' which is now a valid statement starter (interactive commands)
if s == '!' then goto continue end
assert(not string.find(s, "[a-zA-Z_]") == not load(s .. "=1", ""))
assert(not string.find(s, "[a-zA-Z_0-9]") ==
not load("a" .. s .. "1 = 1", ""))
::continue::
end

View File

@@ -0,0 +1,97 @@
-- testes/lush/interactive.lua
-- Tests for prefix-based interactive command execution (issue #14).
print "testing interactive commands"
-- ===== BASIC: _ IS SET CORRECTLY =====
-- basic interactive command sets _
!true
assert(type(_) == "table")
assert(_.code == 0)
assert(_.stdout == "")
assert(_.stderr == "")
-- exit code preserved
!false
assert(_.code == 1)
-- specific exit code
!sh -c "exit 42"
assert(_.code == 42)
-- command not found
!nonexistent_command_xyz_999
assert(_.code == 127)
-- ===== INTERPOLATION =====
do
local name = "hello"
!echo ${name}
assert(_.code == 0)
assert(_.stdout == "")
end
-- interpolation with expression
do
local x = 21
!sh -c "exit ${x * 2}"
assert(_.code == 42)
end
-- ===== PIPING =====
-- exit code from last stage
!echo hello | sh -c "exit 7"
assert(_.code == 7)
-- piping: first stage output goes to second
!echo hello | cat
assert(_.code == 0)
-- ===== _ IS OVERWRITTEN =====
!true
assert(_.code == 0)
!false
assert(_.code == 1)
-- ===== INSIDE LUA BLOCKS =====
do
!true
assert(_.code == 0)
end
do
if true then
!true
assert(_.code == 0)
end
end
do
for i = 1, 3 do
!true
assert(_.code == 0)
end
end
do
local function run_cmd()
!true
return _.code
end
assert(run_cmd() == 0)
end
-- ===== EMPTY COMMAND (just whitespace after !) =====
do
-- set _ to something first, then run empty interactive
!true
assert(_.code == 0)
end
print "OK"