summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornicm <nicm>2019-05-23 11:13:30 +0000
committernicm <nicm>2019-05-23 11:13:30 +0000
commit723010ba72e337832402f8e44981c02caa30b476 (patch)
treea7fa1089f4d5dc368a44702489cf176afbcfec55
parent5571d7a21c5dc920eb9d25336b5bb7b04d72fc9d (diff)
Replace the split parser code (cfg.c and cmd-string.c) with a single
parser using yacc(1). This is a major change but is clearer and simpler and allows some edge cases to be made more consistent, as well as tidying up how aliases are handled. It will also allow some further improvements later. Entirely the same parser is now used for parsing the configuration file and for string commands. This means that constructs previously only available in .tmux.conf, such as %if, can now be used in string commands (for example, those given to if-shell - not commands invoked from the shell, they are still parsed by the shell itself). The only syntax change I am aware of is that #{} outside quotes or a comment is now considered a format and not a comment, so #{ is now a syntax error (notably, if it is at the start of a line). This also adds two new sections to the man page documenting the syntax and outlining how parsing and command execution works. Thanks to everyone who sent me test configs (they still all parse without errors - but this doesn't mean they still work as intended!). Thanks to Avi Halachmi for testing and man page improvements, also to jmc@ for reviewing the man page changes.
-rw-r--r--Makefile2
-rw-r--r--cfg.c238
-rw-r--r--cmd-command-prompt.c32
-rw-r--r--cmd-confirm-before.c33
-rw-r--r--cmd-display-panes.c35
-rw-r--r--cmd-if-shell.c91
-rw-r--r--cmd-list.c28
-rw-r--r--cmd-parse.y1207
-rw-r--r--cmd-queue.c14
-rw-r--r--cmd-source-file.c10
-rw-r--r--cmd-string.c393
-rw-r--r--cmd.c188
-rw-r--r--control.c22
-rw-r--r--key-bindings.c13
-rw-r--r--menu.c35
-rw-r--r--mode-tree.c27
-rw-r--r--options.c19
-rw-r--r--tmux.1268
-rw-r--r--tmux.h64
19 files changed, 1820 insertions, 899 deletions
diff --git a/Makefile b/Makefile
index 90ecbd84..b15da7be 100644
--- a/Makefile
+++ b/Makefile
@@ -38,6 +38,7 @@ SRCS= alerts.c \
cmd-move-window.c \
cmd-new-session.c \
cmd-new-window.c \
+ cmd-parse.y \
cmd-paste-buffer.c \
cmd-pipe-pane.c \
cmd-queue.c \
@@ -63,7 +64,6 @@ SRCS= alerts.c \
cmd-show-options.c \
cmd-source-file.c \
cmd-split-window.c \
- cmd-string.c \
cmd-swap-pane.c \
cmd-swap-window.c \
cmd-switch-client.c \
diff --git a/cfg.c b/cfg.c
index 0b74ec35..aa5c13b7 100644
--- a/cfg.c
+++ b/cfg.c
@@ -27,17 +27,6 @@
#include "tmux.h"
-/* Condition for %if, %elif, %else and %endif. */
-struct cfg_cond {
- size_t line; /* line number of %if */
- int met; /* condition was met */
- int skip; /* skip later %elif/%else */
- int saw_else; /* saw a %else */
-
- TAILQ_ENTRY(cfg_cond) entry;
-};
-TAILQ_HEAD(cfg_conds, cfg_cond);
-
struct client *cfg_client;
static char *cfg_file;
int cfg_finished;
@@ -86,9 +75,8 @@ start_cfg(void)
struct client *c;
/*
- * Configuration files are loaded without a client, so NULL is passed
- * into load_cfg() and commands run in the global queue with
- * item->client NULL.
+ * Configuration files are loaded without a client, so commands are run
+ * in the global queue with item->client NULL.
*
* However, we must block the initial client (but just the initial
* client) so that its command runs after the configuration is loaded.
@@ -103,11 +91,11 @@ start_cfg(void)
}
if (cfg_file == NULL)
- load_cfg(TMUX_CONF, NULL, NULL, CFG_QUIET, NULL);
+ load_cfg(TMUX_CONF, NULL, NULL, CMD_PARSE_QUIET, NULL);
if (cfg_file == NULL && (home = find_home()) != NULL) {
xasprintf(&cfg_file, "%s/.tmux.conf", home);
- flags = CFG_QUIET;
+ flags = CMD_PARSE_QUIET;
}
if (cfg_file != NULL)
load_cfg(cfg_file, NULL, NULL, flags, NULL);
@@ -115,214 +103,54 @@ start_cfg(void)
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
}
-static int
-cfg_check_cond(const char *path, size_t line, const char *p, int *skip,
- struct client *c, struct cmd_find_state *fs)
-{
- struct format_tree *ft;
- char *s;
- int result;
-
- while (isspace((u_char)*p))
- p++;
- if (p[0] == '\0') {
- cfg_add_cause("%s:%zu: invalid condition", path, line);
- *skip = 1;
- return (0);
- }
-
- ft = format_create(NULL, NULL, FORMAT_NONE, FORMAT_NOJOBS);
- if (fs != NULL)
- format_defaults(ft, c, fs->s, fs->wl, fs->wp);
- else
- format_defaults(ft, c, NULL, NULL, NULL);
- s = format_expand(ft, p);
- result = format_true(s);
- free(s);
- format_free(ft);
-
- *skip = result;
- return (result);
-}
-
-static void
-cfg_handle_if(const char *path, size_t line, struct cfg_conds *conds,
- const char *p, struct client *c, struct cmd_find_state *fs)
-{
- struct cfg_cond *cond;
- struct cfg_cond *parent = TAILQ_FIRST(conds);
-
- /*
- * Add a new condition. If a previous condition exists and isn't
- * currently met, this new one also can't be met.
- */
- cond = xcalloc(1, sizeof *cond);
- cond->line = line;
- if (parent == NULL || parent->met)
- cond->met = cfg_check_cond(path, line, p, &cond->skip, c, fs);
- else
- cond->skip = 1;
- cond->saw_else = 0;
- TAILQ_INSERT_HEAD(conds, cond, entry);
-}
-
-static void
-cfg_handle_elif(const char *path, size_t line, struct cfg_conds *conds,
- const char *p, struct client *c, struct cmd_find_state *fs)
-{
- struct cfg_cond *cond = TAILQ_FIRST(conds);
-
- /*
- * If a previous condition exists and wasn't met, check this
- * one instead and change the state.
- */
- if (cond == NULL || cond->saw_else)
- cfg_add_cause("%s:%zu: unexpected %%elif", path, line);
- else if (!cond->skip)
- cond->met = cfg_check_cond(path, line, p, &cond->skip, c, fs);
- else
- cond->met = 0;
-}
-
-static void
-cfg_handle_else(const char *path, size_t line, struct cfg_conds *conds)
-{
- struct cfg_cond *cond = TAILQ_FIRST(conds);
-
- /*
- * If a previous condition exists and wasn't met and wasn't already
- * %else, use this one instead.
- */
- if (cond == NULL || cond->saw_else) {
- cfg_add_cause("%s:%zu: unexpected %%else", path, line);
- return;
- }
- cond->saw_else = 1;
- cond->met = !cond->skip;
- cond->skip = 1;
-}
-
-static void
-cfg_handle_endif(const char *path, size_t line, struct cfg_conds *conds)
-{
- struct cfg_cond *cond = TAILQ_FIRST(conds);
-
- /*
- * Remove previous condition if one exists.
- */
- if (cond == NULL) {
- cfg_add_cause("%s:%zu: unexpected %%endif", path, line);
- return;
- }
- TAILQ_REMOVE(conds, cond, entry);
- free(cond);
-}
-
-static void
-cfg_handle_directive(const char *p, const char *path, size_t line,
- struct cfg_conds *conds, struct client *c, struct cmd_find_state *fs)
-{
- int n = 0;
-
- while (p[n] != '\0' && !isspace((u_char)p[n]))
- n++;
- if (strncmp(p, "%if", n) == 0)
- cfg_handle_if(path, line, conds, p + n, c, fs);
- else if (strncmp(p, "%elif", n) == 0)
- cfg_handle_elif(path, line, conds, p + n, c, fs);
- else if (strcmp(p, "%else") == 0)
- cfg_handle_else(path, line, conds);
- else if (strcmp(p, "%endif") == 0)
- cfg_handle_endif(path, line, conds);
- else
- cfg_add_cause("%s:%zu: invalid directive: %s", path, line, p);
-}
-
int
load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags,
struct cmdq_item **new_item)
{
FILE *f;
- const char delim[3] = { '\\', '\\', '\0' };
- u_int found = 0;
- size_t line = 0;
- char *buf, *cause1, *p, *q;
- struct cmd_list *cmdlist;
+ struct cmd_parse_input pi;
+ struct cmd_parse_result *pr;
struct cmdq_item *new_item0;
- struct cfg_cond *cond, *cond1;
- struct cfg_conds conds;
- struct cmd_find_state *fs = NULL;
- struct client *fc = NULL;
-
- if (item != NULL) {
- fs = &item->target;
- fc = cmd_find_client(item, NULL, 1);
- }
- TAILQ_INIT(&conds);
+ if (new_item != NULL)
+ *new_item = NULL;
log_debug("loading %s", path);
if ((f = fopen(path, "rb")) == NULL) {
- if (errno == ENOENT && (flags & CFG_QUIET))
+ if (errno == ENOENT && (flags & CMD_PARSE_QUIET))
return (0);
cfg_add_cause("%s: %s", path, strerror(errno));
return (-1);
}
- while ((buf = fparseln(f, NULL, &line, delim, 0)) != NULL) {
- log_debug("%s: %s", path, buf);
-
- p = buf;
- while (isspace((u_char)*p))
- p++;
- if (*p == '\0') {
- free(buf);
- continue;
- }
- q = p + strlen(p) - 1;
- while (q != p && isspace((u_char)*q))
- *q-- = '\0';
-
- if (*p == '%') {
- cfg_handle_directive(p, path, line, &conds, fc, fs);
- continue;
- }
- cond = TAILQ_FIRST(&conds);
- if (cond != NULL && !cond->met)
- continue;
-
- cmdlist = cmd_string_parse(p, path, line, &cause1);
- if (cmdlist == NULL) {
- free(buf);
- if (cause1 == NULL)
- continue;
- cfg_add_cause("%s:%zu: %s", path, line, cause1);
- free(cause1);
- continue;
- }
- free(buf);
-
- new_item0 = cmdq_get_command(cmdlist, NULL, NULL, 0);
- if (item != NULL) {
- cmdq_insert_after(item, new_item0);
- item = new_item0;
- } else
- cmdq_append(c, new_item0);
- cmd_list_free(cmdlist);
-
- found++;
- }
- fclose(f);
+ memset(&pi, 0, sizeof pi);
+ pi.flags = flags;
+ pi.file = path;
- TAILQ_FOREACH_REVERSE_SAFE(cond, &conds, cfg_conds, entry, cond1) {
- cfg_add_cause("%s:%zu: unterminated %%if", path, cond->line);
- TAILQ_REMOVE(&conds, cond, entry);
- free(cond);
+ pr = cmd_parse_from_file(f, &pi);
+ fclose(f);
+ if (pr->status == CMD_PARSE_EMPTY)
+ return (0);
+ if (pr->status == CMD_PARSE_ERROR) {
+ cfg_add_cause("%s", pr->error);
+ free(pr->error);
+ return (-1);
+ }
+ if (flags & CMD_PARSE_PARSEONLY) {
+ cmd_list_free(pr->cmdlist);
+ return (0);
}
+ new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ if (item != NULL)
+ cmdq_insert_after(item, new_item0);
+ else
+ cmdq_append(c, new_item0);
+ cmd_list_free(pr->cmdlist);
+
if (new_item != NULL)
- *new_item = item;
- return (found);
+ *new_item = new_item0;
+ return (0);
}
void
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index a3cc22c8..603ddb0a 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -134,10 +134,10 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
int done)
{
struct cmd_command_prompt_cdata *cdata = data;
- struct cmd_list *cmdlist;
struct cmdq_item *new_item;
- char *cause, *new_template, *prompt, *ptr;
+ char *new_template, *prompt, *ptr;
char *input = NULL;
+ struct cmd_parse_result *pr;
if (s == NULL)
return (0);
@@ -164,20 +164,22 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
return (1);
}
- cmdlist = cmd_string_parse(new_template, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL)
- new_item = cmdq_get_error(cause);
- else
- new_item = NULL;
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
- cmd_list_free(cmdlist);
- }
-
- if (new_item != NULL)
+ pr = cmd_parse_from_string(new_template, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ new_item = NULL;
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
cmdq_append(c, new_item);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_append(c, new_item);
+ break;
+ }
if (!done)
free(new_template);
diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c
index 4017a6f9..be21a78b 100644
--- a/cmd-confirm-before.c
+++ b/cmd-confirm-before.c
@@ -87,32 +87,33 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
__unused int done)
{
struct cmd_confirm_before_data *cdata = data;
- struct cmd_list *cmdlist;
struct cmdq_item *new_item;
- char *cause;
+ struct cmd_parse_result *pr;
if (c->flags & CLIENT_DEAD)
return (0);
if (s == NULL || *s == '\0')
return (0);
- if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
+ if (tolower((u_char)s[0]) != 'y' || s[1] != '\0')
return (0);
- cmdlist = cmd_string_parse(cdata->cmd, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL)
- new_item = cmdq_get_error(cause);
- else
- new_item = NULL;
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
- cmd_list_free(cmdlist);
- }
-
- if (new_item != NULL)
+ pr = cmd_parse_from_string(cdata->cmd, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ new_item = NULL;
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
cmdq_append(c, new_item);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_append(c, new_item);
+ break;
+ }
return (0);
}
diff --git a/cmd-display-panes.c b/cmd-display-panes.c
index 6e331ae1..aeeb6936 100644
--- a/cmd-display-panes.c
+++ b/cmd-display-panes.c
@@ -197,11 +197,11 @@ static int
cmd_display_panes_key(struct client *c, struct key_event *event)
{
struct cmd_display_panes_data *cdata = c->overlay_data;
- struct cmd_list *cmdlist;
struct cmdq_item *new_item;
- char *cmd, *expanded, *cause;
+ char *cmd, *expanded;
struct window *w = c->session->curw->window;
struct window_pane *wp;
+ struct cmd_parse_result *pr;
if (event->key < '0' || event->key > '9')
return (1);
@@ -214,22 +214,21 @@ cmd_display_panes_key(struct client *c, struct key_event *event)
xasprintf(&expanded, "%%%u", wp->id);
cmd = cmd_template_replace(cdata->command, expanded, 1);
- cmdlist = cmd_string_parse(cmd, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL)
- new_item = cmdq_get_error(cause);
- else
- new_item = NULL;
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
- cmd_list_free(cmdlist);
- }
- if (new_item != NULL) {
- if (cdata->item != NULL)
- cmdq_insert_after(cdata->item, new_item);
- else
- cmdq_append(c, new_item);
+ pr = cmd_parse_from_string(cmd, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ new_item = NULL;
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
+ cmdq_append(c, new_item);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_append(c, new_item);
+ break;
}
free(cmd);
diff --git a/cmd-if-shell.c b/cmd-if-shell.c
index 480912df..40e2b1c3 100644
--- a/cmd-if-shell.c
+++ b/cmd-if-shell.c
@@ -49,8 +49,7 @@ const struct cmd_entry cmd_if_shell_entry = {
};
struct cmd_if_shell_data {
- char *file;
- u_int line;
+ struct cmd_parse_input input;
char *cmd_if;
char *cmd_else;
@@ -64,51 +63,62 @@ static enum cmd_retval
cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = self->args;
- struct cmdq_shared *shared = item->shared;
+ struct mouse_event *m = &item->shared->mouse;
struct cmd_if_shell_data *cdata;
- char *shellcmd, *cmd, *cause;
- struct cmd_list *cmdlist;
+ char *shellcmd, *cmd;
struct cmdq_item *new_item;
struct client *c = cmd_find_client(item, NULL, 1);
struct session *s = item->target.s;
struct winlink *wl = item->target.wl;
struct window_pane *wp = item->target.wp;
+ struct cmd_parse_input pi;
+ struct cmd_parse_result *pr;
shellcmd = format_single(item, args->argv[0], c, s, wl, wp);
if (args_has(args, 'F')) {
- cmd = NULL;
if (*shellcmd != '0' && *shellcmd != '\0')
cmd = args->argv[1];
else if (args->argc == 3)
cmd = args->argv[2];
+ else
+ cmd = NULL;
free(shellcmd);
if (cmd == NULL)
return (CMD_RETURN_NORMAL);
- cmdlist = cmd_string_parse(cmd, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL) {
- cmdq_error(item, "%s", cause);
- free(cause);
- }
+
+ memset(&pi, 0, sizeof pi);
+ if (self->file != NULL)
+ pi.file = self->file;
+ pi.line = self->line;
+ pi.item = item;
+ pi.c = c;
+ cmd_find_copy_state(&pi.fs, &item->target);
+
+ pr = cmd_parse_from_string(cmd, &pi);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ break;
+ case CMD_PARSE_ERROR:
+ cmdq_error(item, "%s", pr->error);
+ free(pr->error);
return (CMD_RETURN_ERROR);
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, m, 0);
+ cmdq_insert_after(item, new_item);
+ cmd_list_free(pr->cmdlist);
+ break;
}
- new_item = cmdq_get_command(cmdlist, NULL, &shared->mouse, 0);
- cmdq_insert_after(item, new_item);
- cmd_list_free(cmdlist);
return (CMD_RETURN_NORMAL);
}
cdata = xcalloc(1, sizeof *cdata);
- if (self->file != NULL) {
- cdata->file = xstrdup(self->file);
- cdata->line = self->line;
- }
cdata->cmd_if = xstrdup(args->argv[1]);
if (args->argc == 3)
cdata->cmd_else = xstrdup(args->argv[2]);
else
cdata->cmd_else = NULL;
+ memcpy(&cdata->mouse, m, sizeof cdata->mouse);
cdata->client = item->client;
if (cdata->client != NULL)
@@ -118,7 +128,16 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
cdata->item = item;
else
cdata->item = NULL;
- memcpy(&cdata->mouse, &shared->mouse, sizeof cdata->mouse);
+
+ memset(&cdata->input, 0, sizeof cdata->input);
+ if (self->file != NULL)
+ cdata->input.file = xstrdup(self->file);
+ cdata->input.line = self->line;
+ cdata->input.item = cdata->item;
+ cdata->input.c = c;
+ if (cdata->input.c != NULL)
+ cdata->input.c->references++;
+ cmd_find_copy_state(&cdata->input.fs, &item->target);
if (job_run(shellcmd, s, server_client_get_cwd(item->client, s), NULL,
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0) == NULL) {
@@ -139,11 +158,11 @@ cmd_if_shell_callback(struct job *job)
{
struct cmd_if_shell_data *cdata = job_get_data(job);
struct client *c = cdata->client;
- struct cmd_list *cmdlist;
+ struct mouse_event *m = &cdata->mouse;
struct cmdq_item *new_item;
- char *cause, *cmd, *file = cdata->file;
- u_int line = cdata->line;
+ char *cmd;
int status;
+ struct cmd_parse_result *pr;
status = job_get_status(job);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
@@ -153,17 +172,20 @@ cmd_if_shell_callback(struct job *job)
if (cmd == NULL)
goto out;
- cmdlist = cmd_string_parse(cmd, file, line, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL && cdata->item != NULL)
- cmdq_error(cdata->item, "%s", cause);
- free(cause);
+ pr = cmd_parse_from_string(cmd, &cdata->input);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
new_item = NULL;
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, &cdata->mouse, 0);
- cmd_list_free(cmdlist);
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, m, 0);
+ cmd_list_free(pr->cmdlist);
+ break;
}
-
if (new_item != NULL) {
if (cdata->item == NULL)
cmdq_append(c, new_item);
@@ -187,6 +209,9 @@ cmd_if_shell_free(void *data)
free(cdata->cmd_else);
free(cdata->cmd_if);
- free(cdata->file);
+ if (cdata->input.c != NULL)
+ server_client_unref(cdata->input.c);
+ free((void *)cdata->input.file);
+
free(cdata);
}
diff --git a/cmd-list.c b/cmd-list.c
index 282533cf..ead0fb61 100644
--- a/cmd-list.c
+++ b/cmd-list.c
@@ -23,17 +23,39 @@
#include "tmux.h"
-static struct cmd_list *
+static u_int cmd_list_next_group = 1;
+
+struct cmd_list *
cmd_list_new(void)
{
struct cmd_list *cmdlist;
cmdlist = xcalloc(1, sizeof *cmdlist);
cmdlist->references = 1;
+ cmdlist->group = cmd_list_next_group++;
TAILQ_INIT(&cmdlist->list);
return (cmdlist);
}
+void
+cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
+{
+ cmd->group = cmdlist->group;
+ TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry);
+}
+
+void
+cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
+{
+ struct cmd *cmd, *cmd1;
+
+ TAILQ_FOREACH_SAFE(cmd, &from->list, qentry, cmd1) {
+ TAILQ_REMOVE(&from->list, cmd, qentry);
+ TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry);
+ }
+ cmdlist->group = cmd_list_next_group++;
+}
+
struct cmd_list *
cmd_list_parse(int argc, char **argv, const char *file, u_int line,
char **cause)
@@ -100,9 +122,7 @@ cmd_list_free(struct cmd_list *cmdlist)
TAILQ_FOREACH_SAFE(cmd, &cmdlist->list, qentry, cmd1) {
TAILQ_REMOVE(&cmdlist->list, cmd, qentry);
- args_free(cmd->args);
- free(cmd->file);
- free(cmd);
+ cmd_free(cmd);
}
free(cmdlist);
diff --git a/cmd-parse.y b/cmd-parse.y
new file mode 100644
index 00000000..8b8f33ab
--- /dev/null
+++ b/cmd-parse.y
@@ -0,0 +1,1207 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static int yylex(void);
+static int yyparse(void);
+static int printflike(1,2) yyerror(const char *, ...);
+
+static char *yylex_token(int);
+static char *yylex_format(void);
+
+struct cmd_parse_scope {
+ int flag;
+ TAILQ_ENTRY (cmd_parse_scope) entry;
+};
+
+struct cmd_parse_command {
+ char *name;
+ u_int line;
+
+ int argc;
+ char **argv;
+
+ TAILQ_ENTRY(cmd_parse_command) entry;
+};
+TAILQ_HEAD(cmd_parse_commands, cmd_parse_command);
+
+struct cmd_parse_state {
+ FILE *f;
+ int eof;
+ struct cmd_parse_input *input;
+ u_int escapes;
+
+ char *error;
+ struct cmd_parse_commands commands;
+
+ struct cmd_parse_scope *scope;
+ TAILQ_HEAD(, cmd_parse_scope) stack;
+};
+static struct cmd_parse_state parse_state;
+
+static char *cmd_parse_get_error(const char *, u_int, const char *);
+static char *cmd_parse_get_strerror(const char *, u_int);
+static void cmd_parse_free_command(struct cmd_parse_command *);
+static void cmd_parse_free_commands(struct cmd_parse_commands *);
+
+%}
+
+%union
+{
+ char *token;
+ struct {
+ int argc;
+ char **argv;
+ } arguments;
+ int flag;
+ struct {
+ int flag;
+ struct cmd_parse_commands commands;
+ } elif;
+ struct cmd_parse_commands commands;
+ struct cmd_parse_command *command;
+}
+
+%token ERROR
+%token IF
+%token ELSE
+%token ELIF
+%token ENDIF
+%token <token> FORMAT TOKEN EQUALS
+
+%type <token> argument expanded
+%type <arguments> arguments
+%type <flag> if_open if_elif
+%type <elif> elif elif1
+%type <commands> statements statement commands condition condition1
+%type <command> command
+
+%%
+
+lines : /* empty */
+ | statements
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_CONCAT(&ps->commands, &$1, entry);
+ }
+
+statements : statement '\n'
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ }
+ | statements statement '\n'
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ TAILQ_CONCAT(&$$, &$2, entry);
+ }
+
+
+statement : condition
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag)
+ TAILQ_CONCAT(&$$, &$1, entry);
+ else
+ cmd_parse_free_commands(&$1);
+ }
+ | assignment
+ {
+ TAILQ_INIT(&$$);
+ }
+ | commands
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag)
+ TAILQ_CONCAT(&$$, &$1, entry);
+ else
+ cmd_parse_free_commands(&$1);
+ }
+
+expanded : FORMAT
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_input *pi = ps->input;
+ struct format_tree *ft;
+ struct client *c = pi->c;
+ struct cmd_find_state *fs;
+ int flags = FORMAT_NOJOBS;
+
+ if (cmd_find_valid_state(&pi->fs))
+ fs = &pi->fs;
+ else
+ fs = NULL;
+ ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
+ if (fs != NULL)
+ format_defaults(ft, c, fs->s, fs->wl, fs->wp);
+ else
+ format_defaults(ft, c, NULL, NULL, NULL);
+
+ $$ = format_expand(ft, $1);
+ format_free(ft);
+ free($1);
+ }
+
+assignment : /* empty */
+ | EQUALS
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ int flags = ps->input->flags;
+
+ if ((~flags & CMD_PARSE_PARSEONLY) &&
+ (ps->scope == NULL || ps->scope->flag))
+ environ_put(global_environ, $1);
+ free($1);
+ }
+
+if_open : IF expanded
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ $$ = scope->flag = format_true($2);
+ free($2);
+
+ if (ps->scope != NULL)
+ TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry);
+ ps->scope = scope;
+ }
+
+if_else : ELSE
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ scope->flag = !ps->scope->flag;
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+
+if_elif : ELIF expanded
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ $$ = scope->flag = format_true($2);
+ free($2);
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+
+if_close : ENDIF
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ free(ps->scope);
+ ps->scope = TAILQ_FIRST(&ps->stack);
+ if (ps->scope != NULL)
+ TAILQ_REMOVE(&ps->stack, ps->scope, entry);
+ }
+
+condition : if_open '\n' statements if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1)
+ TAILQ_CONCAT(&$$, &$3, entry);
+ else
+ cmd_parse_free_commands(&$3);
+ }
+ | if_open '\n' statements if_else '\n' statements if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$3, entry);
+ cmd_parse_free_commands(&$6);
+ } else {
+ TAILQ_CONCAT(&$$, &$6, entry);
+ cmd_parse_free_commands(&$3);
+ }
+ }
+ | if_open '\n' statements elif if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$3, entry);
+ cmd_parse_free_commands(&$4.commands);
+ } else if ($4.flag) {
+ TAILQ_CONCAT(&$$, &$4.commands, entry);
+ cmd_parse_free_commands(&$3);
+ } else {
+ cmd_parse_free_commands(&$3);
+ cmd_parse_free_commands(&$4.commands);
+ }
+ }
+ | if_open '\n' statements elif if_else '\n' statements if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$3, entry);
+ cmd_parse_free_commands(&$4.commands);
+ cmd_parse_free_commands(&$7);
+ } else if ($4.flag) {
+ TAILQ_CONCAT(&$$, &$4.commands, entry);
+ cmd_parse_free_commands(&$3);
+ cmd_parse_free_commands(&$7);
+ } else {
+ TAILQ_CONCAT(&$$, &$7, entry);
+ cmd_parse_free_commands(&$3);
+ cmd_parse_free_commands(&$4.commands);
+ }
+ }
+
+elif : if_elif '\n' statements
+ {
+ TAILQ_INIT(&$$.commands);
+ if ($1)
+ TAILQ_CONCAT(&$$.commands, &$3, entry);
+ else
+ cmd_parse_free_commands(&$3);
+ $$.flag = $1;
+ }
+ | if_elif '\n' statements elif
+ {
+ TAILQ_INIT(&$$.commands);
+ if ($1) {
+ $$.flag = 1;
+ TAILQ_CONCAT(&$$.commands, &$3, entry);
+ cmd_parse_free_commands(&$4.commands);
+ } else {
+ $$.flag = $4.flag;
+ TAILQ_CONCAT(&$$.commands, &$4.commands, entry);
+ cmd_parse_free_commands(&$3);
+ }
+ }
+
+
+commands : command
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag)
+ TAILQ_INSERT_TAIL(&$$, $1, entry);
+ else
+ cmd_parse_free_command($1);
+ }
+ | commands ';'
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ }
+ | commands ';' condition1
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ TAILQ_CONCAT(&$$, &$3, entry);
+ }
+ | commands ';' command
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag) {
+ TAILQ_CONCAT(&$$, &$1, entry);
+ TAILQ_INSERT_TAIL(&$$, $3, entry);
+ } else {
+ cmd_parse_free_commands(&$1);
+ cmd_parse_free_command($3);
+ }
+ }
+ | condition1