diff --git a/linit.c b/linit.c index 00d06f7e..e9955e97 100644 --- a/linit.c +++ b/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 */ @@ -59,5 +86,6 @@ LUALIB_API void luaL_openselectedlibs (lua_State *L, int load, int preload) { } lua_assert((mask >> 1) == LUA_UTF8LIBK); lua_pop(L, 1); /* remove PRELOAD table */ + opencommand(L); /* register __command global */ } diff --git a/llex.c b/llex.c index f8bb3ea4..37be9b96 100644 --- a/llex.c +++ b/llex.c @@ -49,7 +49,8 @@ static const char *const luaX_tokens [] = { "return", "then", "true", "until", "while", "//", "..", "...", "==", ">=", "<=", "~=", "<<", ">>", "::", "", - "", "", "", "" + "", "", "", "", + "", "" }; @@ -186,6 +187,7 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, ls->source = source; /* all three strings here ("_ENV", "break", "global") were fixed, so they cannot be collected */ + ls->in_command = 0; ls->envn = luaS_newliteral(L, LUA_ENV); /* get env string */ ls->brkn = luaS_newliteral(L, "break"); /* get "break" string */ #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) { luaZ_resetbuffer(ls->buff); for (;;) { @@ -541,6 +630,9 @@ static int llex (LexState *ls, SemInfo *seminfo) { read_string(ls, ls->current, seminfo); return TK_STRING; } + case '`': { /* explicit command `ls -l` with ${} interpolation */ + return read_command(ls, seminfo); + } case '.': { /* '.', '..', '...', or number */ save_and_next(ls); if (check_next1(ls, '.')) { @@ -601,4 +693,3 @@ int luaX_lookahead (LexState *ls) { ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); return ls->lookahead.token; } - diff --git a/llex.h b/llex.h index 37016e8a..111e7a57 100644 --- a/llex.h +++ b/llex.h @@ -39,7 +39,9 @@ enum RESERVED { TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_SHL, TK_SHR, 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 */ @@ -77,6 +79,7 @@ typedef struct LexState { TString *envn; /* environment variable name */ TString *brkn; /* "break" name (used as a label) */ TString *glbn; /* "global" name (when not a reserved word) */ + lu_byte in_command; /* nonzero when continuing a backtick string after ${} */ } LexState; @@ -88,6 +91,7 @@ LUAI_FUNC void luaX_next (LexState *ls); LUAI_FUNC int luaX_lookahead (LexState *ls); LUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s); LUAI_FUNC const char *luaX_token2str (LexState *ls, int token); +LUAI_FUNC int luaX_readcommandcont (LexState *ls); #endif diff --git a/lparser.c b/lparser.c index 77141e79..53c74fcd 100644 --- a/lparser.c +++ b/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' ** 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) { - /* primaryexp -> NAME | '(' expr ')' */ + /* primaryexp -> NAME | '(' expr ')' | COMMAND */ switch (ls->t.token) { case '(': { int line = ls->linenumber; @@ -1209,6 +1317,10 @@ static void primaryexp (LexState *ls, expdesc *v) { singlevar(ls, v); return; } + case TK_COMMAND: case TK_COMMAND_INTERP: { + commandexp(ls, v); + return; + } default: { 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 */ return cl; /* closure is on the stack, too */ } -