Annotation of src/usr.bin/tmux/cmd-parse.y, Revision 1.26
1.26 ! nicm 1: /* $OpenBSD: cmd-parse.y,v 1.25 2020/04/13 16:19:37 nicm Exp $ */
1.1 nicm 2:
3: /*
4: * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15: * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16: * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18:
19: %{
20:
21: #include <sys/types.h>
22:
23: #include <ctype.h>
24: #include <errno.h>
25: #include <pwd.h>
1.12 nicm 26: #include <stdlib.h>
1.1 nicm 27: #include <string.h>
28: #include <unistd.h>
29:
30: #include "tmux.h"
31:
32: static int yylex(void);
33: static int yyparse(void);
34: static int printflike(1,2) yyerror(const char *, ...);
35:
36: static char *yylex_token(int);
37: static char *yylex_format(void);
38:
39: struct cmd_parse_scope {
40: int flag;
41: TAILQ_ENTRY (cmd_parse_scope) entry;
42: };
43:
44: struct cmd_parse_command {
45: char *name;
46: u_int line;
47:
48: int argc;
49: char **argv;
50:
51: TAILQ_ENTRY(cmd_parse_command) entry;
52: };
53: TAILQ_HEAD(cmd_parse_commands, cmd_parse_command);
54:
55: struct cmd_parse_state {
56: FILE *f;
1.5 nicm 57:
58: const char *buf;
59: size_t len;
60: size_t off;
61:
1.15 nicm 62: int condition;
1.8 nicm 63: int eol;
1.1 nicm 64: int eof;
65: struct cmd_parse_input *input;
66: u_int escapes;
67:
68: char *error;
1.13 nicm 69: struct cmd_parse_commands *commands;
1.1 nicm 70:
71: struct cmd_parse_scope *scope;
72: TAILQ_HEAD(, cmd_parse_scope) stack;
73: };
74: static struct cmd_parse_state parse_state;
75:
76: static char *cmd_parse_get_error(const char *, u_int, const char *);
77: static void cmd_parse_free_command(struct cmd_parse_command *);
1.13 nicm 78: static struct cmd_parse_commands *cmd_parse_new_commands(void);
1.1 nicm 79: static void cmd_parse_free_commands(struct cmd_parse_commands *);
1.14 nicm 80: static void cmd_parse_print_commands(struct cmd_parse_input *, u_int,
81: struct cmd_list *);
1.1 nicm 82:
83: %}
84:
85: %union
86: {
87: char *token;
88: struct {
89: int argc;
90: char **argv;
91: } arguments;
92: int flag;
93: struct {
94: int flag;
1.13 nicm 95: struct cmd_parse_commands *commands;
1.1 nicm 96: } elif;
1.13 nicm 97: struct cmd_parse_commands *commands;
1.1 nicm 98: struct cmd_parse_command *command;
99: }
100:
101: %token ERROR
1.24 nicm 102: %token HIDDEN
1.1 nicm 103: %token IF
104: %token ELSE
105: %token ELIF
106: %token ENDIF
107: %token <token> FORMAT TOKEN EQUALS
108:
1.15 nicm 109: %type <token> argument expanded format
1.1 nicm 110: %type <arguments> arguments
111: %type <flag> if_open if_elif
112: %type <elif> elif elif1
113: %type <commands> statements statement commands condition condition1
114: %type <command> command
115:
116: %%
117:
118: lines : /* empty */
119: | statements
120: {
121: struct cmd_parse_state *ps = &parse_state;
122:
1.13 nicm 123: ps->commands = $1;
1.1 nicm 124: }
125:
126: statements : statement '\n'
127: {
1.13 nicm 128: $$ = $1;
1.1 nicm 129: }
130: | statements statement '\n'
131: {
1.13 nicm 132: $$ = $1;
133: TAILQ_CONCAT($$, $2, entry);
134: free($2);
1.1 nicm 135: }
136:
1.22 nicm 137: statement : /* empty */
138: {
139: $$ = xmalloc (sizeof *$$);
140: TAILQ_INIT($$);
141: }
1.24 nicm 142: | hidden_assignment
143: {
144: $$ = xmalloc (sizeof *$$);
145: TAILQ_INIT($$);
146: }
1.22 nicm 147: | condition
1.1 nicm 148: {
149: struct cmd_parse_state *ps = &parse_state;
150:
151: if (ps->scope == NULL || ps->scope->flag)
1.13 nicm 152: $$ = $1;
153: else {
154: $$ = cmd_parse_new_commands();
155: cmd_parse_free_commands($1);
156: }
1.1 nicm 157: }
158: | commands
159: {
160: struct cmd_parse_state *ps = &parse_state;
161:
162: if (ps->scope == NULL || ps->scope->flag)
1.13 nicm 163: $$ = $1;
164: else {
165: $$ = cmd_parse_new_commands();
166: cmd_parse_free_commands($1);
167: }
1.1 nicm 168: }
169:
1.15 nicm 170: format : FORMAT
171: {
172: $$ = $1;
173: }
174: | TOKEN
175: {
176: $$ = $1;
177: }
178:
179: expanded : format
1.1 nicm 180: {
181: struct cmd_parse_state *ps = &parse_state;
182: struct cmd_parse_input *pi = ps->input;
183: struct format_tree *ft;
184: struct client *c = pi->c;
1.18 nicm 185: struct cmd_find_state *fsp;
186: struct cmd_find_state fs;
1.1 nicm 187: int flags = FORMAT_NOJOBS;
188:
189: if (cmd_find_valid_state(&pi->fs))
1.18 nicm 190: fsp = &pi->fs;
191: else {
192: cmd_find_from_client(&fs, c, 0);
193: fsp = &fs;
194: }
1.1 nicm 195: ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
1.18 nicm 196: format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
1.1 nicm 197:
198: $$ = format_expand(ft, $1);
199: format_free(ft);
200: free($1);
201: }
202:
1.22 nicm 203: optional_assignment : /* empty */
204: | assignment
205:
206: assignment : EQUALS
1.1 nicm 207: {
208: struct cmd_parse_state *ps = &parse_state;
209: int flags = ps->input->flags;
210:
211: if ((~flags & CMD_PARSE_PARSEONLY) &&
212: (ps->scope == NULL || ps->scope->flag))
1.24 nicm 213: environ_put(global_environ, $1, 0);
1.1 nicm 214: free($1);
215: }
216:
1.24 nicm 217: hidden_assignment : HIDDEN EQUALS
218: {
219: struct cmd_parse_state *ps = &parse_state;
220: int flags = ps->input->flags;
221:
222: if ((~flags & CMD_PARSE_PARSEONLY) &&
223: (ps->scope == NULL || ps->scope->flag))
224: environ_put(global_environ, $2, ENVIRON_HIDDEN);
225: free($2);
226: }
227:
1.1 nicm 228: if_open : IF expanded
229: {
230: struct cmd_parse_state *ps = &parse_state;
231: struct cmd_parse_scope *scope;
232:
233: scope = xmalloc(sizeof *scope);
234: $$ = scope->flag = format_true($2);
235: free($2);
236:
237: if (ps->scope != NULL)
238: TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry);
239: ps->scope = scope;
240: }
241:
242: if_else : ELSE
243: {
244: struct cmd_parse_state *ps = &parse_state;
245: struct cmd_parse_scope *scope;
246:
247: scope = xmalloc(sizeof *scope);
248: scope->flag = !ps->scope->flag;
249:
250: free(ps->scope);
251: ps->scope = scope;
252: }
253:
254: if_elif : ELIF expanded
255: {
256: struct cmd_parse_state *ps = &parse_state;
257: struct cmd_parse_scope *scope;
258:
259: scope = xmalloc(sizeof *scope);
260: $$ = scope->flag = format_true($2);
261: free($2);
262:
263: free(ps->scope);
264: ps->scope = scope;
265: }
266:
267: if_close : ENDIF
268: {
269: struct cmd_parse_state *ps = &parse_state;
270:
271: free(ps->scope);
272: ps->scope = TAILQ_FIRST(&ps->stack);
273: if (ps->scope != NULL)
274: TAILQ_REMOVE(&ps->stack, ps->scope, entry);
275: }
276:
277: condition : if_open '\n' statements if_close
278: {
279: if ($1)
1.13 nicm 280: $$ = $3;
281: else {
282: $$ = cmd_parse_new_commands();
283: cmd_parse_free_commands($3);
284: }
1.1 nicm 285: }
286: | if_open '\n' statements if_else '\n' statements if_close
287: {
288: if ($1) {
1.13 nicm 289: $$ = $3;
290: cmd_parse_free_commands($6);
1.1 nicm 291: } else {
1.13 nicm 292: $$ = $6;
293: cmd_parse_free_commands($3);
1.1 nicm 294: }
295: }
296: | if_open '\n' statements elif if_close
297: {
298: if ($1) {
1.13 nicm 299: $$ = $3;
300: cmd_parse_free_commands($4.commands);
1.1 nicm 301: } else if ($4.flag) {
1.13 nicm 302: $$ = $4.commands;
303: cmd_parse_free_commands($3);
1.1 nicm 304: } else {
1.13 nicm 305: $$ = cmd_parse_new_commands();
306: cmd_parse_free_commands($3);
307: cmd_parse_free_commands($4.commands);
1.1 nicm 308: }
309: }
310: | if_open '\n' statements elif if_else '\n' statements if_close
311: {
312: if ($1) {
1.13 nicm 313: $$ = $3;
314: cmd_parse_free_commands($4.commands);
315: cmd_parse_free_commands($7);
1.1 nicm 316: } else if ($4.flag) {
1.13 nicm 317: $$ = $4.commands;
318: cmd_parse_free_commands($3);
319: cmd_parse_free_commands($7);
320: } else {
321: $$ = $7;
322: cmd_parse_free_commands($3);
323: cmd_parse_free_commands($4.commands);
1.1 nicm 324: }
325: }
326:
327: elif : if_elif '\n' statements
328: {
1.13 nicm 329: if ($1) {
330: $$.flag = 1;
331: $$.commands = $3;
332: } else {
333: $$.flag = 0;
334: $$.commands = cmd_parse_new_commands();
335: cmd_parse_free_commands($3);
336: }
1.1 nicm 337: }
338: | if_elif '\n' statements elif
339: {
340: if ($1) {
341: $$.flag = 1;
1.13 nicm 342: $$.commands = $3;
343: cmd_parse_free_commands($4.commands);
344: } else if ($4.flag) {
345: $$.flag = 1;
346: $$.commands = $4.commands;
347: cmd_parse_free_commands($3);
1.1 nicm 348: } else {
1.13 nicm 349: $$.flag = 0;
350: $$.commands = cmd_parse_new_commands();
351: cmd_parse_free_commands($3);
352: cmd_parse_free_commands($4.commands);
1.1 nicm 353: }
354: }
355:
356: commands : command
357: {
358: struct cmd_parse_state *ps = &parse_state;
359:
1.13 nicm 360: $$ = cmd_parse_new_commands();
1.23 nicm 361: if ($1->name != NULL &&
362: (ps->scope == NULL || ps->scope->flag))
1.13 nicm 363: TAILQ_INSERT_TAIL($$, $1, entry);
1.1 nicm 364: else
365: cmd_parse_free_command($1);
366: }
367: | commands ';'
368: {
1.13 nicm 369: $$ = $1;
1.1 nicm 370: }
371: | commands ';' condition1
372: {
1.13 nicm 373: $$ = $1;
374: TAILQ_CONCAT($$, $3, entry);
375: free($3);
1.1 nicm 376: }
377: | commands ';' command
378: {
379: struct cmd_parse_state *ps = &parse_state;
380:
1.23 nicm 381: if ($3->name != NULL &&
382: (ps->scope == NULL || ps->scope->flag)) {
1.13 nicm 383: $$ = $1;
384: TAILQ_INSERT_TAIL($$, $3, entry);
1.1 nicm 385: } else {
1.13 nicm 386: $$ = cmd_parse_new_commands();
387: cmd_parse_free_commands($1);
1.1 nicm 388: cmd_parse_free_command($3);
389: }
390: }
391: | condition1
392: {
1.13 nicm 393: $$ = $1;
1.1 nicm 394: }
395:
1.22 nicm 396: command : assignment
397: {
398: struct cmd_parse_state *ps = &parse_state;
399:
400: $$ = xcalloc(1, sizeof *$$);
401: $$->name = NULL;
402: $$->line = ps->input->line;
403: }
404: | optional_assignment TOKEN
1.1 nicm 405: {
406: struct cmd_parse_state *ps = &parse_state;
407:
408: $$ = xcalloc(1, sizeof *$$);
409: $$->name = $2;
1.10 nicm 410: $$->line = ps->input->line;
1.1 nicm 411:
412: }
1.22 nicm 413: | optional_assignment TOKEN arguments
1.1 nicm 414: {
415: struct cmd_parse_state *ps = &parse_state;
416:
417: $$ = xcalloc(1, sizeof *$$);
418: $$->name = $2;
1.10 nicm 419: $$->line = ps->input->line;
1.1 nicm 420:
421: $$->argc = $3.argc;
422: $$->argv = $3.argv;
423: }
424:
425: condition1 : if_open commands if_close
426: {
427: if ($1)
1.13 nicm 428: $$ = $2;
429: else {
430: $$ = cmd_parse_new_commands();
431: cmd_parse_free_commands($2);
432: }
1.1 nicm 433: }
434: | if_open commands if_else commands if_close
435: {
436: if ($1) {
1.13 nicm 437: $$ = $2;
438: cmd_parse_free_commands($4);
1.1 nicm 439: } else {
1.13 nicm 440: $$ = $4;
441: cmd_parse_free_commands($2);
1.1 nicm 442: }
443: }
444: | if_open commands elif1 if_close
445: {
446: if ($1) {
1.13 nicm 447: $$ = $2;
448: cmd_parse_free_commands($3.commands);
1.1 nicm 449: } else if ($3.flag) {
1.13 nicm 450: $$ = $3.commands;
451: cmd_parse_free_commands($2);
1.1 nicm 452: } else {
1.13 nicm 453: $$ = cmd_parse_new_commands();
454: cmd_parse_free_commands($2);
455: cmd_parse_free_commands($3.commands);
1.1 nicm 456: }
457: }
458: | if_open commands elif1 if_else commands if_close
459: {
460: if ($1) {
1.13 nicm 461: $$ = $2;
462: cmd_parse_free_commands($3.commands);
463: cmd_parse_free_commands($5);
1.1 nicm 464: } else if ($3.flag) {
1.13 nicm 465: $$ = $3.commands;
466: cmd_parse_free_commands($2);
467: cmd_parse_free_commands($5);
468: } else {
469: $$ = $5;
470: cmd_parse_free_commands($2);
471: cmd_parse_free_commands($3.commands);
1.1 nicm 472: }
473: }
474:
475: elif1 : if_elif commands
476: {
1.13 nicm 477: if ($1) {
478: $$.flag = 1;
479: $$.commands = $2;
480: } else {
481: $$.flag = 0;
482: $$.commands = cmd_parse_new_commands();
483: cmd_parse_free_commands($2);
484: }
1.1 nicm 485: }
486: | if_elif commands elif1
487: {
488: if ($1) {
489: $$.flag = 1;
1.13 nicm 490: $$.commands = $2;
491: cmd_parse_free_commands($3.commands);
492: } else if ($3.flag) {
493: $$.flag = 1;
494: $$.commands = $3.commands;
495: cmd_parse_free_commands($2);
1.1 nicm 496: } else {
1.13 nicm 497: $$.flag = 0;
498: $$.commands = cmd_parse_new_commands();
499: cmd_parse_free_commands($2);
500: cmd_parse_free_commands($3.commands);
1.1 nicm 501: }
502: }
503:
504: arguments : argument
505: {
506: $$.argc = 1;
507: $$.argv = xreallocarray(NULL, 1, sizeof *$$.argv);
508:
509: $$.argv[0] = $1;
510: }
511: | argument arguments
512: {
513: cmd_prepend_argv(&$2.argc, &$2.argv, $1);
514: free($1);
515: $$ = $2;
516: }
517:
518: argument : TOKEN
519: {
520: $$ = $1;
521: }
522: | EQUALS
523: {
524: $$ = $1;
525: }
526:
527: %%
528:
529: static char *
530: cmd_parse_get_error(const char *file, u_int line, const char *error)
531: {
532: char *s;
533:
534: if (file == NULL)
535: s = xstrdup(error);
536: else
537: xasprintf (&s, "%s:%u: %s", file, line, error);
538: return (s);
539: }
540:
541: static void
1.14 nicm 542: cmd_parse_print_commands(struct cmd_parse_input *pi, u_int line,
543: struct cmd_list *cmdlist)
544: {
545: char *s;
546:
547: if (pi->item != NULL && (pi->flags & CMD_PARSE_VERBOSE)) {
548: s = cmd_list_print(cmdlist, 0);
1.16 nicm 549: if (pi->file != NULL)
550: cmdq_print(pi->item, "%s:%u: %s", pi->file, line, s);
551: else
552: cmdq_print(pi->item, "%u: %s", line, s);
1.14 nicm 553: free(s);
554: }
555: }
556:
557: static void
1.1 nicm 558: cmd_parse_free_command(struct cmd_parse_command *cmd)
559: {
560: free(cmd->name);
561: cmd_free_argv(cmd->argc, cmd->argv);
562: free(cmd);
563: }
564:
1.13 nicm 565: static struct cmd_parse_commands *
566: cmd_parse_new_commands(void)
567: {
568: struct cmd_parse_commands *cmds;
569:
570: cmds = xmalloc(sizeof *cmds);
571: TAILQ_INIT (cmds);
572: return (cmds);
573: }
574:
1.1 nicm 575: static void
576: cmd_parse_free_commands(struct cmd_parse_commands *cmds)
577: {
578: struct cmd_parse_command *cmd, *cmd1;
579:
580: TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) {
581: TAILQ_REMOVE(cmds, cmd, entry);
582: cmd_parse_free_command(cmd);
583: }
1.13 nicm 584: free(cmds);
1.1 nicm 585: }
586:
587: static struct cmd_parse_commands *
1.5 nicm 588: cmd_parse_run_parser(char **cause)
1.1 nicm 589: {
1.13 nicm 590: struct cmd_parse_state *ps = &parse_state;
591: struct cmd_parse_scope *scope, *scope1;
592: int retval;
1.1 nicm 593:
1.13 nicm 594: ps->commands = NULL;
1.1 nicm 595: TAILQ_INIT(&ps->stack);
596:
597: retval = yyparse();
598: TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) {
599: TAILQ_REMOVE(&ps->stack, scope, entry);
600: free(scope);
601: }
602: if (retval != 0) {
603: *cause = ps->error;
604: return (NULL);
605: }
606:
1.13 nicm 607: if (ps->commands == NULL)
608: return (cmd_parse_new_commands());
609: return (ps->commands);
1.1 nicm 610: }
611:
1.5 nicm 612: static struct cmd_parse_commands *
613: cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause)
614: {
615: struct cmd_parse_state *ps = &parse_state;
616:
617: memset(ps, 0, sizeof *ps);
618: ps->input = pi;
619: ps->f = f;
620: return (cmd_parse_run_parser(cause));
621: }
622:
623: static struct cmd_parse_commands *
624: cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi,
625: char **cause)
626: {
627: struct cmd_parse_state *ps = &parse_state;
628:
629: memset(ps, 0, sizeof *ps);
630: ps->input = pi;
631: ps->buf = buf;
632: ps->len = len;
633: return (cmd_parse_run_parser(cause));
634: }
635:
1.4 nicm 636: static struct cmd_parse_result *
637: cmd_parse_build_commands(struct cmd_parse_commands *cmds,
638: struct cmd_parse_input *pi)
1.1 nicm 639: {
640: static struct cmd_parse_result pr;
1.4 nicm 641: struct cmd_parse_commands *cmds2;
1.1 nicm 642: struct cmd_parse_command *cmd, *cmd2, *next, *next2, *after;
643: u_int line = UINT_MAX;
644: int i;
645: struct cmd_list *cmdlist = NULL, *result;
646: struct cmd *add;
647: char *alias, *cause, *s;
648:
1.4 nicm 649: /* Check for an empty list. */
1.1 nicm 650: if (TAILQ_EMPTY(cmds)) {
1.13 nicm 651: cmd_parse_free_commands(cmds);
1.1 nicm 652: pr.status = CMD_PARSE_EMPTY;
653: return (&pr);
654: }
655:
656: /*
657: * Walk the commands and expand any aliases. Each alias is parsed
658: * individually to a new command list, any trailing arguments appended
659: * to the last command, and all commands inserted into the original
660: * command list.
661: */
662: TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) {
663: alias = cmd_get_alias(cmd->name);
664: if (alias == NULL)
665: continue;
666:
667: line = cmd->line;
668: log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias);
669:
670: pi->line = line;
1.5 nicm 671: cmds2 = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause);
1.1 nicm 672: free(alias);
673: if (cmds2 == NULL) {
674: pr.status = CMD_PARSE_ERROR;
675: pr.error = cause;
676: goto out;
677: }
678:
679: cmd2 = TAILQ_LAST(cmds2, cmd_parse_commands);
680: if (cmd2 == NULL) {
681: TAILQ_REMOVE(cmds, cmd, entry);
682: cmd_parse_free_command(cmd);
683: continue;
684: }
685: for (i = 0; i < cmd->argc; i++)
686: cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]);
687:
688: after = cmd;
689: TAILQ_FOREACH_SAFE(cmd2, cmds2, entry, next2) {
690: cmd2->line = line;
691: TAILQ_REMOVE(cmds2, cmd2, entry);
692: TAILQ_INSERT_AFTER(cmds, after, cmd2, entry);
693: after = cmd2;
694: }
695: cmd_parse_free_commands(cmds2);
696:
697: TAILQ_REMOVE(cmds, cmd, entry);
698: cmd_parse_free_command(cmd);
699: }
700:
701: /*
702: * Parse each command into a command list. Create a new command list
1.25 nicm 703: * for each line (unless the flag is set) so they get a new group (so
704: * the queue knows which ones to remove if a command fails when
705: * executed).
1.1 nicm 706: */
707: result = cmd_list_new();
708: TAILQ_FOREACH(cmd, cmds, entry) {
709: log_debug("%s: %u %s", __func__, cmd->line, cmd->name);
710: cmd_log_argv(cmd->argc, cmd->argv, __func__);
711:
1.25 nicm 712: if (cmdlist == NULL ||
713: ((~pi->flags & CMD_PARSE_ONEGROUP) && cmd->line != line)) {
1.1 nicm 714: if (cmdlist != NULL) {
1.14 nicm 715: cmd_parse_print_commands(pi, line, cmdlist);
1.1 nicm 716: cmd_list_move(result, cmdlist);
717: cmd_list_free(cmdlist);
718: }
719: cmdlist = cmd_list_new();
720: }
721: line = cmd->line;
722:
723: cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name);
724: add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause);
725: if (add == NULL) {
726: cmd_list_free(result);
727: pr.status = CMD_PARSE_ERROR;
728: pr.error = cmd_parse_get_error(pi->file, line, cause);
729: free(cause);
1.20 nicm 730: cmd_list_free(cmdlist);
1.1 nicm 731: goto out;
732: }
733: cmd_list_append(cmdlist, add);
734: }
735: if (cmdlist != NULL) {
1.14 nicm 736: cmd_parse_print_commands(pi, line, cmdlist);
1.1 nicm 737: cmd_list_move(result, cmdlist);
738: cmd_list_free(cmdlist);
739: }
740:
1.2 nicm 741: s = cmd_list_print(result, 0);
1.1 nicm 742: log_debug("%s: %s", __func__, s);
743: free(s);
744:
745: pr.status = CMD_PARSE_SUCCESS;
746: pr.cmdlist = result;
747:
748: out:
749: cmd_parse_free_commands(cmds);
750:
751: return (&pr);
752: }
753:
754: struct cmd_parse_result *
1.4 nicm 755: cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
756: {
757: static struct cmd_parse_result pr;
758: struct cmd_parse_input input;
759: struct cmd_parse_commands *cmds;
760: char *cause;
761:
762: if (pi == NULL) {
763: memset(&input, 0, sizeof input);
764: pi = &input;
765: }
766: memset(&pr, 0, sizeof pr);
767:
1.5 nicm 768: cmds = cmd_parse_do_file(f, pi, &cause);
1.4 nicm 769: if (cmds == NULL) {
770: pr.status = CMD_PARSE_ERROR;
771: pr.error = cause;
772: return (&pr);
773: }
774: return (cmd_parse_build_commands(cmds, pi));
775: }
776:
777: struct cmd_parse_result *
1.1 nicm 778: cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
779: {
1.25 nicm 780: struct cmd_parse_input input;
781:
782: if (pi == NULL) {
783: memset(&input, 0, sizeof input);
784: pi = &input;
785: }
786:
787: /*
788: * When parsing a string, put commands in one group even if there are
789: * multiple lines. This means { a \n b } is identical to "a ; b" when
790: * given as an argument to another command.
791: */
792: pi->flags |= CMD_PARSE_ONEGROUP;
1.21 nicm 793: return (cmd_parse_from_buffer(s, strlen(s), pi));
1.26 ! nicm 794: }
! 795:
! 796: enum cmd_parse_status
! 797: cmd_parse_and_insert(const char *s, struct cmd_parse_input *pi,
! 798: struct cmdq_item *after, struct cmdq_state *state, char **error)
! 799: {
! 800: struct cmd_parse_result *pr;
! 801: struct cmdq_item *item;
! 802:
! 803: pr = cmd_parse_from_string(s, pi);
! 804: switch (pr->status) {
! 805: case CMD_PARSE_EMPTY:
! 806: break;
! 807: case CMD_PARSE_ERROR:
! 808: if (error != NULL)
! 809: *error = pr->error;
! 810: else
! 811: free(pr->error);
! 812: break;
! 813: case CMD_PARSE_SUCCESS:
! 814: item = cmdq_get_command(pr->cmdlist, state);
! 815: cmdq_insert_after(after, item);
! 816: cmd_list_free(pr->cmdlist);
! 817: break;
! 818: }
! 819: return (pr->status);
! 820: }
! 821:
! 822: enum cmd_parse_status
! 823: cmd_parse_and_append(const char *s, struct cmd_parse_input *pi,
! 824: struct client *c, struct cmdq_state *state, char **error)
! 825: {
! 826: struct cmd_parse_result *pr;
! 827: struct cmdq_item *item;
! 828:
! 829: pr = cmd_parse_from_string(s, pi);
! 830: switch (pr->status) {
! 831: case CMD_PARSE_EMPTY:
! 832: break;
! 833: case CMD_PARSE_ERROR:
! 834: if (error != NULL)
! 835: *error = pr->error;
! 836: else
! 837: free(pr->error);
! 838: break;
! 839: case CMD_PARSE_SUCCESS:
! 840: item = cmdq_get_command(pr->cmdlist, state);
! 841: cmdq_append(c, item);
! 842: cmd_list_free(pr->cmdlist);
! 843: break;
! 844: }
! 845: return (pr->status);
1.21 nicm 846: }
847:
848: struct cmd_parse_result *
849: cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi)
850: {
1.1 nicm 851: static struct cmd_parse_result pr;
1.4 nicm 852: struct cmd_parse_input input;
1.5 nicm 853: struct cmd_parse_commands *cmds;
854: char *cause;
1.1 nicm 855:
1.4 nicm 856: if (pi == NULL) {
857: memset(&input, 0, sizeof input);
858: pi = &input;
859: }
860: memset(&pr, 0, sizeof pr);
861:
1.21 nicm 862: if (len == 0) {
1.1 nicm 863: pr.status = CMD_PARSE_EMPTY;
864: pr.cmdlist = NULL;
865: pr.error = NULL;
866: return (&pr);
867: }
868:
1.21 nicm 869: cmds = cmd_parse_do_buffer(buf, len, pi, &cause);
1.5 nicm 870: if (cmds == NULL) {
1.1 nicm 871: pr.status = CMD_PARSE_ERROR;
1.5 nicm 872: pr.error = cause;
873: return (&pr);
1.1 nicm 874: }
1.5 nicm 875: return (cmd_parse_build_commands(cmds, pi));
1.4 nicm 876: }
877:
878: struct cmd_parse_result *
879: cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
880: {
881: struct cmd_parse_input input;
882: struct cmd_parse_commands *cmds;
883: struct cmd_parse_command *cmd;
884: char **copy, **new_argv;
885: size_t size;
886: int i, last, new_argc;
887:
888: /*
889: * The commands are already split up into arguments, so just separate
890: * into a set of commands by ';'.
891: */
892:
893: if (pi == NULL) {
894: memset(&input, 0, sizeof input);
895: pi = &input;
896: }
897: cmd_log_argv(argc, argv, "%s", __func__);
898:
1.13 nicm 899: cmds = cmd_parse_new_commands();
1.4 nicm 900: copy = cmd_copy_argv(argc, argv);
901:
902: last = 0;
903: for (i = 0; i < argc; i++) {
904: size = strlen(copy[i]);
905: if (size == 0 || copy[i][size - 1] != ';')
906: continue;
907: copy[i][--size] = '\0';
908: if (size > 0 && copy[i][size - 1] == '\\') {
909: copy[i][size - 1] = ';';
910: continue;
911: }
912:
913: new_argc = i - last;
914: new_argv = copy + last;
915: if (size != 0)
916: new_argc++;
917:
918: if (new_argc != 0) {
919: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
920: i);
921:
922: cmd = xcalloc(1, sizeof *cmd);
923: cmd->name = xstrdup(new_argv[0]);
924: cmd->line = pi->line;
925:
926: cmd->argc = new_argc - 1;
927: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
928:
929: TAILQ_INSERT_TAIL(cmds, cmd, entry);
930: }
931:
932: last = i + 1;
933: }
934: if (last != argc) {
935: new_argv = copy + last;
936: new_argc = argc - last;
937:
938: if (new_argc != 0) {
939: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
940: last);
941:
942: cmd = xcalloc(1, sizeof *cmd);
943: cmd->name = xstrdup(new_argv[0]);
944: cmd->line = pi->line;
945:
946: cmd->argc = new_argc - 1;
947: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
948:
949: TAILQ_INSERT_TAIL(cmds, cmd, entry);
950: }
951: }
1.13 nicm 952:
953: cmd_free_argv(argc, copy);
1.4 nicm 954: return (cmd_parse_build_commands(cmds, pi));
1.1 nicm 955: }
956:
957: static int printflike(1, 2)
958: yyerror(const char *fmt, ...)
959: {
960: struct cmd_parse_state *ps = &parse_state;
961: struct cmd_parse_input *pi = ps->input;
962: va_list ap;
963: char *error;
964:
965: if (ps->error != NULL)
966: return (0);
967:
968: va_start(ap, fmt);
969: xvasprintf(&error, fmt, ap);
970: va_end(ap);
971:
972: ps->error = cmd_parse_get_error(pi->file, pi->line, error);
973: free(error);
974: return (0);
975: }
976:
977: static int
978: yylex_is_var(char ch, int first)
979: {
980: if (ch == '=')
981: return (0);
982: if (first && isdigit((u_char)ch))
983: return (0);
984: return (isalnum((u_char)ch) || ch == '_');
985: }
986:
987: static void
988: yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
989: {
990: if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
991: fatalx("buffer is too big");
992: *buf = xrealloc(*buf, (*len) + 1 + addlen);
993: memcpy((*buf) + *len, add, addlen);
994: (*len) += addlen;
995: }
996:
997: static void
998: yylex_append1(char **buf, size_t *len, char add)
999: {
1000: yylex_append(buf, len, &add, 1);
1001: }
1002:
1003: static int
1.5 nicm 1004: yylex_getc1(void)
1005: {
1006: struct cmd_parse_state *ps = &parse_state;
1007: int ch;
1008:
1009: if (ps->f != NULL)
1010: ch = getc(ps->f);
1011: else {
1012: if (ps->off == ps->len)
1013: ch = EOF;
1014: else
1015: ch = ps->buf[ps->off++];
1016: }
1017: return (ch);
1018: }
1019:
1020: static void
1021: yylex_ungetc(int ch)
1022: {
1023: struct cmd_parse_state *ps = &parse_state;
1024:
1025: if (ps->f != NULL)
1026: ungetc(ch, ps->f);
1027: else if (ps->off > 0 && ch != EOF)
1028: ps->off--;
1029: }
1030:
1031: static int
1.1 nicm 1032: yylex_getc(void)
1033: {
1034: struct cmd_parse_state *ps = &parse_state;
1035: int ch;
1036:
1037: if (ps->escapes != 0) {
1038: ps->escapes--;
1039: return ('\\');
1040: }
1041: for (;;) {
1.5 nicm 1042: ch = yylex_getc1();
1.1 nicm 1043: if (ch == '\\') {
1044: ps->escapes++;
1045: continue;
1046: }
1047: if (ch == '\n' && (ps->escapes % 2) == 1) {
1048: ps->input->line++;
1049: ps->escapes--;
1050: continue;
1051: }
1052:
1053: if (ps->escapes != 0) {
1.5 nicm 1054: yylex_ungetc(ch);
1.1 nicm 1055: ps->escapes--;
1056: return ('\\');
1057: }
1058: return (ch);
1059: }
1060: }
1061:
1062: static char *
1063: yylex_get_word(int ch)
1064: {
1.5 nicm 1065: char *buf;
1066: size_t len;
1.1 nicm 1067:
1068: len = 0;
1069: buf = xmalloc(1);
1070:
1071: do
1072: yylex_append1(&buf, &len, ch);
1073: while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
1.5 nicm 1074: yylex_ungetc(ch);
1.1 nicm 1075:
1076: buf[len] = '\0';
1077: log_debug("%s: %s", __func__, buf);
1078: return (buf);
1079: }
1080:
1081: static int
1082: yylex(void)
1083: {
1084: struct cmd_parse_state *ps = &parse_state;
1085: char *token, *cp;
1.15 nicm 1086: int ch, next, condition;
1.1 nicm 1087:
1.8 nicm 1088: if (ps->eol)
1089: ps->input->line++;
1090: ps->eol = 0;
1091:
1.15 nicm 1092: condition = ps->condition;
1093: ps->condition = 0;
1094:
1.1 nicm 1095: for (;;) {
1096: ch = yylex_getc();
1097:
1098: if (ch == EOF) {
1099: /*
1100: * Ensure every file or string is terminated by a
1101: * newline. This keeps the parser simpler and avoids
1102: * having to add a newline to each string.
1103: */
1104: if (ps->eof)
1105: break;
1106: ps->eof = 1;
1107: return ('\n');
1108: }
1109:
1110: if (ch == ' ' || ch == '\t') {
1111: /*
1112: * Ignore whitespace.
1113: */
1114: continue;
1115: }
1116:
1117: if (ch == '\n') {
1118: /*
1119: * End of line. Update the line number.
1120: */
1.8 nicm 1121: ps->eol = 1;
1.1 nicm 1122: return ('\n');
1123: }
1124:
1125: if (ch == ';') {
1126: /*
1127: * A semicolon is itself.
1128: */
1129: return (';');
1130: }
1131:
1132: if (ch == '#') {
1133: /*
1.15 nicm 1134: * #{ after a condition opens a format; anything else
1135: * is a comment, ignore up to the end of the line.
1.1 nicm 1136: */
1137: next = yylex_getc();
1.15 nicm 1138: if (condition && next == '{') {
1.1 nicm 1139: yylval.token = yylex_format();
1140: if (yylval.token == NULL)
1141: return (ERROR);
1142: return (FORMAT);
1143: }
1144: while (next != '\n' && next != EOF)
1145: next = yylex_getc();
1146: if (next == '\n') {
1147: ps->input->line++;
1148: return ('\n');
1149: }
1150: continue;
1151: }
1152:
1153: if (ch == '%') {
1154: /*
1.11 nicm 1155: * % is a condition unless it is all % or all numbers,
1156: * then it is a token.
1.1 nicm 1157: */
1158: yylval.token = yylex_get_word('%');
1.11 nicm 1159: for (cp = yylval.token; *cp != '\0'; cp++) {
1160: if (*cp != '%' && !isdigit((u_char)*cp))
1161: break;
1162: }
1163: if (*cp == '\0')
1.1 nicm 1164: return (TOKEN);
1.15 nicm 1165: ps->condition = 1;
1.24 nicm 1166: if (strcmp(yylval.token, "%hidden") == 0) {
1167: free(yylval.token);
1168: return (HIDDEN);
1169: }
1.1 nicm 1170: if (strcmp(yylval.token, "%if") == 0) {
1171: free(yylval.token);
1172: return (IF);
1173: }
1174: if (strcmp(yylval.token, "%else") == 0) {
1175: free(yylval.token);
1176: return (ELSE);
1177: }
1178: if (strcmp(yylval.token, "%elif") == 0) {
1179: free(yylval.token);
1180: return (ELIF);
1181: }
1182: if (strcmp(yylval.token, "%endif") == 0) {
1183: free(yylval.token);
1184: return (ENDIF);
1185: }
1186: free(yylval.token);
1187: return (ERROR);
1188: }
1189:
1190: /*
1191: * Otherwise this is a token.
1192: */
1193: token = yylex_token(ch);
1194: if (token == NULL)
1195: return (ERROR);
1196: yylval.token = token;
1197:
1198: if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
1199: for (cp = token + 1; *cp != '='; cp++) {
1200: if (!yylex_is_var(*cp, 0))
1201: break;
1202: }
1203: if (*cp == '=')
1204: return (EQUALS);
1205: }
1206: return (TOKEN);
1207: }
1208: return (0);
1209: }
1210:
1211: static char *
1212: yylex_format(void)
1213: {
1214: char *buf;
1215: size_t len;
1216: int ch, brackets = 1;
1217:
1218: len = 0;
1219: buf = xmalloc(1);
1220:
1221: yylex_append(&buf, &len, "#{", 2);
1222: for (;;) {
1223: if ((ch = yylex_getc()) == EOF || ch == '\n')
1224: goto error;
1225: if (ch == '#') {
1226: if ((ch = yylex_getc()) == EOF || ch == '\n')
1227: goto error;
1228: if (ch == '{')
1229: brackets++;
1230: yylex_append1(&buf, &len, '#');
1231: } else if (ch == '}') {
1232: if (brackets != 0 && --brackets == 0) {
1233: yylex_append1(&buf, &len, ch);
1234: break;
1235: }
1236: }
1237: yylex_append1(&buf, &len, ch);
1238: }
1239: if (brackets != 0)
1240: goto error;
1241:
1242: buf[len] = '\0';
1243: log_debug("%s: %s", __func__, buf);
1244: return (buf);
1245:
1246: error:
1247: free(buf);
1248: return (NULL);
1249: }
1250:
1251: static int
1252: yylex_token_escape(char **buf, size_t *len)
1253: {
1.7 nicm 1254: int ch, type, o2, o3;
1.1 nicm 1255: u_int size, i, tmp;
1256: char s[9];
1257: struct utf8_data ud;
1258:
1.7 nicm 1259: ch = yylex_getc();
1260:
1261: if (ch >= '4' && ch <= '7') {
1262: yyerror("invalid octal escape");
1263: return (0);
1264: }
1265: if (ch >= '0' && ch <= '3') {
1266: o2 = yylex_getc();
1267: if (o2 >= '0' && o2 <= '7') {
1268: o3 = yylex_getc();
1269: if (o3 >= '0' && o3 <= '7') {
1270: ch = 64 * (ch - '0') +
1271: 8 * (o2 - '0') +
1272: (o3 - '0');
1273: yylex_append1(buf, len, ch);
1274: return (1);
1275: }
1276: }
1277: yyerror("invalid octal escape");
1278: return (0);
1279: }
1280:
1281: switch (ch) {
1.1 nicm 1282: case EOF:
1283: return (0);
1.9 nicm 1284: case 'a':
1285: ch = '\a';
1286: break;
1287: case 'b':
1288: ch = '\b';
1289: break;
1.1 nicm 1290: case 'e':
1291: ch = '\033';
1.9 nicm 1292: break;
1293: case 'f':
1294: ch = '\f';
1295: break;
1296: case 's':
1297: ch = ' ';
1298: break;
1299: case 'v':
1300: ch = '\v';
1.1 nicm 1301: break;
1302: case 'r':
1303: ch = '\r';
1304: break;
1305: case 'n':
1306: ch = '\n';
1307: break;
1308: case 't':
1309: ch = '\t';
1310: break;
1311: case 'u':
1312: type = 'u';
1313: size = 4;
1314: goto unicode;
1315: case 'U':
1316: type = 'U';
1317: size = 8;
1318: goto unicode;
1319: }
1320:
1321: yylex_append1(buf, len, ch);
1322: return (1);
1323:
1324: unicode:
1325: for (i = 0; i < size; i++) {
1326: ch = yylex_getc();
1327: if (ch == EOF || ch == '\n')
1328: return (0);
1329: if (!isxdigit((u_char)ch)) {
1330: yyerror("invalid \\%c argument", type);
1331: return (0);
1332: }
1333: s[i] = ch;
1334: }
1335: s[i] = '\0';
1336:
1337: if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
1338: (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
1339: yyerror("invalid \\%c argument", type);
1340: return (0);
1341: }
1342: if (utf8_split(tmp, &ud) != UTF8_DONE) {
1343: yyerror("invalid \\%c argument", type);
1344: return (0);
1345: }
1346: yylex_append(buf, len, ud.data, ud.size);
1347: return (1);
1348: }
1349:
1350: static int
1351: yylex_token_variable(char **buf, size_t *len)
1352: {
1353: struct environ_entry *envent;
1354: int ch, brackets = 0;
1.19 nicm 1355: char name[1024];
1.1 nicm 1356: size_t namelen = 0;
1357: const char *value;
1358:
1359: ch = yylex_getc();
1360: if (ch == EOF)
1361: return (0);
1362: if (ch == '{')
1363: brackets = 1;
1364: else {
1365: if (!yylex_is_var(ch, 1)) {
1366: yylex_append1(buf, len, '$');
1.5 nicm 1367: yylex_ungetc(ch);
1.1 nicm 1368: return (1);
1369: }
1370: name[namelen++] = ch;
1371: }
1372:
1373: for (;;) {
1374: ch = yylex_getc();
1375: if (brackets && ch == '}')
1376: break;
1377: if (ch == EOF || !yylex_is_var(ch, 0)) {
1378: if (!brackets) {
1.5 nicm 1379: yylex_ungetc(ch);
1.1 nicm 1380: break;
1381: }
1382: yyerror("invalid environment variable");
1383: return (0);
1384: }
1385: if (namelen == (sizeof name) - 2) {
1386: yyerror("environment variable is too long");
1387: return (0);
1388: }
1389: name[namelen++] = ch;
1390: }
1391: name[namelen] = '\0';
1392:
1393: envent = environ_find(global_environ, name);
1394: if (envent != NULL) {
1395: value = envent->value;
1396: log_debug("%s: %s -> %s", __func__, name, value);
1397: yylex_append(buf, len, value, strlen(value));
1398: }
1399: return (1);
1400: }
1401:
1402: static int
1403: yylex_token_tilde(char **buf, size_t *len)
1404: {
1405: struct environ_entry *envent;
1406: int ch;
1.19 nicm 1407: char name[1024];
1.1 nicm 1408: size_t namelen = 0;
1409: struct passwd *pw;
1410: const char *home = NULL;
1411:
1412: for (;;) {
1413: ch = yylex_getc();
1414: if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
1.5 nicm 1415: yylex_ungetc(ch);
1.1 nicm 1416: break;
1417: }
1418: if (namelen == (sizeof name) - 2) {
1419: yyerror("user name is too long");
1420: return (0);
1421: }
1422: name[namelen++] = ch;
1423: }
1424: name[namelen] = '\0';
1425:
1426: if (*name == '\0') {
1427: envent = environ_find(global_environ, "HOME");
1428: if (envent != NULL && *envent->value != '\0')
1429: home = envent->value;
1430: else if ((pw = getpwuid(getuid())) != NULL)
1431: home = pw->pw_dir;
1432: } else {
1433: if ((pw = getpwnam(name)) != NULL)
1434: home = pw->pw_dir;
1435: }
1436: if (home == NULL)
1437: return (0);
1438:
1439: log_debug("%s: ~%s -> %s", __func__, name, home);
1440: yylex_append(buf, len, home, strlen(home));
1441: return (1);
1442: }
1443:
1.6 nicm 1444: static int
1445: yylex_token_brace(char **buf, size_t *len)
1446: {
1447: struct cmd_parse_state *ps = &parse_state;
1.17 nicm 1448: int ch, lines = 0, nesting = 1, escape = 0;
1449: int quote = '\0', token = 0;
1.6 nicm 1450:
1451: /*
1452: * Extract a string up to the matching unquoted '}', including newlines
1453: * and handling nested braces.
1454: *
1455: * To detect the final and intermediate braces which affect the nesting
1456: * depth, we scan the input as if it was a tmux config file, and ignore
1457: * braces which would be considered quoted, escaped, or in a comment.
1458: *
1.17 nicm 1459: * We update the token state after every character because '#' begins a
1460: * comment only when it begins a token. For simplicity, we treat an
1461: * unquoted directive format as comment.
1462: *
1.6 nicm 1463: * The result is verbatim copy of the input excluding the final brace.
1464: */
1465:
1466: for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) {
1467: yylex_append1(buf, len, ch);
1468: if (ch == '\n')
1469: lines++;
1470:
1471: /*
1472: * If the previous character was a backslash (escape is set),
1473: * escape anything if unquoted or in double quotes, otherwise
1474: * escape only '\n' and '\\'.
1475: */
1476: if (escape &&
1477: (quote == '\0' ||
1478: quote == '"' ||
1479: ch == '\n' ||
1480: ch == '\\')) {
1481: escape = 0;
1.17 nicm 1482: if (ch != '\n')
1483: token = 1;
1.6 nicm 1484: continue;
1485: }
1486:
1487: /*
1488: * The character is not escaped. If it is a backslash, set the
1489: * escape flag.
1490: */
1491: if (ch == '\\') {
1492: escape = 1;
1493: continue;
1494: }
1495: escape = 0;
1496:
1497: /* A newline always resets to unquoted. */
1498: if (ch == '\n') {
1.17 nicm 1499: quote = token = 0;
1.6 nicm 1500: continue;
1501: }
1502:
1503: if (quote) {
1504: /*
1505: * Inside quotes or comment. Check if this is the
1506: * closing quote.
1507: */
1508: if (ch == quote && quote != '#')
1509: quote = 0;
1.17 nicm 1510: token = 1; /* token continues regardless */
1511: } else {
1.6 nicm 1512: /* Not inside quotes or comment. */
1513: switch (ch) {
1514: case '"':
1515: case '\'':
1516: case '#':
1.17 nicm 1517: /* Beginning of quote or maybe comment. */
1518: if (ch != '#' || !token)
1519: quote = ch;
1520: token = 1;
1521: break;
1522: case ' ':
1523: case '\t':
1524: case ';':
1525: /* Delimiter - token resets. */
1526: token = 0;
1.6 nicm 1527: break;
1528: case '{':
1529: nesting++;
1.17 nicm 1530: token = 0; /* new commands set - token resets */
1.6 nicm 1531: break;
1532: case '}':
1533: nesting--;
1.17 nicm 1534: token = 1; /* same as after quotes */
1.6 nicm 1535: if (nesting == 0) {
1536: (*len)--; /* remove closing } */
1537: ps->input->line += lines;
1538: return (1);
1539: }
1540: break;
1.17 nicm 1541: default:
1542: token = 1;
1543: break;
1.6 nicm 1544: }
1545: }
1546: }
1547:
1548: /*
1.17 nicm 1549: * Update line count after error as reporting the opening line is more
1550: * useful than EOF.
1.6 nicm 1551: */
1552: yyerror("unterminated brace string");
1553: ps->input->line += lines;
1554: return (0);
1555: }
1556:
1.1 nicm 1557: static char *
1558: yylex_token(int ch)
1559: {
1560: char *buf;
1561: size_t len;
1562: enum { START,
1563: NONE,
1564: DOUBLE_QUOTES,
1565: SINGLE_QUOTES } state = NONE, last = START;
1566:
1567: len = 0;
1568: buf = xmalloc(1);
1569:
1570: for (;;) {
1571: /*
1572: * EOF or \n are always the end of the token. If inside quotes
1573: * they are an error.
1574: */
1575: if (ch == EOF || ch == '\n') {
1576: if (state != NONE)
1577: goto error;
1578: break;
1579: }
1580:
1581: /* Whitespace or ; ends a token unless inside quotes. */
1582: if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
1583: break;
1584:
1585: /*
1586: * \ ~ and $ are expanded except in single quotes.
1587: */
1588: if (ch == '\\' && state != SINGLE_QUOTES) {
1589: if (!yylex_token_escape(&buf, &len))
1590: goto error;
1591: goto skip;
1592: }
1593: if (ch == '~' && last != state && state != SINGLE_QUOTES) {
1594: if (!yylex_token_tilde(&buf, &len))
1595: goto error;
1596: goto skip;
1597: }
1598: if (ch == '$' && state != SINGLE_QUOTES) {
1599: if (!yylex_token_variable(&buf, &len))
1600: goto error;
1601: goto skip;
1602: }
1.6 nicm 1603: if (ch == '{' && state == NONE) {
1604: if (!yylex_token_brace(&buf, &len))
1605: goto error;
1606: goto skip;
1607: }
1608: if (ch == '}' && state == NONE)
1609: goto error; /* unmatched (matched ones were handled) */
1.1 nicm 1610:
1611: /*
1612: * ' and " starts or end quotes (and is consumed).
1613: */
1614: if (ch == '\'') {
1615: if (state == NONE) {
1616: state = SINGLE_QUOTES;
1617: goto next;
1618: }
1619: if (state == SINGLE_QUOTES) {
1620: state = NONE;
1621: goto next;
1622: }
1623: }
1624: if (ch == '"') {
1625: if (state == NONE) {
1626: state = DOUBLE_QUOTES;
1627: goto next;
1628: }
1629: if (state == DOUBLE_QUOTES) {
1630: state = NONE;
1631: goto next;
1632: }
1633: }
1634:
1635: /*
1636: * Otherwise add the character to the buffer.
1637: */
1638: yylex_append1(&buf, &len, ch);
1639:
1640: skip:
1641: last = state;
1642:
1643: next:
1644: ch = yylex_getc();
1645: }
1.5 nicm 1646: yylex_ungetc(ch);
1.1 nicm 1647:
1648: buf[len] = '\0';
1649: log_debug("%s: %s", __func__, buf);
1650: return (buf);
1651:
1652: error:
1653: free(buf);
1654: return (NULL);
1655: }