Annotation of src/usr.bin/tmux/cmd-parse.y, Revision 1.25
1.25 ! nicm 1: /* $OpenBSD: cmd-parse.y,v 1.24 2020/03/31 17:14:40 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));
794: }
795:
796: struct cmd_parse_result *
797: cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi)
798: {
1.1 nicm 799: static struct cmd_parse_result pr;
1.4 nicm 800: struct cmd_parse_input input;
1.5 nicm 801: struct cmd_parse_commands *cmds;
802: char *cause;
1.1 nicm 803:
1.4 nicm 804: if (pi == NULL) {
805: memset(&input, 0, sizeof input);
806: pi = &input;
807: }
808: memset(&pr, 0, sizeof pr);
809:
1.21 nicm 810: if (len == 0) {
1.1 nicm 811: pr.status = CMD_PARSE_EMPTY;
812: pr.cmdlist = NULL;
813: pr.error = NULL;
814: return (&pr);
815: }
816:
1.21 nicm 817: cmds = cmd_parse_do_buffer(buf, len, pi, &cause);
1.5 nicm 818: if (cmds == NULL) {
1.1 nicm 819: pr.status = CMD_PARSE_ERROR;
1.5 nicm 820: pr.error = cause;
821: return (&pr);
1.1 nicm 822: }
1.5 nicm 823: return (cmd_parse_build_commands(cmds, pi));
1.4 nicm 824: }
825:
826: struct cmd_parse_result *
827: cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
828: {
829: struct cmd_parse_input input;
830: struct cmd_parse_commands *cmds;
831: struct cmd_parse_command *cmd;
832: char **copy, **new_argv;
833: size_t size;
834: int i, last, new_argc;
835:
836: /*
837: * The commands are already split up into arguments, so just separate
838: * into a set of commands by ';'.
839: */
840:
841: if (pi == NULL) {
842: memset(&input, 0, sizeof input);
843: pi = &input;
844: }
845: cmd_log_argv(argc, argv, "%s", __func__);
846:
1.13 nicm 847: cmds = cmd_parse_new_commands();
1.4 nicm 848: copy = cmd_copy_argv(argc, argv);
849:
850: last = 0;
851: for (i = 0; i < argc; i++) {
852: size = strlen(copy[i]);
853: if (size == 0 || copy[i][size - 1] != ';')
854: continue;
855: copy[i][--size] = '\0';
856: if (size > 0 && copy[i][size - 1] == '\\') {
857: copy[i][size - 1] = ';';
858: continue;
859: }
860:
861: new_argc = i - last;
862: new_argv = copy + last;
863: if (size != 0)
864: new_argc++;
865:
866: if (new_argc != 0) {
867: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
868: i);
869:
870: cmd = xcalloc(1, sizeof *cmd);
871: cmd->name = xstrdup(new_argv[0]);
872: cmd->line = pi->line;
873:
874: cmd->argc = new_argc - 1;
875: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
876:
877: TAILQ_INSERT_TAIL(cmds, cmd, entry);
878: }
879:
880: last = i + 1;
881: }
882: if (last != argc) {
883: new_argv = copy + last;
884: new_argc = argc - last;
885:
886: if (new_argc != 0) {
887: cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__,
888: last);
889:
890: cmd = xcalloc(1, sizeof *cmd);
891: cmd->name = xstrdup(new_argv[0]);
892: cmd->line = pi->line;
893:
894: cmd->argc = new_argc - 1;
895: cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1);
896:
897: TAILQ_INSERT_TAIL(cmds, cmd, entry);
898: }
899: }
1.13 nicm 900:
901: cmd_free_argv(argc, copy);
1.4 nicm 902: return (cmd_parse_build_commands(cmds, pi));
1.1 nicm 903: }
904:
905: static int printflike(1, 2)
906: yyerror(const char *fmt, ...)
907: {
908: struct cmd_parse_state *ps = &parse_state;
909: struct cmd_parse_input *pi = ps->input;
910: va_list ap;
911: char *error;
912:
913: if (ps->error != NULL)
914: return (0);
915:
916: va_start(ap, fmt);
917: xvasprintf(&error, fmt, ap);
918: va_end(ap);
919:
920: ps->error = cmd_parse_get_error(pi->file, pi->line, error);
921: free(error);
922: return (0);
923: }
924:
925: static int
926: yylex_is_var(char ch, int first)
927: {
928: if (ch == '=')
929: return (0);
930: if (first && isdigit((u_char)ch))
931: return (0);
932: return (isalnum((u_char)ch) || ch == '_');
933: }
934:
935: static void
936: yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
937: {
938: if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
939: fatalx("buffer is too big");
940: *buf = xrealloc(*buf, (*len) + 1 + addlen);
941: memcpy((*buf) + *len, add, addlen);
942: (*len) += addlen;
943: }
944:
945: static void
946: yylex_append1(char **buf, size_t *len, char add)
947: {
948: yylex_append(buf, len, &add, 1);
949: }
950:
951: static int
1.5 nicm 952: yylex_getc1(void)
953: {
954: struct cmd_parse_state *ps = &parse_state;
955: int ch;
956:
957: if (ps->f != NULL)
958: ch = getc(ps->f);
959: else {
960: if (ps->off == ps->len)
961: ch = EOF;
962: else
963: ch = ps->buf[ps->off++];
964: }
965: return (ch);
966: }
967:
968: static void
969: yylex_ungetc(int ch)
970: {
971: struct cmd_parse_state *ps = &parse_state;
972:
973: if (ps->f != NULL)
974: ungetc(ch, ps->f);
975: else if (ps->off > 0 && ch != EOF)
976: ps->off--;
977: }
978:
979: static int
1.1 nicm 980: yylex_getc(void)
981: {
982: struct cmd_parse_state *ps = &parse_state;
983: int ch;
984:
985: if (ps->escapes != 0) {
986: ps->escapes--;
987: return ('\\');
988: }
989: for (;;) {
1.5 nicm 990: ch = yylex_getc1();
1.1 nicm 991: if (ch == '\\') {
992: ps->escapes++;
993: continue;
994: }
995: if (ch == '\n' && (ps->escapes % 2) == 1) {
996: ps->input->line++;
997: ps->escapes--;
998: continue;
999: }
1000:
1001: if (ps->escapes != 0) {
1.5 nicm 1002: yylex_ungetc(ch);
1.1 nicm 1003: ps->escapes--;
1004: return ('\\');
1005: }
1006: return (ch);
1007: }
1008: }
1009:
1010: static char *
1011: yylex_get_word(int ch)
1012: {
1.5 nicm 1013: char *buf;
1014: size_t len;
1.1 nicm 1015:
1016: len = 0;
1017: buf = xmalloc(1);
1018:
1019: do
1020: yylex_append1(&buf, &len, ch);
1021: while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
1.5 nicm 1022: yylex_ungetc(ch);
1.1 nicm 1023:
1024: buf[len] = '\0';
1025: log_debug("%s: %s", __func__, buf);
1026: return (buf);
1027: }
1028:
1029: static int
1030: yylex(void)
1031: {
1032: struct cmd_parse_state *ps = &parse_state;
1033: char *token, *cp;
1.15 nicm 1034: int ch, next, condition;
1.1 nicm 1035:
1.8 nicm 1036: if (ps->eol)
1037: ps->input->line++;
1038: ps->eol = 0;
1039:
1.15 nicm 1040: condition = ps->condition;
1041: ps->condition = 0;
1042:
1.1 nicm 1043: for (;;) {
1044: ch = yylex_getc();
1045:
1046: if (ch == EOF) {
1047: /*
1048: * Ensure every file or string is terminated by a
1049: * newline. This keeps the parser simpler and avoids
1050: * having to add a newline to each string.
1051: */
1052: if (ps->eof)
1053: break;
1054: ps->eof = 1;
1055: return ('\n');
1056: }
1057:
1058: if (ch == ' ' || ch == '\t') {
1059: /*
1060: * Ignore whitespace.
1061: */
1062: continue;
1063: }
1064:
1065: if (ch == '\n') {
1066: /*
1067: * End of line. Update the line number.
1068: */
1.8 nicm 1069: ps->eol = 1;
1.1 nicm 1070: return ('\n');
1071: }
1072:
1073: if (ch == ';') {
1074: /*
1075: * A semicolon is itself.
1076: */
1077: return (';');
1078: }
1079:
1080: if (ch == '#') {
1081: /*
1.15 nicm 1082: * #{ after a condition opens a format; anything else
1083: * is a comment, ignore up to the end of the line.
1.1 nicm 1084: */
1085: next = yylex_getc();
1.15 nicm 1086: if (condition && next == '{') {
1.1 nicm 1087: yylval.token = yylex_format();
1088: if (yylval.token == NULL)
1089: return (ERROR);
1090: return (FORMAT);
1091: }
1092: while (next != '\n' && next != EOF)
1093: next = yylex_getc();
1094: if (next == '\n') {
1095: ps->input->line++;
1096: return ('\n');
1097: }
1098: continue;
1099: }
1100:
1101: if (ch == '%') {
1102: /*
1.11 nicm 1103: * % is a condition unless it is all % or all numbers,
1104: * then it is a token.
1.1 nicm 1105: */
1106: yylval.token = yylex_get_word('%');
1.11 nicm 1107: for (cp = yylval.token; *cp != '\0'; cp++) {
1108: if (*cp != '%' && !isdigit((u_char)*cp))
1109: break;
1110: }
1111: if (*cp == '\0')
1.1 nicm 1112: return (TOKEN);
1.15 nicm 1113: ps->condition = 1;
1.24 nicm 1114: if (strcmp(yylval.token, "%hidden") == 0) {
1115: free(yylval.token);
1116: return (HIDDEN);
1117: }
1.1 nicm 1118: if (strcmp(yylval.token, "%if") == 0) {
1119: free(yylval.token);
1120: return (IF);
1121: }
1122: if (strcmp(yylval.token, "%else") == 0) {
1123: free(yylval.token);
1124: return (ELSE);
1125: }
1126: if (strcmp(yylval.token, "%elif") == 0) {
1127: free(yylval.token);
1128: return (ELIF);
1129: }
1130: if (strcmp(yylval.token, "%endif") == 0) {
1131: free(yylval.token);
1132: return (ENDIF);
1133: }
1134: free(yylval.token);
1135: return (ERROR);
1136: }
1137:
1138: /*
1139: * Otherwise this is a token.
1140: */
1141: token = yylex_token(ch);
1142: if (token == NULL)
1143: return (ERROR);
1144: yylval.token = token;
1145:
1146: if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
1147: for (cp = token + 1; *cp != '='; cp++) {
1148: if (!yylex_is_var(*cp, 0))
1149: break;
1150: }
1151: if (*cp == '=')
1152: return (EQUALS);
1153: }
1154: return (TOKEN);
1155: }
1156: return (0);
1157: }
1158:
1159: static char *
1160: yylex_format(void)
1161: {
1162: char *buf;
1163: size_t len;
1164: int ch, brackets = 1;
1165:
1166: len = 0;
1167: buf = xmalloc(1);
1168:
1169: yylex_append(&buf, &len, "#{", 2);
1170: for (;;) {
1171: if ((ch = yylex_getc()) == EOF || ch == '\n')
1172: goto error;
1173: if (ch == '#') {
1174: if ((ch = yylex_getc()) == EOF || ch == '\n')
1175: goto error;
1176: if (ch == '{')
1177: brackets++;
1178: yylex_append1(&buf, &len, '#');
1179: } else if (ch == '}') {
1180: if (brackets != 0 && --brackets == 0) {
1181: yylex_append1(&buf, &len, ch);
1182: break;
1183: }
1184: }
1185: yylex_append1(&buf, &len, ch);
1186: }
1187: if (brackets != 0)
1188: goto error;
1189:
1190: buf[len] = '\0';
1191: log_debug("%s: %s", __func__, buf);
1192: return (buf);
1193:
1194: error:
1195: free(buf);
1196: return (NULL);
1197: }
1198:
1199: static int
1200: yylex_token_escape(char **buf, size_t *len)
1201: {
1.7 nicm 1202: int ch, type, o2, o3;
1.1 nicm 1203: u_int size, i, tmp;
1204: char s[9];
1205: struct utf8_data ud;
1206:
1.7 nicm 1207: ch = yylex_getc();
1208:
1209: if (ch >= '4' && ch <= '7') {
1210: yyerror("invalid octal escape");
1211: return (0);
1212: }
1213: if (ch >= '0' && ch <= '3') {
1214: o2 = yylex_getc();
1215: if (o2 >= '0' && o2 <= '7') {
1216: o3 = yylex_getc();
1217: if (o3 >= '0' && o3 <= '7') {
1218: ch = 64 * (ch - '0') +
1219: 8 * (o2 - '0') +
1220: (o3 - '0');
1221: yylex_append1(buf, len, ch);
1222: return (1);
1223: }
1224: }
1225: yyerror("invalid octal escape");
1226: return (0);
1227: }
1228:
1229: switch (ch) {
1.1 nicm 1230: case EOF:
1231: return (0);
1.9 nicm 1232: case 'a':
1233: ch = '\a';
1234: break;
1235: case 'b':
1236: ch = '\b';
1237: break;
1.1 nicm 1238: case 'e':
1239: ch = '\033';
1.9 nicm 1240: break;
1241: case 'f':
1242: ch = '\f';
1243: break;
1244: case 's':
1245: ch = ' ';
1246: break;
1247: case 'v':
1248: ch = '\v';
1.1 nicm 1249: break;
1250: case 'r':
1251: ch = '\r';
1252: break;
1253: case 'n':
1254: ch = '\n';
1255: break;
1256: case 't':
1257: ch = '\t';
1258: break;
1259: case 'u':
1260: type = 'u';
1261: size = 4;
1262: goto unicode;
1263: case 'U':
1264: type = 'U';
1265: size = 8;
1266: goto unicode;
1267: }
1268:
1269: yylex_append1(buf, len, ch);
1270: return (1);
1271:
1272: unicode:
1273: for (i = 0; i < size; i++) {
1274: ch = yylex_getc();
1275: if (ch == EOF || ch == '\n')
1276: return (0);
1277: if (!isxdigit((u_char)ch)) {
1278: yyerror("invalid \\%c argument", type);
1279: return (0);
1280: }
1281: s[i] = ch;
1282: }
1283: s[i] = '\0';
1284:
1285: if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
1286: (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
1287: yyerror("invalid \\%c argument", type);
1288: return (0);
1289: }
1290: if (utf8_split(tmp, &ud) != UTF8_DONE) {
1291: yyerror("invalid \\%c argument", type);
1292: return (0);
1293: }
1294: yylex_append(buf, len, ud.data, ud.size);
1295: return (1);
1296: }
1297:
1298: static int
1299: yylex_token_variable(char **buf, size_t *len)
1300: {
1301: struct environ_entry *envent;
1302: int ch, brackets = 0;
1.19 nicm 1303: char name[1024];
1.1 nicm 1304: size_t namelen = 0;
1305: const char *value;
1306:
1307: ch = yylex_getc();
1308: if (ch == EOF)
1309: return (0);
1310: if (ch == '{')
1311: brackets = 1;
1312: else {
1313: if (!yylex_is_var(ch, 1)) {
1314: yylex_append1(buf, len, '$');
1.5 nicm 1315: yylex_ungetc(ch);
1.1 nicm 1316: return (1);
1317: }
1318: name[namelen++] = ch;
1319: }
1320:
1321: for (;;) {
1322: ch = yylex_getc();
1323: if (brackets && ch == '}')
1324: break;
1325: if (ch == EOF || !yylex_is_var(ch, 0)) {
1326: if (!brackets) {
1.5 nicm 1327: yylex_ungetc(ch);
1.1 nicm 1328: break;
1329: }
1330: yyerror("invalid environment variable");
1331: return (0);
1332: }
1333: if (namelen == (sizeof name) - 2) {
1334: yyerror("environment variable is too long");
1335: return (0);
1336: }
1337: name[namelen++] = ch;
1338: }
1339: name[namelen] = '\0';
1340:
1341: envent = environ_find(global_environ, name);
1342: if (envent != NULL) {
1343: value = envent->value;
1344: log_debug("%s: %s -> %s", __func__, name, value);
1345: yylex_append(buf, len, value, strlen(value));
1346: }
1347: return (1);
1348: }
1349:
1350: static int
1351: yylex_token_tilde(char **buf, size_t *len)
1352: {
1353: struct environ_entry *envent;
1354: int ch;
1.19 nicm 1355: char name[1024];
1.1 nicm 1356: size_t namelen = 0;
1357: struct passwd *pw;
1358: const char *home = NULL;
1359:
1360: for (;;) {
1361: ch = yylex_getc();
1362: if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
1.5 nicm 1363: yylex_ungetc(ch);
1.1 nicm 1364: break;
1365: }
1366: if (namelen == (sizeof name) - 2) {
1367: yyerror("user name is too long");
1368: return (0);
1369: }
1370: name[namelen++] = ch;
1371: }
1372: name[namelen] = '\0';
1373:
1374: if (*name == '\0') {
1375: envent = environ_find(global_environ, "HOME");
1376: if (envent != NULL && *envent->value != '\0')
1377: home = envent->value;
1378: else if ((pw = getpwuid(getuid())) != NULL)
1379: home = pw->pw_dir;
1380: } else {
1381: if ((pw = getpwnam(name)) != NULL)
1382: home = pw->pw_dir;
1383: }
1384: if (home == NULL)
1385: return (0);
1386:
1387: log_debug("%s: ~%s -> %s", __func__, name, home);
1388: yylex_append(buf, len, home, strlen(home));
1389: return (1);
1390: }
1391:
1.6 nicm 1392: static int
1393: yylex_token_brace(char **buf, size_t *len)
1394: {
1395: struct cmd_parse_state *ps = &parse_state;
1.17 nicm 1396: int ch, lines = 0, nesting = 1, escape = 0;
1397: int quote = '\0', token = 0;
1.6 nicm 1398:
1399: /*
1400: * Extract a string up to the matching unquoted '}', including newlines
1401: * and handling nested braces.
1402: *
1403: * To detect the final and intermediate braces which affect the nesting
1404: * depth, we scan the input as if it was a tmux config file, and ignore
1405: * braces which would be considered quoted, escaped, or in a comment.
1406: *
1.17 nicm 1407: * We update the token state after every character because '#' begins a
1408: * comment only when it begins a token. For simplicity, we treat an
1409: * unquoted directive format as comment.
1410: *
1.6 nicm 1411: * The result is verbatim copy of the input excluding the final brace.
1412: */
1413:
1414: for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) {
1415: yylex_append1(buf, len, ch);
1416: if (ch == '\n')
1417: lines++;
1418:
1419: /*
1420: * If the previous character was a backslash (escape is set),
1421: * escape anything if unquoted or in double quotes, otherwise
1422: * escape only '\n' and '\\'.
1423: */
1424: if (escape &&
1425: (quote == '\0' ||
1426: quote == '"' ||
1427: ch == '\n' ||
1428: ch == '\\')) {
1429: escape = 0;
1.17 nicm 1430: if (ch != '\n')
1431: token = 1;
1.6 nicm 1432: continue;
1433: }
1434:
1435: /*
1436: * The character is not escaped. If it is a backslash, set the
1437: * escape flag.
1438: */
1439: if (ch == '\\') {
1440: escape = 1;
1441: continue;
1442: }
1443: escape = 0;
1444:
1445: /* A newline always resets to unquoted. */
1446: if (ch == '\n') {
1.17 nicm 1447: quote = token = 0;
1.6 nicm 1448: continue;
1449: }
1450:
1451: if (quote) {
1452: /*
1453: * Inside quotes or comment. Check if this is the
1454: * closing quote.
1455: */
1456: if (ch == quote && quote != '#')
1457: quote = 0;
1.17 nicm 1458: token = 1; /* token continues regardless */
1459: } else {
1.6 nicm 1460: /* Not inside quotes or comment. */
1461: switch (ch) {
1462: case '"':
1463: case '\'':
1464: case '#':
1.17 nicm 1465: /* Beginning of quote or maybe comment. */
1466: if (ch != '#' || !token)
1467: quote = ch;
1468: token = 1;
1469: break;
1470: case ' ':
1471: case '\t':
1472: case ';':
1473: /* Delimiter - token resets. */
1474: token = 0;
1.6 nicm 1475: break;
1476: case '{':
1477: nesting++;
1.17 nicm 1478: token = 0; /* new commands set - token resets */
1.6 nicm 1479: break;
1480: case '}':
1481: nesting--;
1.17 nicm 1482: token = 1; /* same as after quotes */
1.6 nicm 1483: if (nesting == 0) {
1484: (*len)--; /* remove closing } */
1485: ps->input->line += lines;
1486: return (1);
1487: }
1488: break;
1.17 nicm 1489: default:
1490: token = 1;
1491: break;
1.6 nicm 1492: }
1493: }
1494: }
1495:
1496: /*
1.17 nicm 1497: * Update line count after error as reporting the opening line is more
1498: * useful than EOF.
1.6 nicm 1499: */
1500: yyerror("unterminated brace string");
1501: ps->input->line += lines;
1502: return (0);
1503: }
1504:
1.1 nicm 1505: static char *
1506: yylex_token(int ch)
1507: {
1508: char *buf;
1509: size_t len;
1510: enum { START,
1511: NONE,
1512: DOUBLE_QUOTES,
1513: SINGLE_QUOTES } state = NONE, last = START;
1514:
1515: len = 0;
1516: buf = xmalloc(1);
1517:
1518: for (;;) {
1519: /*
1520: * EOF or \n are always the end of the token. If inside quotes
1521: * they are an error.
1522: */
1523: if (ch == EOF || ch == '\n') {
1524: if (state != NONE)
1525: goto error;
1526: break;
1527: }
1528:
1529: /* Whitespace or ; ends a token unless inside quotes. */
1530: if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
1531: break;
1532:
1533: /*
1534: * \ ~ and $ are expanded except in single quotes.
1535: */
1536: if (ch == '\\' && state != SINGLE_QUOTES) {
1537: if (!yylex_token_escape(&buf, &len))
1538: goto error;
1539: goto skip;
1540: }
1541: if (ch == '~' && last != state && state != SINGLE_QUOTES) {
1542: if (!yylex_token_tilde(&buf, &len))
1543: goto error;
1544: goto skip;
1545: }
1546: if (ch == '$' && state != SINGLE_QUOTES) {
1547: if (!yylex_token_variable(&buf, &len))
1548: goto error;
1549: goto skip;
1550: }
1.6 nicm 1551: if (ch == '{' && state == NONE) {
1552: if (!yylex_token_brace(&buf, &len))
1553: goto error;
1554: goto skip;
1555: }
1556: if (ch == '}' && state == NONE)
1557: goto error; /* unmatched (matched ones were handled) */
1.1 nicm 1558:
1559: /*
1560: * ' and " starts or end quotes (and is consumed).
1561: */
1562: if (ch == '\'') {
1563: if (state == NONE) {
1564: state = SINGLE_QUOTES;
1565: goto next;
1566: }
1567: if (state == SINGLE_QUOTES) {
1568: state = NONE;
1569: goto next;
1570: }
1571: }
1572: if (ch == '"') {
1573: if (state == NONE) {
1574: state = DOUBLE_QUOTES;
1575: goto next;
1576: }
1577: if (state == DOUBLE_QUOTES) {
1578: state = NONE;
1579: goto next;
1580: }
1581: }
1582:
1583: /*
1584: * Otherwise add the character to the buffer.
1585: */
1586: yylex_append1(&buf, &len, ch);
1587:
1588: skip:
1589: last = state;
1590:
1591: next:
1592: ch = yylex_getc();
1593: }
1.5 nicm 1594: yylex_ungetc(ch);
1.1 nicm 1595:
1596: buf[len] = '\0';
1597: log_debug("%s: %s", __func__, buf);
1598: return (buf);
1599:
1600: error:
1601: free(buf);
1602: return (NULL);
1603: }