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:
@@ -1,6 +1,6 @@
|
|||||||
# Issue #14 — Prefix-based interactive command execution
|
# Issue #14 — Prefix-based interactive command execution
|
||||||
|
|
||||||
**Status:** open
|
**Status:** done
|
||||||
**Alternative to:** #10, #12
|
**Alternative to:** #10, #12
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|||||||
215
lcmd.c
215
lcmd.c
@@ -319,14 +319,15 @@ static void push_result_table (lua_State *L, int code,
|
|||||||
** are captured. Middle stages' stderr goes to the parent's stderr (inherited).
|
** are captured. Middle stages' stderr goes to the parent's stderr (inherited).
|
||||||
** Returns 1 (one result table on the Lua stack).
|
** Returns 1 (one result table on the Lua stack).
|
||||||
*/
|
*/
|
||||||
static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
static int exec_pipeline (lua_State *L, char **stages, int nstages,
|
||||||
|
int interactive) {
|
||||||
ParsedArgs *pa = NULL;
|
ParsedArgs *pa = NULL;
|
||||||
int (*inter_pipes)[2] = NULL; /* inter_pipes[i] connects stage i → i+1 */
|
int (*inter_pipes)[2] = NULL; /* inter_pipes[i] connects stage i → i+1 */
|
||||||
int out_pipe[2], err_pipe[2]; /* capture pipes for last stage */
|
int out_pipe[2] = {-1, -1}, err_pipe[2] = {-1, -1};
|
||||||
pid_t *pids = NULL;
|
pid_t *pids = NULL;
|
||||||
int i, last_code = -1;
|
int i, last_code = -1;
|
||||||
DynBuf buf_out, buf_err;
|
DynBuf buf_out, buf_err;
|
||||||
struct sigaction sa_old, sa_new;
|
struct sigaction sa_old_pipe, sa_old_int, sa_old_quit, sa_new;
|
||||||
|
|
||||||
/* parse all stages */
|
/* parse all stages */
|
||||||
pa = (ParsedArgs *)calloc((size_t)nstages, sizeof(ParsedArgs));
|
pa = (ParsedArgs *)calloc((size_t)nstages, sizeof(ParsedArgs));
|
||||||
@@ -364,45 +365,57 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* capture pipes for last stage */
|
/* capture pipes for last stage (only in non-interactive mode) */
|
||||||
if (pipe(out_pipe) != 0) {
|
if (!interactive) {
|
||||||
if (inter_pipes) {
|
if (pipe(out_pipe) != 0) {
|
||||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
if (inter_pipes) {
|
||||||
free(inter_pipes);
|
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||||
|
free(inter_pipes);
|
||||||
|
}
|
||||||
|
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||||
|
free(pa);
|
||||||
|
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
if (pipe(err_pipe) != 0) {
|
||||||
free(pa);
|
close(out_pipe[0]); close(out_pipe[1]);
|
||||||
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
if (inter_pipes) {
|
||||||
}
|
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||||
if (pipe(err_pipe) != 0) {
|
free(inter_pipes);
|
||||||
close(out_pipe[0]); close(out_pipe[1]);
|
}
|
||||||
if (inter_pipes) {
|
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
free(pa);
|
||||||
free(inter_pipes);
|
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
|
||||||
free(pa);
|
|
||||||
return luaL_error(L, "pipe() failed: %s", strerror(errno));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ignore SIGPIPE in parent */
|
/* ignore signals in parent */
|
||||||
memset(&sa_new, 0, sizeof(sa_new));
|
memset(&sa_new, 0, sizeof(sa_new));
|
||||||
sa_new.sa_handler = SIG_IGN;
|
sa_new.sa_handler = SIG_IGN;
|
||||||
sigemptyset(&sa_new.sa_mask);
|
sigemptyset(&sa_new.sa_mask);
|
||||||
sigaction(SIGPIPE, &sa_new, &sa_old);
|
sigaction(SIGPIPE, &sa_new, &sa_old_pipe);
|
||||||
|
if (interactive) {
|
||||||
|
sigaction(SIGINT, &sa_new, &sa_old_int);
|
||||||
|
sigaction(SIGQUIT, &sa_new, &sa_old_quit);
|
||||||
|
}
|
||||||
|
|
||||||
/* allocate pid array */
|
/* allocate pid array */
|
||||||
pids = (pid_t *)calloc((size_t)nstages, sizeof(pid_t));
|
pids = (pid_t *)calloc((size_t)nstages, sizeof(pid_t));
|
||||||
if (pids == NULL) {
|
if (pids == NULL) {
|
||||||
close(out_pipe[0]); close(out_pipe[1]);
|
if (!interactive) {
|
||||||
close(err_pipe[0]); close(err_pipe[1]);
|
close(out_pipe[0]); close(out_pipe[1]);
|
||||||
|
close(err_pipe[0]); close(err_pipe[1]);
|
||||||
|
}
|
||||||
if (inter_pipes) {
|
if (inter_pipes) {
|
||||||
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
for (i = 0; i < nstages - 1; i++) { close(inter_pipes[i][0]); close(inter_pipes[i][1]); }
|
||||||
free(inter_pipes);
|
free(inter_pipes);
|
||||||
}
|
}
|
||||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||||
free(pa);
|
free(pa);
|
||||||
sigaction(SIGPIPE, &sa_old, NULL);
|
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||||
|
if (interactive) {
|
||||||
|
sigaction(SIGINT, &sa_old_int, NULL);
|
||||||
|
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||||
|
}
|
||||||
return luaL_error(L, "out of memory");
|
return luaL_error(L, "out of memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,15 +428,21 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
|||||||
kill(pids[j], SIGTERM);
|
kill(pids[j], SIGTERM);
|
||||||
waitpid(pids[j], NULL, 0);
|
waitpid(pids[j], NULL, 0);
|
||||||
}
|
}
|
||||||
close(out_pipe[0]); close(out_pipe[1]);
|
if (!interactive) {
|
||||||
close(err_pipe[0]); close(err_pipe[1]);
|
close(out_pipe[0]); close(out_pipe[1]);
|
||||||
|
close(err_pipe[0]); close(err_pipe[1]);
|
||||||
|
}
|
||||||
if (inter_pipes) {
|
if (inter_pipes) {
|
||||||
for (int j = 0; j < nstages - 1; j++) { close(inter_pipes[j][0]); close(inter_pipes[j][1]); }
|
for (int j = 0; j < nstages - 1; j++) { close(inter_pipes[j][0]); close(inter_pipes[j][1]); }
|
||||||
free(inter_pipes);
|
free(inter_pipes);
|
||||||
}
|
}
|
||||||
for (int j = 0; j < nstages; j++) free_argv(&pa[j]);
|
for (int j = 0; j < nstages; j++) free_argv(&pa[j]);
|
||||||
free(pa); free(pids);
|
free(pa); free(pids);
|
||||||
sigaction(SIGPIPE, &sa_old, NULL);
|
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||||
|
if (interactive) {
|
||||||
|
sigaction(SIGINT, &sa_old_int, NULL);
|
||||||
|
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||||
|
}
|
||||||
return luaL_error(L, "fork() failed: %s", strerror(errno));
|
return luaL_error(L, "fork() failed: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,9 +450,13 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
|||||||
/* === child process for stage i === */
|
/* === child process for stage i === */
|
||||||
int j;
|
int j;
|
||||||
|
|
||||||
/* restore default SIGPIPE */
|
/* restore default signals */
|
||||||
sa_new.sa_handler = SIG_DFL;
|
sa_new.sa_handler = SIG_DFL;
|
||||||
sigaction(SIGPIPE, &sa_new, NULL);
|
sigaction(SIGPIPE, &sa_new, NULL);
|
||||||
|
if (interactive) {
|
||||||
|
sigaction(SIGINT, &sa_new, NULL);
|
||||||
|
sigaction(SIGQUIT, &sa_new, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/* set up stdin */
|
/* set up stdin */
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
@@ -443,11 +466,12 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
|||||||
/* set up stdout */
|
/* set up stdout */
|
||||||
if (i < nstages - 1) {
|
if (i < nstages - 1) {
|
||||||
dup2(inter_pipes[i][1], STDOUT_FILENO);
|
dup2(inter_pipes[i][1], STDOUT_FILENO);
|
||||||
} else {
|
} else if (!interactive) {
|
||||||
/* last stage: capture stdout and stderr */
|
/* last stage: capture stdout and stderr */
|
||||||
dup2(out_pipe[1], STDOUT_FILENO);
|
dup2(out_pipe[1], STDOUT_FILENO);
|
||||||
dup2(err_pipe[1], STDERR_FILENO);
|
dup2(err_pipe[1], STDERR_FILENO);
|
||||||
}
|
}
|
||||||
|
/* else: interactive last stage inherits terminal */
|
||||||
|
|
||||||
/* close all pipe fds in child */
|
/* close all pipe fds in child */
|
||||||
if (inter_pipes) {
|
if (inter_pipes) {
|
||||||
@@ -456,8 +480,10 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
|||||||
close(inter_pipes[j][1]);
|
close(inter_pipes[j][1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(out_pipe[0]); close(out_pipe[1]);
|
if (!interactive) {
|
||||||
close(err_pipe[0]); close(err_pipe[1]);
|
close(out_pipe[0]); close(out_pipe[1]);
|
||||||
|
close(err_pipe[0]); close(err_pipe[1]);
|
||||||
|
}
|
||||||
|
|
||||||
execvp(pa[i].argv[0], pa[i].argv);
|
execvp(pa[i].argv[0], pa[i].argv);
|
||||||
|
|
||||||
@@ -483,17 +509,20 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
|||||||
}
|
}
|
||||||
free(inter_pipes);
|
free(inter_pipes);
|
||||||
}
|
}
|
||||||
close(out_pipe[1]);
|
|
||||||
close(err_pipe[1]);
|
|
||||||
|
|
||||||
/* free parsed args (children have already exec'd) */
|
/* free parsed args (children have already exec'd) */
|
||||||
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
for (i = 0; i < nstages; i++) free_argv(&pa[i]);
|
||||||
free(pa);
|
free(pa);
|
||||||
|
|
||||||
/* read captured output from last stage */
|
|
||||||
dynbuf_init(&buf_out);
|
dynbuf_init(&buf_out);
|
||||||
dynbuf_init(&buf_err);
|
dynbuf_init(&buf_err);
|
||||||
read_pipes(out_pipe[0], err_pipe[0], &buf_out, &buf_err);
|
|
||||||
|
if (!interactive) {
|
||||||
|
close(out_pipe[1]);
|
||||||
|
close(err_pipe[1]);
|
||||||
|
/* read captured output from last stage */
|
||||||
|
read_pipes(out_pipe[0], err_pipe[0], &buf_out, &buf_err);
|
||||||
|
}
|
||||||
|
|
||||||
/* wait for all children, keep last stage's exit code */
|
/* wait for all children, keep last stage's exit code */
|
||||||
for (i = 0; i < nstages; i++) {
|
for (i = 0; i < nstages; i++) {
|
||||||
@@ -510,8 +539,12 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages) {
|
|||||||
}
|
}
|
||||||
free(pids);
|
free(pids);
|
||||||
|
|
||||||
/* restore SIGPIPE */
|
/* restore signals */
|
||||||
sigaction(SIGPIPE, &sa_old, NULL);
|
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||||
|
if (interactive) {
|
||||||
|
sigaction(SIGINT, &sa_old_int, NULL);
|
||||||
|
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/* push result table */
|
/* push result table */
|
||||||
push_result_table(L, last_code, &buf_out, &buf_err);
|
push_result_table(L, last_code, &buf_out, &buf_err);
|
||||||
@@ -541,7 +574,7 @@ int luaB_command (lua_State *L) {
|
|||||||
|
|
||||||
/* multi-stage pipeline: delegate to exec_pipeline */
|
/* multi-stage pipeline: delegate to exec_pipeline */
|
||||||
if (nstages > 1) {
|
if (nstages > 1) {
|
||||||
int result = exec_pipeline(L, stages, nstages);
|
int result = exec_pipeline(L, stages, nstages, 0);
|
||||||
free(stages[0]); /* free backing buffer */
|
free(stages[0]); /* free backing buffer */
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -657,3 +690,107 @@ int luaB_command (lua_State *L) {
|
|||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== luaB_interactive ===== */
|
||||||
|
|
||||||
|
int luaB_interactive (lua_State *L) {
|
||||||
|
const char *cmd = luaL_checkstring(L, 1);
|
||||||
|
char *stages[MAX_PIPELINE_STAGES];
|
||||||
|
int nstages;
|
||||||
|
ParsedArgs pa;
|
||||||
|
pid_t pid;
|
||||||
|
int status, code;
|
||||||
|
struct sigaction sa_old_pipe, sa_old_int, sa_old_quit, sa_new;
|
||||||
|
DynBuf buf_out, buf_err;
|
||||||
|
|
||||||
|
/* try to split into pipeline stages */
|
||||||
|
if (split_pipeline(cmd, stages, &nstages) != 0)
|
||||||
|
return luaL_error(L, "invalid pipeline syntax in command");
|
||||||
|
|
||||||
|
/* multi-stage pipeline: delegate to exec_pipeline with interactive=1 */
|
||||||
|
if (nstages > 1) {
|
||||||
|
exec_pipeline(L, stages, nstages, 1);
|
||||||
|
free(stages[0]);
|
||||||
|
lua_setglobal(L, "_");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* single stage */
|
||||||
|
free(stages[0]);
|
||||||
|
|
||||||
|
if (parse_argv(cmd, &pa) != 0)
|
||||||
|
return luaL_error(L, "unterminated quote in command");
|
||||||
|
|
||||||
|
if (pa.argc == 0) {
|
||||||
|
free_argv(&pa);
|
||||||
|
dynbuf_init(&buf_out);
|
||||||
|
dynbuf_init(&buf_err);
|
||||||
|
push_result_table(L, 0, &buf_out, &buf_err);
|
||||||
|
lua_setglobal(L, "_");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ignore SIGINT, SIGQUIT, SIGPIPE in parent */
|
||||||
|
memset(&sa_new, 0, sizeof(sa_new));
|
||||||
|
sa_new.sa_handler = SIG_IGN;
|
||||||
|
sigemptyset(&sa_new.sa_mask);
|
||||||
|
sigaction(SIGPIPE, &sa_new, &sa_old_pipe);
|
||||||
|
sigaction(SIGINT, &sa_new, &sa_old_int);
|
||||||
|
sigaction(SIGQUIT, &sa_new, &sa_old_quit);
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
free_argv(&pa);
|
||||||
|
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||||
|
sigaction(SIGINT, &sa_old_int, NULL);
|
||||||
|
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||||
|
return luaL_error(L, "fork() failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
/* child: restore signal defaults, inherit terminal */
|
||||||
|
sa_new.sa_handler = SIG_DFL;
|
||||||
|
sigaction(SIGPIPE, &sa_new, NULL);
|
||||||
|
sigaction(SIGINT, &sa_new, NULL);
|
||||||
|
sigaction(SIGQUIT, &sa_new, NULL);
|
||||||
|
|
||||||
|
execvp(pa.argv[0], pa.argv);
|
||||||
|
|
||||||
|
/* exec failed */
|
||||||
|
{
|
||||||
|
const char *err = strerror(errno);
|
||||||
|
size_t namelen = strlen(pa.argv[0]);
|
||||||
|
size_t errlen = strlen(err);
|
||||||
|
(void)write(STDERR_FILENO, pa.argv[0], namelen);
|
||||||
|
(void)write(STDERR_FILENO, ": ", 2);
|
||||||
|
(void)write(STDERR_FILENO, err, errlen);
|
||||||
|
(void)write(STDERR_FILENO, "\n", 1);
|
||||||
|
}
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parent */
|
||||||
|
free_argv(&pa);
|
||||||
|
waitpid(pid, &status, 0);
|
||||||
|
|
||||||
|
/* restore signals */
|
||||||
|
sigaction(SIGPIPE, &sa_old_pipe, NULL);
|
||||||
|
sigaction(SIGINT, &sa_old_int, NULL);
|
||||||
|
sigaction(SIGQUIT, &sa_old_quit, NULL);
|
||||||
|
|
||||||
|
if (WIFEXITED(status))
|
||||||
|
code = WEXITSTATUS(status);
|
||||||
|
else if (WIFSIGNALED(status))
|
||||||
|
code = 128 + WTERMSIG(status);
|
||||||
|
else
|
||||||
|
code = -1;
|
||||||
|
|
||||||
|
/* build {code=N, stdout="", stderr=""} and set as global _ */
|
||||||
|
dynbuf_init(&buf_out);
|
||||||
|
dynbuf_init(&buf_err);
|
||||||
|
push_result_table(L, code, &buf_out, &buf_err);
|
||||||
|
lua_setglobal(L, "_");
|
||||||
|
|
||||||
|
return 0; /* void — no Lua return values */
|
||||||
|
}
|
||||||
|
|||||||
1
lcmd.h
1
lcmd.h
@@ -10,5 +10,6 @@
|
|||||||
#include "lua.h"
|
#include "lua.h"
|
||||||
|
|
||||||
int luaB_command (lua_State *L);
|
int luaB_command (lua_State *L);
|
||||||
|
int luaB_interactive (lua_State *L);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
2
linit.c
2
linit.c
@@ -71,6 +71,8 @@ static int luaB_setenv (lua_State *L) {
|
|||||||
static void opencommand (lua_State *L) {
|
static void opencommand (lua_State *L) {
|
||||||
lua_pushcfunction(L, luaB_command);
|
lua_pushcfunction(L, luaB_command);
|
||||||
lua_setglobal(L, "__command");
|
lua_setglobal(L, "__command");
|
||||||
|
lua_pushcfunction(L, luaB_interactive);
|
||||||
|
lua_setglobal(L, "__interactive");
|
||||||
lua_pushcfunction(L, luaB_getenv);
|
lua_pushcfunction(L, luaB_getenv);
|
||||||
lua_setglobal(L, "__getenv");
|
lua_setglobal(L, "__getenv");
|
||||||
lua_pushcfunction(L, luaB_setenv);
|
lua_pushcfunction(L, luaB_setenv);
|
||||||
|
|||||||
36
llex.c
36
llex.c
@@ -50,7 +50,8 @@ static const char *const luaX_tokens [] = {
|
|||||||
"//", "..", "...", "==", ">=", "<=", "~=",
|
"//", "..", "...", "==", ">=", "<=", "~=",
|
||||||
"<<", ">>", "::", "<eof>",
|
"<<", ">>", "::", "<eof>",
|
||||||
"<number>", "<integer>", "<name>", "<string>",
|
"<number>", "<integer>", "<name>", "<string>",
|
||||||
"<command>", "<command_interp>", "<envvar>"
|
"<command>", "<command_interp>", "<envvar>",
|
||||||
|
"<interactive>", "<interactive_interp>"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -190,7 +191,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->cmd_mode = 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)
|
||||||
@@ -478,14 +479,29 @@ static void read_string (LexState *ls, int del, SemInfo *seminfo) {
|
|||||||
** or TK_COMMAND_INTERP if an interpolation ${...} was found.
|
** or TK_COMMAND_INTERP if an interpolation ${...} was found.
|
||||||
*/
|
*/
|
||||||
static int read_command_body (LexState *ls, SemInfo *seminfo) {
|
static int read_command_body (LexState *ls, SemInfo *seminfo) {
|
||||||
|
int interactive = (ls->cmd_mode == 2);
|
||||||
luaZ_resetbuffer(ls->buff);
|
luaZ_resetbuffer(ls->buff);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
switch (ls->current) {
|
switch (ls->current) {
|
||||||
case EOZ:
|
case EOZ:
|
||||||
|
if (interactive) {
|
||||||
|
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
|
||||||
|
luaZ_bufflen(ls->buff));
|
||||||
|
ls->cmd_mode = 0;
|
||||||
|
return TK_INTERACTIVE;
|
||||||
|
}
|
||||||
lexerror(ls, "unfinished command", TK_EOS);
|
lexerror(ls, "unfinished command", TK_EOS);
|
||||||
break; /* to avoid warnings */
|
break; /* to avoid warnings */
|
||||||
case '\n':
|
case '\n':
|
||||||
case '\r':
|
case '\r':
|
||||||
|
if (interactive) {
|
||||||
|
/* newline terminates interactive command (don't consume it —
|
||||||
|
leave for inclinenumber on next llex() call) */
|
||||||
|
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
|
||||||
|
luaZ_bufflen(ls->buff));
|
||||||
|
ls->cmd_mode = 0;
|
||||||
|
return TK_INTERACTIVE;
|
||||||
|
}
|
||||||
save(ls, '\n');
|
save(ls, '\n');
|
||||||
inclinenumber(ls);
|
inclinenumber(ls);
|
||||||
break;
|
break;
|
||||||
@@ -496,8 +512,7 @@ static int read_command_body (LexState *ls, SemInfo *seminfo) {
|
|||||||
/* store fragment so far */
|
/* store fragment so far */
|
||||||
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->in_command = 1;
|
return interactive ? TK_INTERACTIVE_INTERP : TK_COMMAND_INTERP;
|
||||||
return TK_COMMAND_INTERP;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
save(ls, '$'); /* not an interpolation, keep literal '$' */
|
save(ls, '$'); /* not an interpolation, keep literal '$' */
|
||||||
@@ -505,10 +520,15 @@ static int read_command_body (LexState *ls, SemInfo *seminfo) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '`': {
|
case '`': {
|
||||||
|
if (interactive) {
|
||||||
|
/* backtick is literal in interactive mode */
|
||||||
|
save_and_next(ls);
|
||||||
|
break;
|
||||||
|
}
|
||||||
next(ls); /* skip closing '`' */
|
next(ls); /* skip closing '`' */
|
||||||
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->in_command = 0;
|
ls->cmd_mode = 0;
|
||||||
return TK_COMMAND;
|
return TK_COMMAND;
|
||||||
}
|
}
|
||||||
case '\\': { /* escape sequences */
|
case '\\': { /* escape sequences */
|
||||||
@@ -541,6 +561,7 @@ static int read_command_body (LexState *ls, SemInfo *seminfo) {
|
|||||||
|
|
||||||
static int read_command (LexState *ls, SemInfo *seminfo) {
|
static int read_command (LexState *ls, SemInfo *seminfo) {
|
||||||
next(ls); /* skip opening '`' */
|
next(ls); /* skip opening '`' */
|
||||||
|
ls->cmd_mode = 1;
|
||||||
return read_command_body(ls, seminfo);
|
return read_command_body(ls, seminfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,6 +657,11 @@ static int llex (LexState *ls, SemInfo *seminfo) {
|
|||||||
case '`': { /* explicit command `ls -l` with ${} interpolation */
|
case '`': { /* explicit command `ls -l` with ${} interpolation */
|
||||||
return read_command(ls, seminfo);
|
return read_command(ls, seminfo);
|
||||||
}
|
}
|
||||||
|
case '!': { /* interactive command !ls -l */
|
||||||
|
next(ls); /* skip '!' */
|
||||||
|
ls->cmd_mode = 2;
|
||||||
|
return read_command_body(ls, seminfo);
|
||||||
|
}
|
||||||
case '$': { /* environment variable $NAME */
|
case '$': { /* environment variable $NAME */
|
||||||
next(ls);
|
next(ls);
|
||||||
if (lislalpha(ls->current)) { /* $NAME */
|
if (lislalpha(ls->current)) { /* $NAME */
|
||||||
|
|||||||
6
llex.h
6
llex.h
@@ -42,7 +42,9 @@ enum RESERVED {
|
|||||||
TK_FLT, TK_INT, TK_NAME, TK_STRING,
|
TK_FLT, TK_INT, TK_NAME, TK_STRING,
|
||||||
TK_COMMAND,
|
TK_COMMAND,
|
||||||
TK_COMMAND_INTERP,
|
TK_COMMAND_INTERP,
|
||||||
TK_ENVVAR
|
TK_ENVVAR,
|
||||||
|
TK_INTERACTIVE,
|
||||||
|
TK_INTERACTIVE_INTERP
|
||||||
};
|
};
|
||||||
|
|
||||||
/* number of reserved words */
|
/* number of reserved words */
|
||||||
@@ -80,7 +82,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 ${} */
|
lu_byte cmd_mode; /* 0=normal, 1=backtick command, 2=interactive command */
|
||||||
} LexState;
|
} LexState;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
84
lparser.c
84
lparser.c
@@ -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) {
|
static void statement (LexState *ls) {
|
||||||
int line = ls->linenumber; /* may be needed for error messages */
|
int line = ls->linenumber; /* may be needed for error messages */
|
||||||
enterlevel(ls);
|
enterlevel(ls);
|
||||||
@@ -2275,6 +2354,11 @@ static void statement (LexState *ls) {
|
|||||||
envstat(ls);
|
envstat(ls);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TK_INTERACTIVE:
|
||||||
|
case TK_INTERACTIVE_INTERP: { /* stat -> !command */
|
||||||
|
interactivestat(ls);
|
||||||
|
break;
|
||||||
|
}
|
||||||
#if defined(LUA_COMPAT_GLOBAL)
|
#if defined(LUA_COMPAT_GLOBAL)
|
||||||
case TK_NAME: {
|
case TK_NAME: {
|
||||||
/* compatibility code to parse global keyword when "global"
|
/* compatibility code to parse global keyword when "global"
|
||||||
|
|||||||
@@ -124,9 +124,12 @@ lexerror([['alo \98]], "<eof>")
|
|||||||
-- valid characters in variable names
|
-- valid characters in variable names
|
||||||
for i = 0, 255 do
|
for i = 0, 255 do
|
||||||
local s = string.char(i)
|
local s = string.char(i)
|
||||||
|
-- skip '!' which is now a valid statement starter (interactive commands)
|
||||||
|
if s == '!' then goto continue end
|
||||||
assert(not string.find(s, "[a-zA-Z_]") == not load(s .. "=1", ""))
|
assert(not string.find(s, "[a-zA-Z_]") == not load(s .. "=1", ""))
|
||||||
assert(not string.find(s, "[a-zA-Z_0-9]") ==
|
assert(not string.find(s, "[a-zA-Z_0-9]") ==
|
||||||
not load("a" .. s .. "1 = 1", ""))
|
not load("a" .. s .. "1 = 1", ""))
|
||||||
|
::continue::
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
97
testes/lush/interactive.lua
Normal file
97
testes/lush/interactive.lua
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
-- testes/lush/interactive.lua
|
||||||
|
-- Tests for prefix-based interactive command execution (issue #14).
|
||||||
|
|
||||||
|
print "testing interactive commands"
|
||||||
|
|
||||||
|
-- ===== BASIC: _ IS SET CORRECTLY =====
|
||||||
|
|
||||||
|
-- basic interactive command sets _
|
||||||
|
!true
|
||||||
|
assert(type(_) == "table")
|
||||||
|
assert(_.code == 0)
|
||||||
|
assert(_.stdout == "")
|
||||||
|
assert(_.stderr == "")
|
||||||
|
|
||||||
|
-- exit code preserved
|
||||||
|
!false
|
||||||
|
assert(_.code == 1)
|
||||||
|
|
||||||
|
-- specific exit code
|
||||||
|
!sh -c "exit 42"
|
||||||
|
assert(_.code == 42)
|
||||||
|
|
||||||
|
-- command not found
|
||||||
|
!nonexistent_command_xyz_999
|
||||||
|
assert(_.code == 127)
|
||||||
|
|
||||||
|
-- ===== INTERPOLATION =====
|
||||||
|
|
||||||
|
do
|
||||||
|
local name = "hello"
|
||||||
|
!echo ${name}
|
||||||
|
assert(_.code == 0)
|
||||||
|
assert(_.stdout == "")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- interpolation with expression
|
||||||
|
do
|
||||||
|
local x = 21
|
||||||
|
!sh -c "exit ${x * 2}"
|
||||||
|
assert(_.code == 42)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ===== PIPING =====
|
||||||
|
|
||||||
|
-- exit code from last stage
|
||||||
|
!echo hello | sh -c "exit 7"
|
||||||
|
assert(_.code == 7)
|
||||||
|
|
||||||
|
-- piping: first stage output goes to second
|
||||||
|
!echo hello | cat
|
||||||
|
assert(_.code == 0)
|
||||||
|
|
||||||
|
-- ===== _ IS OVERWRITTEN =====
|
||||||
|
|
||||||
|
!true
|
||||||
|
assert(_.code == 0)
|
||||||
|
!false
|
||||||
|
assert(_.code == 1)
|
||||||
|
|
||||||
|
-- ===== INSIDE LUA BLOCKS =====
|
||||||
|
|
||||||
|
do
|
||||||
|
!true
|
||||||
|
assert(_.code == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
if true then
|
||||||
|
!true
|
||||||
|
assert(_.code == 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
for i = 1, 3 do
|
||||||
|
!true
|
||||||
|
assert(_.code == 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local function run_cmd()
|
||||||
|
!true
|
||||||
|
return _.code
|
||||||
|
end
|
||||||
|
assert(run_cmd() == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ===== EMPTY COMMAND (just whitespace after !) =====
|
||||||
|
|
||||||
|
do
|
||||||
|
-- set _ to something first, then run empty interactive
|
||||||
|
!true
|
||||||
|
assert(_.code == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
print "OK"
|
||||||
Reference in New Issue
Block a user