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 unsigned 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 int lastline = -1;
78 typedef struct {
79 union {
80 char *string;
81 } v;
82 int lineno;
83 } YYSTYPE;
85 %}
87 %token DEFINE ELSE END ERROR FINALLY FOR IF INCLUDE PRINTF
88 %token RENDER TQFOREACH UNSAFE URLESCAPE
89 %token <v.string> STRING
90 %type <v.string> string
91 %type <v.string> stringy
93 %%
95 grammar : /* empty */
96 | grammar include
97 | grammar verbatim
98 | grammar block
99 | grammar error { file->errors++; }
102 include : INCLUDE STRING {
103 struct file *nfile;
105 if ((nfile = pushfile($2, 0)) == NULL) {
106 yyerror("failed to include file %s", $2);
107 free($2);
108 YYERROR;
110 free($2);
112 file = nfile;
113 lungetc('\n');
117 verbatim : '!' verbatim1 '!' {
118 if (in_define) {
119 /* TODO: check template status and exit in case */
124 verbatim1 : /* empty */
125 | verbatim1 STRING {
126 if (*$2 != '\0') {
127 dbg();
128 puts($2);
130 free($2);
134 verbatims : /* empty */
135 | verbatims verbatim
138 raw : STRING {
139 dbg();
140 printf("if ((tp_ret = tp->tp_puts(tp, ");
141 printq($1);
142 puts(")) == -1) goto err;");
144 free($1);
148 block : define body end {
149 puts("err:");
150 puts("return tp_ret;");
151 puts("}");
152 in_define = 0;
154 | define body finally end {
155 puts("return tp_ret;");
156 puts("}");
157 in_define = 0;
161 define : '{' DEFINE string '}' {
162 in_define = 1;
164 dbg();
165 printf("int\n%s\n{\n", $3);
166 puts("int tp_ret = 0;");
168 free($3);
172 body : /* empty */
173 | body verbatim
174 | body raw
175 | body special
178 special : '{' RENDER string '}' {
179 dbg();
180 printf("if ((tp_ret = %s) == -1) goto err;\n", $3);
181 free($3);
183 | printf
184 | if body endif { puts("}"); }
185 | loop
186 | '{' string '|' UNSAFE '}' {
187 dbg();
188 printf("if ((tp_ret = tp->tp_puts(tp, %s)) == -1)\n",
189 $2);
190 puts("goto err;");
191 free($2);
193 | '{' string '|' URLESCAPE '}' {
194 dbg();
195 printf("if ((tp_ret = tp_urlescape(tp, %s)) == -1)\n",
196 $2);
197 puts("goto err;");
198 free($2);
200 | '{' string '}' {
201 dbg();
202 printf("if ((tp_ret = tp->tp_escape(tp, %s)) == -1)\n",
203 $2);
204 puts("goto err;");
205 free($2);
209 printf : '{' PRINTF {
210 dbg();
211 printf("if (asprintf(&tp->tp_tmp, ");
212 } printfargs '}' {
213 puts(") == -1)");
214 puts("goto err;");
215 puts("if ((tp_ret = tp->tp_escape(tp, tp->tp_tmp)) "
216 "== -1)");
217 puts("goto err;");
218 puts("free(tp->tp_tmp);");
219 puts("tp->tp_tmp = NULL;");
223 printfargs : /* empty */
224 | printfargs STRING {
225 printf(" %s", $2);
226 free($2);
230 if : '{' IF stringy '}' {
231 dbg();
232 printf("if (%s) {\n", $3);
233 free($3);
237 endif : end
238 | else body end
239 | elsif body endif
242 elsif : '{' ELSE IF stringy '}' {
243 dbg();
244 printf("} else if (%s) {\n", $4);
245 free($4);
249 else : '{' ELSE '}' {
250 dbg();
251 puts("} else {");
255 loop : '{' FOR stringy '}' {
256 printf("for (%s) {\n", $3);
257 free($3);
258 } body end {
259 puts("}");
261 | '{' TQFOREACH STRING STRING STRING '}' {
262 printf("TAILQ_FOREACH(%s, %s, %s) {\n",
263 $3, $4, $5);
264 free($3);
265 free($4);
266 free($5);
267 } body end {
268 puts("}");
272 end : '{' END '}'
275 finally : '{' FINALLY '}' {
276 dbg();
277 puts("err:");
278 } verbatims
281 string : STRING string {
282 if (asprintf(&$$, "%s %s", $1, $2) == -1)
283 err(1, "asprintf");
284 free($1);
285 free($2);
287 | STRING
290 stringy : STRING
291 | STRING stringy {
292 if (asprintf(&$$, "%s %s", $1, $2) == -1)
293 err(1, "asprintf");
294 free($1);
295 free($2);
297 | '|' stringy {
298 if (asprintf(&$$, "|%s", $2) == -1)
299 err(1, "asprintf");
300 free($2);
304 %%
306 struct keywords {
307 const char *k_name;
308 int k_val;
309 };
311 int
312 yyerror(const char *fmt, ...)
314 va_list ap;
315 char *msg;
317 file->errors++;
318 va_start(ap, fmt);
319 if (vasprintf(&msg, fmt, ap) == -1)
320 err(1, "yyerror vasprintf");
321 va_end(ap);
322 fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg);
323 free(msg);
324 return (0);
327 int
328 kw_cmp(const void *k, const void *e)
330 return (strcmp(k, ((const struct keywords *)e)->k_name));
333 int
334 lookup(char *s)
336 /* this has to be sorted always */
337 static const struct keywords keywords[] = {
338 { "define", DEFINE },
339 { "else", ELSE },
340 { "end", END },
341 { "finally", FINALLY },
342 { "for", FOR },
343 { "if", IF },
344 { "include", INCLUDE },
345 { "printf", PRINTF },
346 { "render", RENDER },
347 { "tailq-foreach", TQFOREACH },
348 { "unsafe", UNSAFE },
349 { "urlescape", URLESCAPE },
350 };
351 const struct keywords *p;
353 p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]),
354 kw_cmp);
356 if (p)
357 return (p->k_val);
358 else
359 return (STRING);
362 #define START_EXPAND 1
363 #define DONE_EXPAND 2
365 static int expanding;
367 int
368 igetc(void)
370 int c;
372 while (1) {
373 if (file->ungetpos > 0)
374 c = file->ungetbuf[--file->ungetpos];
375 else
376 c = getc(file->stream);
378 if (c == START_EXPAND)
379 expanding = 1;
380 else if (c == DONE_EXPAND)
381 expanding = 0;
382 else
383 break;
385 return (c);
388 int
389 lgetc(int quotec)
391 int c;
393 if (quotec) {
394 if ((c = igetc()) == EOF) {
395 yyerror("reached end of filewhile parsing "
396 "quoted string");
397 if (file == topfile || popfile() == EOF)
398 return (EOF);
399 return (quotec);
401 return (c);
404 c = igetc();
405 if (c == '\t' || c == ' ') {
406 /* Compress blanks to a sigle space. */
407 do {
408 c = getc(file->stream);
409 } while (c == '\t' || c == ' ');
410 ungetc(c, file->stream);
411 c = ' ';
414 if (c == EOF) {
415 /*
416 * Fake EOL when hit EOF for the first time. This gets line
417 * count rigchtif last line included file is syntactically
418 * invalid and has no newline.
419 */
420 if (file->eof_reached == 0) {
421 file->eof_reached = 1;
422 return ('\n');
424 while (c == EOF) {
425 if (file == topfile || popfile() == EOF)
426 return (EOF);
427 c = igetc();
430 return (c);
433 void
434 lungetc(int c)
436 if (c == EOF)
437 return;
439 if (file->ungetpos >= file->ungetsize) {
440 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
441 if (p == NULL)
442 err(1, "reallocarray");
443 file->ungetbuf = p;
444 file->ungetsize *= 2;
446 file->ungetbuf[file->ungetpos++] = c;
449 int
450 findeol(void)
452 int c;
454 /* skip to either EOF or the first real EOL */
455 while (1) {
456 c = lgetc(0);
457 if (c == '\n') {
458 file->lineno++;
459 break;
461 if (c == EOF)
462 break;
464 return (ERROR);
467 int
468 yylex(void)
470 char buf[8096];
471 char *p = buf;
472 int c;
473 int token;
474 int starting = 0;
475 int ending = 0;
477 if (!in_define && block == 0) {
478 while ((c = lgetc(0)) != '{' && c != EOF) {
479 if (c == '\n')
480 file->lineno++;
483 if (c == EOF)
484 return (0);
486 newblock:
487 c = lgetc(0);
488 if (c == '{' || c == '!') {
489 if (c == '{')
490 block = '}';
491 else
492 block = c;
493 return (c);
495 if (c == '\n')
496 file->lineno++;
499 while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\n') {
500 if (c == '\n')
501 file->lineno++;
504 if (c == EOF) {
505 yyerror("unterminated block");
506 return (0);
509 yylval.lineno = file->lineno;
511 if (block != 0 && c == block) {
512 if ((c = lgetc(0)) == '}') {
513 if (block == '!') {
514 block = 0;
515 return ('!');
517 block = 0;
518 return ('}');
520 lungetc(c);
521 c = block;
524 if (in_define && block == 0) {
525 if (c == '{')
526 goto newblock;
528 do {
529 if (starting) {
530 if (c == '!' || c == '{') {
531 lungetc('{');
532 lungetc(c);
533 break;
535 starting = 0;
536 lungetc(c);
537 c = '{';
538 } else if (c == '{') {
539 starting = 1;
540 continue;
543 *p++ = c;
544 if ((size_t)(p - buf) >= sizeof(buf)) {
545 yyerror("string too long");
546 return (findeol());
548 } while ((c = lgetc(0)) != EOF && c != '\n');
549 *p = '\0';
550 if (c == EOF) {
551 yyerror("unterminated block");
552 return (0);
554 if (c == '\n')
555 file->lineno++;
556 if ((yylval.v.string = strdup(buf)) == NULL)
557 err(1, "strdup");
558 return (STRING);
561 if (block == '!') {
562 do {
563 if (ending) {
564 if (c == '}') {
565 lungetc(c);
566 lungetc(block);
567 break;
569 ending = 0;
570 lungetc(c);
571 c = block;
572 } else if (c == '!') {
573 ending = 1;
574 continue;
577 *p++ = c;
578 if ((size_t)(p - buf) >= sizeof(buf)) {
579 yyerror("line too long");
580 return (findeol());
582 } while ((c = lgetc(0)) != EOF && c != '\n');
583 *p = '\0';
585 if (c == EOF) {
586 yyerror("unterminated block");
587 return (0);
589 if (c == '\n')
590 file->lineno++;
592 if ((yylval.v.string = strdup(buf)) == NULL)
593 err(1, "strdup");
594 return (STRING);
597 if (c == '|')
598 return (c);
600 do {
601 if (c == '|') {
602 lungetc(c);
603 break;
606 if (ending) {
607 if (c == '}') {
608 lungetc(c);
609 lungetc('}');
610 break;
612 ending = 0;
613 lungetc(c);
614 c = block;
615 } else if (c == '}') {
616 ending = 1;
617 continue;
620 *p++ = c;
621 if ((size_t)(p - buf) >= sizeof(buf)) {
622 yyerror("string too long");
623 return (findeol());
625 } while ((c = lgetc(0)) != EOF && !isspace((unsigned char)c));
626 *p = '\0';
628 if (c == EOF) {
629 yyerror("unterminated block");
630 return (0);
632 if (c == '\n')
633 file->lineno++;
634 if ((token = lookup(buf)) == STRING)
635 if ((yylval.v.string = strdup(buf)) == NULL)
636 err(1, "strdup");
637 return (token);
640 struct file *
641 pushfile(const char *name, int secret)
643 struct file *nfile;
645 if ((nfile = calloc(1, sizeof(*nfile))) == NULL)
646 err(1, "calloc");
647 if ((nfile->name = strdup(name)) == NULL)
648 err(1, "strdup");
649 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
650 warn("can't open %s", nfile->name);
651 free(nfile->name);
652 free(nfile);
653 return (NULL);
655 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
656 nfile->ungetsize = 16;
657 nfile->ungetbuf = malloc(nfile->ungetsize);
658 if (nfile->ungetbuf == NULL)
659 err(1, "malloc");
660 TAILQ_INSERT_TAIL(&files, nfile, entry);
661 return (nfile);
664 int
665 popfile(void)
667 struct file *prev;
669 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
670 prev->errors += file->errors;
672 TAILQ_REMOVE(&files, file, entry);
673 fclose(file->stream);
674 free(file->name);
675 free(file->ungetbuf);
676 free(file);
677 file = prev;
678 return (file ? 0 : EOF);
681 int
682 parse(const char *filename)
684 if ((file = pushfile(filename, 0)) == 0)
685 return (-1);
686 topfile = file;
688 yyparse();
689 errors = file->errors;
690 popfile();
692 return (errors ? -1 : 0);
695 void
696 dbg(void)
698 if (nodebug)
699 return;
701 if (yylval.lineno == lastline + 1) {
702 lastline = yylval.lineno;
703 return;
705 lastline = yylval.lineno;
707 printf("#line %d ", yylval.lineno);
708 printq(file->name);
709 putchar('\n');
712 void
713 printq(const char *str)
715 putchar('"');
716 for (; *str; ++str) {
717 if (*str == '"')
718 putchar('\\');
719 putchar(*str);
721 putchar('"');