Compile backtick commands as __command() calls with ${} interpolation
Backtick expressions now compile as calls to a global __command()
function instead of being treated as plain strings. Interpolation
via ${expr} is supported, with each interpolated value wrapped in
tostring() for type safety. Commands are parsed in primaryexp so
suffix operations like `.stdout` work directly.
Adds a stub __command that returns {code, stdout, stderr} for testing
until the real implementation (issue #03).
This commit is contained in:
28
linit.c
28
linit.c
@@ -40,6 +40,33 @@ static const luaL_Reg stdlibs[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Stub __command function for backtick command syntax.
|
||||||
|
** Returns a table {code=0, stdout=cmdstring, stderr=""}.
|
||||||
|
** The real implementation will be provided by issue #03.
|
||||||
|
*/
|
||||||
|
static int luaB_command (lua_State *L) {
|
||||||
|
const char *cmd = luaL_checkstring(L, 1);
|
||||||
|
lua_createtable(L, 0, 3);
|
||||||
|
lua_pushinteger(L, 0);
|
||||||
|
lua_setfield(L, -2, "code");
|
||||||
|
lua_pushstring(L, cmd);
|
||||||
|
lua_setfield(L, -2, "stdout");
|
||||||
|
lua_pushliteral(L, "");
|
||||||
|
lua_setfield(L, -2, "stderr");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Register shell built-in globals (called after standard libraries).
|
||||||
|
*/
|
||||||
|
static void opencommand (lua_State *L) {
|
||||||
|
lua_pushcfunction(L, luaB_command);
|
||||||
|
lua_setglobal(L, "__command");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** require and preload selected standard libraries
|
** require and preload selected standard libraries
|
||||||
*/
|
*/
|
||||||
@@ -59,5 +86,6 @@ LUALIB_API void luaL_openselectedlibs (lua_State *L, int load, int preload) {
|
|||||||
}
|
}
|
||||||
lua_assert((mask >> 1) == LUA_UTF8LIBK);
|
lua_assert((mask >> 1) == LUA_UTF8LIBK);
|
||||||
lua_pop(L, 1); /* remove PRELOAD table */
|
lua_pop(L, 1); /* remove PRELOAD table */
|
||||||
|
opencommand(L); /* register __command global */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
95
llex.c
95
llex.c
@@ -49,7 +49,8 @@ static const char *const luaX_tokens [] = {
|
|||||||
"return", "then", "true", "until", "while",
|
"return", "then", "true", "until", "while",
|
||||||
"//", "..", "...", "==", ">=", "<=", "~=",
|
"//", "..", "...", "==", ">=", "<=", "~=",
|
||||||
"<<", ">>", "::", "<eof>",
|
"<<", ">>", "::", "<eof>",
|
||||||
"<number>", "<integer>", "<name>", "<string>"
|
"<number>", "<integer>", "<name>", "<string>",
|
||||||
|
"<command>", "<command_interp>"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -186,6 +187,7 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source,
|
|||||||
ls->source = source;
|
ls->source = source;
|
||||||
/* all three strings here ("_ENV", "break", "global") were fixed,
|
/* all three strings here ("_ENV", "break", "global") were fixed,
|
||||||
so they cannot be collected */
|
so they cannot be collected */
|
||||||
|
ls->in_command = 0;
|
||||||
ls->envn = luaS_newliteral(L, LUA_ENV); /* get env string */
|
ls->envn = luaS_newliteral(L, LUA_ENV); /* get env string */
|
||||||
ls->brkn = luaS_newliteral(L, "break"); /* get "break" string */
|
ls->brkn = luaS_newliteral(L, "break"); /* get "break" string */
|
||||||
#if defined(LUA_COMPAT_GLOBAL)
|
#if defined(LUA_COMPAT_GLOBAL)
|
||||||
@@ -464,6 +466,93 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Read a backtick command string, handling ${expr} interpolation.
|
||||||
|
** Called initially when '`' is seen, and also continued via
|
||||||
|
** luaX_readcommandcont() after the parser processes an interpolated
|
||||||
|
** expression.
|
||||||
|
** Returns TK_COMMAND if the string is complete (closing ` found),
|
||||||
|
** or TK_COMMAND_INTERP if an interpolation ${...} was found.
|
||||||
|
*/
|
||||||
|
static int read_command_body (LexState *ls, SemInfo *seminfo) {
|
||||||
|
luaZ_resetbuffer(ls->buff);
|
||||||
|
for (;;) {
|
||||||
|
switch (ls->current) {
|
||||||
|
case EOZ:
|
||||||
|
lexerror(ls, "unfinished command", TK_EOS);
|
||||||
|
break; /* to avoid warnings */
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
save(ls, '\n');
|
||||||
|
inclinenumber(ls);
|
||||||
|
break;
|
||||||
|
case '$': {
|
||||||
|
next(ls);
|
||||||
|
if (ls->current == '{') {
|
||||||
|
next(ls); /* skip '{' */
|
||||||
|
/* store fragment so far */
|
||||||
|
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
|
||||||
|
luaZ_bufflen(ls->buff));
|
||||||
|
ls->in_command = 1;
|
||||||
|
return TK_COMMAND_INTERP;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
save(ls, '$'); /* not an interpolation, keep literal '$' */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '`': {
|
||||||
|
next(ls); /* skip closing '`' */
|
||||||
|
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
|
||||||
|
luaZ_bufflen(ls->buff));
|
||||||
|
ls->in_command = 0;
|
||||||
|
return TK_COMMAND;
|
||||||
|
}
|
||||||
|
case '\\': { /* escape sequences */
|
||||||
|
int c;
|
||||||
|
save_and_next(ls); /* keep '\\' for error messages */
|
||||||
|
switch (ls->current) {
|
||||||
|
case 'n': c = '\n'; goto cmd_read_save;
|
||||||
|
case 't': c = '\t'; goto cmd_read_save;
|
||||||
|
case '\\': c = '\\'; goto cmd_read_save;
|
||||||
|
case '`': c = '`'; goto cmd_read_save;
|
||||||
|
case '$': c = '$'; goto cmd_read_save;
|
||||||
|
default: {
|
||||||
|
/* not a recognized escape, keep both chars literally */
|
||||||
|
break; /* backslash already saved, current will be saved next */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
cmd_read_save:
|
||||||
|
next(ls);
|
||||||
|
luaZ_buffremove(ls->buff, 1); /* remove '\\' */
|
||||||
|
save(ls, c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
save_and_next(ls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int read_command (LexState *ls, SemInfo *seminfo) {
|
||||||
|
next(ls); /* skip opening '`' */
|
||||||
|
return read_command_body(ls, seminfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Continue reading a backtick command string after the parser has
|
||||||
|
** consumed the '}' that closes an interpolated ${expr}.
|
||||||
|
*/
|
||||||
|
int luaX_readcommandcont (LexState *ls) {
|
||||||
|
int tk = read_command_body(ls, &ls->t.seminfo);
|
||||||
|
ls->t.token = tk;
|
||||||
|
return tk;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int llex (LexState *ls, SemInfo *seminfo) {
|
static int llex (LexState *ls, SemInfo *seminfo) {
|
||||||
luaZ_resetbuffer(ls->buff);
|
luaZ_resetbuffer(ls->buff);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -541,6 +630,9 @@ static int llex (LexState *ls, SemInfo *seminfo) {
|
|||||||
read_string(ls, ls->current, seminfo);
|
read_string(ls, ls->current, seminfo);
|
||||||
return TK_STRING;
|
return TK_STRING;
|
||||||
}
|
}
|
||||||
|
case '`': { /* explicit command `ls -l` with ${} interpolation */
|
||||||
|
return read_command(ls, seminfo);
|
||||||
|
}
|
||||||
case '.': { /* '.', '..', '...', or number */
|
case '.': { /* '.', '..', '...', or number */
|
||||||
save_and_next(ls);
|
save_and_next(ls);
|
||||||
if (check_next1(ls, '.')) {
|
if (check_next1(ls, '.')) {
|
||||||
@@ -601,4 +693,3 @@ int luaX_lookahead (LexState *ls) {
|
|||||||
ls->lookahead.token = llex(ls, &ls->lookahead.seminfo);
|
ls->lookahead.token = llex(ls, &ls->lookahead.seminfo);
|
||||||
return ls->lookahead.token;
|
return ls->lookahead.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
llex.h
6
llex.h
@@ -39,7 +39,9 @@ enum RESERVED {
|
|||||||
TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE,
|
TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE,
|
||||||
TK_SHL, TK_SHR,
|
TK_SHL, TK_SHR,
|
||||||
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_INTERP
|
||||||
};
|
};
|
||||||
|
|
||||||
/* number of reserved words */
|
/* number of reserved words */
|
||||||
@@ -77,6 +79,7 @@ typedef struct LexState {
|
|||||||
TString *envn; /* environment variable name */
|
TString *envn; /* environment variable name */
|
||||||
TString *brkn; /* "break" name (used as a label) */
|
TString *brkn; /* "break" name (used as a label) */
|
||||||
TString *glbn; /* "global" name (when not a reserved word) */
|
TString *glbn; /* "global" name (when not a reserved word) */
|
||||||
|
lu_byte in_command; /* nonzero when continuing a backtick string after ${} */
|
||||||
} LexState;
|
} LexState;
|
||||||
|
|
||||||
|
|
||||||
@@ -88,6 +91,7 @@ LUAI_FUNC void luaX_next (LexState *ls);
|
|||||||
LUAI_FUNC int luaX_lookahead (LexState *ls);
|
LUAI_FUNC int luaX_lookahead (LexState *ls);
|
||||||
LUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s);
|
LUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s);
|
||||||
LUAI_FUNC const char *luaX_token2str (LexState *ls, int token);
|
LUAI_FUNC const char *luaX_token2str (LexState *ls, int token);
|
||||||
|
LUAI_FUNC int luaX_readcommandcont (LexState *ls);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
115
lparser.c
115
lparser.c
@@ -540,6 +540,31 @@ static void singlevar (LexState *ls, expdesc *var) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Compile a command expression: emit code equivalent to
|
||||||
|
** __command(cmdstring)
|
||||||
|
** where cmdstring is already built (possibly via concatenation
|
||||||
|
** from interpolation fragments).
|
||||||
|
*/
|
||||||
|
static void codecommand (LexState *ls, expdesc *v, expdesc *cmdstr) {
|
||||||
|
FuncState *fs = ls->fs;
|
||||||
|
int base, line;
|
||||||
|
expdesc func;
|
||||||
|
TString *cmdname = luaX_newstring(ls, "__command", 9);
|
||||||
|
line = ls->linenumber;
|
||||||
|
/* look up __command as _ENV["__command"] */
|
||||||
|
buildglobal(ls, cmdname, &func);
|
||||||
|
luaK_exp2nextreg(fs, &func);
|
||||||
|
base = func.u.info;
|
||||||
|
/* push the command string argument */
|
||||||
|
luaK_exp2nextreg(fs, cmdstr);
|
||||||
|
/* emit OP_CALL: 1 arg, 1 result */
|
||||||
|
init_exp(v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 2));
|
||||||
|
luaK_fixline(fs, line);
|
||||||
|
fs->freereg = cast_byte(base + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Adjust the number of results from an expression list 'e' with 'nexps'
|
** Adjust the number of results from an expression list 'e' with 'nexps'
|
||||||
** expressions to 'nvars' values.
|
** expressions to 'nvars' values.
|
||||||
@@ -1194,8 +1219,91 @@ static void funcargs (LexState *ls, expdesc *f) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Parse a backtick command expression (TK_COMMAND or TK_COMMAND_INTERP).
|
||||||
|
** Compiles `cmd` as __command("cmd") and `cmd ${expr}` as
|
||||||
|
** __command("cmd " .. tostring(expr) .. "").
|
||||||
|
** On entry, ls->t.token is TK_COMMAND or TK_COMMAND_INTERP.
|
||||||
|
** On exit, the token has been consumed (ls->t has the next token).
|
||||||
|
*/
|
||||||
|
static void commandexp (LexState *ls, expdesc *v) {
|
||||||
|
if (ls->t.token == TK_COMMAND) {
|
||||||
|
/* simple command with no interpolation */
|
||||||
|
expdesc cmdstr;
|
||||||
|
codestring(&cmdstr, ls->t.seminfo.ts);
|
||||||
|
luaX_next(ls);
|
||||||
|
codecommand(ls, v, &cmdstr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* command with ${expr} interpolation */
|
||||||
|
FuncState *fs = ls->fs;
|
||||||
|
expdesc func, cmdstr;
|
||||||
|
int base, nconcat = 0;
|
||||||
|
int line = ls->linenumber;
|
||||||
|
TString *cmdname = luaX_newstring(ls, "__command", 9);
|
||||||
|
TString *tsname = luaX_newstring(ls, "tostring", 8);
|
||||||
|
/* load __command function first so it occupies the base register */
|
||||||
|
buildglobal(ls, cmdname, &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;
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void primaryexp (LexState *ls, expdesc *v) {
|
static void primaryexp (LexState *ls, expdesc *v) {
|
||||||
/* primaryexp -> NAME | '(' expr ')' */
|
/* primaryexp -> NAME | '(' expr ')' | COMMAND */
|
||||||
switch (ls->t.token) {
|
switch (ls->t.token) {
|
||||||
case '(': {
|
case '(': {
|
||||||
int line = ls->linenumber;
|
int line = ls->linenumber;
|
||||||
@@ -1209,6 +1317,10 @@ static void primaryexp (LexState *ls, expdesc *v) {
|
|||||||
singlevar(ls, v);
|
singlevar(ls, v);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case TK_COMMAND: case TK_COMMAND_INTERP: {
|
||||||
|
commandexp(ls, v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
luaX_syntaxerror(ls, "unexpected symbol");
|
luaX_syntaxerror(ls, "unexpected symbol");
|
||||||
}
|
}
|
||||||
@@ -2192,4 +2304,3 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,
|
|||||||
L->top.p--; /* remove scanner's table */
|
L->top.p--; /* remove scanner's table */
|
||||||
return cl; /* closure is on the stack, too */
|
return cl; /* closure is on the stack, too */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user