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.
This commit is contained in:
nicm 2020-06-04 07:12:05 +00:00
parent 3f6af4156f
commit b3782d2dc8
4 changed files with 85 additions and 160 deletions

View File

@ -227,6 +227,11 @@ args_escape(const char *s)
return (escaped); return (escaped);
} }
if (strchr(s, ' ') != NULL && strchr(s, '\'') == NULL) {
xasprintf(&escaped, "'%s'", s);
return (escaped);
}
flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
if (s[strcspn(s, quoted)] != '\0') if (s[strcspn(s, quoted)] != '\0')
flags |= VIS_DQ; flags |= VIS_DQ;

View File

@ -43,7 +43,6 @@ struct cmd_parse_scope {
}; };
struct cmd_parse_command { struct cmd_parse_command {
char *name;
u_int line; u_int line;
int argc; 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 void cmd_parse_free_command(struct cmd_parse_command *);
static struct cmd_parse_commands *cmd_parse_new_commands(void); static struct cmd_parse_commands *cmd_parse_new_commands(void);
static void cmd_parse_free_commands(struct cmd_parse_commands *); 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, static void cmd_parse_print_commands(struct cmd_parse_input *, u_int,
struct cmd_list *); struct cmd_list *);
@ -111,7 +111,8 @@ static void cmd_parse_print_commands(struct cmd_parse_input *, u_int,
%type <arguments> arguments %type <arguments> arguments
%type <flag> if_open if_elif %type <flag> if_open if_elif
%type <elif> elif elif1 %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 %type <command> command
%% %%
@ -359,7 +360,7 @@ commands : command
struct cmd_parse_state *ps = &parse_state; struct cmd_parse_state *ps = &parse_state;
$$ = cmd_parse_new_commands(); $$ = cmd_parse_new_commands();
if ($1->name != NULL && if ($1->argc != 0 &&
(ps->scope == NULL || ps->scope->flag)) (ps->scope == NULL || ps->scope->flag))
TAILQ_INSERT_TAIL($$, $1, entry); TAILQ_INSERT_TAIL($$, $1, entry);
else else
@ -379,7 +380,7 @@ commands : command
{ {
struct cmd_parse_state *ps = &parse_state; struct cmd_parse_state *ps = &parse_state;
if ($3->name != NULL && if ($3->argc != 0 &&
(ps->scope == NULL || ps->scope->flag)) { (ps->scope == NULL || ps->scope->flag)) {
$$ = $1; $$ = $1;
TAILQ_INSERT_TAIL($$, $3, entry); TAILQ_INSERT_TAIL($$, $3, entry);
@ -399,7 +400,6 @@ command : assignment
struct cmd_parse_state *ps = &parse_state; struct cmd_parse_state *ps = &parse_state;
$$ = xcalloc(1, sizeof *$$); $$ = xcalloc(1, sizeof *$$);
$$->name = NULL;
$$->line = ps->input->line; $$->line = ps->input->line;
} }
| optional_assignment TOKEN | optional_assignment TOKEN
@ -407,20 +407,21 @@ command : assignment
struct cmd_parse_state *ps = &parse_state; struct cmd_parse_state *ps = &parse_state;
$$ = xcalloc(1, sizeof *$$); $$ = xcalloc(1, sizeof *$$);
$$->name = $2;
$$->line = ps->input->line; $$->line = ps->input->line;
cmd_prepend_argv(&$$->argc, &$$->argv, $2);
} }
| optional_assignment TOKEN arguments | optional_assignment TOKEN arguments
{ {
struct cmd_parse_state *ps = &parse_state; struct cmd_parse_state *ps = &parse_state;
$$ = xcalloc(1, sizeof *$$); $$ = xcalloc(1, sizeof *$$);
$$->name = $2;
$$->line = ps->input->line; $$->line = ps->input->line;
$$->argc = $3.argc; $$->argc = $3.argc;
$$->argv = $3.argv; $$->argv = $3.argv;
cmd_prepend_argv(&$$->argc, &$$->argv, $2);
} }
condition1 : if_open commands if_close condition1 : if_open commands if_close
@ -524,6 +525,20 @@ argument : TOKEN
{ {
$$ = $1; $$ = $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 static void
cmd_parse_free_command(struct cmd_parse_command *cmd) cmd_parse_free_command(struct cmd_parse_command *cmd)
{ {
free(cmd->name);
cmd_free_argv(cmd->argc, cmd->argv); cmd_free_argv(cmd->argc, cmd->argv);
free(cmd); free(cmd);
} }
@ -585,6 +599,30 @@ cmd_parse_free_commands(struct cmd_parse_commands *cmds)
free(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 * static struct cmd_parse_commands *
cmd_parse_run_parser(char **cause) cmd_parse_run_parser(char **cause)
{ {
@ -645,7 +683,7 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
int i; int i;
struct cmd_list *cmdlist = NULL, *result; struct cmd_list *cmdlist = NULL, *result;
struct cmd *add; struct cmd *add;
char *alias, *cause, *s; char *name, *alias, *cause, *s;
/* Check for an empty list. */ /* Check for an empty list. */
if (TAILQ_EMPTY(cmds)) { if (TAILQ_EMPTY(cmds)) {
@ -661,12 +699,14 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
* command list. * command list.
*/ */
TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) { 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) if (alias == NULL)
continue; continue;
line = cmd->line; 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; pi->line = line;
cmds2 = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause); 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); cmd_parse_free_command(cmd);
continue; 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]); cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]);
after = cmd; after = cmd;
@ -707,7 +747,8 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
*/ */
result = cmd_list_new(); result = cmd_list_new();
TAILQ_FOREACH(cmd, cmds, entry) { 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__); cmd_log_argv(cmd->argc, cmd->argv, __func__);
if (cmdlist == NULL || if (cmdlist == NULL ||
@ -721,7 +762,6 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
} }
line = cmd->line; line = cmd->line;
cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name);
add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause); add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause);
if (add == NULL) { if (add == NULL) {
cmd_list_free(result); cmd_list_free(result);
@ -921,11 +961,10 @@ cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
i); i);
cmd = xcalloc(1, sizeof *cmd); cmd = xcalloc(1, sizeof *cmd);
cmd->name = xstrdup(new_argv[0]);
cmd->line = pi->line; cmd->line = pi->line;
cmd->argc = new_argc - 1; cmd->argc = new_argc;
cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1); cmd->argv = cmd_copy_argv(new_argc, new_argv);
TAILQ_INSERT_TAIL(cmds, cmd, entry); TAILQ_INSERT_TAIL(cmds, cmd, entry);
} }
@ -941,11 +980,10 @@ cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
last); last);
cmd = xcalloc(1, sizeof *cmd); cmd = xcalloc(1, sizeof *cmd);
cmd->name = xstrdup(new_argv[0]);
cmd->line = pi->line; cmd->line = pi->line;
cmd->argc = new_argc - 1; cmd->argc = new_argc;
cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1); cmd->argv = cmd_copy_argv(new_argc, new_argv);
TAILQ_INSERT_TAIL(cmds, cmd, entry); TAILQ_INSERT_TAIL(cmds, cmd, entry);
} }
@ -1123,11 +1161,11 @@ yylex(void)
return ('\n'); return ('\n');
} }
if (ch == ';') { if (ch == ';' || ch == '{' || ch == '}') {
/* /*
* A semicolon is itself. * A semicolon or { or } is itself.
*/ */
return (';'); return (ch);
} }
if (ch == '#') { if (ch == '#') {
@ -1442,119 +1480,6 @@ yylex_token_tilde(char **buf, size_t *len)
return (1); 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 * static char *
yylex_token(int ch) yylex_token(int ch)
{ {
@ -1580,7 +1505,8 @@ yylex_token(int ch)
} }
/* Whitespace or ; ends a token unless inside quotes. */ /* Whitespace or ; ends a token unless inside quotes. */
if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE) if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') &&
state == NONE)
break; break;
/* /*
@ -1601,11 +1527,6 @@ yylex_token(int ch)
goto error; goto error;
goto skip; goto skip;
} }
if (ch == '{' && state == NONE) {
if (!yylex_token_brace(&buf, &len))
goto error;
goto skip;
}
if (ch == '}' && state == NONE) if (ch == '}' && state == NONE)
goto error; /* unmatched (matched ones were handled) */ goto error; /* unmatched (matched ones were handled) */

16
cmd.c
View File

@ -357,25 +357,27 @@ cmd_free_argv(int argc, char **argv)
char * char *
cmd_stringify_argv(int argc, char **argv) cmd_stringify_argv(int argc, char **argv)
{ {
char *buf; char *buf = NULL, *s;
size_t len = 0;
int i; int i;
size_t len;
if (argc == 0) if (argc == 0)
return (xstrdup("")); return (xstrdup(""));
len = 0;
buf = NULL;
for (i = 0; i < argc; i++) { 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); buf = xrealloc(buf, len);
if (i == 0) if (i == 0)
*buf = '\0'; *buf = '\0';
else else
strlcat(buf, " ", len); strlcat(buf, " ", len);
strlcat(buf, argv[i], len); strlcat(buf, s, len);
free(s);
} }
return (buf); return (buf);
} }

19
tmux.1
View File

@ -552,17 +552,14 @@ is removed) and are not treated as having any special meaning - so for example
variable. variable.
.El .El
.Pp .Pp
Braces are similar to single quotes in that the text inside is taken literally Braces are parsed as a configuration file (so conditions such as
without any replacements but this also includes line continuation. .Ql %if
Braces can span multiple lines in which case a literal newline is included in the are processed) and then converted into a string.
string. They are designed to avoid the need for additional escaping when passing a
They are designed to avoid the need for additional escaping when passing a group group of
of
.Nm .Nm
or shell commands as an argument (for example to commands as an argument (for example to
.Ic if-shell .Ic if-shell ) .
or
.Ic pipe-pane ) .
These two examples produce an identical command - note that no escaping is These two examples produce an identical command - note that no escaping is
needed when using {}: needed when using {}:
.Bd -literal -offset indent .Bd -literal -offset indent
@ -570,7 +567,7 @@ if-shell true {
display -p 'brace-dollar-foo: }$foo' 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 .Ed
.Pp .Pp
Braces may be enclosed inside braces, for example: Braces may be enclosed inside braces, for example: