summaryrefslogtreecommitdiff
path: root/usr.bin/tmux
diff options
context:
space:
mode:
authorNicholas Marriott <nicm@cvs.openbsd.org>2020-06-04 07:12:06 +0000
committerNicholas Marriott <nicm@cvs.openbsd.org>2020-06-04 07:12:06 +0000
commit10646ca6ccc4c522cb1694d2ad975be2cac96f4f (patch)
treef2dde6277a6f5f4dcab38b7af7219a9202d94a00 /usr.bin/tmux
parentb212416942e2f3a1215ec9f47edab7ca780b9e01 (diff)
Instead of using a custom parse function to process {}, treat it as a
set of statements and parse with yacc, then convert back to a string as the last step. This means the rules are consistent inside and outside {}, %if and friends work at the right time, and the final result isn't littered with unnecessary newlines.
Diffstat (limited to 'usr.bin/tmux')
-rw-r--r--usr.bin/tmux/arguments.c7
-rw-r--r--usr.bin/tmux/cmd-parse.y207
-rw-r--r--usr.bin/tmux/cmd.c18
-rw-r--r--usr.bin/tmux/tmux.123
4 files changed, 90 insertions, 165 deletions
diff --git a/usr.bin/tmux/arguments.c b/usr.bin/tmux/arguments.c
index da92c77efd3..750fcd3ce55 100644
--- a/usr.bin/tmux/arguments.c
+++ b/usr.bin/tmux/arguments.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: arguments.c,v 1.33 2020/05/25 18:17:14 nicm Exp $ */
+/* $OpenBSD: arguments.c,v 1.34 2020/06/04 07:12:05 nicm Exp $ */
/*
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -227,6 +227,11 @@ args_escape(const char *s)
return (escaped);
}
+ if (strchr(s, ' ') != NULL && strchr(s, '\'') == NULL) {
+ xasprintf(&escaped, "'%s'", s);
+ return (escaped);
+ }
+
flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
if (s[strcspn(s, quoted)] != '\0')
flags |= VIS_DQ;
diff --git a/usr.bin/tmux/cmd-parse.y b/usr.bin/tmux/cmd-parse.y
index 31fd928d945..639d7709020 100644
--- a/usr.bin/tmux/cmd-parse.y
+++ b/usr.bin/tmux/cmd-parse.y
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd-parse.y,v 1.27 2020/05/25 18:57:24 nicm Exp $ */
+/* $OpenBSD: cmd-parse.y,v 1.28 2020/06/04 07:12:05 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -43,7 +43,6 @@ struct cmd_parse_scope {
};
struct cmd_parse_command {
- char *name;
u_int line;
int argc;
@@ -78,6 +77,7 @@ static char *cmd_parse_get_error(const char *, u_int, const char *);
static void cmd_parse_free_command(struct cmd_parse_command *);
static struct cmd_parse_commands *cmd_parse_new_commands(void);
static void cmd_parse_free_commands(struct cmd_parse_commands *);
+static char *cmd_parse_commands_to_string(struct cmd_parse_commands *);
static void cmd_parse_print_commands(struct cmd_parse_input *, u_int,
struct cmd_list *);
@@ -111,7 +111,8 @@ static void cmd_parse_print_commands(struct cmd_parse_input *, u_int,
%type <arguments> arguments
%type <flag> if_open if_elif
%type <elif> elif elif1
-%type <commands> statements statement commands condition condition1
+%type <commands> argument_statements statements statement
+%type <commands> commands condition condition1
%type <command> command
%%
@@ -359,7 +360,7 @@ commands : command
struct cmd_parse_state *ps = &parse_state;
$$ = cmd_parse_new_commands();
- if ($1->name != NULL &&
+ if ($1->argc != 0 &&
(ps->scope == NULL || ps->scope->flag))
TAILQ_INSERT_TAIL($$, $1, entry);
else
@@ -379,7 +380,7 @@ commands : command
{
struct cmd_parse_state *ps = &parse_state;
- if ($3->name != NULL &&
+ if ($3->argc != 0 &&
(ps->scope == NULL || ps->scope->flag)) {
$$ = $1;
TAILQ_INSERT_TAIL($$, $3, entry);
@@ -399,7 +400,6 @@ command : assignment
struct cmd_parse_state *ps = &parse_state;
$$ = xcalloc(1, sizeof *$$);
- $$->name = NULL;
$$->line = ps->input->line;
}
| optional_assignment TOKEN
@@ -407,20 +407,21 @@ command : assignment
struct cmd_parse_state *ps = &parse_state;
$$ = xcalloc(1, sizeof *$$);
- $$->name = $2;
$$->line = ps->input->line;
+ cmd_prepend_argv(&$$->argc, &$$->argv, $2);
+
}
| optional_assignment TOKEN arguments
{
struct cmd_parse_state *ps = &parse_state;
$$ = xcalloc(1, sizeof *$$);
- $$->name = $2;
$$->line = ps->input->line;
$$->argc = $3.argc;
$$->argv = $3.argv;
+ cmd_prepend_argv(&$$->argc, &$$->argv, $2);
}
condition1 : if_open commands if_close
@@ -524,6 +525,20 @@ argument : TOKEN
{
$$ = $1;
}
+ | '{' argument_statements
+ {
+ $$ = cmd_parse_commands_to_string($2);
+ cmd_parse_free_commands($2);
+ }
+
+argument_statements : statement '}'
+ {
+ $$ = $1;
+ }
+ | statements '}'
+ {
+ $$ = $1;
+ }
%%
@@ -558,7 +573,6 @@ cmd_parse_print_commands(struct cmd_parse_input *pi, u_int line,
static void
cmd_parse_free_command(struct cmd_parse_command *cmd)
{
- free(cmd->name);
cmd_free_argv(cmd->argc, cmd->argv);
free(cmd);
}
@@ -585,6 +599,30 @@ cmd_parse_free_commands(struct cmd_parse_commands *cmds)
free(cmds);
}
+static char *
+cmd_parse_commands_to_string(struct cmd_parse_commands *cmds)
+{
+ struct cmd_parse_command *cmd;
+ char *string = NULL, *s, *line;
+
+ TAILQ_FOREACH(cmd, cmds, entry) {
+ line = cmd_stringify_argv(cmd->argc, cmd->argv);
+ if (string == NULL)
+ s = line;
+ else {
+ xasprintf(&s, "%s ; %s", s, line);
+ free(line);
+ }
+
+ free(string);
+ string = s;
+ }
+ if (string == NULL)
+ string = xstrdup("");
+ log_debug("%s: %s", __func__, string);
+ return (string);
+}
+
static struct cmd_parse_commands *
cmd_parse_run_parser(char **cause)
{
@@ -645,7 +683,7 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
int i;
struct cmd_list *cmdlist = NULL, *result;
struct cmd *add;
- char *alias, *cause, *s;
+ char *name, *alias, *cause, *s;
/* Check for an empty list. */
if (TAILQ_EMPTY(cmds)) {
@@ -661,12 +699,14 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
* command list.
*/
TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) {
- alias = cmd_get_alias(cmd->name);
+ name = cmd->argv[0];
+
+ alias = cmd_get_alias(name);
if (alias == NULL)
continue;
line = cmd->line;
- log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias);
+ log_debug("%s: %u %s = %s", __func__, line, name, alias);
pi->line = line;
cmds2 = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause);
@@ -683,7 +723,7 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
cmd_parse_free_command(cmd);
continue;
}
- for (i = 0; i < cmd->argc; i++)
+ for (i = 1; i < cmd->argc; i++)
cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]);
after = cmd;
@@ -707,7 +747,8 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
*/
result = cmd_list_new();
TAILQ_FOREACH(cmd, cmds, entry) {
- log_debug("%s: %u %s", __func__, cmd->line, cmd->name);
+ name = cmd->argv[0];
+ log_debug("%s: %u %s", __func__, cmd->line, name);
cmd_log_argv(cmd->argc, cmd->argv, __func__);
if (cmdlist == NULL ||
@@ -721,7 +762,6 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
}
line = cmd->line;
- cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name);
add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause);
if (add == NULL) {
cmd_list_free(result);
@@ -921,11 +961,10 @@ cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
i);
cmd = xcalloc(1, sizeof *cmd);
- cmd->name = xstrdup(new_argv[0]);
cmd->line = pi->line;
- cmd->argc = new_argc - 1;
- cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
+ cmd->argc = new_argc;
+ cmd->argv = cmd_copy_argv(new_argc, new_argv);
TAILQ_INSERT_TAIL(cmds, cmd, entry);
}
@@ -941,11 +980,10 @@ cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
last);
cmd = xcalloc(1, sizeof *cmd);
- cmd->name = xstrdup(new_argv[0]);
cmd->line = pi->line;
- cmd->argc = new_argc - 1;
- cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
+ cmd->argc = new_argc;
+ cmd->argv = cmd_copy_argv(new_argc, new_argv);
TAILQ_INSERT_TAIL(cmds, cmd, entry);
}
@@ -1123,11 +1161,11 @@ yylex(void)
return ('\n');
}
- if (ch == ';') {
+ if (ch == ';' || ch == '{' || ch == '}') {
/*
- * A semicolon is itself.
+ * A semicolon or { or } is itself.
*/
- return (';');
+ return (ch);
}
if (ch == '#') {
@@ -1442,119 +1480,6 @@ yylex_token_tilde(char **buf, size_t *len)
return (1);
}
-static int
-yylex_token_brace(char **buf, size_t *len)
-{
- struct cmd_parse_state *ps = &parse_state;
- int ch, lines = 0, nesting = 1, escape = 0;
- int quote = '\0', token = 0;
-
- /*
- * Extract a string up to the matching unquoted '}', including newlines
- * and handling nested braces.
- *
- * To detect the final and intermediate braces which affect the nesting
- * depth, we scan the input as if it was a tmux config file, and ignore
- * braces which would be considered quoted, escaped, or in a comment.
- *
- * We update the token state after every character because '#' begins a
- * comment only when it begins a token. For simplicity, we treat an
- * unquoted directive format as comment.
- *
- * The result is verbatim copy of the input excluding the final brace.
- */
-
- for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) {
- yylex_append1(buf, len, ch);
- if (ch == '\n')
- lines++;
-
- /*
- * If the previous character was a backslash (escape is set),
- * escape anything if unquoted or in double quotes, otherwise
- * escape only '\n' and '\\'.
- */
- if (escape &&
- (quote == '\0' ||
- quote == '"' ||
- ch == '\n' ||
- ch == '\\')) {
- escape = 0;
- if (ch != '\n')
- token = 1;
- continue;
- }
-
- /*
- * The character is not escaped. If it is a backslash, set the
- * escape flag.
- */
- if (ch == '\\') {
- escape = 1;
- continue;
- }
- escape = 0;
-
- /* A newline always resets to unquoted. */
- if (ch == '\n') {
- quote = token = 0;
- continue;
- }
-
- if (quote) {
- /*
- * Inside quotes or comment. Check if this is the
- * closing quote.
- */
- if (ch == quote && quote != '#')
- quote = 0;
- token = 1; /* token continues regardless */
- } else {
- /* Not inside quotes or comment. */
- switch (ch) {
- case '"':
- case '\'':
- case '#':
- /* Beginning of quote or maybe comment. */
- if (ch != '#' || !token)
- quote = ch;
- token = 1;
- break;
- case ' ':
- case '\t':
- case ';':
- /* Delimiter - token resets. */
- token = 0;
- break;
- case '{':
- nesting++;
- token = 0; /* new commands set - token resets */
- break;
- case '}':
- nesting--;
- token = 1; /* same as after quotes */
- if (nesting == 0) {
- (*len)--; /* remove closing } */
- ps->input->line += lines;
- return (1);
- }
- break;
- default:
- token = 1;
- break;
- }
- }
- }
-
- /*
- * Update line count after error as reporting the opening line is more
- * useful than EOF.
- */
- yyerror("unterminated brace string");
- ps->input->line += lines;
- return (0);
-}
-
static char *
yylex_token(int ch)
{
@@ -1580,7 +1505,8 @@ yylex_token(int ch)
}
/* Whitespace or ; ends a token unless inside quotes. */
- if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
+ if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') &&
+ state == NONE)
break;
/*
@@ -1601,11 +1527,6 @@ yylex_token(int ch)
goto error;
goto skip;
}
- if (ch == '{' && state == NONE) {
- if (!yylex_token_brace(&buf, &len))
- goto error;
- goto skip;
- }
if (ch == '}' && state == NONE)
goto error; /* unmatched (matched ones were handled) */
diff --git a/usr.bin/tmux/cmd.c b/usr.bin/tmux/cmd.c
index 7e6f7fc1f86..5e116fc36bc 100644
--- a/usr.bin/tmux/cmd.c
+++ b/usr.bin/tmux/cmd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd.c,v 1.161 2020/05/16 16:02:24 nicm Exp $ */
+/* $OpenBSD: cmd.c,v 1.162 2020/06/04 07:12:05 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -357,25 +357,27 @@ cmd_free_argv(int argc, char **argv)
char *
cmd_stringify_argv(int argc, char **argv)
{
- char *buf;
+ char *buf = NULL, *s;
+ size_t len = 0;
int i;
- size_t len;
if (argc == 0)
return (xstrdup(""));
- len = 0;
- buf = NULL;
-
for (i = 0; i < argc; i++) {
- len += strlen(argv[i]) + 1;
+ s = args_escape(argv[i]);
+ log_debug("%s: %u %s = %s", __func__, i, argv[i], s);
+
+ len += strlen(s) + 1;
buf = xrealloc(buf, len);
if (i == 0)
*buf = '\0';
else
strlcat(buf, " ", len);
- strlcat(buf, argv[i], len);
+ strlcat(buf, s, len);
+
+ free(s);
}
return (buf);
}
diff --git a/usr.bin/tmux/tmux.1 b/usr.bin/tmux/tmux.1
index 45e96cc0277..692d66c44a0 100644
--- a/usr.bin/tmux/tmux.1
+++ b/usr.bin/tmux/tmux.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: tmux.1,v 1.775 2020/05/29 13:42:13 nicm Exp $
+.\" $OpenBSD: tmux.1,v 1.776 2020/06/04 07:12:05 nicm Exp $
.\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: May 29 2020 $
+.Dd $Mdocdate: June 4 2020 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -552,17 +552,14 @@ is removed) and are not treated as having any special meaning - so for example
variable.
.El
.Pp
-Braces are similar to single quotes in that the text inside is taken literally
-without any replacements but this also includes line continuation.
-Braces can span multiple lines in which case a literal newline is included in the
-string.
-They are designed to avoid the need for additional escaping when passing a group
-of
+Braces are parsed as a configuration file (so conditions such as
+.Ql %if
+are processed) and then converted into a string.
+They are designed to avoid the need for additional escaping when passing a
+group of
.Nm
-or shell commands as an argument (for example to
-.Ic if-shell
-or
-.Ic pipe-pane ) .
+commands as an argument (for example to
+.Ic if-shell ) .
These two examples produce an identical command - note that no escaping is
needed when using {}:
.Bd -literal -offset indent
@@ -570,7 +567,7 @@ if-shell true {
display -p 'brace-dollar-foo: }$foo'
}
-if-shell true "\en display -p 'brace-dollar-foo: }\e$foo'\en"
+if-shell true "display -p 'brace-dollar-foo: }\e$foo'"
.Ed
.Pp
Braces may be enclosed inside braces, for example: