1 /* $OpenBSD: parse.y,v 1.109 2012/10/14 11:58:23 gilles Exp $ */
2
3 /*
4 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
5 * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
6 * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
7 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
8 * Copyright (c) 2001 Markus Friedl. All rights reserved.
9 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
10 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
11 *
12 * Permission to use, copy, modify, and distribute this software for any
13 * purpose with or without fee is hereby granted, provided that the above
14 * copyright notice and this permission notice appear in all copies.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 */
24
25 %{
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/tree.h>
29 #include <sys/param.h>
30 #include <sys/socket.h>
31 #include <sys/stat.h>
32 #include <sys/ioctl.h>
33
34 #include <ctype.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <event.h>
38 #include <inttypes.h>
39 #include <netdb.h>
40 #include <paths.h>
41 #include <pwd.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <util.h>
47
48 #include "smtpscript.h"
49
50 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
51 static struct file {
52 TAILQ_ENTRY(file) entry;
53 FILE *stream;
54 char *name;
55 int lineno;
56 int errors;
57 } *file, *topfile;
58 struct file *pushfile(const char *, int);
59 int popfile(void);
60 int check_file_secrecy(int, const char *);
61 int yyparse(void);
62 int yylex(void);
63 int kw_cmp(const void *, const void *);
64 int lookup(char *);
65 int lgetc(int);
66 int lungetc(int);
67 int findeol(void);
68 int yyerror(const char *, ...)
69 __attribute__ ((format (printf, 1, 2)));
70
71 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
72 struct sym {
73 TAILQ_ENTRY(sym) entry;
74 int used;
75 int persist;
76 char *nam;
77 char *val;
78 };
79 int symset(const char *, const char *, int);
80 char *symget(const char *);
81
82 void push_op(struct op *);
83 struct op *peek_op(void);
84 struct op *pop_op(void);
85
86 #define MAXDEPTH 50
87
88 static struct op * opstack[MAXDEPTH];
89 static int opstackidx;
90
91 static int errors = 0;
92
93 static struct script *currscript;
94 static struct procedure *currproc;
95
96 int delaytonum(char *);
97
98 typedef struct {
99 union {
100 int64_t number;
101 char *string;
102 struct op *op;
103 } v;
104 int lineno;
105 } YYSTYPE;
106
107 %}
108
109 %token INCLUDE PORT REPEAT RANDOM NOOP
110 %token PROC TESTCASE NAME NO_AUTOCONNECT EXPECT FAIL SKIP
111 %token CALL CONNECT DISCONNECT STARTTLS SLEEP WRITE WRITELN
112 %token SMTP OK TEMPFAIL PERMFAIL HELO
113 %token ERROR ARROW
114 %token <v.string> STRING
115 %token <v.number> NUMBER
116 %type <v.number> quantifier port duration
117 %type <v.op> statement block
118 %%
119
120 grammar : /* empty */
121 | grammar '\n'
122 | grammar include '\n'
123 | grammar varset '\n'
124 | grammar proc '\n'
125 | grammar testcase '\n'
126 | grammar error '\n' { file->errors++; }
127 ;
128
129 include : INCLUDE STRING {
130 struct file *nfile;
131
132 if ((nfile = pushfile($2, 0)) == NULL) {
133 yyerror("failed to include file %s", $2);
134 free($2);
135 YYERROR;
136 }
137 free($2);
138
139 file = nfile;
140 lungetc('\n');
141 }
142 ;
143
144 varset : STRING '=' STRING {
145 if (symset($1, $3, 0) == -1)
146 errx(1, "cannot store variable");
147 free($1);
148 free($3);
149 }
150 ;
151
152 optnl : '\n' optnl
153 |
154 ;
155
156 nl : '\n' optnl
157 ;
158
159 quantifier : /* empty */ { $$ = 1; }
160 | 's' { $$ = 1000; }
161 | 'm' { $$ = 60 * 1000; }
162 | 'h' { $$ = 3600 * 1000; }
163 ;
164
165 duration : NUMBER quantifier {
166 if ($1 < 0) {
167 yyerror("invalid duration: %" PRId64, $1);
168 YYERROR;
169 }
170 $$ = $1 * $2;
171 }
172 ;
173
174 port : PORT STRING {
175 struct servent *servent;
176
177 servent = getservbyname($2, "tcp");
178 if (servent == NULL) {
179 yyerror("port %s is invalid", $2);
180 free($2);
181 YYERROR;
182 }
183 $$ = ntohs(servent->s_port);
184 free($2);
185 }
186 | PORT NUMBER {
187 if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
188 yyerror("invalid port: %" PRId64, $2);
189 YYERROR;
190 }
191 $$ = $2;
192 }
193 | /* empty */ {
194 $$ = 25;
195 }
196 ;
197
198 statement : block
199 | REPEAT NUMBER { push_op(NULL); } statement {
200 pop_op();
201 $$ = op_repeat(peek_op(), $2, $4);
202 }
203 | RANDOM { push_op(NULL); } block {
204 pop_op();
205 $$ = op_random(peek_op(), $3);
206 }
207 | CALL STRING {
208 struct procedure *p;
209 p = procedure_get_by_name(currscript, $2);
210 if (p == NULL) {
211 yyerror("call to undefined proc \"%s\"", $2);
212 file->errors++;
213 } else if (p == currproc) {
214 yyerror("recursive call to proc \"%s\"", $2);
215 file->errors++;
216 } else {
217 $$ = op_call(peek_op(), p);
218 }
219 free($2);
220 }
221 | NOOP {
222 $$ = op_noop(peek_op());
223 }
224 | SLEEP duration {
225 $$ = op_sleep(peek_op(), $2);
226 }
227 | FAIL STRING {
228 $$ = op_fail(peek_op(), $2);
229 }
230 | CONNECT STRING port {
231 $$ = op_connect(peek_op(), $2, $3);
232 }
233 | DISCONNECT {
234 $$ = op_disconnect(peek_op());
235 }
236 | STARTTLS {
237 $$ = op_starttls(peek_op());
238 }
239 | WRITE STRING {
240 $$ = op_write(peek_op(), $2, strlen($2));
241 }
242 | WRITELN STRING {
243 $$ = op_printf(peek_op(), "%s\r\n", $2);
244 free($2);
245 }
246 | EXPECT DISCONNECT {
247 $$ = op_expect_disconnect(peek_op());
248 }
249 | EXPECT SMTP {
250 $$ = op_expect_smtp_response(peek_op(),
251 RESP_SMTP_ANY | RESP_SMTP_MULTILINE);
252 }
253 | EXPECT SMTP OK {
254 $$ = op_expect_smtp_response(peek_op(),
255 RESP_SMTP_OK);
256 }
257 | EXPECT SMTP HELO {
258 $$ = op_expect_smtp_response(peek_op(),
259 RESP_SMTP_OK | RESP_SMTP_MULTILINE);
260 }
261 | EXPECT SMTP TEMPFAIL {
262 $$ = op_expect_smtp_response(peek_op(),
263 RESP_SMTP_TEMPFAIL);
264 }
265 | EXPECT SMTP PERMFAIL {
266 $$ = op_expect_smtp_response(peek_op(),
267 RESP_SMTP_PERMFAIL);
268 }
269 ;
270
271 statement_list : statement nl statement_list
272 | statement
273 | /* EMPTY */
274 ;
275
276 block : '{' {
277 push_op(op_block(peek_op()));
278 } optnl statement_list '}' {
279 $$ = pop_op();
280 }
281 ;
282
283 procparam : '%' STRING {
284 if (proc_addvar(currproc, $2) == -1) {
285 yyerror("cannot add parameter %s", $2);
286 file->errors++;
287 }
288 }
289 ;
290
291 procparams : procparam procparams
292 | /* EMPTY */
293 ;
294
295 proc : PROC STRING {
296 printf("# proc %s\n", $2);
297 currproc = procedure_create(currscript, $2);
298 if (currproc == NULL)
299 file->errors++;
300 } procparams block {
301 if (currproc)
302 currproc->root = $5;
303 }
304 ;
305
306 testopt_name : NAME STRING {
307 if (procedure_get_by_name(currscript, $2)) {
308 file->errors++;
309 } else {
310 free(currproc->name);
311 currproc->name = ($2);
312 }
313 }
314 | /* EMPTY */
315 ;
316
317 testopt_cnx : NO_AUTOCONNECT {
318 currproc->flags |= PROC_NOCONNECT;
319 }
320 | /* EMPTY */
321 ;
322 testopt_fail : EXPECT FAIL {
323 currproc->flags |= PROC_EXPECTFAIL;
324 }
325 | /* EMPTY */
326 ;
327
328 testopt_skip : SKIP {
329 currproc->flags |= PROC_SKIP;
330 }
331 | /* EMPTY */
332 ;
333
334 testcaseopts : testopt_name testopt_cnx testopt_fail testopt_skip;
335
336 testcase : TESTCASE {
337 char buf[1024];
338 snprintf(buf, sizeof buf, "<%s:%i>",
339 file->name, file->lineno);
340 currproc = procedure_create(currscript, strdup(buf));
341 if (currproc) {
342 currproc->flags |= PROC_TESTCASE;
343 } else {
344 file->errors++;
345 }
346 } testcaseopts block {
347 currproc->root = $4;
348 }
349 ;
350 %%
351
352 struct keywords {
353 const char *k_name;
354 int k_val;
355 };
356
357 int
yyerror(const char * fmt,...)358 yyerror(const char *fmt, ...)
359 {
360 va_list ap;
361
362 file->errors++;
363 va_start(ap, fmt);
364 fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);
365 vfprintf(stderr, fmt, ap);
366 fprintf(stderr, "\n");
367 va_end(ap);
368 return (0);
369 }
370
371 int
kw_cmp(const void * k,const void * e)372 kw_cmp(const void *k, const void *e)
373 {
374 return (strcmp(k, ((const struct keywords *)e)->k_name));
375 }
376
377 int
lookup(char * s)378 lookup(char *s)
379 {
380 /* this has to be sorted always */
381 static const struct keywords keywords[] = {
382 { "call", CALL },
383 { "connect", CONNECT },
384 { "disconnect", DISCONNECT },
385 { "expect", EXPECT },
386 { "fail", FAIL },
387 { "helo", HELO },
388 { "name", NAME },
389 { "no-autoconnect", NO_AUTOCONNECT },
390 { "noop", NOOP },
391 { "ok", OK },
392 { "permfail", PERMFAIL },
393 { "port", PORT },
394 { "proc", PROC },
395 { "random", RANDOM },
396 { "repeat", REPEAT },
397 { "skip", SKIP },
398 { "sleep", SLEEP },
399 { "smtp", SMTP },
400 { "starttls", STARTTLS },
401 { "tempfail", TEMPFAIL },
402 { "test-case", TESTCASE },
403 { "write", WRITE },
404 { "writeln", WRITELN },
405 };
406 const struct keywords *p;
407
408 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
409 sizeof(keywords[0]), kw_cmp);
410
411 if (p)
412 return (p->k_val);
413 else
414 return (STRING);
415 }
416
417 #define MAXPUSHBACK 128
418
419 char *parsebuf;
420 int parseindex;
421 char pushback_buffer[MAXPUSHBACK];
422 int pushback_index = 0;
423
424 int
lgetc(int quotec)425 lgetc(int quotec)
426 {
427 int c, next;
428
429 if (parsebuf) {
430 /* Read character from the parsebuffer instead of input. */
431 if (parseindex >= 0) {
432 c = parsebuf[parseindex++];
433 if (c != '\0')
434 return (c);
435 parsebuf = NULL;
436 } else
437 parseindex++;
438 }
439
440 if (pushback_index)
441 return (pushback_buffer[--pushback_index]);
442
443 if (quotec) {
444 if ((c = getc(file->stream)) == EOF) {
445 yyerror("reached end of file while parsing "
446 "quoted string");
447 if (file == topfile || popfile() == EOF)
448 return (EOF);
449 return (quotec);
450 }
451 return (c);
452 }
453
454 while ((c = getc(file->stream)) == '\\') {
455 next = getc(file->stream);
456 if (next != '\n') {
457 c = next;
458 break;
459 }
460 yylval.lineno = file->lineno;
461 file->lineno++;
462 }
463
464 while (c == EOF) {
465 if (file == topfile || popfile() == EOF)
466 return (EOF);
467 c = getc(file->stream);
468 }
469 return (c);
470 }
471
472 int
lungetc(int c)473 lungetc(int c)
474 {
475 if (c == EOF)
476 return (EOF);
477 if (parsebuf) {
478 parseindex--;
479 if (parseindex >= 0)
480 return (c);
481 }
482 if (pushback_index < MAXPUSHBACK-1)
483 return (pushback_buffer[pushback_index++] = c);
484 else
485 return (EOF);
486 }
487
488 int
findeol(void)489 findeol(void)
490 {
491 int c;
492
493 parsebuf = NULL;
494 pushback_index = 0;
495
496 /* skip to either EOF or the first real EOL */
497 while (1) {
498 c = lgetc(0);
499 if (c == '\n') {
500 file->lineno++;
501 break;
502 }
503 if (c == EOF)
504 break;
505 }
506 return (ERROR);
507 }
508
509 int
yylex(void)510 yylex(void)
511 {
512 char buf[8096];
513 char *p, *val;
514 int quotec, next, c;
515 int token;
516
517 top:
518 p = buf;
519 while ((c = lgetc(0)) == ' ' || c == '\t')
520 ; /* nothing */
521
522 yylval.lineno = file->lineno;
523 if (c == '#')
524 while ((c = lgetc(0)) != '\n' && c != EOF)
525 ; /* nothing */
526 if (c == '$' && parsebuf == NULL) {
527 while (1) {
528 if ((c = lgetc(0)) == EOF)
529 return (0);
530
531 if (p + 1 >= buf + sizeof(buf) - 1) {
532 yyerror("string too long");
533 return (findeol());
534 }
535 if (isalnum(c) || c == '_') {
536 *p++ = (char)c;
537 continue;
538 }
539 *p = '\0';
540 lungetc(c);
541 break;
542 }
543 val = symget(buf);
544 if (val == NULL) {
545 yyerror("macro '%s' not defined", buf);
546 return (findeol());
547 }
548 parsebuf = val;
549 parseindex = 0;
550 goto top;
551 }
552
553 switch (c) {
554 case '\'':
555 case '"':
556 quotec = c;
557 while (1) {
558 if ((c = lgetc(quotec)) == EOF)
559 return (0);
560 if (c == '\n') {
561 file->lineno++;
562 continue;
563 } else if (c == '\\') {
564 if ((next = lgetc(quotec)) == EOF)
565 return (0);
566 if (next == quotec || c == ' ' || c == '\t')
567 c = next;
568 else if (next == '\n') {
569 file->lineno++;
570 continue;
571 } else
572 lungetc(next);
573 } else if (c == quotec) {
574 *p = '\0';
575 break;
576 }
577 if (p + 1 >= buf + sizeof(buf) - 1) {
578 yyerror("string too long");
579 return (findeol());
580 }
581 *p++ = (char)c;
582 }
583 yylval.v.string = strdup(buf);
584 if (yylval.v.string == NULL)
585 err(1, "yylex: strdup");
586 return (STRING);
587 }
588
589 #define allowed_to_end_number(x) \
590 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
591
592 if (c == '-' || isdigit(c)) {
593 do {
594 *p++ = c;
595 if ((unsigned)(p-buf) >= sizeof(buf)) {
596 yyerror("string too long");
597 return (findeol());
598 }
599 } while ((c = lgetc(0)) != EOF && isdigit(c));
600 lungetc(c);
601 if (p == buf + 1 && buf[0] == '-')
602 goto nodigits;
603 if (c == EOF || allowed_to_end_number(c)) {
604 const char *errstr = NULL;
605
606 *p = '\0';
607 yylval.v.number = strtonum(buf, LLONG_MIN,
608 LLONG_MAX, &errstr);
609 if (errstr) {
610 yyerror("\"%s\" invalid number: %s",
611 buf, errstr);
612 return (findeol());
613 }
614 return (NUMBER);
615 } else {
616 nodigits:
617 while (p > buf + 1)
618 lungetc(*--p);
619 c = *--p;
620 if (c == '-')
621 return (c);
622 }
623 }
624
625 if (c == '=') {
626 if ((c = lgetc(0)) != EOF && c == '>')
627 return (ARROW);
628 lungetc(c);
629 c = '=';
630 }
631
632 #define allowed_in_string(x) \
633 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
634 x != '{' && x != '}' && x != '<' && x != '>' && \
635 x != '!' && x != '=' && x != '#' && \
636 x != ','))
637
638 if (isalnum(c) || c == ':' || c == '_') {
639 do {
640 *p++ = c;
641 if ((unsigned)(p-buf) >= sizeof(buf)) {
642 yyerror("string too long");
643 return (findeol());
644 }
645 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
646 lungetc(c);
647 *p = '\0';
648 if ((token = lookup(buf)) == STRING)
649 if ((yylval.v.string = strdup(buf)) == NULL)
650 err(1, "yylex: strdup");
651 return (token);
652 }
653 if (c == '\n') {
654 yylval.lineno = file->lineno;
655 file->lineno++;
656 }
657 if (c == EOF)
658 return (0);
659 return (c);
660 }
661
662 int
check_file_secrecy(int fd,const char * fname)663 check_file_secrecy(int fd, const char *fname)
664 {
665 struct stat st;
666
667 if (fstat(fd, &st)) {
668 warn("cannot stat %s", fname);
669 return (-1);
670 }
671 if (st.st_uid != 0 && st.st_uid != getuid()) {
672 warnx("%s: owner not root or current user", fname);
673 return (-1);
674 }
675 if (st.st_mode & (S_IRWXG | S_IRWXO)) {
676 warnx("%s: group/world readable/writeable", fname);
677 return (-1);
678 }
679 return (0);
680 }
681
682 struct file *
pushfile(const char * name,int secret)683 pushfile(const char *name, int secret)
684 {
685 struct file *nfile;
686
687 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
688 warn("malloc");
689 return (NULL);
690 }
691 if ((nfile->name = strdup(name)) == NULL) {
692 warn("malloc");
693 free(nfile);
694 return (NULL);
695 }
696 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
697 warn("%s", nfile->name);
698 free(nfile->name);
699 free(nfile);
700 return (NULL);
701 } else if (secret &&
702 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
703 fclose(nfile->stream);
704 free(nfile->name);
705 free(nfile);
706 return (NULL);
707 }
708 nfile->lineno = 1;
709 TAILQ_INSERT_TAIL(&files, nfile, entry);
710 return (nfile);
711 }
712
713 int
popfile(void)714 popfile(void)
715 {
716 struct file *prev;
717
718 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
719 prev->errors += file->errors;
720
721 TAILQ_REMOVE(&files, file, entry);
722 fclose(file->stream);
723 free(file->name);
724 free(file);
725 file = prev;
726 return (file ? 0 : EOF);
727 }
728
729 struct script *
parse_script(const char * filename)730 parse_script(const char *filename)
731 {
732 errors = 0;
733
734 currscript = calloc(1, sizeof *currscript);
735 TAILQ_INIT(&currscript->procs);
736 currproc = NULL;
737
738 opstackidx = 0;
739
740 if ((file = pushfile(filename, 0)) == NULL)
741 return (NULL);
742
743 topfile = file;
744
745 /*
746 * parse configuration
747 */
748 setservent(1);
749 yyparse();
750 errors = file->errors;
751 popfile();
752 endservent();
753
754 if (errors)
755 return (NULL);
756
757 return (currscript);
758 }
759
760 int
symset(const char * nam,const char * val,int persist)761 symset(const char *nam, const char *val, int persist)
762 {
763 struct sym *sym;
764
765 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
766 sym = TAILQ_NEXT(sym, entry))
767 ; /* nothing */
768
769 if (sym != NULL) {
770 if (sym->persist == 1)
771 return (0);
772 else {
773 free(sym->nam);
774 free(sym->val);
775 TAILQ_REMOVE(&symhead, sym, entry);
776 free(sym);
777 }
778 }
779 if ((sym = calloc(1, sizeof(*sym))) == NULL)
780 return (-1);
781
782 sym->nam = strdup(nam);
783 if (sym->nam == NULL) {
784 free(sym);
785 return (-1);
786 }
787 sym->val = strdup(val);
788 if (sym->val == NULL) {
789 free(sym->nam);
790 free(sym);
791 return (-1);
792 }
793 sym->used = 0;
794 sym->persist = persist;
795 TAILQ_INSERT_TAIL(&symhead, sym, entry);
796 return (0);
797 }
798
799 int
cmdline_symset(char * s)800 cmdline_symset(char *s)
801 {
802 char *sym, *val;
803 int ret;
804 size_t len;
805
806 if ((val = strrchr(s, '=')) == NULL)
807 return (-1);
808
809 len = strlen(s) - strlen(val) + 1;
810 if ((sym = malloc(len)) == NULL)
811 errx(1, "cmdline_symset: malloc");
812
813 (void)strlcpy(sym, s, len);
814
815 ret = symset(sym, val + 1, 1);
816 free(sym);
817
818 return (ret);
819 }
820
821 char *
symget(const char * nam)822 symget(const char *nam)
823 {
824 struct sym *sym;
825
826 TAILQ_FOREACH(sym, &symhead, entry)
827 if (strcmp(nam, sym->nam) == 0) {
828 sym->used = 1;
829 return (sym->val);
830 }
831 return (NULL);
832 }
833
834 int
delaytonum(char * str)835 delaytonum(char *str)
836 {
837 unsigned int factor;
838 size_t len;
839 const char *errstr = NULL;
840 int delay;
841
842 /* we need at least 1 digit and 1 unit */
843 len = strlen(str);
844 if (len < 2)
845 goto bad;
846
847 switch(str[len - 1]) {
848
849 case 's':
850 factor = 1;
851 break;
852
853 case 'm':
854 factor = 60;
855 break;
856
857 case 'h':
858 factor = 60 * 60;
859 break;
860
861 case 'd':
862 factor = 24 * 60 * 60;
863 break;
864
865 default:
866 goto bad;
867 }
868
869 str[len - 1] = '\0';
870 delay = strtonum(str, 1, INT_MAX / factor, &errstr);
871 if (errstr)
872 goto bad;
873
874 return (delay * factor);
875
876 bad:
877 return (-1);
878 }
879
880
881 void
push_op(struct op * op)882 push_op(struct op *op)
883 {
884 if (opstackidx == MAXDEPTH) {
885 yyerror("too deep");
886 return;
887 }
888 opstack[opstackidx++] = op;
889 }
890
891 struct op *
pop_op(void)892 pop_op(void)
893 {
894 if (opstackidx == 0)
895 return (NULL);
896 return (opstack[--opstackidx]);
897 }
898
899 struct op *
peek_op(void)900 peek_op(void)
901 {
902 if (opstackidx == 0)
903 return (NULL);
904 return (opstack[opstackidx - 1]);
905 }
906