Blob


1 /*
2 * Copyright (c) 2022 Omar Polo <op@openbsd.org>
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(FILE *, 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 FILE *fp;
75 static int block;
76 static int in_define;
77 static int errors;
78 static int lastline = -1;
80 typedef struct {
81 union {
82 char *string;
83 } v;
84 int lineno;
85 } YYSTYPE;
87 %}
89 %token DEFINE ELSE END ERROR FINALLY FOR IF INCLUDE PRINTF
90 %token RENDER TQFOREACH UNSAFE URLESCAPE WHILE
91 %token <v.string> STRING
92 %type <v.string> string nstring
93 %type <v.string> stringy
95 %%
97 grammar : /* empty */
98 | grammar include
99 | grammar verbatim
100 | grammar block
101 | grammar error { file->errors++; }
104 include : INCLUDE STRING {
105 struct file *nfile;
107 if ((nfile = pushfile($2, 0)) == NULL) {
108 yyerror("failed to include file %s", $2);
109 free($2);
110 YYERROR;
112 free($2);
114 file = nfile;
115 lungetc('\n');
119 verbatim : '!' verbatim1 '!' {
120 if (in_define) {
121 /* TODO: check template status and exit in case */
126 verbatim1 : /* empty */
127 | verbatim1 STRING {
128 if (*$2 != '\0') {
129 dbg();
130 fprintf(fp, "%s\n", $2);
132 free($2);
136 verbatims : /* empty */
137 | verbatims verbatim
140 raw : nstring {
141 dbg();
142 fprintf(fp, "if ((tp_ret = tp_write(tp, ");
143 printq($1);
144 fprintf(fp, ", %zu)) == -1) goto err;\n",
145 strlen($1));
147 free($1);
151 block : define body end {
152 fputs("err:\n", fp);
153 fputs("return tp_ret;\n", fp);
154 fputs("}\n", fp);
155 in_define = 0;
157 | define body finally end {
158 fputs("return tp_ret;\n", fp);
159 fputs("}\n", fp);
160 in_define = 0;
164 define : '{' DEFINE string '}' {
165 in_define = 1;
167 dbg();
168 fprintf(fp, "int\n%s\n{\n", $3);
169 fputs("int tp_ret = 0;\n", fp);
171 free($3);
175 body : /* empty */
176 | body verbatim
177 | body raw
178 | body special
181 special : '{' RENDER string '}' {
182 dbg();
183 fprintf(fp, "if ((tp_ret = %s) == -1) goto err;\n",
184 $3);
185 free($3);
187 | printf
188 | if body endif { fputs("}\n", fp); }
189 | loop
190 | '{' string '|' UNSAFE '}' {
191 dbg();
192 fprintf(fp,
193 "if ((tp_ret = tp_writes(tp, %s)) == -1)\n",
194 $2);
195 fputs("goto err;\n", fp);
196 free($2);
198 | '{' string '|' URLESCAPE '}' {
199 dbg();
200 fprintf(fp,
201 "if ((tp_ret = tp_urlescape(tp, %s)) == -1)\n",
202 $2);
203 fputs("goto err;\n", fp);
204 free($2);
206 | '{' string '}' {
207 dbg();
208 fprintf(fp,
209 "if ((tp_ret = tp_htmlescape(tp, %s)) == -1)\n",
210 $2);
211 fputs("goto err;\n", fp);
212 free($2);
216 printf : '{' PRINTF {
217 dbg();
218 fprintf(fp, "if (asprintf(&tp->tp_tmp, ");
219 } printfargs '}' {
220 fputs(") == -1)\n", fp);
221 fputs("goto err;\n", fp);
222 fputs("if ((tp_ret = tp_htmlescape(tp, tp->tp_tmp)) "
223 "== -1)\n", fp);
224 fputs("goto err;\n", fp);
225 fputs("free(tp->tp_tmp);\n", fp);
226 fputs("tp->tp_tmp = NULL;\n", fp);
230 printfargs : /* empty */
231 | printfargs STRING {
232 fprintf(fp, " %s", $2);
233 free($2);
237 if : '{' IF stringy '}' {
238 dbg();
239 fprintf(fp, "if (%s) {\n", $3);
240 free($3);
244 endif : end
245 | else body end
246 | elsif body endif
249 elsif : '{' ELSE IF stringy '}' {
250 dbg();
251 fprintf(fp, "} else if (%s) {\n", $4);
252 free($4);
256 else : '{' ELSE '}' {
257 dbg();
258 fputs("} else {\n", fp);
262 loop : '{' FOR stringy '}' {
263 fprintf(fp, "for (%s) {\n", $3);
264 free($3);
265 } body end {
266 fputs("}\n", fp);
268 | '{' TQFOREACH STRING STRING STRING '}' {
269 fprintf(fp, "TAILQ_FOREACH(%s, %s, %s) {\n",
270 $3, $4, $5);
271 free($3);
272 free($4);
273 free($5);
274 } body end {
275 fputs("}\n", fp);
277 | '{' WHILE stringy '}' {
278 fprintf(fp, "while (%s) {\n", $3);
279 free($3);
280 } body end {
281 fputs("}\n", fp);
285 end : '{' END '}'
288 finally : '{' FINALLY '}' {
289 dbg();
290 fputs("err:\n", fp);
291 } verbatims
294 nstring : STRING nstring {
295 if (asprintf(&$$, "%s%s", $1, $2) == -1)
296 err(1, "asprintf");
297 free($1);
298 free($2);
300 | STRING
303 string : STRING string {
304 if (asprintf(&$$, "%s %s", $1, $2) == -1)
305 err(1, "asprintf");
306 free($1);
307 free($2);
309 | STRING
312 stringy : STRING
313 | STRING stringy {
314 if (asprintf(&$$, "%s %s", $1, $2) == -1)
315 err(1, "asprintf");
316 free($1);
317 free($2);
319 | '|' stringy {
320 if (asprintf(&$$, "|%s", $2) == -1)
321 err(1, "asprintf");
322 free($2);
326 %%
328 struct keywords {
329 const char *k_name;
330 int k_val;
331 };
333 int
334 yyerror(const char *fmt, ...)
336 va_list ap;
337 char *msg;
339 file->errors++;
340 va_start(ap, fmt);
341 if (vasprintf(&msg, fmt, ap) == -1)
342 err(1, "yyerror vasprintf");
343 va_end(ap);
344 fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg);
345 free(msg);
346 return (0);
349 int
350 kw_cmp(const void *k, const void *e)
352 return (strcmp(k, ((const struct keywords *)e)->k_name));
355 int
356 lookup(char *s)
358 /* this has to be sorted always */
359 static const struct keywords keywords[] = {
360 { "define", DEFINE },
361 { "else", ELSE },
362 { "end", END },
363 { "finally", FINALLY },
364 { "for", FOR },
365 { "if", IF },
366 { "include", INCLUDE },
367 { "printf", PRINTF },
368 { "render", RENDER },
369 { "tailq-foreach", TQFOREACH },
370 { "unsafe", UNSAFE },
371 { "urlescape", URLESCAPE },
372 { "while", WHILE },
373 };
374 const struct keywords *p;
376 p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]),
377 kw_cmp);
379 if (p)
380 return (p->k_val);
381 else
382 return (STRING);
385 #define START_EXPAND 1
386 #define DONE_EXPAND 2
388 static int expanding;
390 int
391 igetc(void)
393 int c;
395 while (1) {
396 if (file->ungetpos > 0)
397 c = file->ungetbuf[--file->ungetpos];
398 else
399 c = getc(file->stream);
401 if (c == START_EXPAND)
402 expanding = 1;
403 else if (c == DONE_EXPAND)
404 expanding = 0;
405 else
406 break;
408 return (c);
411 int
412 lgetc(int quotec)
414 int c;
416 if (quotec) {
417 if ((c = igetc()) == EOF) {
418 yyerror("reached end of filewhile parsing "
419 "quoted string");
420 if (file == topfile || popfile() == EOF)
421 return (EOF);
422 return (quotec);
424 return (c);
427 c = igetc();
428 if (c == '\t' || c == ' ') {
429 /* Compress blanks to a sigle space. */
430 do {
431 c = getc(file->stream);
432 } while (c == '\t' || c == ' ');
433 ungetc(c, file->stream);
434 c = ' ';
437 if (c == EOF) {
438 /*
439 * Fake EOL when hit EOF for the first time. This gets line
440 * count right if last line in included file is syntactically
441 * invalid and has no newline.
442 */
443 if (file->eof_reached == 0) {
444 file->eof_reached = 1;
445 return ('\n');
447 while (c == EOF) {
448 if (file == topfile || popfile() == EOF)
449 return (EOF);
450 c = igetc();
453 return (c);
456 void
457 lungetc(int c)
459 if (c == EOF)
460 return;
462 if (file->ungetpos >= file->ungetsize) {
463 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
464 if (p == NULL)
465 err(1, "reallocarray");
466 file->ungetbuf = p;
467 file->ungetsize *= 2;
469 file->ungetbuf[file->ungetpos++] = c;
472 int
473 findeol(void)
475 int c;
477 /* skip to either EOF or the first real EOL */
478 while (1) {
479 c = lgetc(0);
480 if (c == '\n') {
481 file->lineno++;
482 break;
484 if (c == EOF)
485 break;
487 return (ERROR);
490 int
491 yylex(void)
493 char buf[8096];
494 char *p = buf;
495 int c;
496 int token;
497 int starting = 0;
498 int ending = 0;
499 int quote = 0;
501 if (!in_define && block == 0) {
502 while ((c = lgetc(0)) != '{' && c != EOF) {
503 if (c == '\n')
504 file->lineno++;
507 if (c == EOF)
508 return (0);
510 newblock:
511 c = lgetc(0);
512 if (c == '{' || c == '!') {
513 if (c == '{')
514 block = '}';
515 else
516 block = c;
517 return (c);
519 if (c == '\n')
520 file->lineno++;
523 while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\n') {
524 if (c == '\n')
525 file->lineno++;
528 if (c == EOF) {
529 yyerror("unterminated block");
530 return (0);
533 yylval.lineno = file->lineno;
535 if (block != 0 && c == block) {
536 if ((c = lgetc(0)) == '}') {
537 if (block == '!') {
538 block = 0;
539 return ('!');
541 block = 0;
542 return ('}');
544 lungetc(c);
545 c = block;
548 if (in_define && block == 0) {
549 if (c == '{')
550 goto newblock;
552 do {
553 if (starting) {
554 if (c == '!' || c == '{') {
555 lungetc(c);
556 lungetc('{');
557 break;
559 starting = 0;
560 lungetc(c);
561 c = '{';
562 } else if (c == '{') {
563 starting = 1;
564 continue;
565 } else if (c == '\n')
566 break;
568 *p++ = c;
569 if ((size_t)(p - buf) >= sizeof(buf)) {
570 yyerror("string too long");
571 return (findeol());
573 } while ((c = lgetc(0)) != EOF);
574 *p = '\0';
575 if (c == EOF) {
576 yyerror("unterminated block");
577 return (0);
579 if (c == '\n')
580 file->lineno++;
581 if ((yylval.v.string = strdup(buf)) == NULL)
582 err(1, "strdup");
583 return (STRING);
586 if (block == '!') {
587 do {
588 if (ending) {
589 if (c == '}') {
590 lungetc(c);
591 lungetc(block);
592 break;
594 ending = 0;
595 lungetc(c);
596 c = block;
597 } else if (c == '!') {
598 ending = 1;
599 continue;
600 } else if (c == '\n')
601 break;
603 *p++ = c;
604 if ((size_t)(p - buf) >= sizeof(buf)) {
605 yyerror("line too long");
606 return (findeol());
608 } while ((c = lgetc(0)) != EOF);
609 *p = '\0';
611 if (c == EOF) {
612 yyerror("unterminated block");
613 return (0);
615 if (c == '\n')
616 file->lineno++;
618 if ((yylval.v.string = strdup(buf)) == NULL)
619 err(1, "strdup");
620 return (STRING);
623 if (c == '|')
624 return (c);
626 do {
627 if (!quote && isspace((unsigned char)c))
628 break;
630 if (c == '"')
631 quote = !quote;
633 if (!quote && c == '|') {
634 lungetc(c);
635 break;
638 if (ending) {
639 if (c == '}') {
640 lungetc(c);
641 lungetc('}');
642 break;
644 ending = 0;
645 lungetc(c);
646 c = block;
647 } else if (!quote && c == '}') {
648 ending = 1;
649 continue;
652 *p++ = c;
653 if ((size_t)(p - buf) >= sizeof(buf)) {
654 yyerror("string too long");
655 return (findeol());
657 } while ((c = lgetc(0)) != EOF);
658 *p = '\0';
660 if (c == EOF) {
661 yyerror(quote ? "unterminated quote" : "unterminated block");
662 return (0);
664 if (c == '\n')
665 file->lineno++;
666 if ((token = lookup(buf)) == STRING)
667 if ((yylval.v.string = strdup(buf)) == NULL)
668 err(1, "strdup");
669 return (token);
672 struct file *
673 pushfile(const char *name, int secret)
675 struct file *nfile;
677 if ((nfile = calloc(1, sizeof(*nfile))) == NULL)
678 err(1, "calloc");
679 if ((nfile->name = strdup(name)) == NULL)
680 err(1, "strdup");
681 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
682 warn("can't open %s", nfile->name);
683 free(nfile->name);
684 free(nfile);
685 return (NULL);
687 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
688 nfile->ungetsize = 16;
689 nfile->ungetbuf = malloc(nfile->ungetsize);
690 if (nfile->ungetbuf == NULL)
691 err(1, "malloc");
692 TAILQ_INSERT_TAIL(&files, nfile, entry);
693 return (nfile);
696 int
697 popfile(void)
699 struct file *prev;
701 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
702 prev->errors += file->errors;
704 TAILQ_REMOVE(&files, file, entry);
705 fclose(file->stream);
706 free(file->name);
707 free(file->ungetbuf);
708 free(file);
709 file = prev;
710 return (file ? 0 : EOF);
713 int
714 parse(FILE *outfile, const char *filename)
716 fp = outfile;
718 if ((file = pushfile(filename, 0)) == 0)
719 return (-1);
720 topfile = file;
722 yyparse();
723 errors = file->errors;
724 popfile();
726 return (errors ? -1 : 0);
729 void
730 dbg(void)
732 if (nodebug)
733 return;
735 if (yylval.lineno == lastline + 1) {
736 lastline = yylval.lineno;
737 return;
739 lastline = yylval.lineno;
741 fprintf(fp, "#line %d ", yylval.lineno);
742 printq(file->name);
743 putc('\n', fp);
746 void
747 printq(const char *str)
749 putc('"', fp);
750 for (; *str; ++str) {
751 if (*str == '"')
752 putc('\\', fp);
753 putc(*str, fp);
755 putc('"', fp);