Implement $(cmd) subcommand syntax in commands (issue #27)

Add $() for inline subcommand substitution that runs a command and
inserts its stdout (trailing newlines stripped) into the outer command
string. Supports nesting, and mixing with ${expr} and $NAME.
This commit is contained in:
Cormac Shannon
2026-03-18 09:29:51 +00:00
parent 42baabde34
commit 1766e40a68
8 changed files with 143 additions and 7 deletions

View File

@@ -556,23 +556,59 @@ static void singlevar (LexState *ls, expdesc *var) {
static void codeenvget (LexState *ls, expdesc *v, TString *name);
/*
** Parse a single interpolation fragment in a command: either ${expr}
** or $NAME. Emits tostring(<value>), reads the continuation fragment,
** Parse a single interpolation fragment in a command: ${expr}, $NAME,
** or $(cmd). Emits tostring(<value>), reads the continuation fragment,
** and pushes it as a string constant.
** For ${expr}: advances past the command token, parses expr, checks '}'.
** For $NAME: the identifier was already collected by the lexer into
** cmd_envvar — no tokens to consume, just emit getenv(name).
** For $(cmd): enters subcommand mode (cmd_mode=3), parses the inner
** command with its own interpolation loop, emits subcmd().
*/
static void parseinterp (LexState *ls, TString *tsname, int *nconcat) {
FuncState *fs = ls->fs;
expdesc interp, tostrfn;
int tostr_base;
TString *envname = ls->cmd_envvar;
lu_byte subcmd = ls->cmd_subcmd;
ls->cmd_envvar = NULL;
ls->cmd_subcmd = 0;
buildglobal(ls, tsname, &tostrfn);
luaK_exp2nextreg(fs, &tostrfn);
tostr_base = tostrfn.u.info;
if (envname != NULL) {
if (subcmd) {
/* $(cmd) — parse inner command as subcommand */
expdesc subfunc, cmdstr;
int subbase, subnconcat = 0;
lu_byte outer_saved = ls->saved_cmd_mode;
TString *sub_tsname = NULL;
codelushfunc(fs, LUSH_OP_SUBCMD, &subfunc);
subbase = subfunc.u.info;
/* enter subcommand lexing mode (terminated by ')') */
ls->saved_cmd_mode = 3;
luaX_readcommandcont(ls); /* read first fragment of inner command */
codestring(&cmdstr, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &cmdstr);
subnconcat++;
/* inner interpolation loop */
while (ls->cmd_mode != 0) {
if (sub_tsname == NULL)
sub_tsname = luaX_newstring(ls, "tostring", 8);
parseinterp(ls, sub_tsname, &subnconcat);
}
/* concatenate inner fragments if needed */
if (subnconcat > 1) {
int first = subbase + 1;
luaK_codeABC(fs, OP_CONCAT, first, subnconcat, 0);
fs->freereg = cast_byte(first + 1);
}
/* call subcmd(inner_cmd_string) */
init_exp(&interp, VCALL, luaK_codeABC(fs, OP_CALL, subbase, 2, 2));
fs->freereg = cast_byte(subbase + 1);
/* restore outer command context */
ls->saved_cmd_mode = outer_saved;
}
else if (envname != NULL) {
/* $NAME — emit getenv(name) */
codeenvget(ls, &interp, envname);
}