Blob


1 /*
2 * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
3 * Copyright (c) 2007-2016 Reyk Floeter <reyk@openbsd.org>
4 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
5 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
6 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
7 * Copyright (c) 2001 Markus Friedl. All rights reserved.
8 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
9 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
10 *
11 * Permission to use, copy, modify, and distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 */
24 %{
26 #include <sys/queue.h>
28 #include <ctype.h>
29 #include <err.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <stdint.h>
34 #include <string.h>
35 #include <unistd.h>
37 #ifndef nitems
38 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
39 #endif
41 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
42 static struct file {
43 TAILQ_ENTRY(file) entry;
44 FILE *stream;
45 char *name;
46 size_t ungetpos;
47 size_t ungetsize;
48 u_char *ungetbuf;
49 int eof_reached;
50 int lineno;
51 int errors;
52 } *file, *topfile;
53 int parse(const char *);
54 struct file *pushfile(const char *, int);
55 int popfile(void);
56 int yyparse(void);
57 int yylex(void);
58 int yyerror(const char *, ...)
59 __attribute__((__format__ (printf, 1, 2)))
60 __attribute__((__nonnull__ (1)));
61 int kw_cmp(const void *, const void *);
62 int lookup(char *);
63 int igetc(void);
64 int lgetc(int);
65 void lungetc(int);
66 int findeol(void);
68 void dbg(void);
69 void printq(const char *);
71 extern int nodebug;
73 static int block;
74 static int in_define;
75 static int errors;
76 static char lerr[32];
78 typedef struct {
79 union {
80 char *string;
81 } v;
82 int lineno;
83 } YYSTYPE;
85 %}
87 %token DEFINE ELSE END ERROR ESCAPE FINALLY IF INCLUDE
88 %token RENDER TQFOREACH UNSAFE URLESCAPE
89 %token <v.string> STRING
90 %type <v.string> string
92 %%
94 grammar : /* empty */
95 | grammar include
96 | grammar verbatim
97 | grammar block
98 | grammar error { file->errors++; }
99 ;
101 include : INCLUDE STRING {
102 struct file *nfile;
104 if ((nfile = pushfile($2, 0)) == NULL) {
105 yyerror("failed to include file %s", $2);
106 free($2);
107 YYERROR;
109 free($2);
111 file = nfile;
112 lungetc('\n');
116 verbatim : '!' verbatim1 '!' {
117 if (in_define) {
118 /* TODO: check template status and exit in case */
123 verbatim1 : /* empty */
124 | verbatim1 STRING {
125 if (*$2 != '\0') {
126 dbg();
127 puts($2);
129 free($2);
133 verbatims : /* empty */
134 | verbatims verbatim
137 raw : STRING {
138 dbg();
139 printf("if (tp->tp_puts(tp, ");
140 printq($1);
141 printf(") == -1) goto %s;\n", lerr);
143 free($1);
147 block : define body end {
148 printf("%s:\n", lerr);
149 puts("return tp->tp_ret;");
150 puts("}");
151 in_define = 0;
153 | define body finally end {
154 puts("return tp->tp_ret;");
155 puts("}");
156 in_define = 0;
160 define : '{' DEFINE string '}' {
161 in_define = 1;
162 (void)snprintf(lerr, sizeof(lerr), "err%llu",
163 (unsigned long long)arc4random());
165 dbg();
166 printf("int\n%s\n{\n", $3);
168 free($3);
172 body : /* empty */
173 | body verbatim
174 | body raw
175 | body special
178 special : '{' RENDER string '}' {
179 dbg();
180 if (strrchr($3, ')') != NULL)
181 printf("if (%s == -1) goto %s;\n",
182 $3, lerr);
183 else
184 printf("if (%s != NULL && %s(tp) == -1) "
185 "goto %s;\n", $3, $3, lerr);
186 free($3);
188 | if body endif { puts("}"); }
189 | loop
190 | '{' string '|' ESCAPE '}' {
191 dbg();
192 printf("if (tp->tp_escape(tp, %s) == -1) goto %s;\n",
193 $2, lerr);
194 free($2);
196 | '{' string '|' UNSAFE '}' {
197 dbg();
198 printf("if (tp->tp_puts(tp, %s) == -1) goto %s;\n",
199 $2, lerr);
200 free($2);
202 | '{' string '|' URLESCAPE '}' {
203 dbg();
204 printf("if (tp_urlescape(tp, %s) == -1) goto %s;\n",
205 $2, lerr);
206 free($2);
208 | '{' string '}' {
209 dbg();
210 printf("if (tp->tp_escape(tp, %s) == -1) goto %s;\n",
211 $2, lerr);
212 free($2);
216 if : '{' IF string '}' {
217 dbg();
218 printf("if (%s) {\n", $3);
219 free($3);
223 endif : end
224 | else body end
225 | elsif body endif
228 elsif : '{' ELSE IF string '}' {
229 dbg();
230 printf("} else if (%s) {\n", $4);
231 free($4);
235 else : '{' ELSE '}' {
236 dbg();
237 puts("} else {");
241 loop : '{' TQFOREACH STRING STRING STRING '}' {
242 printf("TAILQ_FOREACH(%s, %s, %s) {\n",
243 $3, $4, $5);
244 free($3);
245 free($4);
246 free($5);
247 } body end {
248 puts("}");
252 end : '{' END '}'
255 finally : '{' FINALLY '}' {
256 dbg();
257 printf("%s:\n", lerr);
258 } verbatims
261 string : STRING string {
262 if (asprintf(&$$, "%s %s", $1, $2) == -1)
263 err(1, "asprintf");
264 free($1);
265 free($2);
267 | STRING
270 %%
272 struct keywords {
273 const char *k_name;
274 int k_val;
275 };
277 int
278 yyerror(const char *fmt, ...)
280 va_list ap;
281 char *msg;
283 file->errors++;
284 va_start(ap, fmt);
285 if (vasprintf(&msg, fmt, ap) == -1)
286 err(1, "yyerror vasprintf");
287 va_end(ap);
288 fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg);
289 free(msg);
290 return (0);
293 int
294 kw_cmp(const void *k, const void *e)
296 return (strcmp(k, ((const struct keywords *)e)->k_name));
299 int
300 lookup(char *s)
302 /* this has to be sorted always */
303 static const struct keywords keywords[] = {
304 { "define", DEFINE },
305 { "else", ELSE },
306 { "end", END },
307 { "escape", ESCAPE },
308 { "finally", FINALLY },
309 { "if", IF },
310 { "include", INCLUDE },
311 { "render", RENDER },
312 { "tailq-foreach", TQFOREACH },
313 { "unsafe", UNSAFE },
314 { "urlescape", URLESCAPE },
315 };
316 const struct keywords *p;
318 p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]),
319 kw_cmp);
321 if (p)
322 return (p->k_val);
323 else
324 return (STRING);
327 #define START_EXPAND 1
328 #define DONE_EXPAND 2
330 static int expanding;
332 int
333 igetc(void)
335 int c;
337 while (1) {
338 if (file->ungetpos > 0)
339 c = file->ungetbuf[--file->ungetpos];
340 else
341 c = getc(file->stream);
343 if (c == START_EXPAND)
344 expanding = 1;
345 else if (c == DONE_EXPAND)
346 expanding = 0;
347 else
348 break;
350 return (c);
353 int
354 lgetc(int quotec)
356 int c, next;
358 if (quotec) {
359 if ((c = igetc()) == EOF) {
360 yyerror("reached end of filewhile parsing "
361 "quoted string");
362 if (file == topfile || popfile() == EOF)
363 return (EOF);
364 return (quotec);
366 return (c);
369 while ((c = igetc()) == '\\') {
370 next = igetc();
371 if (next != '\n') {
372 c = next;
373 break;
375 yylval.lineno = file->lineno;
376 file->lineno++;
378 if (c == '\t' || c == ' ') {
379 /* Compress blanks to a sigle space. */
380 do {
381 c = getc(file->stream);
382 } while (c == '\t' || c == ' ');
383 ungetc(c, file->stream);
384 c = ' ';
387 if (c == EOF) {
388 /*
389 * Fake EOL when hit EOF for the first time. This gets line
390 * count rigchtif last line included file is syntactically
391 * invalid and has no newline.
392 */
393 if (file->eof_reached == 0) {
394 file->eof_reached = 1;
395 return ('\n');
397 while (c == EOF) {
398 if (file == topfile || popfile() == EOF)
399 return (EOF);
400 c = igetc();
403 return (c);
406 void
407 lungetc(int c)
409 if (c == EOF)
410 return;
412 if (file->ungetpos >= file->ungetsize) {
413 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
414 if (p == NULL)
415 err(1, "reallocarray");
416 file->ungetbuf = p;
417 file->ungetsize *= 2;
419 file->ungetbuf[file->ungetpos++] = c;
422 int
423 findeol(void)
425 int c;
427 /* skip to either EOF or the first real EOL */
428 while (1) {
429 c = lgetc(0);
430 if (c == '\n') {
431 file->lineno++;
432 break;
434 if (c == EOF)
435 break;
437 return (ERROR);
440 int
441 yylex(void)
443 char buf[8096];
444 char *p = buf;
445 int c;
446 int token;
447 int starting = 0;
448 int ending = 0;
450 if (!in_define && block == 0) {
451 while ((c = lgetc(0)) != '{' && c != EOF) {
452 if (c == '\n')
453 file->lineno++;
456 if (c == EOF)
457 return (0);
459 newblock:
460 c = lgetc(0);
461 if (c == '{' || c == '!') {
462 if (c == '{')
463 block = '}';
464 else
465 block = c;
466 return (c);
468 if (c == '\n')
469 file->lineno++;
472 while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\n') {
473 if (c == '\n')
474 file->lineno++;
477 if (c == EOF) {
478 yyerror("unterminated block");
479 return (0);
482 yylval.lineno = file->lineno;
484 if (block != 0 && c == block) {
485 if ((c = lgetc(0)) == '}') {
486 if (block == '!') {
487 block = 0;
488 return ('!');
490 block = 0;
491 return ('}');
493 lungetc(c);
494 c = block;
497 if (in_define && block == 0) {
498 if (c == '{')
499 goto newblock;
501 do {
502 if (starting) {
503 if (c == '!' || c == '{') {
504 lungetc('{');
505 lungetc(c);
506 break;
508 starting = 0;
509 lungetc(c);
510 c = '{';
511 } else if (c == '{') {
512 starting = 1;
513 continue;
516 *p++ = c;
517 if ((size_t)(p - buf) >= sizeof(buf)) {
518 yyerror("string too long");
519 return (findeol());
521 } while ((c = lgetc(0)) != EOF && c != '\n');
522 *p = '\0';
523 if (c == EOF) {
524 yyerror("unterminated block");
525 return (0);
527 if (c == '\n')
528 file->lineno++;
529 if ((yylval.v.string = strdup(buf)) == NULL)
530 err(1, "strdup");
531 return (STRING);
534 if (block == '!') {
535 do {
536 if (ending) {
537 if (c == '}') {
538 lungetc(c);
539 lungetc(block);
540 break;
542 ending = 0;
543 lungetc(c);
544 c = block;
545 } else if (c == '!') {
546 ending = 1;
547 continue;
550 *p++ = c;
551 if ((size_t)(p - buf) >= sizeof(buf)) {
552 yyerror("line too long");
553 return (findeol());
555 } while ((c = lgetc(0)) != EOF && c != '\n');
556 *p = '\0';
558 if (c == EOF) {
559 yyerror("unterminated block");
560 return (0);
562 if (c == '\n')
563 file->lineno++;
565 if ((yylval.v.string = strdup(buf)) == NULL)
566 err(1, "strdup");
567 return (STRING);
570 if (c == '|')
571 return (c);
573 do {
574 if (c == '|') {
575 lungetc(c);
576 break;
579 if (ending) {
580 if (c == '}') {
581 lungetc(c);
582 lungetc('}');
583 break;
585 ending = 0;
586 lungetc(c);
587 c = block;
588 } else if (c == '}') {
589 ending = 1;
590 continue;
593 *p++ = c;
594 if ((size_t)(p - buf) >= sizeof(buf)) {
595 yyerror("string too long");
596 return (findeol());
598 } while ((c = lgetc(0)) != EOF && !isspace((unsigned char)c));
599 *p = '\0';
601 if (c == EOF) {
602 yyerror("unterminated block");
603 return (0);
605 if (c == '\n')
606 file->lineno++;
607 if ((token = lookup(buf)) == STRING)
608 if ((yylval.v.string = strdup(buf)) == NULL)
609 err(1, "strdup");
610 return (token);
613 struct file *
614 pushfile(const char *name, int secret)
616 struct file *nfile;
618 if ((nfile = calloc(1, sizeof(*nfile))) == NULL)
619 err(1, "calloc");
620 if ((nfile->name = strdup(name)) == NULL)
621 err(1, "strdup");
622 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
623 warn("can't open %s", nfile->name);
624 free(nfile->name);
625 free(nfile);
626 return (NULL);
628 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
629 nfile->ungetsize = 16;
630 nfile->ungetbuf = malloc(nfile->ungetsize);
631 if (nfile->ungetbuf == NULL)
632 err(1, "malloc");
633 TAILQ_INSERT_TAIL(&files, nfile, entry);
634 return (nfile);
637 int
638 popfile(void)
640 struct file *prev;
642 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
643 prev->errors += file->errors;
645 TAILQ_REMOVE(&files, file, entry);
646 fclose(file->stream);
647 free(file->name);
648 free(file->ungetbuf);
649 free(file);
650 file = prev;
651 return (file ? 0 : EOF);
654 int
655 parse(const char *filename)
657 if ((file = pushfile(filename, 0)) == 0)
658 return (-1);
659 topfile = file;
661 yyparse();
662 errors = file->errors;
663 popfile();
665 return (errors ? -1 : 0);
668 void
669 dbg(void)
671 if (nodebug)
672 return;
674 printf("#line %d ", yylval.lineno);
675 printq(file->name);
676 putchar('\n');
679 void
680 printq(const char *str)
682 putchar('"');
683 for (; *str; ++str) {
684 if (*str == '"')
685 putchar('\\');
686 putchar(*str);
688 putchar('"');