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