File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / bird / conf / cf-lex.l
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Aug 22 12:33:54 2017 UTC (6 years, 10 months ago) by misho
Branches: bird, MAIN
CVS tags: v1_6_3p0, v1_6_3, HEAD
bird 1.6.3

    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>