mirror of
https://github.com/tmux/tmux.git
synced 2025-01-06 07:48:48 +00:00
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:
parent
3f6af4156f
commit
b3782d2dc8
@ -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;
|
||||
|
205
cmd-parse.y
205
cmd-parse.y
@ -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) */
|
||||
|
||||
|
16
cmd.c
16
cmd.c
@ -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);
|
||||
}
|
||||
|
19
tmux.1
19
tmux.1
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user