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

@@ -2206,6 +2206,85 @@ static void envstat (LexState *ls) {
}
/*
** Parse an interactive command statement (!cmd).
** Compiles !cmd as __interactive("cmd") with result discarded.
** The C function sets global _ = {code=N, stdout="", stderr=""}.
*/
static void interactivestat (LexState *ls) {
FuncState *fs = ls->fs;
if (ls->t.token == TK_INTERACTIVE) {
/* simple interactive command, no interpolation */
int base, line;
expdesc func, cmdstr, v;
TString *fname = luaX_newstring(ls, "__interactive", 13);
line = ls->linenumber;
buildglobal(ls, fname, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info;
codestring(&cmdstr, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &cmdstr);
luaX_next(ls); /* consume TK_INTERACTIVE */
init_exp(&v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 1));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base);
}
else {
/* interactive command with ${expr} interpolation */
expdesc func, cmdstr, v;
int base, nconcat = 0;
int line = ls->linenumber;
TString *fname = luaX_newstring(ls, "__interactive", 13);
TString *tsname = luaX_newstring(ls, "tostring", 8);
buildglobal(ls, fname, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info;
/* load the first fragment */
codestring(&cmdstr, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &cmdstr);
nconcat++;
for (;;) {
expdesc interp, tostrfn;
int tostr_base;
luaX_next(ls); /* advance past TK_INTERACTIVE_INTERP */
buildglobal(ls, tsname, &tostrfn);
luaK_exp2nextreg(fs, &tostrfn);
tostr_base = tostrfn.u.info;
expr(ls, &interp);
check(ls, '}');
luaK_exp2nextreg(fs, &interp);
luaK_codeABC(fs, OP_CALL, tostr_base, 2, 2);
fs->freereg = cast_byte(tostr_base + 1);
nconcat++;
if (luaX_readcommandcont(ls) == TK_INTERACTIVE_INTERP) {
expdesc frag;
codestring(&frag, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &frag);
nconcat++;
}
else {
/* TK_INTERACTIVE: final fragment */
expdesc frag;
codestring(&frag, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &frag);
nconcat++;
break;
}
}
luaX_next(ls); /* advance past final TK_INTERACTIVE */
if (nconcat > 1) {
int first = base + 1;
luaK_codeABC(fs, OP_CONCAT, first, nconcat, 0);
fs->freereg = cast_byte(first + 1);
}
/* emit void call (C=1 means 0 return values) */
init_exp(&v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 1));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base);
}
}
static void statement (LexState *ls) {
int line = ls->linenumber; /* may be needed for error messages */
enterlevel(ls);
@@ -2275,6 +2354,11 @@ static void statement (LexState *ls) {
envstat(ls);
break;
}
case TK_INTERACTIVE:
case TK_INTERACTIVE_INTERP: { /* stat -> !command */
interactivestat(ls);
break;
}
#if defined(LUA_COMPAT_GLOBAL)
case TK_NAME: {
/* compatibility code to parse global keyword when "global"