Annotation of embedaddon/libpdel/tmpl/tmpl_parse.c, revision 1.1.1.1
1.1 misho 1:
2: /*
3: * Copyright (c) 2001-2002 Packet Design, LLC.
4: * All rights reserved.
5: *
6: * Subject to the following obligations and disclaimer of warranty,
7: * use and redistribution of this software, in source or object code
8: * forms, with or without modifications are expressly permitted by
9: * Packet Design; provided, however, that:
10: *
11: * (i) Any and all reproductions of the source or object code
12: * must include the copyright notice above and the following
13: * disclaimer of warranties; and
14: * (ii) No rights are granted, in any manner or form, to use
15: * Packet Design trademarks, including the mark "PACKET DESIGN"
16: * on advertising, endorsements, or otherwise except as such
17: * appears in the above copyright notice or in the software.
18: *
19: * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
20: * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
21: * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
22: * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
23: * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
24: * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
25: * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
26: * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
27: * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
28: * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
29: * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
30: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
31: * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
32: * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
33: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
35: * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
36: * THE POSSIBILITY OF SUCH DAMAGE.
37: *
38: * Author: Archie Cobbs <archie@freebsd.org>
39: */
40:
41: #include "tmpl_internal.h"
42: #ifdef __FreeBSD__
43: #include <machine/param.h>
44: #endif
45:
46: #ifndef __FreeBSD__
47: #define __printflike(x,y)
48: #endif
49:
50: #define isidchar(c) (isalnum(c) || (c) == '_')
51:
52: /* Parsing */
53: static int parse_function(struct tmpl *tmpl, FILE *input,
54: struct func_call *call, int special_ok);
55: static int parse_argument(struct tmpl *tmpl, FILE *input,
56: struct func_arg *arg);
57: static int set_text(struct tmpl *tmpl, FILE *input, int nl_white,
58: struct tmpl_elem *elem, off_t start, off_t end);
59: static int add_elem(struct tmpl *tmpl);
60:
61: /* Semantic analysis */
62: static int scan_elements(struct tmpl *tmpl,
63: int start, int in_loop, int *num_errors);
64: static int set_error(struct tmpl *tmpl, struct func_call *call,
65: const char *fmt, ...) __printflike(3, 4);
66:
67: /* Memory management */
68: static void _tmpl_compact_func(const char *mtype,
69: struct func_call *call, char *mem, size_t *bsize);
70: static void _tmpl_compact_arg(const char *mtype,
71: struct func_arg *arg, char *mem, size_t *bsize);
72:
73: /* Built-in function handlers */
74: static tmpl_handler_t add_func;
75: static tmpl_handler_t and_func;
76: static tmpl_handler_t atsign_func;
77: static tmpl_handler_t cat_func;
78: static tmpl_handler_t div_func;
79: static tmpl_handler_t equal_func;
80: static tmpl_handler_t error_func;
81: static tmpl_handler_t eval_func;
82: static tmpl_handler_t flush_func;
83: static tmpl_handler_t ge_func;
84: static tmpl_handler_t get_func;
85: static tmpl_handler_t gt_func;
86: static tmpl_handler_t htmlencode_func;
87: static tmpl_handler_t invoke_func;
88: static tmpl_handler_t le_func;
89: static tmpl_handler_t loopindex_func;
90: static tmpl_handler_t lt_func;
91: static tmpl_handler_t mod_func;
92: static tmpl_handler_t mul_func;
93: static tmpl_handler_t not_func;
94: static tmpl_handler_t or_func;
95: static tmpl_handler_t output_func;
96: static tmpl_handler_t set_func;
97: static tmpl_handler_t sub_func;
98: static tmpl_handler_t urlencode_func;
99:
100: #if TMPL_DEBUG
101: /* Debugging */
102: static char *funcstr(const char *mtype, const struct func_call *call);
103: static char *argstr(const char *mtype, const struct func_arg *arg);
104: static const char *typestr(const struct tmpl_elem *elem);
105: #endif /* TMPL_DEBUG */
106:
107: /*
108: * Built-in functions
109: */
110: struct builtin_info {
111: const char *name;
112: enum func_type type;
113: tmpl_handler_t *handler;
114: int min_args;
115: int max_args;
116: };
117:
118: static const char tmpl_function_string[] = { TMPL_FUNCTION_CHARACTER, '\0' };
119:
120: static const struct builtin_info builtin_funcs[] = {
121: { "loop", TY_LOOP, NULL, 1, 1 },
122: { "endloop", TY_ENDLOOP, NULL, 0, 0 },
123: { "while", TY_WHILE, NULL, 1, 1 },
124: { "endwhile", TY_ENDWHILE, NULL, 0, 0 },
125: { "if", TY_IF, NULL, 1, 1 },
126: { "elif", TY_ELIF, NULL, 1, 1 },
127: { "else", TY_ELSE, NULL, 0, 0 },
128: { "endif", TY_ENDIF, NULL, 0, 0 },
129: { "define", TY_DEFINE, NULL, 1, 1 },
130: { "enddef", TY_ENDDEF, NULL, 0, 0 },
131: { "continue", TY_CONTINUE, NULL, 0, 0 },
132: { "break", TY_BREAK, NULL, 0, 0 },
133: { "return", TY_RETURN, NULL, 0, 1 },
134: { "equal", TY_NORMAL, equal_func, 2, 2 },
135: { "not", TY_NORMAL, not_func, 1, 1 },
136: { "and", TY_NORMAL, and_func, 1, INT_MAX },
137: { "or", TY_NORMAL, or_func, 1, INT_MAX },
138: { "add", TY_NORMAL, add_func, 1, INT_MAX },
139: { "sub", TY_NORMAL, sub_func, 1, INT_MAX },
140: { "mul", TY_NORMAL, mul_func, 2, INT_MAX },
141: { "div", TY_NORMAL, div_func, 2, 2 },
142: { "mod", TY_NORMAL, mod_func, 2, 2 },
143: { "lt", TY_NORMAL, lt_func, 2, 2 },
144: { "gt", TY_NORMAL, gt_func, 2, 2 },
145: { "le", TY_NORMAL, le_func, 2, 2 },
146: { "ge", TY_NORMAL, ge_func, 2, 2 },
147: { "eval", TY_NORMAL, eval_func, 1, 1 },
148: { "invoke", TY_NORMAL, invoke_func, 0, 0 },
149: { "cat", TY_NORMAL, cat_func, 0, INT_MAX },
150: { "error", TY_NORMAL, error_func, 1, 1 },
151: { "loopindex", TY_NORMAL, loopindex_func, 0, 1 },
152: { "get", TY_NORMAL, get_func, 1, 1 },
153: { "set", TY_NORMAL, set_func, 2, 2 },
154: { "urlencode", TY_NORMAL, urlencode_func, 1, 1 },
155: { "htmlencode", TY_NORMAL, htmlencode_func, 1, 1 },
156: { "flush", TY_NORMAL, flush_func, 0, 0 },
157: { "output", TY_NORMAL, output_func, 1, 1 },
158: { tmpl_function_string,
159: TY_NORMAL, atsign_func, 0, 0 },
160: { NULL, 0, NULL, 0, 0 }
161: };
162:
163: /************************************************************************
164: * PARSING *
165: ************************************************************************/
166:
167: /* Cleanup info */
168: struct tmpl_parse_info {
169: struct tmpl *tmpl;
170: struct func_call call;
171: };
172:
173: /* Internal functions */
174: static void tmpl_parse_cleanup(void *arg);
175:
176: /*
177: * Parse an input stream.
178: */
179: int
180: _tmpl_parse(struct tmpl *tmpl, FILE *input, int *num_errors)
181: {
182: struct tmpl_parse_info info;
183: struct tmpl_elem *elem = NULL;
184: int nl_white = 0;
185: off_t start = 0;
186: int rtn = -1;
187: size_t bsize;
188: off_t pos;
189: int ch;
190: #if TMPL_DEBUG
191: int i;
192: #endif
193:
194: /* Set up cleanup in case of thread cancellation */
195: info.tmpl = tmpl;
196: memset(&info.call, 0, sizeof(info.call));
197: pthread_cleanup_push(tmpl_parse_cleanup, &info);
198:
199: /* Save starting position */
200: if ((pos = ftello(input)) == -1)
201: goto fail;
202:
203: /* Read lines and parse input into elements */
204: *num_errors = 0;
205: while ((ch = getc(input)) != EOF) {
206: off_t end_func;
207:
208: /* Create a new element if necessary */
209: if (elem == NULL) {
210: if (add_elem(tmpl) == -1)
211: goto fail;
212: elem = &tmpl->elems[tmpl->num_elems - 1];
213: start = pos;
214: nl_white = 1;
215: }
216:
217: /* Look for the initial character of a function invocation */
218: if (ch != TMPL_FUNCTION_CHARACTER) {
219: if (nl_white && (!isspace(ch)
220: || (pos == start && ch != '\n')))
221: nl_white = 0;
222: pos++; /* more normal text */
223: continue;
224: }
225:
226: /* Try to parse a function */
227: if (parse_function(tmpl, input, &info.call, 1) == -1) {
228: if (errno == EINVAL) {
229: (*num_errors)++;
230: nl_white = 0;
231: pos++;
232: continue; /* ignore parse error */
233: }
234: goto fail;
235: }
236:
237: /* Remember input position just past the end of the function */
238: if ((end_func = ftello(input)) == -1) {
239: _tmpl_free_call(tmpl->mtype, &info.call);
240: goto fail;
241: }
242:
243: /*
244: * We parsed a function. Terminate the previous text
245: * element (if not empty) and add the function element.
246: */
247: if (start != pos) {
248: if (set_text(tmpl, input,
249: nl_white, elem, start, pos) == -1) {
250: _tmpl_free_call(tmpl->mtype, &info.call);
251: goto fail;
252: }
253: if (add_elem(tmpl) == -1) {
254: _tmpl_free_call(tmpl->mtype, &info.call);
255: goto fail;
256: }
257: elem = &tmpl->elems[tmpl->num_elems - 1];
258: nl_white = 1;
259: }
260: elem->call = info.call;
261: memset(&info.call, 0, sizeof(info.call));
262:
263: /* Reset input position just past the end of the function */
264: pos = end_func;
265: if (fseeko(input, pos, SEEK_SET) == -1)
266: goto fail;
267:
268: /* Force a new text element to start */
269: elem = NULL;
270: }
271:
272: /* Terminate last text element, if any */
273: if (elem != NULL
274: && set_text(tmpl, input, nl_white, elem, start, pos) == -1)
275: goto fail;
276:
277: #if TMPL_DEBUG
278: DBG("Initial parse results: %d elements\n", tmpl->num_elems);
279: for (i = 0; i < tmpl->num_elems; i++)
280: DBG("%4d: %s\n", i, _tmpl_elemstr(&tmpl->elems[i], i));
281: #endif
282:
283: /* Fill in semantic data */
284: if (scan_elements(tmpl, -1, 0, num_errors) == -1)
285: goto fail;
286:
287: #if TMPL_DEBUG
288: DBG("After semantic check:\n");
289: for (i = 0; i < tmpl->num_elems; i++)
290: DBG("%4d: %s\n", i, _tmpl_elemstr(&tmpl->elems[i], i));
291: #endif
292:
293: /* Compress all static information into a single memory block */
294: bsize = tmpl->num_elems * sizeof(*tmpl->elems);
295: _tmpl_compact_elems(tmpl->mtype,
296: tmpl->elems, tmpl->num_elems, NULL, &bsize);
297: if ((tmpl->eblock = MALLOC(tmpl->mtype, bsize)) == NULL)
298: goto fail;
299: bsize = 0;
300: _tmpl_compact(tmpl->mtype, tmpl->eblock, &tmpl->elems,
301: tmpl->num_elems * sizeof(*tmpl->elems), &bsize);
302: _tmpl_compact_elems(tmpl->mtype, tmpl->elems,
303: tmpl->num_elems, tmpl->eblock, &bsize);
304:
305: /* Success */
306: rtn = 0;
307:
308: fail:;
309: /* Done */
310: pthread_cleanup_pop(0);
311: return (rtn);
312: }
313:
314: /*
315: * Cleanup for _tmpl_parse()
316: */
317: static void
318: tmpl_parse_cleanup(void *arg)
319: {
320: struct tmpl_parse_info *const info = arg;
321:
322: _tmpl_free_call(info->tmpl->mtype, &info->call);
323: }
324:
325: /*
326: * Traverse parsed elements and size/move all allocated memory.
327: */
328: void
329: _tmpl_compact_elems(const char *mtype, struct tmpl_elem *elems,
330: int num_elems, char *mem, size_t *bsize)
331: {
332: int i;
333:
334: for (i = 0; i < num_elems; i++) {
335: struct tmpl_elem *const elem = &elems[i];
336:
337: if (elem->text != NULL) {
338: if ((elem->flags & TMPL_ELEM_MMAP_TEXT) == 0) {
339: _tmpl_compact(mtype, mem,
340: &elem->text, elem->len, bsize);
341: }
342: } else {
343: *bsize = ALIGN(*bsize);
344: _tmpl_compact_func(mtype, &elem->call, mem, bsize);
345: }
346: }
347: }
348:
349: /*
350: * Traverse function call and size/move all allocated memory.
351: */
352: static void
353: _tmpl_compact_func(const char *mtype,
354: struct func_call *call, char *mem, size_t *bsize)
355: {
356: int i;
357:
358: /* Do call function name */
359: _tmpl_compact(mtype, mem,
360: &call->funcname, strlen(call->funcname) + 1, bsize);
361:
362: /* Do call arguments array */
363: *bsize = ALIGN(*bsize);
364: _tmpl_compact(mtype, mem, &call->args,
365: call->nargs * sizeof(*call->args), bsize);
366:
367: /* Do call arguments */
368: for (i = 0; i < call->nargs; i++)
369: _tmpl_compact_arg(mtype, &call->args[i], mem, bsize);
370: }
371:
372: /*
373: * Traverse function call argument and size/move all allocated memory.
374: */
375: static void
376: _tmpl_compact_arg(const char *mtype,
377: struct func_arg *arg, char *mem, size_t *bsize)
378: {
379: if (arg->is_literal) {
380: _tmpl_compact(mtype, mem, &arg->u.literal,
381: strlen(arg->u.literal) + 1, bsize);
382: } else
383: _tmpl_compact_func(mtype, &arg->u.call, mem, bsize);
384: }
385:
386: /*
387: * Compact a region of memory
388: */
389: void
390: _tmpl_compact(const char *mtype, char *mem,
391: void *ptrp, size_t len, size_t *bsize)
392: {
393: void **const pp = ptrp;
394:
395: if (mem != NULL) {
396: memcpy(mem + *bsize, *pp, len);
397: FREE(mtype, *pp);
398: *pp = mem + *bsize;
399: }
400: *bsize += len;
401: }
402:
403: /*
404: * Finalize a text parse element.
405: */
406: static int
407: set_text(struct tmpl *tmpl, FILE *input, int nl_white,
408: struct tmpl_elem *elem, off_t start, off_t end)
409: {
410: const u_int len = end - start;
411: int ch;
412: int i;
413:
414: assert(elem->flags == 0);
415: if (tmpl->mmap_addr != NULL) {
416: elem->text = (char *)tmpl->mmap_addr + start;
417: elem->flags |= TMPL_ELEM_MMAP_TEXT;
418: } else {
419: if ((elem->text = MALLOC(tmpl->mtype, len)) == NULL)
420: return (-1);
421: if (fseeko(input, start, SEEK_SET) == -1)
422: return (-1);
423: for (i = 0; i < len; i++) {
424: if ((ch = getc(input)) == EOF) {
425: if (!ferror(input))
426: errno = EIO;
427: return (-1);
428: }
429: elem->text[i] = (char)ch;
430: }
431: }
432: if (nl_white)
433: elem->flags |= TMPL_ELEM_NL_WHITE;
434: elem->len = len;
435: return (0);
436: }
437:
438: /*
439: * Grow an array of elements by one. Secretly we allocate in
440: * chunks of size 'chunk'.
441: */
442: static int
443: add_elem(struct tmpl *tmpl)
444: {
445: static const int chunk = 128;
446: void *new_array;
447:
448: if (tmpl->num_elems % chunk != 0) {
449: tmpl->num_elems++;
450: return (0);
451: }
452: if ((new_array = REALLOC(tmpl->mtype, tmpl->elems,
453: (tmpl->num_elems + chunk) * sizeof(*tmpl->elems))) == NULL)
454: return (-1);
455: tmpl->elems = new_array;
456: memset(&tmpl->elems[tmpl->num_elems], 0, chunk * sizeof(*tmpl->elems));
457: tmpl->num_elems++;
458: return (0);
459: }
460:
461: /*
462: * Parse a function call. The input stream is assumed to be pointing
463: * just past the '@'. Upon return it will be pointing to the character
464: * after the function call if successful, otherwise it will be reset
465: * to where it started from.
466: *
467: * If errno is EINVAL upon return, then there was a parse error.
468: */
469: static int
470: parse_function(struct tmpl *tmpl, FILE *input,
471: struct func_call *call, int special_ok)
472: {
473: const struct builtin_info *builtin;
474: off_t start_pos;
475: int errno_save;
476: int noargs = 0;
477: void *mem;
478: int len;
479: int ch;
480:
481: /* Save starting position */
482: memset(call, 0, sizeof(*call));
483: if ((start_pos = ftello(input)) == -1)
484: return (-1);
485:
486: /* Get function name; special case for "@@" */
487: if ((ch = getc(input)) == TMPL_FUNCTION_CHARACTER)
488: len = 1;
489: else {
490: for (len = 0; ch != EOF && isidchar(ch); len++)
491: ch = getc(input);
492: if (len == 0)
493: goto parse_err;
494: }
495:
496: /* Re-scan function name into malloc'd buffer */
497: if ((call->funcname = MALLOC(tmpl->mtype, len + 1)) == NULL)
498: goto parse_err;
499: if (fseeko(input, start_pos, SEEK_SET) == -1)
500: goto sys_err;
501: if (fread(call->funcname, 1, len, input) != len) {
502: if (!ferror(input))
503: goto parse_err;
504: goto sys_err;
505: }
506: call->funcname[len] = '\0';
507:
508: /* Check for built-in function; some do not take arguments */
509: for (builtin = builtin_funcs; builtin->name != NULL; builtin++) {
510: if (strcmp(call->funcname, builtin->name) == 0) {
511: if (builtin->min_args == 0)
512: noargs = 1;
513: break;
514: }
515: }
516: ch = getc(input);
517: if (noargs && ch != '(') {
518: if (ch != EOF)
519: ungetc(ch, input);
520: goto got_args;
521: }
522:
523: /* Find opening parenthesis */
524: while (isspace(ch))
525: ch = getc(input);
526: if (ch != '(')
527: goto parse_err;
528:
529: /* Parse function call arguments */
530: while (1) {
531: ch = getc(input);
532: while (isspace(ch)) /* skip white space */
533: ch = getc(input);
534: if (ch == ')') /* no more arguments */
535: break;
536: if (call->nargs > 0) {
537: if (ch != ',') /* eat comma */
538: goto parse_err;
539: ch = getc(input);
540: while (isspace(ch)) /* skip white space */
541: ch = getc(input);
542: }
543: ungetc(ch, input);
544: if ((mem = REALLOC(tmpl->mtype, call->args,
545: (call->nargs + 1) * sizeof(*call->args))) == NULL)
546: goto sys_err;
547: call->args = mem;
548: if (parse_argument(tmpl, input, &call->args[call->nargs]) == -1)
549: goto sys_err;
550: call->nargs++;
551: }
552:
553: got_args:
554: /* Set up function call for this function */
555: if (builtin->name != NULL) {
556: if (call->nargs < builtin->min_args) {
557: if (set_error(tmpl, call,
558: "at %s %d argument%s %s for \"%c%s\"",
559: "least", builtin->min_args,
560: builtin->min_args == 1 ? "" : "s",
561: "required", TMPL_FUNCTION_CHARACTER,
562: builtin->name) == -1)
563: goto sys_err;
564: return (0);
565: }
566: if (call->nargs > builtin->max_args) {
567: if (set_error(tmpl, call,
568: "at %s %d argument%s %s for \"%c%s\"",
569: "most", builtin->max_args,
570: builtin->max_args == 1 ? "" : "s",
571: "allowed", TMPL_FUNCTION_CHARACTER,
572: builtin->name) == -1)
573: goto sys_err;
574: return (0);
575: }
576: if (!special_ok && builtin->handler == NULL) {
577: if (set_error(tmpl, call, "illegal nested \"%c%s\"",
578: TMPL_FUNCTION_CHARACTER, builtin->name) == -1)
579: goto sys_err;
580: return (0);
581: }
582: call->type = builtin->type;
583: call->handler = builtin->handler;
584: } else {
585: call->type = TY_NORMAL;
586: call->handler = NULL;
587: }
588: return (0);
589:
590: /* Error */
591: parse_err:
592: errno = EINVAL;
593: sys_err:
594: _tmpl_free_call(tmpl->mtype, call);
595: errno_save = errno;
596: if (fseeko(input, start_pos, SEEK_SET) == -1)
597: return (-1);
598: errno = errno_save;
599: return (-1);
600: }
601:
602: /*
603: * Parse a function argument. The input stream is assumed to be pointing
604: * at the first non-white space character. Upon return it will be
605: * pointing to the character after the argument if successful,
606: * otherwise it will be reset to where it started.
607: *
608: * If errno is EINVAL upon return, then there was a parse error.
609: */
610: static int
611: parse_argument(struct tmpl *tmpl, FILE *input, struct func_arg *arg)
612: {
613: off_t start_pos;
614:
615: /* Save starting position */
616: memset(arg, 0, sizeof(*arg));
617: if ((start_pos = ftello(input)) == -1)
618: return (-1);
619:
620: /* Parse argument */
621: switch (getc(input)) {
622: case '"':
623: arg->is_literal = 1;
624: if ((arg->u.literal = string_dequote(input,
625: tmpl->mtype)) == NULL)
626: return (-1);
627: break;
628: case TMPL_FUNCTION_CHARACTER:
629: arg->is_literal = 0;
630: if (parse_function(tmpl, input, &arg->u.call, 0) == -1)
631: return (-1);
632: break;
633: default:
634: if (fseeko(input, start_pos, SEEK_SET) != -1)
635: errno = EINVAL;
636: return (-1);
637: }
638: return (0);
639: }
640:
641: /************************************************************************
642: * SEMANTIC ANALYSIS *
643: ************************************************************************/
644:
645: /*
646: * Scan an element list. Returns -1 if error.
647: */
648: static int
649: scan_elements(struct tmpl *tmpl, int start, int in_loop, int *num_errors)
650: {
651: struct tmpl_elem *const elem = start == -1 ? NULL : &tmpl->elems[start];
652: int ret = 0;
653: int i;
654:
655: assert (elem == NULL || elem->call.type != TY_NORMAL);
656: for (i = start + 1; i < tmpl->num_elems; i++) {
657: struct tmpl_elem *const elem2 = &tmpl->elems[i];
658:
659: if (elem2->text != NULL)
660: continue;
661: switch (elem2->call.type) {
662: case TY_NORMAL:
663: break;
664: case TY_LOOP:
665: if ((i = scan_elements(tmpl, i, 1, num_errors)) == -1)
666: return (-1);
667: break;
668: case TY_WHILE:
669: if ((i = scan_elements(tmpl, i, 1, num_errors)) == -1)
670: return (-1);
671: break;
672: case TY_IF:
673: elem2->u.u_if.elsie = -1;
674: if ((i = scan_elements(tmpl,
675: i, in_loop, num_errors)) == -1)
676: return (-1);
677: break;
678: case TY_DEFINE:
679: if ((i = scan_elements(tmpl, i, 0, num_errors)) == -1)
680: return (-1);
681: break;
682: case TY_ENDLOOP:
683: if (!elem || elem->call.type != TY_LOOP)
684: goto unexpected;
685: elem->u.u_loop.endloop = i - start;
686: return (i);
687: case TY_ENDWHILE:
688: if (!elem || elem->call.type != TY_WHILE)
689: goto unexpected;
690: elem->u.u_while.endwhile = i - start;
691: return (i);
692: case TY_ELSE:
693: case TY_ELIF:
694: if (!elem
695: || (elem->call.type != TY_IF
696: && elem->call.type != TY_ELIF))
697: goto unexpected;
698: if (elem->u.u_if.elsie != -1)
699: goto unexpected;
700: elem->u.u_if.elsie = i - start;
701: if (elem2->call.type == TY_ELIF) {
702: elem2->u.u_if.elsie = -1;
703: if ((i = scan_elements(tmpl,
704: i, in_loop, num_errors)) == -1)
705: return (-1);
706: if (tmpl->elems[i].call.type == TY_ENDIF) {
707: elem->u.u_if.endif = i - start;
708: return (i);
709: }
710: }
711: break;
712: case TY_ENDIF:
713: if (!elem
714: || (elem->call.type != TY_IF
715: && elem->call.type != TY_ELIF
716: && elem->call.type != TY_ELSE))
717: goto unexpected;
718: elem->u.u_if.endif = i - start;
719: return (i);
720: case TY_ENDDEF:
721: if (!elem || elem->call.type != TY_DEFINE)
722: goto unexpected;
723: elem->u.u_define.enddef = i - start;
724: return (i);
725: case TY_BREAK:
726: case TY_CONTINUE:
727: if (!in_loop)
728: goto unexpected;
729: break;
730: case TY_RETURN:
731: break;
732: default:
733: assert(0);
734: }
735: continue;
736: unexpected:
737: {
738: char *fname;
739:
740: (*num_errors)++;
741: if ((fname = STRDUP(tmpl->mtype,
742: elem2->call.funcname)) == NULL)
743: return (-1);
744: if (set_error(tmpl, &elem2->call, "unexpected %c%s",
745: TMPL_FUNCTION_CHARACTER, fname) == -1) {
746: FREE(tmpl->mtype, fname);
747: return (-1);
748: }
749: FREE(tmpl->mtype, fname);
750: }
751: }
752:
753: /* We ran out of elements */
754: if (elem == NULL)
755: return (0);
756: (*num_errors)++;
757: switch (elem->call.type) {
758: case TY_LOOP:
759: ret = set_error(tmpl, &elem->call, "%cloop without %cendloop",
760: TMPL_FUNCTION_CHARACTER, TMPL_FUNCTION_CHARACTER);
761: break;
762: case TY_WHILE:
763: ret = set_error(tmpl, &elem->call, "%cwhile without %cendwhile",
764: TMPL_FUNCTION_CHARACTER, TMPL_FUNCTION_CHARACTER);
765: break;
766: case TY_IF:
767: case TY_ELSE:
768: case TY_ELIF:
769: ret = set_error(tmpl, &elem->call, "%cif without %cendif",
770: TMPL_FUNCTION_CHARACTER, TMPL_FUNCTION_CHARACTER);
771: break;
772: case TY_DEFINE:
773: ret = set_error(tmpl, &elem->call, "%cdefine without %cenddef",
774: TMPL_FUNCTION_CHARACTER, TMPL_FUNCTION_CHARACTER);
775: break;
776: case TY_NORMAL: /* set_error() already called */
777: break;
778: default:
779: assert(0);
780: }
781: return (ret);
782: }
783:
784: /************************************************************************
785: * ERROR HANDLING *
786: ************************************************************************/
787:
788: /*
789: * Set up a function call that prints an error.
790: * Replaces the previuous call.
791: */
792: static int
793: set_error(struct tmpl *tmpl, struct func_call *call, const char *fmt, ...)
794: {
795: char *string;
796: va_list args;
797: int slen;
798:
799: _tmpl_free_call(tmpl->mtype, call);
800: if ((call->funcname = STRDUP(tmpl->mtype, "error")) == NULL)
801: return (-1);
802: if ((call->args = MALLOC(tmpl->mtype, sizeof(*call->args))) == NULL) {
803: _tmpl_free_call(tmpl->mtype, call);
804: return (-1);
805: }
806: va_start(args, fmt);
807: slen = vsnprintf(NULL, 0, fmt, args); /* just get length */
808: if ((string = MALLOC(tmpl->mtype, slen + 1)) != NULL)
809: vsnprintf(string, slen + 1, fmt, args);
810: va_end(args);
811: if (string == NULL) {
812: _tmpl_free_call(tmpl->mtype, call);
813: return (-1);
814: }
815: call->nargs = 1;
816: call->args[0].is_literal = 1;
817: call->args[0].u.literal = string;
818: call->handler = error_func;
819: call->type = TY_NORMAL;
820: return (0);
821: }
822:
823: /************************************************************************
824: * BUILT-IN FUNCTIONS *
825: ************************************************************************/
826:
827: /*
828: * @error()
829: */
830: static char *
831: error_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
832: {
833: *errmsgp = STRDUP(ctx->mtype, av[1]);
834: return (NULL);
835: }
836:
837: /*
838: * @equal()
839: */
840: static char *
841: equal_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
842: {
843: return (STRDUP(ctx->mtype, strcmp(av[1], av[2]) == 0 ? "1" : "0"));
844: }
845:
846: /*
847: * @not()
848: */
849: static char *
850: not_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
851: {
852: return (STRDUP(ctx->mtype, _tmpl_true(av[1]) ? "0" : "1"));
853: }
854:
855: /*
856: * @and()
857: */
858: static char *
859: and_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
860: {
861: int is_true = 1;
862: int i;
863:
864: for (i = 1; i < ac; i++)
865: is_true &= _tmpl_true(av[i]);
866: return (STRDUP(ctx->mtype, is_true ? "1" : "0"));
867: }
868:
869: /*
870: * @or()
871: */
872: static char *
873: or_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
874: {
875: int is_true = 0;
876: int i;
877:
878: for (i = 1; i < ac; i++)
879: is_true |= _tmpl_true(av[i]);
880: return (STRDUP(ctx->mtype, is_true ? "1" : "0"));
881: }
882:
883: /*
884: * @add()
885: */
886: static char *
887: add_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
888: {
889: long long sum = 0;
890: char buf[32];
891: int i;
892:
893: for (i = 1; i < ac; i++)
894: sum += strtoll(av[i], NULL, 0);
895: snprintf(buf, sizeof(buf), "%lld", sum);
896: return (STRDUP(ctx->mtype, buf));
897: }
898:
899: /*
900: * @sub()
901: */
902: static char *
903: sub_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
904: {
905: long long result;
906: char buf[32];
907: int i;
908:
909: result = strtoll(av[1], NULL, 0);
910: for (i = 2; i < ac; i++)
911: result -= strtoll(av[i], NULL, 0);
912: snprintf(buf, sizeof(buf), "%lld", result);
913: return (STRDUP(ctx->mtype, buf));
914: }
915:
916: /*
917: * @mul()
918: */
919: static char *
920: mul_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
921: {
922: long long product = 1;
923: char buf[32];
924: int i;
925:
926: for (i = 1; i < ac; i++)
927: product *= strtoll(av[i], NULL, 0);
928: snprintf(buf, sizeof(buf), "%lld", product);
929: return (STRDUP(ctx->mtype, buf));
930: }
931:
932: /*
933: * @div()
934: */
935: static char *
936: div_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
937: {
938: long long x, y;
939: char buf[32];
940:
941: x = strtoll(av[1], NULL, 0);
942: y = strtoll(av[2], NULL, 0);
943: if (y == 0)
944: return (STRDUP(ctx->mtype, "divide by zero"));
945: snprintf(buf, sizeof(buf), "%lld", x / y);
946: return (STRDUP(ctx->mtype, buf));
947: }
948:
949: /*
950: * @mod()
951: */
952: static char *
953: mod_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
954: {
955: long long x, y;
956: char buf[32];
957:
958: x = strtoll(av[1], NULL, 0);
959: y = strtoll(av[2], NULL, 0);
960: if (y == 0)
961: return (STRDUP(ctx->mtype, "divide by zero"));
962: snprintf(buf, sizeof(buf), "%lld", x % y);
963: return (STRDUP(ctx->mtype, buf));
964: }
965:
966: /*
967: * @lt()
968: */
969: static char *
970: lt_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
971: {
972: char buf[32];
973:
974: snprintf(buf, sizeof(buf), "%d",
975: strtoll(av[1], NULL, 0) < strtoll(av[2], NULL, 0));
976: return (STRDUP(ctx->mtype, buf));
977: }
978:
979: /*
980: * @gt()
981: */
982: static char *
983: gt_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
984: {
985: char buf[32];
986:
987: snprintf(buf, sizeof(buf), "%d",
988: strtoll(av[1], NULL, 0) > strtoll(av[2], NULL, 0));
989: return (STRDUP(ctx->mtype, buf));
990: }
991:
992: /*
993: * @le()
994: */
995: static char *
996: le_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
997: {
998: char buf[32];
999:
1000: snprintf(buf, sizeof(buf), "%d",
1001: strtoll(av[1], NULL, 0) <= strtoll(av[2], NULL, 0));
1002: return (STRDUP(ctx->mtype, buf));
1003: }
1004:
1005: /*
1006: * @ge()
1007: */
1008: static char *
1009: ge_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1010: {
1011: char buf[32];
1012:
1013: snprintf(buf, sizeof(buf), "%d",
1014: strtoll(av[1], NULL, 0) >= strtoll(av[2], NULL, 0));
1015: return (STRDUP(ctx->mtype, buf));
1016: }
1017:
1018: /*
1019: * @eval()
1020: */
1021: static char *
1022: eval_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1023: {
1024: FILE *const output_save = ctx->output;
1025: struct tmpl *tmpl = NULL;
1026: char *rtn = NULL;
1027: FILE *input = NULL;
1028: int esave;
1029:
1030: /* Create string output buffer to capture output */
1031: if ((ctx->output = string_buf_output(ctx->mtype)) == NULL)
1032: goto fail;
1033:
1034: /* Create new template using string argument as input */
1035: if ((input = string_buf_input(av[1], strlen(av[1]), 0)) == NULL)
1036: goto fail;
1037: if ((tmpl = tmpl_create(input, NULL, ctx->mtype)) == NULL)
1038: goto fail;
1039:
1040: /* Execute parsed template using existing context */
1041: _tmpl_execute_elems(ctx, tmpl->elems, 0, tmpl->num_elems);
1042:
1043: /* Get the resulting output as a string */
1044: rtn = string_buf_content(ctx->output, 1);
1045:
1046: fail:
1047: /* Clean up and return */
1048: esave = errno;
1049: if (input != NULL)
1050: fclose(input);
1051: tmpl_destroy(&tmpl);
1052: if (ctx->output != NULL)
1053: fclose(ctx->output);
1054: ctx->output = output_save;
1055: errno = esave;
1056: return (rtn);
1057: }
1058:
1059: /*
1060: * @invoke()
1061: */
1062: static char *
1063: invoke_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1064: {
1065: struct func_call call;
1066: char *rtn;
1067: int i;
1068:
1069: /* Initialize function call structure */
1070: memset(&call, 0, sizeof(call));
1071: call.type = TY_NORMAL;
1072:
1073: /* Get argument count */
1074: if ((i = _tmpl_find_var(ctx, "argc")) == -1
1075: || (call.nargs = strtol(ctx->vars[i].value, NULL, 0)) <= 0) {
1076: ASPRINTF(ctx->mtype, errmsgp,
1077: "%cinvoke(): \"argc\" must be greater than zero",
1078: TMPL_FUNCTION_CHARACTER);
1079: return (NULL);
1080: }
1081: call.nargs--;
1082:
1083: /* Get function and arguments */
1084: if ((call.args = MALLOC(ctx->mtype,
1085: call.nargs * sizeof(*call.args))) == NULL)
1086: return (NULL);
1087: for (i = 0; i <= call.nargs; i++) {
1088: const char *value;
1089: char name[16];
1090: int j;
1091:
1092: /* Get argN */
1093: snprintf(name, sizeof(name), "arg%d", i);
1094: value = ((j = _tmpl_find_var(ctx, name)) != -1) ?
1095: ctx->vars[j].value : "";
1096:
1097: /* Set argN */
1098: if (i == 0)
1099: call.funcname = (char *)value;
1100: else {
1101: call.args[i - 1].is_literal = 1;
1102: call.args[i - 1].u.literal = (char *)value;
1103: }
1104: }
1105:
1106: /* Invoke function */
1107: rtn = _tmpl_invoke(ctx, errmsgp, &call);
1108:
1109: /* Done */
1110: FREE(ctx->mtype, call.args);
1111: return (rtn);
1112: }
1113:
1114: /*
1115: * @cat()
1116: */
1117: static char *
1118: cat_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1119: {
1120: int len, slen;
1121: char *cat;
1122: int i;
1123:
1124: for (len = 0, i = 1; i < ac; i++)
1125: len += strlen(av[i]);
1126: if ((cat = MALLOC(ctx->mtype, len + 1)) == NULL)
1127: return (NULL);
1128: for (len = 0, i = 1; i < ac; i++) {
1129: slen = strlen(av[i]);
1130: memcpy(cat + len, av[i], slen);
1131: len += slen;
1132: }
1133: cat[len] = '\0';
1134: return (cat);
1135: }
1136:
1137: /*
1138: * @get()
1139: */
1140: static char *
1141: get_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1142: {
1143: int i;
1144:
1145: if ((i = _tmpl_find_var(ctx, av[1])) == -1)
1146: return (STRDUP(ctx->mtype, ""));
1147: return (STRDUP(ctx->mtype, ctx->vars[i].value));
1148: }
1149:
1150: /*
1151: * @set()
1152: */
1153: static char *
1154: set_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1155: {
1156: if (tmpl_ctx_set_var(ctx, av[1], av[2]) == -1)
1157: return (NULL);
1158: return (STRDUP(ctx->mtype, ""));
1159: }
1160:
1161: /*
1162: * @urlencode()
1163: */
1164: static char *
1165: urlencode_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1166: {
1167: char *s = av[1];
1168: char *enc;
1169: char *t;
1170:
1171: if ((enc = MALLOC(ctx->mtype, (strlen(s) * 3) + 1)) == NULL)
1172: return (NULL);
1173: for (t = enc; *s != '\0'; s++) {
1174: if (!isalnum(*s) && strchr("$-_.+!*'(),:@&=~", *s) == NULL)
1175: t += sprintf(t, "%%%02x", (u_char)*s);
1176: else
1177: *t++ = *s;
1178: }
1179: *t = '\0';
1180: return (enc);
1181: }
1182:
1183: /*
1184: * @htmlencode()
1185: */
1186: static char *
1187: htmlencode_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1188: {
1189: char *s = av[1];
1190: char *esc;
1191: char *t;
1192:
1193: if ((esc = MALLOC(ctx->mtype, (strlen(s) * 6) + 1)) == NULL)
1194: return (NULL);
1195: for (t = esc; *s != '\0'; s++) {
1196: switch (*s) {
1197: case '<':
1198: t += sprintf(t, "<");
1199: break;
1200: case '>':
1201: t += sprintf(t, ">");
1202: break;
1203: case '"':
1204: t += sprintf(t, """);
1205: break;
1206: case '&':
1207: t += sprintf(t, "&");
1208: break;
1209: default:
1210: if ((u_char)*s >= 0x7e)
1211: t += sprintf(t, "&#%d;", (u_char)*s);
1212: else
1213: *t++ = *s;
1214: break;
1215: }
1216: }
1217: *t = '\0';
1218: return (esc);
1219: }
1220:
1221: /*
1222: * @flush()
1223: */
1224: static char *
1225: flush_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1226: {
1227: fflush(ctx->output);
1228: if (ctx->orig_output != ctx->output)
1229: fflush(ctx->orig_output);
1230: return (STRDUP(ctx->mtype, ""));
1231: }
1232:
1233: /*
1234: * @output()
1235: */
1236: static char *
1237: output_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1238: {
1239: fputs(av[1], ctx->orig_output);
1240: return (STRDUP(ctx->mtype, ""));
1241: }
1242:
1243: /*
1244: * @loopindex()
1245: */
1246: static char *
1247: loopindex_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1248: {
1249: struct loop_ctx *loop;
1250: long level;
1251: char buf[64];
1252: char *eptr;
1253: int i;
1254:
1255: if (ac == 1)
1256: level = 0;
1257: else {
1258: level = strtol(av[1], &eptr, 0);
1259: if (*av[1] == '\0' || *eptr != '\0'
1260: || level < 0 || level == LONG_MAX)
1261: return (STRDUP(ctx->mtype, "-1"));
1262: }
1263: for (i = 0, loop = ctx->loop;
1264: i < level && loop != NULL;
1265: i++, loop = loop->outer);
1266: if (loop == NULL) {
1267: snprintf(buf, sizeof(buf), "%c%s called not within any loop",
1268: TMPL_FUNCTION_CHARACTER, av[0]);
1269: *errmsgp = STRDUP(ctx->mtype, buf);
1270: return (NULL);
1271: }
1272: snprintf(buf, sizeof(buf), "%d", loop->index);
1273: return (STRDUP(ctx->mtype, buf));
1274: }
1275:
1276: /*
1277: * @@
1278: */
1279: static char *
1280: atsign_func(struct tmpl_ctx *ctx, char **errmsgp, int ac, char **av)
1281: {
1282: return (STRDUP(ctx->mtype, tmpl_function_string));
1283: }
1284:
1285: /************************************************************************
1286: * DEBUGGING *
1287: ************************************************************************/
1288:
1289: #if TMPL_DEBUG
1290:
1291: #define BUFSIZE 128
1292:
1293: /*
1294: * Return a text version of a template element.
1295: */
1296: const char *
1297: _tmpl_elemstr(const struct tmpl_elem *elem, int index)
1298: {
1299: static char buf[BUFSIZE];
1300: char *s;
1301:
1302: snprintf(buf, sizeof(buf), "at 0x%04lx-0x%04lx type=%s",
1303: (u_long)elem->start, (u_long)elem->start + elem->len,
1304: typestr(elem));
1305: if (elem->text != NULL) {
1306: int i;
1307:
1308: strlcat(buf, " text=\"", sizeof(buf));
1309: for (i = 0; i < elem->len; i++) {
1310: switch (elem->text[i]) {
1311: case '\n':
1312: strlcat(buf, "\\n", sizeof(buf));
1313: break;
1314: case '\r':
1315: strlcat(buf, "\\r", sizeof(buf));
1316: break;
1317: case '\t':
1318: strlcat(buf, "\\t", sizeof(buf));
1319: break;
1320: default:
1321: if (isprint(elem->text[i])) {
1322: snprintf(buf + strlen(buf),
1323: sizeof(buf) - strlen(buf),
1324: "%c", elem->text[i]);
1325: } else {
1326: snprintf(buf + strlen(buf),
1327: sizeof(buf) - strlen(buf),
1328: "\\x%02x", (u_char)elem->text[i]);
1329: }
1330: }
1331: }
1332: strlcat(buf, "\"", sizeof(buf));
1333: return (buf);
1334: }
1335: s = funcstr(TYPED_MEM_TEMP, &elem->call);
1336: snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %s", s);
1337: FREE(TYPED_MEM_TEMP, s);
1338: switch (elem->call.type) {
1339: case TY_LOOP:
1340: snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1341: " end=%d", index + elem->u.u_loop.endloop);
1342: break;
1343: case TY_IF:
1344: case TY_ELIF:
1345: snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1346: " else=%d endif=%d",
1347: index + elem->u.u_if.elsie, index + elem->u.u_if.endif);
1348: break;
1349: case TY_WHILE:
1350: snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1351: " end=%d", index + elem->u.u_while.endwhile);
1352: break;
1353: case TY_DEFINE:
1354: snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1355: " end=%d", index + elem->u.u_define.enddef);
1356: break;
1357: default:
1358: break;
1359: }
1360: return (buf);
1361: }
1362:
1363: /*
1364: * Returns a malloc'd string
1365: */
1366: static char *
1367: funcstr(const char *mtype, const struct func_call *call)
1368: {
1369: char buf[BUFSIZE];
1370: char *s;
1371: int i;
1372:
1373: snprintf(buf, sizeof(buf), "%c%s(",
1374: TMPL_FUNCTION_CHARACTER, call->funcname);
1375: for (i = 0; i < call->nargs; i++) {
1376: s = argstr(mtype, &call->args[i]);
1377: snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1378: "%s%s", s, (i < call->nargs - 1) ? ", " : "");
1379: FREE(mtype, s);
1380: }
1381: snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ")");
1382: return (STRDUP(mtype, buf));
1383: }
1384:
1385: /*
1386: * Returns a malloc'd string
1387: */
1388: static char *
1389: argstr(const char *mtype, const struct func_arg *arg)
1390: {
1391: char buf[BUFSIZE];
1392:
1393: if (arg->is_literal) {
1394: snprintf(buf, sizeof(buf), "\"%s\"", arg->u.literal);
1395: return (STRDUP(mtype, buf));
1396: } else
1397: return (funcstr(mtype, &arg->u.call));
1398: }
1399:
1400: static const char *
1401: typestr(const struct tmpl_elem *elem)
1402: {
1403: if (elem->text != NULL)
1404: return ("TEXT");
1405: switch (elem->call.type) {
1406: case TY_NORMAL:
1407: return (elem->call.handler != NULL ? "BUILTIN" : "USER");
1408: case TY_LOOP: return ("LOOP");
1409: case TY_ENDLOOP: return ("ENDLOOP");
1410: case TY_WHILE: return ("WHILE");
1411: case TY_ENDWHILE: return ("ENDWHILE");
1412: case TY_IF: return ("IF");
1413: case TY_ELSE: return ("ELSE");
1414: case TY_ELIF: return ("ELIF");
1415: case TY_ENDIF: return ("ENDIF");
1416: case TY_DEFINE: return ("DEFINE");
1417: case TY_ENDDEF: return ("ENDDEF");
1418: case TY_BREAK: return ("BREAK");
1419: case TY_CONTINUE: return ("CONTINUE");
1420: case TY_RETURN: return ("RETURN");
1421: }
1422: return ("???");
1423: }
1424: #endif /* TMPL_DEBUG */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>