Unify command tokens: remove TK_COMMAND_INTERP and TK_INTERACTIVE_INTERP

Use cmd_mode (already on LexState) to signal whether interpolation
follows, instead of separate *_INTERP token variants. This eliminates
duplicate simple/interpolated code paths in the parser — commandexp()
and interactivestat() now each use a single unified loop that checks
cmd_mode != 0. A shared parseinterp() helper handles each ${expr}
interpolation cycle.
This commit is contained in:
Cormac Shannon
2026-03-12 23:01:31 +00:00
parent f09a033160
commit 5135b16375
3 changed files with 80 additions and 160 deletions

10
llex.c
View File

@@ -50,8 +50,8 @@ static const char *const luaX_tokens [] = {
"//", "..", "...", "==", ">=", "<=", "~=", "//", "..", "...", "==", ">=", "<=", "~=",
"<<", ">>", "::", "<eof>", "<<", ">>", "::", "<eof>",
"<number>", "<integer>", "<name>", "<string>", "<number>", "<integer>", "<name>", "<string>",
"<command>", "<command_interp>", "<envvar>", "<command>", "<envvar>",
"<interactive>", "<interactive_interp>" "<interactive>"
}; };
@@ -475,8 +475,8 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) {
** Called initially when '`' is seen, and also continued via ** Called initially when '`' is seen, and also continued via
** luaX_readcommandcont() after the parser processes an interpolated ** luaX_readcommandcont() after the parser processes an interpolated
** expression. ** expression.
** Returns TK_COMMAND if the string is complete (closing ` found), ** Returns TK_COMMAND or TK_INTERACTIVE in all cases.
** or TK_COMMAND_INTERP if an interpolation ${...} was found. ** When an interpolation ${...} is found, cmd_mode remains non-zero.
*/ */
static int read_command_body (LexState *ls, SemInfo *seminfo) { static int read_command_body (LexState *ls, SemInfo *seminfo) {
int interactive = (ls->cmd_mode == 2); int interactive = (ls->cmd_mode == 2);
@@ -513,7 +513,7 @@ static int read_command_body (LexState *ls, SemInfo *seminfo) {
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff), seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
luaZ_bufflen(ls->buff)); luaZ_bufflen(ls->buff));
ls->saved_cmd_mode = ls->cmd_mode; /* save for readcommandcont */ ls->saved_cmd_mode = ls->cmd_mode; /* save for readcommandcont */
return interactive ? TK_INTERACTIVE_INTERP : TK_COMMAND_INTERP; return interactive ? TK_INTERACTIVE : TK_COMMAND;
} }
else { else {
save(ls, '$'); /* not an interpolation, keep literal '$' */ save(ls, '$'); /* not an interpolation, keep literal '$' */

4
llex.h
View File

@@ -41,10 +41,8 @@ enum RESERVED {
TK_DBCOLON, TK_EOS, TK_DBCOLON, TK_EOS,
TK_FLT, TK_INT, TK_NAME, TK_STRING, TK_FLT, TK_INT, TK_NAME, TK_STRING,
TK_COMMAND, TK_COMMAND,
TK_COMMAND_INTERP,
TK_ENVVAR, TK_ENVVAR,
TK_INTERACTIVE, TK_INTERACTIVE
TK_INTERACTIVE_INTERP
}; };
/* number of reserved words */ /* number of reserved words */

226
lparser.c
View File

@@ -554,24 +554,34 @@ static void singlevar (LexState *ls, expdesc *var) {
/* /*
** Compile a command expression: emit code equivalent to ** Helper: parse a single ${expr} interpolation fragment.
** __command(cmdstring) ** Emits tostring(expr), reads the next command continuation fragment,
** where cmdstring is already built (possibly via concatenation ** and pushes it as a string constant. Returns with the continuation
** from interpolation fragments). ** token already set (TK_COMMAND or TK_INTERACTIVE).
*/ */
static void codecommand (LexState *ls, expdesc *v, expdesc *cmdstr) { static void parseinterp (LexState *ls, TString *tsname, int *nconcat) {
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
int base, line; expdesc interp, tostrfn;
expdesc func; int tostr_base;
line = ls->linenumber; luaX_next(ls); /* advance past TK_COMMAND / TK_INTERACTIVE */
codelushfunc(fs, LUSH_OP_COMMAND, &func); /* emit tostring(expr) */
base = func.u.info; buildglobal(ls, tsname, &tostrfn);
/* push the command string argument */ luaK_exp2nextreg(fs, &tostrfn);
luaK_exp2nextreg(fs, cmdstr); tostr_base = tostrfn.u.info;
/* emit OP_CALL: 1 arg, 1 result */ expr(ls, &interp);
init_exp(v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 2)); check(ls, '}');
luaK_fixline(fs, line); luaK_exp2nextreg(fs, &interp);
fs->freereg = cast_byte(base + 1); luaK_codeABC(fs, OP_CALL, tostr_base, 2, 2);
fs->freereg = cast_byte(tostr_base + 1);
(*nconcat)++;
/* read continuation fragment */
luaX_readcommandcont(ls);
{
expdesc frag;
codestring(&frag, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &frag);
(*nconcat)++;
}
} }
@@ -1230,83 +1240,38 @@ static void funcargs (LexState *ls, expdesc *f) {
/* /*
** Parse a backtick command expression (TK_COMMAND or TK_COMMAND_INTERP). ** Parse a backtick command expression (TK_COMMAND).
** Compiles `cmd` as __command("cmd") and `cmd ${expr}` as ** Compiles `cmd` as __command("cmd") and `cmd ${expr}` as
** __command("cmd " .. tostring(expr) .. ""). ** __command("cmd " .. tostring(expr) .. "").
** On entry, ls->t.token is TK_COMMAND or TK_COMMAND_INTERP. ** On entry, ls->t.token is TK_COMMAND.
** On exit, the token has been consumed (ls->t has the next token). ** On exit, the token has been consumed (ls->t has the next token).
*/ */
static void commandexp (LexState *ls, expdesc *v) { static void commandexp (LexState *ls, expdesc *v) {
if (ls->t.token == TK_COMMAND) { FuncState *fs = ls->fs;
/* simple command with no interpolation */ expdesc func, cmdstr;
expdesc cmdstr; int base, nconcat = 0;
codestring(&cmdstr, ls->t.seminfo.ts); int line = ls->linenumber;
luaX_next(ls); TString *tsname = NULL;
codecommand(ls, v, &cmdstr); codelushfunc(fs, LUSH_OP_COMMAND, &func);
base = func.u.info;
/* load the first fragment */
codestring(&cmdstr, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &cmdstr);
nconcat++;
while (ls->cmd_mode != 0) {
if (tsname == NULL)
tsname = luaX_newstring(ls, "tostring", 8);
parseinterp(ls, tsname, &nconcat);
} }
else { luaX_next(ls); /* consume final TK_COMMAND */
/* command with ${expr} interpolation */ if (nconcat > 1) {
FuncState *fs = ls->fs; int first = base + 1;
expdesc func, cmdstr; luaK_codeABC(fs, OP_CONCAT, first, nconcat, 0);
int base, nconcat = 0; fs->freereg = cast_byte(first + 1);
int line = ls->linenumber;
TString *tsname = luaX_newstring(ls, "tostring", 8);
/* load __command function first so it occupies the base register */
codelushfunc(fs, LUSH_OP_COMMAND, &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;
/* parse the interpolated expression */
luaX_next(ls); /* advance past TK_COMMAND_INTERP */
/* emit tostring(expr): load tostring function, then the expr,
** then OP_CALL — result occupies one register in the concat chain */
buildglobal(ls, tsname, &tostrfn);
luaK_exp2nextreg(fs, &tostrfn);
tostr_base = tostrfn.u.info;
expr(ls, &interp);
check(ls, '}');
/* don't call luaX_next — luaX_readcommandcont reads directly
from ls->current, which is already past the '}' */
luaK_exp2nextreg(fs, &interp);
luaK_codeABC(fs, OP_CALL, tostr_base, 2, 2);
fs->freereg = cast_byte(tostr_base + 1);
nconcat++;
/* continue reading the command string */
if (luaX_readcommandcont(ls) == TK_COMMAND_INTERP) {
/* another fragment + more interpolation coming */
expdesc frag;
codestring(&frag, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &frag);
nconcat++;
/* loop continues */
}
else {
/* TK_COMMAND: final fragment */
expdesc frag;
codestring(&frag, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &frag);
nconcat++;
break;
}
}
/* advance past the final TK_COMMAND token */
luaX_next(ls);
/* emit OP_CONCAT for all fragments (result goes into base+1) */
if (nconcat > 1) {
int first = base + 1;
luaK_codeABC(fs, OP_CONCAT, first, nconcat, 0);
fs->freereg = cast_byte(first + 1);
}
/* emit OP_CALL: 1 arg (the concatenated string), 1 result */
init_exp(v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 2));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base + 1);
} }
init_exp(v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 2));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base + 1);
} }
@@ -1340,7 +1305,7 @@ static void primaryexp (LexState *ls, expdesc *v) {
singlevar(ls, v); singlevar(ls, v);
return; return;
} }
case TK_COMMAND: case TK_COMMAND_INTERP: { case TK_COMMAND: {
commandexp(ls, v); commandexp(ls, v);
return; return;
} }
@@ -2216,71 +2181,29 @@ static void envstat (LexState *ls) {
*/ */
static void interactivestat (LexState *ls) { static void interactivestat (LexState *ls) {
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
if (ls->t.token == TK_INTERACTIVE) { expdesc func, cmdstr, v;
/* simple interactive command, no interpolation */ int base, nconcat = 0;
int base, line; int line = ls->linenumber;
expdesc func, cmdstr, v; TString *tsname = NULL;
line = ls->linenumber; codelushfunc(fs, LUSH_OP_INTERACTIVE, &func);
codelushfunc(fs, LUSH_OP_INTERACTIVE, &func); base = func.u.info;
base = func.u.info; codestring(&cmdstr, ls->t.seminfo.ts);
codestring(&cmdstr, ls->t.seminfo.ts); luaK_exp2nextreg(fs, &cmdstr);
luaK_exp2nextreg(fs, &cmdstr); nconcat++;
luaX_next(ls); /* consume TK_INTERACTIVE */ while (ls->cmd_mode != 0) {
init_exp(&v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 1)); if (tsname == NULL)
luaK_fixline(fs, line); tsname = luaX_newstring(ls, "tostring", 8);
fs->freereg = cast_byte(base); parseinterp(ls, tsname, &nconcat);
} }
else { luaX_next(ls); /* consume final TK_INTERACTIVE */
/* interactive command with ${expr} interpolation */ if (nconcat > 1) {
expdesc func, cmdstr, v; int first = base + 1;
int base, nconcat = 0; luaK_codeABC(fs, OP_CONCAT, first, nconcat, 0);
int line = ls->linenumber; fs->freereg = cast_byte(first + 1);
TString *tsname = luaX_newstring(ls, "tostring", 8);
codelushfunc(fs, LUSH_OP_INTERACTIVE, &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);
} }
init_exp(&v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 1));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base);
} }
@@ -2353,8 +2276,7 @@ static void statement (LexState *ls) {
envstat(ls); envstat(ls);
break; break;
} }
case TK_INTERACTIVE: case TK_INTERACTIVE: { /* stat -> !command */
case TK_INTERACTIVE_INTERP: { /* stat -> !command */
interactivestat(ls); interactivestat(ls);
break; break;
} }