]> git.ipfire.org Git - thirdparty/bird.git/blob - conf/cf-lex.l
Merge branch 'master' into mq-filter-stack
[thirdparty/bird.git] / conf / cf-lex.l
1 /*
2 * BIRD -- Configuration Lexer
3 *
4 * (c) 1998--2000 Martin Mares <mj@ucw.cz>
5 *
6 * Can be freely distributed and used under the terms of the GNU GPL.
7 */
8
9 /**
10 * DOC: Lexical analyzer
11 *
12 * The lexical analyzer used for configuration files and CLI commands
13 * is generated using the |flex| tool accompanied by a couple of
14 * functions maintaining the hash tables containing information about
15 * symbols and keywords.
16 *
17 * Each symbol is represented by a &symbol structure containing name
18 * of the symbol, its lexical scope, symbol class (%SYM_PROTO for a
19 * name of a protocol, %SYM_CONSTANT for a constant etc.) and class
20 * dependent data. When an unknown symbol is encountered, it's
21 * automatically added to the symbol table with class %SYM_VOID.
22 *
23 * The keyword tables are generated from the grammar templates
24 * using the |gen_keywords.m4| script.
25 */
26
27 %{
28 #undef REJECT /* Avoid name clashes */
29
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <stdint.h>
34 #include <unistd.h>
35 #include <libgen.h>
36 #include <glob.h>
37 #include <fcntl.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41
42 #define PARSER 1
43
44 #include "nest/bird.h"
45 #include "nest/route.h"
46 #include "nest/protocol.h"
47 #include "filter/filter.h"
48 #include "filter/f-inst.h"
49 #include "conf/conf.h"
50 #include "conf/cf-parse.tab.h"
51 #include "lib/string.h"
52 #include "lib/hash.h"
53
54 struct keyword {
55 byte *name;
56 int value;
57 struct keyword *next;
58 };
59
60 #include "conf/keywords.h"
61
62 /* Could be defined by Bison in cf-parse.tab.h, inteferes with SYM hash */
63 #ifdef SYM
64 #undef SYM
65 #endif
66
67
68 static uint cf_hash(const byte *c);
69
70 #define KW_KEY(n) n->name
71 #define KW_NEXT(n) n->next
72 #define KW_EQ(a,b) !strcmp(a,b)
73 #define KW_FN(k) cf_hash(k)
74 #define KW_ORDER 8 /* Fixed */
75
76 #define SYM_KEY(n) n->name, n->scope->active
77 #define SYM_NEXT(n) n->next
78 #define SYM_EQ(a,s1,b,s2) !strcmp(a,b) && s1 == s2
79 #define SYM_FN(k,s) cf_hash(k)
80 #define SYM_ORDER 6 /* Initial */
81
82 #define SYM_REHASH sym_rehash
83 #define SYM_PARAMS /8, *1, 2, 2, 6, 20
84
85
86 HASH_DEFINE_REHASH_FN(SYM, struct symbol)
87
88 HASH(struct keyword) kw_hash;
89
90
91 struct sym_scope *conf_this_scope;
92
93 linpool *cfg_mem;
94
95 int (*cf_read_hook)(byte *buf, unsigned int max, int fd);
96 struct include_file_stack *ifs;
97 static struct include_file_stack *ifs_head;
98
99 #define QUOTED_BUFFER_SIZE 4096
100 static BUFFER_(char) quoted_buffer;
101 static char quoted_buffer_data[QUOTED_BUFFER_SIZE];
102 static inline void quoted_buffer_init(void) {
103 quoted_buffer.used = 0;
104 quoted_buffer.size = QUOTED_BUFFER_SIZE;
105 quoted_buffer.data = quoted_buffer_data;
106 }
107
108 #define MAX_INCLUDE_DEPTH 8
109
110 #define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max, ifs->fd);
111 #define YY_NO_UNPUT
112 #define YY_FATAL_ERROR(msg) cf_error(msg)
113 #define YY_USER_ACTION ifs->chno += yyleng; ifs->toklen = yyleng;
114
115 static void cf_include(char *arg, int alen);
116 static int check_eof(void);
117
118 static enum yytokentype cf_lex_symbol(const char *data);
119
120 %}
121
122 %option noyywrap
123 %option noinput
124 %option nounput
125 %option noreject
126
127 %x COMMENT CCOMM CLI QUOTED APOSTROPHED INCLUDE
128
129 ALPHA [a-zA-Z_]
130 DIGIT [0-9]
131 XIGIT [0-9a-fA-F]
132 ALNUM [a-zA-Z_0-9]
133 WHITE [ \t]
134
135 %%
136 ^{WHITE}*include{WHITE}*\" {
137 if (!ifs->depth)
138 cf_error("Include not allowed in CLI");
139
140 BEGIN(INCLUDE);
141 }
142
143 <INCLUDE>[^"\n]+["]{WHITE}*; {
144 char *start, *end;
145
146 start = yytext;
147
148 end = strchr(start, '"');
149 *end = 0;
150
151 if (start == end)
152 cf_error("Include with empty argument");
153
154 cf_include(start, end-start);
155
156 BEGIN(INITIAL);
157 }
158
159 <INCLUDE>["] cf_error("Include with empty argument");
160 <INCLUDE>. cf_error("Unterminated include");
161 <INCLUDE>\n cf_error("Unterminated include");
162 <INCLUDE><<EOF>> cf_error("Unterminated include");
163
164
165 {DIGIT}+:{DIGIT}+ {
166 uint len1 UNUSED, len2;
167 u64 l;
168 char *e;
169
170 errno = 0;
171 l = bstrtoul10(yytext, &e);
172 if (e && (*e != ':') || (errno == ERANGE) || (l >> 32))
173 cf_error("ASN out of range");
174
175 if (l >> 16)
176 {
177 len1 = 32;
178 len2 = 16;
179 cf_lval.i64 = (2ULL << 48) | (((u64) l) << len2);
180 }
181 else
182 {
183 len1 = 16;
184 len2 = 32;
185 cf_lval.i64 = 0 | (((u64) l) << len2);
186 }
187
188 errno = 0;
189 l = bstrtoul10(e+1, &e);
190 if (e && *e || (errno == ERANGE) || (l >> len2))
191 cf_error("Number out of range");
192 cf_lval.i64 |= l;
193
194 return VPN_RD;
195 }
196
197 [02]:{DIGIT}+:{DIGIT}+ {
198 uint len1, len2;
199 u64 l;
200 char *e;
201
202 if (yytext[0] == '0')
203 {
204 cf_lval.i64 = 0;
205 len1 = 16;
206 len2 = 32;
207 }
208 else
209 {
210 cf_lval.i64 = 2ULL << 48;
211 len1 = 32;
212 len2 = 16;
213 }
214
215 errno = 0;
216 l = bstrtoul10(yytext+2, &e);
217 if (e && (*e != ':') || (errno == ERANGE) || (l >> len1))
218 cf_error("ASN out of range");
219 cf_lval.i64 |= ((u64) l) << len2;
220
221 errno = 0;
222 l = bstrtoul10(e+1, &e);
223 if (e && *e || (errno == ERANGE) || (l >> len2))
224 cf_error("Number out of range");
225 cf_lval.i64 |= l;
226
227 return VPN_RD;
228 }
229
230 {DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+:{DIGIT}+ {
231 unsigned long int l;
232 ip4_addr ip4;
233 char *e;
234
235 cf_lval.i64 = 1ULL << 48;
236
237 e = strchr(yytext, ':');
238 *e++ = '\0';
239 if (!ip4_pton(yytext, &ip4))
240 cf_error("Invalid IPv4 address %s in Route Distinguisher", yytext);
241 cf_lval.i64 |= ((u64) ip4_to_u32(ip4)) << 16;
242
243 errno = 0;
244 l = bstrtoul10(e, &e);
245 if (e && *e || (errno == ERANGE) || (l >> 16))
246 cf_error("Number out of range");
247 cf_lval.i64 |= l;
248
249 return VPN_RD;
250 }
251
252 {DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+ {
253 if (!ip4_pton(yytext, &cf_lval.ip4))
254 cf_error("Invalid IPv4 address %s", yytext);
255 return IP4;
256 }
257
258 ({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) {
259 if (!ip6_pton(yytext, &cf_lval.ip6))
260 cf_error("Invalid IPv6 address %s", yytext);
261 return IP6;
262 }
263
264 0x{XIGIT}+ {
265 char *e;
266 unsigned long int l;
267 errno = 0;
268 l = bstrtoul16(yytext+2, &e);
269 if (e && *e || errno == ERANGE || (unsigned long int)(unsigned int) l != l)
270 cf_error("Number out of range");
271 cf_lval.i = l;
272 return NUM;
273 }
274
275 {DIGIT}+ {
276 char *e;
277 unsigned long int l;
278 errno = 0;
279 l = bstrtoul10(yytext, &e);
280 if (e && *e || errno == ERANGE || (unsigned long int)(unsigned int) l != l)
281 cf_error("Number out of range");
282 cf_lval.i = l;
283 return NUM;
284 }
285
286 else: {
287 /* Hack to distinguish if..else from else: in case */
288 return ELSECOL;
289 }
290
291 ['] {
292 BEGIN(APOSTROPHED);
293 quoted_buffer_init();
294 }
295
296 <APOSTROPHED>{ALNUM}|[-]|[.:] BUFFER_PUSH(quoted_buffer) = yytext[0];
297 <APOSTROPHED>\n cf_error("Unterminated symbol");
298 <APOSTROPHED><<EOF>> cf_error("Unterminated symbol");
299 <APOSTROPHED>['] {
300 BEGIN(INITIAL);
301 BUFFER_PUSH(quoted_buffer) = 0;
302 return cf_lex_symbol(quoted_buffer_data);
303 }
304 <APOSTROPHED>. cf_error("Invalid character in apostrophed symbol");
305
306 ({ALPHA}{ALNUM}*) {
307 return cf_lex_symbol(yytext);
308 }
309
310 <CLI>(.|\n) {
311 BEGIN(INITIAL);
312 return CLI_MARKER;
313 }
314
315 \.\. {
316 return DDOT;
317 }
318
319 [={}:;,.()+*/%<>~\[\]?!\|-] {
320 return yytext[0];
321 }
322
323 ["] {
324 BEGIN(QUOTED);
325 quoted_buffer_init();
326 }
327
328 <QUOTED>\n cf_error("Unterminated string");
329 <QUOTED><<EOF>> cf_error("Unterminated string");
330 <QUOTED>["] {
331 BEGIN(INITIAL);
332 BUFFER_PUSH(quoted_buffer) = 0;
333 cf_lval.t = cfg_strdup(quoted_buffer_data);
334 return TEXT;
335 }
336
337 <QUOTED>. BUFFER_PUSH(quoted_buffer) = yytext[0];
338
339 <INITIAL,COMMENT><<EOF>> { if (check_eof()) return END; }
340
341 {WHITE}+
342
343 \n ifs->lino++; ifs->chno = 0;
344
345 # BEGIN(COMMENT);
346
347 \/\* BEGIN(CCOMM);
348
349 . cf_error("Unknown character");
350
351 <COMMENT>\n {
352 ifs->lino++;
353 ifs->chno = 0;
354 BEGIN(INITIAL);
355 }
356
357 <COMMENT>.
358
359 <CCOMM>\*\/ BEGIN(INITIAL);
360 <CCOMM>\n ifs->lino++; ifs->chno = 0;
361 <CCOMM>\/\* cf_error("Comment nesting not supported");
362 <CCOMM><<EOF>> cf_error("Unterminated comment");
363 <CCOMM>.
364
365 \!\= return NEQ;
366 \!\~ return NMA;
367 \<\= return LEQ;
368 \>\= return GEQ;
369 \&\& return AND;
370 \|\| return OR;
371
372 \[\= return PO;
373 \=\] return PC;
374
375 %%
376
377 static uint
378 cf_hash(const byte *c)
379 {
380 uint h = 13 << 24;
381
382 while (*c)
383 h = h + (h >> 2) + (h >> 5) + ((uint) *c++ << 24);
384 return h;
385 }
386
387 /*
388 * IFS stack - it contains structures needed for recursive processing
389 * of include in config files. On the top of the stack is a structure
390 * for currently processed file. Other structures are either for
391 * active files interrupted because of include directive (these have
392 * fd and flex buffer) or for inactive files scheduled to be processed
393 * later (when parent requested including of several files by wildcard
394 * match - these do not have fd and flex buffer yet).
395 *
396 * FIXME: Most of these ifs and include functions are really sysdep/unix.
397 */
398
399 static struct include_file_stack *
400 push_ifs(struct include_file_stack *old)
401 {
402 struct include_file_stack *ret;
403 ret = cfg_allocz(sizeof(struct include_file_stack));
404 ret->lino = 1;
405 ret->prev = old;
406 return ret;
407 }
408
409 static struct include_file_stack *
410 pop_ifs(struct include_file_stack *old)
411 {
412 yy_delete_buffer(old->buffer);
413 close(old->fd);
414 return old->prev;
415 }
416
417 static void
418 enter_ifs(struct include_file_stack *new)
419 {
420 if (!new->buffer)
421 {
422 new->fd = open(new->file_name, O_RDONLY);
423 if (new->fd < 0)
424 {
425 ifs = ifs->up;
426 cf_error("Unable to open included file %s: %m", new->file_name);
427 }
428
429 new->buffer = yy_create_buffer(NULL, YY_BUF_SIZE);
430 }
431
432 yy_switch_to_buffer(new->buffer);
433 }
434
435 /**
436 * cf_lex_unwind - unwind lexer state during error
437 *
438 * cf_lex_unwind() frees the internal state on IFS stack when the lexical
439 * analyzer is terminated by cf_error().
440 */
441 void
442 cf_lex_unwind(void)
443 {
444 struct include_file_stack *n;
445
446 for (n = ifs; n != ifs_head; n = n->prev)
447 {
448 /* Memory is freed automatically */
449 if (n->buffer)
450 yy_delete_buffer(n->buffer);
451 if (n->fd)
452 close(n->fd);
453 }
454
455 ifs = ifs_head;
456 }
457
458 static void
459 cf_include(char *arg, int alen)
460 {
461 struct include_file_stack *base_ifs = ifs;
462 int new_depth, rv, i;
463 char *patt;
464 glob_t g = {};
465
466 new_depth = ifs->depth + 1;
467 if (new_depth > MAX_INCLUDE_DEPTH)
468 cf_error("Max include depth reached");
469
470 /* expand arg to properly handle relative filenames */
471 if (*arg != '/')
472 {
473 int dlen = strlen(ifs->file_name);
474 char *dir = alloca(dlen + 1);
475 patt = alloca(dlen + alen + 2);
476 memcpy(dir, ifs->file_name, dlen + 1);
477 sprintf(patt, "%s/%s", dirname(dir), arg);
478 }
479 else
480 patt = arg;
481
482 /* Skip globbing if there are no wildcards, mainly to get proper
483 response when the included config file is missing */
484 if (!strpbrk(arg, "?*["))
485 {
486 ifs = push_ifs(ifs);
487 ifs->file_name = cfg_strdup(patt);
488 ifs->depth = new_depth;
489 ifs->up = base_ifs;
490 enter_ifs(ifs);
491 return;
492 }
493
494 /* Expand the pattern */
495 rv = glob(patt, GLOB_ERR | GLOB_NOESCAPE, NULL, &g);
496 if (rv == GLOB_ABORTED)
497 cf_error("Unable to match pattern %s: %m", patt);
498 if ((rv != 0) || (g.gl_pathc <= 0))
499 return;
500
501 /*
502 * Now we put all found files to ifs stack in reverse order, they
503 * will be activated and processed in order as ifs stack is popped
504 * by pop_ifs() and enter_ifs() in check_eof().
505 */
506 for(i = g.gl_pathc - 1; i >= 0; i--)
507 {
508 char *fname = g.gl_pathv[i];
509 struct stat fs;
510
511 if (stat(fname, &fs) < 0)
512 {
513 globfree(&g);
514 cf_error("Unable to stat included file %s: %m", fname);
515 }
516
517 if (fs.st_mode & S_IFDIR)
518 continue;
519
520 /* Prepare new stack item */
521 ifs = push_ifs(ifs);
522 ifs->file_name = cfg_strdup(fname);
523 ifs->depth = new_depth;
524 ifs->up = base_ifs;
525 }
526
527 globfree(&g);
528 enter_ifs(ifs);
529 }
530
531 static int
532 check_eof(void)
533 {
534 if (ifs == ifs_head)
535 {
536 /* EOF in main config file */
537 ifs->lino = 1; /* Why this? */
538 return 1;
539 }
540
541 ifs = pop_ifs(ifs);
542 enter_ifs(ifs);
543 return 0;
544 }
545
546 static struct symbol *
547 cf_new_symbol(const byte *c)
548 {
549 struct symbol *s;
550
551 uint l = strlen(c);
552 if (l > SYM_MAX_LEN)
553 cf_error("Symbol too long");
554
555 s = cfg_allocz(sizeof(struct symbol) + l + 1);
556 *s = (struct symbol) { .scope = conf_this_scope, .class = SYM_VOID, };
557 strcpy(s->name, c);
558
559 if (!new_config->sym_hash.data)
560 HASH_INIT(new_config->sym_hash, new_config->pool, SYM_ORDER);
561
562 HASH_INSERT2(new_config->sym_hash, SYM, new_config->pool, s);
563
564 add_tail(&(new_config->symbols), &(s->n));
565
566 return s;
567 }
568
569 /**
570 * cf_find_symbol - find a symbol by name
571 * @cfg: specificed config
572 * @c: symbol name
573 *
574 * This functions searches the symbol table in the config @cfg for a symbol of
575 * given name. First it examines the current scope, then the second recent one
576 * and so on until it either finds the symbol and returns a pointer to its
577 * &symbol structure or reaches the end of the scope chain and returns %NULL to
578 * signify no match.
579 */
580 struct symbol *
581 cf_find_symbol(const struct config *cfg, const byte *c)
582 {
583 struct symbol *s;
584
585 if (cfg->sym_hash.data &&
586 (s = HASH_FIND(cfg->sym_hash, SYM, c, 1)))
587 return s;
588
589 /* In CLI command parsing, fallback points to the current config, otherwise it is NULL. */
590 if (cfg->fallback &&
591 cfg->fallback->sym_hash.data &&
592 (s = HASH_FIND(cfg->fallback->sym_hash, SYM, c, 1)))
593 return s;
594
595 return NULL;
596 }
597
598 /**
599 * cf_get_symbol - get a symbol by name
600 * @c: symbol name
601 *
602 * This functions searches the symbol table of the currently parsed config
603 * (@new_config) for a symbol of given name. It returns either the already
604 * existing symbol or a newly allocated undefined (%SYM_VOID) symbol if no
605 * existing symbol is found.
606 */
607 struct symbol *
608 cf_get_symbol(const byte *c)
609 {
610 return cf_find_symbol(new_config, c) ?: cf_new_symbol(c);
611 }
612
613 /**
614 * cf_localize_symbol - get the local instance of given symbol
615 * @sym: the symbol to localize
616 *
617 * This functions finds the symbol that is local to current scope
618 * for purposes of cf_define_symbol().
619 */
620 struct symbol *
621 cf_localize_symbol(struct symbol *sym)
622 {
623 /* If the symbol type is void, it has been recently allocated just in this scope. */
624 if (!sym->class)
625 return sym;
626
627 /* If the scope is the current, it is already defined in this scope. */
628 if (sym->scope == conf_this_scope)
629 cf_error("Symbol already defined");
630
631 /* Not allocated here yet, doing it now. */
632 return cf_new_symbol(sym->name);
633 }
634
635 struct symbol *
636 cf_default_name(char *template, int *counter)
637 {
638 char buf[SYM_MAX_LEN];
639 struct symbol *s;
640 char *perc = strchr(template, '%');
641
642 for(;;)
643 {
644 bsprintf(buf, template, ++(*counter));
645 s = cf_get_symbol(buf);
646 if (s->class == SYM_VOID)
647 return s;
648 if (!perc)
649 break;
650 }
651 cf_error("Unable to generate default name");
652 }
653
654 static enum yytokentype
655 cf_lex_symbol(const char *data)
656 {
657 /* Have we defined such a symbol? */
658 struct symbol *sym = cf_get_symbol(data);
659 cf_lval.s = sym;
660
661 if (sym->class != SYM_VOID)
662 return CF_SYM_KNOWN;
663
664 /* Is it a keyword? */
665 struct keyword *k = HASH_FIND(kw_hash, KW, data);
666 if (k)
667 {
668 if (k->value > 0)
669 return k->value;
670 else
671 {
672 cf_lval.i = -k->value;
673 return ENUM;
674 }
675 }
676
677 /* OK, undefined symbol */
678 cf_lval.s = sym;
679 return CF_SYM_UNDEFINED;
680 }
681
682 static void
683 cf_lex_init_kh(void)
684 {
685 HASH_INIT(kw_hash, &root_pool, KW_ORDER);
686
687 struct keyword *k;
688 for (k=keyword_list; k->name; k++)
689 HASH_INSERT(kw_hash, KW, k);
690 }
691
692 /**
693 * cf_lex_init - initialize the lexer
694 * @is_cli: true if we're going to parse CLI command, false for configuration
695 * @c: configuration structure
696 *
697 * cf_lex_init() initializes the lexical analyzer and prepares it for
698 * parsing of a new input.
699 */
700 void
701 cf_lex_init(int is_cli, struct config *c)
702 {
703 if (!kw_hash.data)
704 cf_lex_init_kh();
705
706 ifs_head = ifs = push_ifs(NULL);
707 if (!is_cli)
708 {
709 ifs->file_name = c->file_name;
710 ifs->fd = c->file_fd;
711 ifs->depth = 1;
712 }
713
714 yyrestart(NULL);
715 ifs->buffer = YY_CURRENT_BUFFER;
716
717 if (is_cli)
718 BEGIN(CLI);
719 else
720 BEGIN(INITIAL);
721
722 c->root_scope = cfg_allocz(sizeof(struct sym_scope));
723 conf_this_scope = c->root_scope;
724 conf_this_scope->active = 1;
725 }
726
727 /**
728 * cf_push_scope - enter new scope
729 * @sym: symbol representing scope name
730 *
731 * If we want to enter a new scope to process declarations inside
732 * a nested block, we can just call cf_push_scope() to push a new
733 * scope onto the scope stack which will cause all new symbols to be
734 * defined in this scope and all existing symbols to be sought for
735 * in all scopes stored on the stack.
736 */
737 void
738 cf_push_scope(struct symbol *sym)
739 {
740 struct sym_scope *s = cfg_alloc(sizeof(struct sym_scope));
741
742 s->next = conf_this_scope;
743 conf_this_scope = s;
744 s->active = 1;
745 s->name = sym;
746 s->slots = 0;
747 }
748
749 /**
750 * cf_pop_scope - leave a scope
751 *
752 * cf_pop_scope() pops the topmost scope from the scope stack,
753 * leaving all its symbols in the symbol table, but making them
754 * invisible to the rest of the config.
755 */
756 void
757 cf_pop_scope(void)
758 {
759 conf_this_scope->active = 0;
760 conf_this_scope = conf_this_scope->next;
761
762 ASSERT(conf_this_scope);
763 }
764
765 /**
766 * cf_symbol_class_name - get name of a symbol class
767 * @sym: symbol
768 *
769 * This function returns a string representing the class
770 * of the given symbol.
771 */
772 char *
773 cf_symbol_class_name(struct symbol *sym)
774 {
775 switch (sym->class)
776 {
777 case SYM_VOID:
778 return "undefined";
779 case SYM_PROTO:
780 return "protocol";
781 case SYM_TEMPLATE:
782 return "protocol template";
783 case SYM_FUNCTION:
784 return "function";
785 case SYM_FILTER:
786 return "filter";
787 case SYM_TABLE:
788 return "routing table";
789 case SYM_ATTRIBUTE:
790 return "custom attribute";
791 case SYM_CONSTANT_RANGE:
792 return "constant";
793 case SYM_VARIABLE_RANGE:
794 return "variable";
795 default:
796 return "unknown type";
797 }
798 }
799
800
801 /**
802 * DOC: Parser
803 *
804 * Both the configuration and CLI commands are analyzed using a syntax
805 * driven parser generated by the |bison| tool from a grammar which
806 * is constructed from information gathered from grammar snippets by
807 * the |gen_parser.m4| script.
808 *
809 * Grammar snippets are files (usually with extension |.Y|) contributed
810 * by various BIRD modules in order to provide information about syntax of their
811 * configuration and their CLI commands. Each snipped consists of several
812 * sections, each of them starting with a special keyword: |CF_HDR| for
813 * a list of |#include| directives needed by the C code, |CF_DEFINES|
814 * for a list of C declarations, |CF_DECLS| for |bison| declarations
815 * including keyword definitions specified as |CF_KEYWORDS|, |CF_GRAMMAR|
816 * for the grammar rules, |CF_CODE| for auxiliary C code and finally
817 * |CF_END| at the end of the snippet.
818 *
819 * To create references between the snippets, it's possible to define
820 * multi-part rules by utilizing the |CF_ADDTO| macro which adds a new
821 * alternative to a multi-part rule.
822 *
823 * CLI commands are defined using a |CF_CLI| macro. Its parameters are:
824 * the list of keywords determining the command, the list of parameters,
825 * help text for the parameters and help text for the command.
826 *
827 * Values of |enum| filter types can be defined using |CF_ENUM| with
828 * the following parameters: name of filter type, prefix common for all
829 * literals of this type and names of all the possible values.
830 */