Annotation of embedaddon/bird/conf/cf-lex.l, revision 1.1
1.1 ! misho 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 "conf/conf.h"
! 49: #include "conf/cf-parse.tab.h"
! 50: #include "lib/string.h"
! 51:
! 52: struct keyword {
! 53: byte *name;
! 54: int value;
! 55: struct keyword *next;
! 56: };
! 57:
! 58: #include "conf/keywords.h"
! 59:
! 60: #define KW_HASH_SIZE 64
! 61: static struct keyword *kw_hash[KW_HASH_SIZE];
! 62: static int kw_hash_inited;
! 63:
! 64: #define SYM_HASH_SIZE 128
! 65:
! 66: struct sym_scope {
! 67: struct sym_scope *next; /* Next on scope stack */
! 68: struct symbol *name; /* Name of this scope */
! 69: int active; /* Currently entered */
! 70: };
! 71: static struct sym_scope *conf_this_scope;
! 72:
! 73: static int cf_hash(byte *c);
! 74: static inline struct symbol * cf_get_sym(byte *c, uint h0);
! 75:
! 76: linpool *cfg_mem;
! 77:
! 78: int (*cf_read_hook)(byte *buf, unsigned int max, int fd);
! 79: struct include_file_stack *ifs;
! 80: static struct include_file_stack *ifs_head;
! 81:
! 82: #define MAX_INCLUDE_DEPTH 8
! 83:
! 84: #define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max, ifs->fd);
! 85: #define YY_NO_UNPUT
! 86: #define YY_FATAL_ERROR(msg) cf_error(msg)
! 87:
! 88: static void cf_include(char *arg, int alen);
! 89: static int check_eof(void);
! 90:
! 91: %}
! 92:
! 93: %option noyywrap
! 94: %option noinput
! 95: %option nounput
! 96: %option noreject
! 97:
! 98: %x COMMENT CCOMM CLI
! 99:
! 100: ALPHA [a-zA-Z_]
! 101: DIGIT [0-9]
! 102: XIGIT [0-9a-fA-F]
! 103: ALNUM [a-zA-Z_0-9]
! 104: WHITE [ \t]
! 105: include ^{WHITE}*include{WHITE}*\".*\"{WHITE}*;
! 106:
! 107: %%
! 108: {include} {
! 109: char *start, *end;
! 110:
! 111: if (!ifs->depth)
! 112: cf_error("Include not allowed in CLI");
! 113:
! 114: start = strchr(yytext, '"');
! 115: start++;
! 116:
! 117: end = strchr(start, '"');
! 118: *end = 0;
! 119:
! 120: if (start == end)
! 121: cf_error("Include with empty argument");
! 122:
! 123: cf_include(start, end-start);
! 124: }
! 125:
! 126: {DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+ {
! 127: ip4_addr a;
! 128: if (!ip4_pton(yytext, &a))
! 129: cf_error("Invalid IPv4 address %s", yytext);
! 130:
! 131: #ifdef IPV6
! 132: cf_lval.i32 = ip4_to_u32(a);
! 133: return RTRID;
! 134: #else
! 135: cf_lval.a = ipa_from_ip4(a);
! 136: return IPA;
! 137: #endif
! 138: }
! 139:
! 140: ({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) {
! 141: #ifdef IPV6
! 142: if (ipa_pton(yytext, &cf_lval.a))
! 143: return IPA;
! 144: cf_error("Invalid IPv6 address %s", yytext);
! 145: #else
! 146: cf_error("This is an IPv4 router, therefore IPv6 addresses are not supported");
! 147: #endif
! 148: }
! 149:
! 150: 0x{XIGIT}+ {
! 151: char *e;
! 152: unsigned long int l;
! 153: errno = 0;
! 154: l = strtoul(yytext+2, &e, 16);
! 155: if (e && *e || errno == ERANGE || (unsigned long int)(unsigned int) l != l)
! 156: cf_error("Number out of range");
! 157: cf_lval.i = l;
! 158: return NUM;
! 159: }
! 160:
! 161: {DIGIT}+ {
! 162: char *e;
! 163: unsigned long int l;
! 164: errno = 0;
! 165: l = strtoul(yytext, &e, 10);
! 166: if (e && *e || errno == ERANGE || (unsigned long int)(unsigned int) l != l)
! 167: cf_error("Number out of range");
! 168: cf_lval.i = l;
! 169: return NUM;
! 170: }
! 171:
! 172: else: {
! 173: /* Hack to distinguish if..else from else: in case */
! 174: return ELSECOL;
! 175: }
! 176:
! 177: ({ALPHA}{ALNUM}*|[']({ALNUM}|[-]|[\.]|[:])*[']) {
! 178: if(*yytext == '\'') {
! 179: yytext[yyleng-1] = 0;
! 180: yytext++;
! 181: }
! 182: unsigned int h = cf_hash(yytext);
! 183: struct keyword *k = kw_hash[h & (KW_HASH_SIZE-1)];
! 184: while (k)
! 185: {
! 186: if (!strcmp(k->name, yytext))
! 187: {
! 188: if (k->value > 0)
! 189: return k->value;
! 190: else
! 191: {
! 192: cf_lval.i = -k->value;
! 193: return ENUM;
! 194: }
! 195: }
! 196: k=k->next;
! 197: }
! 198: cf_lval.s = cf_get_sym(yytext, h);
! 199: return SYM;
! 200: }
! 201:
! 202: <CLI>(.|\n) {
! 203: BEGIN(INITIAL);
! 204: return CLI_MARKER;
! 205: }
! 206:
! 207: \.\. {
! 208: return DDOT;
! 209: }
! 210:
! 211: [={}:;,.()+*/%<>~\[\]?!\|-] {
! 212: return yytext[0];
! 213: }
! 214:
! 215: ["][^"\n]*["] {
! 216: yytext[yyleng-1] = 0;
! 217: cf_lval.t = cfg_strdup(yytext+1);
! 218: return TEXT;
! 219: }
! 220:
! 221: ["][^"\n]*\n cf_error("Unterminated string");
! 222:
! 223: <INITIAL,COMMENT><<EOF>> { if (check_eof()) return END; }
! 224:
! 225: {WHITE}+
! 226:
! 227: \n ifs->lino++;
! 228:
! 229: # BEGIN(COMMENT);
! 230:
! 231: \/\* BEGIN(CCOMM);
! 232:
! 233: . cf_error("Unknown character");
! 234:
! 235: <COMMENT>\n {
! 236: ifs->lino++;
! 237: BEGIN(INITIAL);
! 238: }
! 239:
! 240: <COMMENT>.
! 241:
! 242: <CCOMM>\*\/ BEGIN(INITIAL);
! 243: <CCOMM>\n ifs->lino++;
! 244: <CCOMM>\/\* cf_error("Comment nesting not supported");
! 245: <CCOMM><<EOF>> cf_error("Unterminated comment");
! 246: <CCOMM>.
! 247:
! 248: \!\= return NEQ;
! 249: \!\~ return NMA;
! 250: \<\= return LEQ;
! 251: \>\= return GEQ;
! 252: \&\& return AND;
! 253: \|\| return OR;
! 254:
! 255: \[\= return PO;
! 256: \=\] return PC;
! 257:
! 258: %%
! 259:
! 260: static int
! 261: cf_hash(byte *c)
! 262: {
! 263: unsigned int h = 13;
! 264:
! 265: while (*c)
! 266: h = (h * 37) + *c++;
! 267: return h;
! 268: }
! 269:
! 270:
! 271: /*
! 272: * IFS stack - it contains structures needed for recursive processing
! 273: * of include in config files. On the top of the stack is a structure
! 274: * for currently processed file. Other structures are either for
! 275: * active files interrupted because of include directive (these have
! 276: * fd and flex buffer) or for inactive files scheduled to be processed
! 277: * later (when parent requested including of several files by wildcard
! 278: * match - these do not have fd and flex buffer yet).
! 279: *
! 280: * FIXME: Most of these ifs and include functions are really sysdep/unix.
! 281: */
! 282:
! 283: static struct include_file_stack *
! 284: push_ifs(struct include_file_stack *old)
! 285: {
! 286: struct include_file_stack *ret;
! 287: ret = cfg_allocz(sizeof(struct include_file_stack));
! 288: ret->lino = 1;
! 289: ret->prev = old;
! 290: return ret;
! 291: }
! 292:
! 293: static struct include_file_stack *
! 294: pop_ifs(struct include_file_stack *old)
! 295: {
! 296: yy_delete_buffer(old->buffer);
! 297: close(old->fd);
! 298: return old->prev;
! 299: }
! 300:
! 301: static void
! 302: enter_ifs(struct include_file_stack *new)
! 303: {
! 304: if (!new->buffer)
! 305: {
! 306: new->fd = open(new->file_name, O_RDONLY);
! 307: if (new->fd < 0)
! 308: {
! 309: ifs = ifs->up;
! 310: cf_error("Unable to open included file %s: %m", new->file_name);
! 311: }
! 312:
! 313: new->buffer = yy_create_buffer(NULL, YY_BUF_SIZE);
! 314: }
! 315:
! 316: yy_switch_to_buffer(new->buffer);
! 317: }
! 318:
! 319: /**
! 320: * cf_lex_unwind - unwind lexer state during error
! 321: *
! 322: * cf_lex_unwind() frees the internal state on IFS stack when the lexical
! 323: * analyzer is terminated by cf_error().
! 324: */
! 325: void
! 326: cf_lex_unwind(void)
! 327: {
! 328: struct include_file_stack *n;
! 329:
! 330: for (n = ifs; n != ifs_head; n = n->prev)
! 331: {
! 332: /* Memory is freed automatically */
! 333: if (n->buffer)
! 334: yy_delete_buffer(n->buffer);
! 335: if (n->fd)
! 336: close(n->fd);
! 337: }
! 338:
! 339: ifs = ifs_head;
! 340: }
! 341:
! 342: static void
! 343: cf_include(char *arg, int alen)
! 344: {
! 345: struct include_file_stack *base_ifs = ifs;
! 346: int new_depth, rv, i;
! 347: char *patt;
! 348: glob_t g = {};
! 349:
! 350: new_depth = ifs->depth + 1;
! 351: if (new_depth > MAX_INCLUDE_DEPTH)
! 352: cf_error("Max include depth reached");
! 353:
! 354: /* expand arg to properly handle relative filenames */
! 355: if (*arg != '/')
! 356: {
! 357: int dlen = strlen(ifs->file_name);
! 358: char *dir = alloca(dlen + 1);
! 359: patt = alloca(dlen + alen + 2);
! 360: memcpy(dir, ifs->file_name, dlen + 1);
! 361: sprintf(patt, "%s/%s", dirname(dir), arg);
! 362: }
! 363: else
! 364: patt = arg;
! 365:
! 366: /* Skip globbing if there are no wildcards, mainly to get proper
! 367: response when the included config file is missing */
! 368: if (!strpbrk(arg, "?*["))
! 369: {
! 370: ifs = push_ifs(ifs);
! 371: ifs->file_name = cfg_strdup(patt);
! 372: ifs->depth = new_depth;
! 373: ifs->up = base_ifs;
! 374: enter_ifs(ifs);
! 375: return;
! 376: }
! 377:
! 378: /* Expand the pattern */
! 379: rv = glob(patt, GLOB_ERR | GLOB_NOESCAPE, NULL, &g);
! 380: if (rv == GLOB_ABORTED)
! 381: cf_error("Unable to match pattern %s: %m", patt);
! 382: if ((rv != 0) || (g.gl_pathc <= 0))
! 383: return;
! 384:
! 385: /*
! 386: * Now we put all found files to ifs stack in reverse order, they
! 387: * will be activated and processed in order as ifs stack is popped
! 388: * by pop_ifs() and enter_ifs() in check_eof().
! 389: */
! 390: for(i = g.gl_pathc - 1; i >= 0; i--)
! 391: {
! 392: char *fname = g.gl_pathv[i];
! 393: struct stat fs;
! 394:
! 395: if (stat(fname, &fs) < 0)
! 396: {
! 397: globfree(&g);
! 398: cf_error("Unable to stat included file %s: %m", fname);
! 399: }
! 400:
! 401: if (fs.st_mode & S_IFDIR)
! 402: continue;
! 403:
! 404: /* Prepare new stack item */
! 405: ifs = push_ifs(ifs);
! 406: ifs->file_name = cfg_strdup(fname);
! 407: ifs->depth = new_depth;
! 408: ifs->up = base_ifs;
! 409: }
! 410:
! 411: globfree(&g);
! 412: enter_ifs(ifs);
! 413: }
! 414:
! 415: static int
! 416: check_eof(void)
! 417: {
! 418: if (ifs == ifs_head)
! 419: {
! 420: /* EOF in main config file */
! 421: ifs->lino = 1; /* Why this? */
! 422: return 1;
! 423: }
! 424:
! 425: ifs = pop_ifs(ifs);
! 426: enter_ifs(ifs);
! 427: return 0;
! 428: }
! 429:
! 430: static struct symbol *
! 431: cf_new_sym(byte *c, uint h0)
! 432: {
! 433: uint h = h0 & (SYM_HASH_SIZE-1);
! 434: struct symbol *s, **ht;
! 435: int l;
! 436:
! 437: if (!new_config->sym_hash)
! 438: new_config->sym_hash = cfg_allocz(SYM_HASH_SIZE * sizeof(struct keyword *));
! 439: ht = new_config->sym_hash;
! 440: l = strlen(c);
! 441: if (l > SYM_MAX_LEN)
! 442: cf_error("Symbol too long");
! 443: s = cfg_alloc(sizeof(struct symbol) + l);
! 444: s->next = ht[h];
! 445: ht[h] = s;
! 446: s->scope = conf_this_scope;
! 447: s->class = SYM_VOID;
! 448: s->def = NULL;
! 449: s->aux = 0;
! 450: strcpy(s->name, c);
! 451: return s;
! 452: }
! 453:
! 454: static struct symbol *
! 455: cf_find_sym(struct config *cfg, byte *c, uint h0)
! 456: {
! 457: uint h = h0 & (SYM_HASH_SIZE-1);
! 458: struct symbol *s, **ht;
! 459:
! 460: if (ht = cfg->sym_hash)
! 461: {
! 462: for(s = ht[h]; s; s=s->next)
! 463: if (!strcmp(s->name, c) && s->scope->active)
! 464: return s;
! 465: }
! 466: if (ht = cfg->sym_fallback)
! 467: {
! 468: /* We know only top-level scope is active */
! 469: for(s = ht[h]; s; s=s->next)
! 470: if (!strcmp(s->name, c) && s->scope->active)
! 471: return s;
! 472: }
! 473:
! 474: return NULL;
! 475: }
! 476:
! 477: static inline struct symbol *
! 478: cf_get_sym(byte *c, uint h0)
! 479: {
! 480: return cf_find_sym(new_config, c, h0) ?: cf_new_sym(c, h0);
! 481: }
! 482:
! 483: /**
! 484: * cf_find_symbol - find a symbol by name
! 485: * @cfg: specificed config
! 486: * @c: symbol name
! 487: *
! 488: * This functions searches the symbol table in the config @cfg for a symbol of
! 489: * given name. First it examines the current scope, then the second recent one
! 490: * and so on until it either finds the symbol and returns a pointer to its
! 491: * &symbol structure or reaches the end of the scope chain and returns %NULL to
! 492: * signify no match.
! 493: */
! 494: struct symbol *
! 495: cf_find_symbol(struct config *cfg, byte *c)
! 496: {
! 497: return cf_find_sym(cfg, c, cf_hash(c));
! 498: }
! 499:
! 500: /**
! 501: * cf_get_symbol - get a symbol by name
! 502: * @c: symbol name
! 503: *
! 504: * This functions searches the symbol table of the currently parsed config
! 505: * (@new_config) for a symbol of given name. It returns either the already
! 506: * existing symbol or a newly allocated undefined (%SYM_VOID) symbol if no
! 507: * existing symbol is found.
! 508: */
! 509: struct symbol *
! 510: cf_get_symbol(byte *c)
! 511: {
! 512: return cf_get_sym(c, cf_hash(c));
! 513: }
! 514:
! 515: struct symbol *
! 516: cf_default_name(char *template, int *counter)
! 517: {
! 518: char buf[SYM_MAX_LEN];
! 519: struct symbol *s;
! 520: char *perc = strchr(template, '%');
! 521:
! 522: for(;;)
! 523: {
! 524: bsprintf(buf, template, ++(*counter));
! 525: s = cf_get_sym(buf, cf_hash(buf));
! 526: if (s->class == SYM_VOID)
! 527: return s;
! 528: if (!perc)
! 529: break;
! 530: }
! 531: cf_error("Unable to generate default name");
! 532: }
! 533:
! 534: /**
! 535: * cf_define_symbol - define meaning of a symbol
! 536: * @sym: symbol to be defined
! 537: * @type: symbol class to assign
! 538: * @def: class dependent data
! 539: *
! 540: * Defines new meaning of a symbol. If the symbol is an undefined
! 541: * one (%SYM_VOID), it's just re-defined to the new type. If it's defined
! 542: * in different scope, a new symbol in current scope is created and the
! 543: * meaning is assigned to it. If it's already defined in the current scope,
! 544: * an error is reported via cf_error().
! 545: *
! 546: * Result: Pointer to the newly defined symbol. If we are in the top-level
! 547: * scope, it's the same @sym as passed to the function.
! 548: */
! 549: struct symbol *
! 550: cf_define_symbol(struct symbol *sym, int type, void *def)
! 551: {
! 552: if (sym->class)
! 553: {
! 554: if (sym->scope == conf_this_scope)
! 555: cf_error("Symbol already defined");
! 556: sym = cf_new_sym(sym->name, cf_hash(sym->name));
! 557: }
! 558: sym->class = type;
! 559: sym->def = def;
! 560: return sym;
! 561: }
! 562:
! 563: static void
! 564: cf_lex_init_kh(void)
! 565: {
! 566: struct keyword *k;
! 567:
! 568: for(k=keyword_list; k->name; k++)
! 569: {
! 570: unsigned h = cf_hash(k->name) & (KW_HASH_SIZE-1);
! 571: k->next = kw_hash[h];
! 572: kw_hash[h] = k;
! 573: }
! 574: kw_hash_inited = 1;
! 575: }
! 576:
! 577: /**
! 578: * cf_lex_init - initialize the lexer
! 579: * @is_cli: true if we're going to parse CLI command, false for configuration
! 580: * @c: configuration structure
! 581: *
! 582: * cf_lex_init() initializes the lexical analyzer and prepares it for
! 583: * parsing of a new input.
! 584: */
! 585: void
! 586: cf_lex_init(int is_cli, struct config *c)
! 587: {
! 588: if (!kw_hash_inited)
! 589: cf_lex_init_kh();
! 590:
! 591: ifs_head = ifs = push_ifs(NULL);
! 592: if (!is_cli)
! 593: {
! 594: ifs->file_name = c->file_name;
! 595: ifs->fd = c->file_fd;
! 596: ifs->depth = 1;
! 597: }
! 598:
! 599: yyrestart(NULL);
! 600: ifs->buffer = YY_CURRENT_BUFFER;
! 601:
! 602: if (is_cli)
! 603: BEGIN(CLI);
! 604: else
! 605: BEGIN(INITIAL);
! 606:
! 607: conf_this_scope = cfg_allocz(sizeof(struct sym_scope));
! 608: conf_this_scope->active = 1;
! 609: }
! 610:
! 611: /**
! 612: * cf_push_scope - enter new scope
! 613: * @sym: symbol representing scope name
! 614: *
! 615: * If we want to enter a new scope to process declarations inside
! 616: * a nested block, we can just call cf_push_scope() to push a new
! 617: * scope onto the scope stack which will cause all new symbols to be
! 618: * defined in this scope and all existing symbols to be sought for
! 619: * in all scopes stored on the stack.
! 620: */
! 621: void
! 622: cf_push_scope(struct symbol *sym)
! 623: {
! 624: struct sym_scope *s = cfg_alloc(sizeof(struct sym_scope));
! 625:
! 626: s->next = conf_this_scope;
! 627: conf_this_scope = s;
! 628: s->active = 1;
! 629: s->name = sym;
! 630: }
! 631:
! 632: /**
! 633: * cf_pop_scope - leave a scope
! 634: *
! 635: * cf_pop_scope() pops the topmost scope from the scope stack,
! 636: * leaving all its symbols in the symbol table, but making them
! 637: * invisible to the rest of the config.
! 638: */
! 639: void
! 640: cf_pop_scope(void)
! 641: {
! 642: conf_this_scope->active = 0;
! 643: conf_this_scope = conf_this_scope->next;
! 644: ASSERT(conf_this_scope);
! 645: }
! 646:
! 647: struct symbol *
! 648: cf_walk_symbols(struct config *cf, struct symbol *sym, int *pos)
! 649: {
! 650: for(;;)
! 651: {
! 652: if (!sym)
! 653: {
! 654: if (*pos >= SYM_HASH_SIZE)
! 655: return NULL;
! 656: sym = cf->sym_hash[(*pos)++];
! 657: }
! 658: else
! 659: sym = sym->next;
! 660: if (sym && sym->scope->active)
! 661: return sym;
! 662: }
! 663: }
! 664:
! 665: /**
! 666: * cf_symbol_class_name - get name of a symbol class
! 667: * @sym: symbol
! 668: *
! 669: * This function returns a string representing the class
! 670: * of the given symbol.
! 671: */
! 672: char *
! 673: cf_symbol_class_name(struct symbol *sym)
! 674: {
! 675: if (cf_symbol_is_constant(sym))
! 676: return "constant";
! 677:
! 678: switch (sym->class)
! 679: {
! 680: case SYM_VOID:
! 681: return "undefined";
! 682: case SYM_PROTO:
! 683: return "protocol";
! 684: case SYM_TEMPLATE:
! 685: return "protocol template";
! 686: case SYM_FUNCTION:
! 687: return "function";
! 688: case SYM_FILTER:
! 689: return "filter";
! 690: case SYM_TABLE:
! 691: return "routing table";
! 692: case SYM_ROA:
! 693: return "ROA table";
! 694: default:
! 695: return "unknown type";
! 696: }
! 697: }
! 698:
! 699:
! 700: /**
! 701: * DOC: Parser
! 702: *
! 703: * Both the configuration and CLI commands are analyzed using a syntax
! 704: * driven parser generated by the |bison| tool from a grammar which
! 705: * is constructed from information gathered from grammar snippets by
! 706: * the |gen_parser.m4| script.
! 707: *
! 708: * Grammar snippets are files (usually with extension |.Y|) contributed
! 709: * by various BIRD modules in order to provide information about syntax of their
! 710: * configuration and their CLI commands. Each snipped consists of several
! 711: * sections, each of them starting with a special keyword: |CF_HDR| for
! 712: * a list of |#include| directives needed by the C code, |CF_DEFINES|
! 713: * for a list of C declarations, |CF_DECLS| for |bison| declarations
! 714: * including keyword definitions specified as |CF_KEYWORDS|, |CF_GRAMMAR|
! 715: * for the grammar rules, |CF_CODE| for auxiliary C code and finally
! 716: * |CF_END| at the end of the snippet.
! 717: *
! 718: * To create references between the snippets, it's possible to define
! 719: * multi-part rules by utilizing the |CF_ADDTO| macro which adds a new
! 720: * alternative to a multi-part rule.
! 721: *
! 722: * CLI commands are defined using a |CF_CLI| macro. Its parameters are:
! 723: * the list of keywords determining the command, the list of parameters,
! 724: * help text for the parameters and help text for the command.
! 725: *
! 726: * Values of |enum| filter types can be defined using |CF_ENUM| with
! 727: * the following parameters: name of filter type, prefix common for all
! 728: * literals of this type and names of all the possible values.
! 729: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>