It's below.  Bonus points go to the one who submits the automake magic
to generated parser.tab.{c,h} on the fly.
/*
 * parser.y
 *
 * Copyright 1998, 1999 Michael Elizabeth Chastain, <mailto:mec@shout.net>.
 * Licensed under the Gnu Public License, Version 2.
 *
 * Parse the language defined in Documentation/kbuild/config-language.txt
 * in the Linux kernel source.
 *
 * Also parses the little language of defconfig and .config files.
 */
/*
 * Global parser state variables.
 */
%{
    #include <string.h>
    #include "mconfig.h"
    int				parser_magic_cookie;
    block_type *		parser_block_top;
    def_line_list_type *	parser_def_line_list;
    int				parser_error_count;
    int				parser_warning_count;
%}
/*
 * Union of all parser node types across all languages.
 */
%union
{
    struct void_tag *		value_void;
    const char *		value_input_point;
    lexeme_type			value_lexeme;
    atom_type *			value_atom;
    atom_list_type *		value_atom_list;
    word_type *			value_word;
    word_list_type *		value_word_list;
    prompt_type *		value_prompt;
    symbol_type *		value_symbol;
    choice_list_type *		value_choice_list;
    unset_list_type *		value_unset_list;
    expr_type *			value_expr;
    statement_type *		value_statement;
    block_type *		value_block;
    def_line_type *		value_def_line;
    def_line_list_type *	value_def_line_list;
}
/*
 * Grammar symbols to handle language type.
 */
%type	<value_void>		start
%type	<value_void>		init
%type	<value_void>		start_language
%token	<value_void>		COOKIE_CONFIG_LANGUAGE
%token	<value_void>		COOKIE_DEF_CONFIG
%token	<value_void>		COOKIE_HELP_TEXT
/*
 * Grammar symbols for Config Language.
 */
%type	<value_block>		block
%type	<value_block>		scoped_block
%token	<value_input_point>	SOURCE
%type	<value_statement>	statement
%token	<value_input_point>	MAINMENU_OPTION
%token	<value_input_point>	ENDMENU
%token	<value_input_point>	MAINMENU_NAME
%token	<value_input_point>	COMMENT
%token	<value_input_point>	TEXT
%token	<value_input_point>	UNSET
%token	<value_input_point>	ASK_BOOL
%token	<value_input_point>	ASK_HEX
%token	<value_input_point>	ASK_INT
%token	<value_input_point>	ASK_STRING
%token	<value_input_point>	ASK_TRISTATE
%token	<value_input_point>	DEF_BOOL
%token	<value_input_point>	DEF_HEX
%token	<value_input_point>	DEF_INT
%token	<value_input_point>	DEF_STRING
%token	<value_input_point>	DEF_TRISTATE
%token	<value_input_point>	DEP_BOOL
%token	<value_input_point>	DEP_MBOOL
%token	<value_input_point>	DEP_HEX
%token	<value_input_point>	DEP_INT
%token	<value_input_point>	DEP_STRING
%token	<value_input_point>	DEP_TRISTATE
%token	<value_input_point>	CHOICE
%token	<value_input_point>	NCHOICE
%token	<value_input_point>	IF
%token	<value_void>		THEN
%token	<value_void>		ELSE
%token	<value_void>		FI
%token	<value_void>		'['
%token	<value_void>		']'
%token	<value_void>		';'
%type	<value_void>		semi_or_nl
%type	<value_expr>		expr
%type	<value_expr>		expr_not
%type	<value_expr>		expr_and
%type	<value_expr>		expr_or
%type	<value_expr>		expr_predicate
%type	<value_expr>		expr_word
%token	<value_input_point>	'!'
%token	<value_input_point>	'='
%token	<value_void>		DASH_O
%token	<value_void>		DASH_A
%type	<value_choice_list>	choice_list
%type	<value_unset_list>	unset_list
%type	<value_prompt>		prompt
%type	<value_symbol>		symbol
%type	<value_word_list>	extra_word_list
%type	<value_word>		word
%type	<value_word>		word_dquote
%type	<value_atom_list>	atom_list
%type	<value_atom>		atom_literal
%type	<value_atom>		atom_variable
%token	<value_input_point>	'"'
%token	<value_input_point>	'$'
%token	<value_lexeme>		LEXEME
/*
 * Grammar symbols for defconfig files.
 */
%token	<value_lexeme>		ISNOTSET
%type	<value_def_line>	def_line
%type	<value_def_line_list>	def_line_list
/*
 * Grammar symbols for help text.
 */
%%
/*
 * The start symbol.
 *
 * This is a kludge.  The lexer gives me a cookie to indicate language type:
 *
 *   arch/$ARCH/Config.in, ordinary Config Language
 *   .config or defconfig file
 *   help text (?)
 *
 * I need this kludge for two reasons.  First, bison gets unhappy if I use
 * multiple parsers, because the lexer needs to see a single set of token
 * constants and single YYLVAL type.  Second, I like to re-use some of the
 * low-level constructs such as atoms, and separate parsers won't let me do
 * that.
 */
start: init start_language
{
}
/*
 * This symbol initializes the return state for each parse.
 */
init:
{
    parser_block_top     = NULL;
    parser_def_line_list = NULL;
    parser_error_count   = 0;
    parser_warning_count = 0;
}
/*
 * Start symbol for Config Language.
 * Wrap the top-level block in a menu if it isn't already one.
 * Reducing to this symbol copies the top block pointer to a global place.
 */
start_language: COOKIE_HELP_TEXT block
{
    /*
	prompt->value.ptr         = "Mconfig Help Menu";
	prompt->value.len         = 17;
     */
    parser_block_top = $2;
}
start_language: COOKIE_CONFIG_LANGUAGE block 
{
    if ( $2->first != NULL && $2->first->next == NULL && $2->first->verb == verb_MENU )
    {
	parser_block_top = $2;
    }
    else
    {
	prompt_type *             prompt;
	statement_type *          statement;
	prompt                    = grab_memory( sizeof(*prompt) );
	prompt->next              = NULL;
	prompt->value.ptr         = "Main Menu";
	prompt->value.len         = 9;
	statement                 = grab_memory( sizeof(*statement) );
	statement->next           = NULL;
	statement->verb           = verb_MENU;
	statement->sc_title       = prompt;
	statement->sc_condition   = NULL;
	statement->sc_block_left  = $2;
	statement->sc_block_right = NULL;
	parser_block_top          = grab_memory( sizeof(*parser_block_top) );
	parser_block_top->first   = statement;
	parser_block_top->last    = statement;
    }
}
/*
 * A block is a list of statements.
 */
block: scoped_block
{
    $$ = $1;
    /* scope_close( ); */
}
scoped_block:
{
    $$        = grab_memory( sizeof(*$$) );
    $$->first = NULL;
    $$->last  = NULL;
    /* scope_open( ); */
}
scoped_block: scoped_block statement
{
    $$ = $1;
    if ( $$->first == NULL )
	$$->first = $2;
    if ( $$->last != NULL )
	$$->last->next = $2;
    $$->last = $2;
}
scoped_block: scoped_block '\n'
{
    $$ = $1;
}
/*
 * The source command is special: it gets executed immediately.
 */
scoped_block: scoped_block SOURCE LEXEME '\n'
{
    char * name;
    const char * error_string;
    $$ = $1;
    name = grab_memory( strlen(argument.ds) + $3.piece.len + 1 );
    sprintf( name, "%s%.*s", argument.ds, $3.piece.len, $3.piece.ptr );
    if ( input_push_file( name, &error_string ) < 0 )
    {
	parser_error( error_string, $2 );
	YYABORT;
    }
}
/*
 * Menu block.
 */
statement: MAINMENU_OPTION word '\n' block ENDMENU '\n'
{
    if ( $2->literal.len < 0 )
	parser_error( "mainmenu_option argument must be literal", $1 );
    if ( $2->literal.len != 12
    ||   memcmp( $2->literal.ptr, "next_comment", 12 ) != 0 )
	parser_error( "mainmenu_option unknown argument %.*s", $1,
			$2->literal.len, $2->literal.ptr );
    if ( $4->first == NULL || $4->first->verb != verb_comment )
	parser_error( "mainmenu_option next_comment must be followed by comment", $1 );
    $$                 = grab_memory( sizeof(*$$) );
    $$->next           = NULL;
    $$->verb           = verb_MENU;
    $$->sc_title       = ($4->first != NULL) ? $4->first->sb_prompt : NULL;
    $$->sc_condition   = NULL;
    $$->sc_block_left  = $4;
    $$->sc_block_right = NULL;
}
/*
 * If statement.
 */
statement: IF '[' expr ']' semi_or_nl THEN block FI '\n'
{
    $$                 = grab_memory( sizeof(*$$) );
    $$->next           = NULL;
    $$->verb           = verb_IF;
    $$->sc_title       = NULL;
    $$->sc_condition   = $3;
    $$->sc_block_left  = $7;
    $$->sc_block_right = NULL;
}
statement: IF '[' expr ']' semi_or_nl THEN block ELSE block FI '\n'
{
    $$                 = grab_memory( sizeof(*$$) );
    $$->next           = NULL;
    $$->verb           = verb_IF;
    $$->sc_title       = NULL;
    $$->sc_condition   = $3;
    $$->sc_block_left  = $7;
    $$->sc_block_right = $9;
    /*
     * FIXME: take the intersection of definitions in $7 and $9
     * and define those for this statement.
     */
}
semi_or_nl: ';' | '\n'
{
    ;
}
/*
 * Simple text-like statements.
 */
statement: MAINMENU_NAME prompt extra_word_list '\n'
{
    if ( $3->first != NULL )
	parser_warning( "mainmenu_name command has extra arguments", $1 );
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_mainmenu_name;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = NULL;
    $$->sb_def_value = NULL;
    $$->sb_dep_list  = NULL;
}
statement: COMMENT prompt extra_word_list '\n'
{
    if ( $3->first != NULL )
	parser_warning( "comment command has extra arguments", $1 );
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_comment;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = NULL;
    $$->sb_def_value = NULL;
    $$->sb_dep_list  = NULL;
}
statement: TEXT prompt extra_word_list '\n'
{
    if ( $3->first != NULL )
	parser_warning( "text command has extra arguments", $1 );
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_text;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = NULL;
    $$->sb_def_value = NULL;
    $$->sb_dep_list  = NULL;
}
/*
 * The UNSET verb.  This is a hack used in arch/{alpha,mips}/config.in.
 * I could actually start ignoring this once I fully implement TANDV/TAODAQ.
 */
statement: UNSET unset_list '\n'
{
    $$                = grab_memory( sizeof(*$$) );
    $$->next          = NULL;
    $$->verb          = verb_unset;
    $$->su_unset_list = $2;
}
/*
 * Command lines: ASK_*.
 */
statement: ASK_BOOL prompt symbol extra_word_list '\n'
{
#ifndef STRICT_TYPES
    if ( $4->first != NULL )
	parser_warning( "bool command has extra arguments", $1 );
#endif
    if ( $3->type == type_unset )
	$3->type = type_bool;
#ifdef STRICT_TYPES
    else if ( $3->type != type_bool )
#else
    else if ( $3->type != type_bool && $3->type != type_nchoice_slave )
#endif
	parser_error( "bool command re-defines a non-bool symbol %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_ask_bool;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = NULL; /* ignore extra arguments even without STRICT_SYNTAX */
    $$->sb_dep_list  = NULL;
}
statement: ASK_HEX prompt symbol word extra_word_list '\n'
{
    if ( $5->first != NULL )
	parser_warning( "hex command has extra arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_hex;
    else if ( $3->type != type_hex )
	parser_error( "hex command re-defines a non-hex symbol %.*s", $1,
		$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_ask_hex;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = $4;
    $$->sb_dep_list  = NULL;
}
statement: ASK_INT prompt symbol word extra_word_list '\n'
{
    if ( $5->first != NULL )
	parser_warning( "int command has extra arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_int;
    else if ( $3->type != type_int )
        parser_error( "int command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_ask_int;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = $4;
    $$->sb_dep_list  = NULL;
}
/*
 * The string command has an optional default value.
 */
statement: ASK_STRING prompt symbol '\n'
{
    word_type *             word;
    parser_warning( "string command needs a default value", $1 );
    if ( $3->type == type_unset )
	$3->type = type_string;
    else if ( $3->type != type_string )
    	parser_error( "string command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    word              = grab_memory( sizeof(*word) );
    word->next        = NULL;
    word->dquote      = 1;
    word->literal.ptr = "";
    word->literal.len = 0;
    word->atom_first  = NULL;
    $$                = grab_memory( sizeof(*$$) );
    $$->next          = NULL;
    $$->verb          = verb_ask_string;
    $$->sb_prompt     = $2;
    $$->sb_symbol     = $3;
    $$->sb_def_value  = word;
    $$->sb_dep_list   = NULL;
}
statement: ASK_STRING prompt symbol word extra_word_list '\n'
{
    if ( $5->first != NULL )
	parser_warning( "string command has extra arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_string;
    else if ( $3->type != type_string )
    	parser_error( "string command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_ask_string;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = $4;
    $$->sb_dep_list  = NULL;
}
statement: ASK_TRISTATE prompt symbol extra_word_list '\n'
{
#ifndef STRICT_TYPES
    if ( $4->first != NULL )
	parser_warning( "tristate command has extra arguments", $1 );
#endif
    if ( $3->type == type_unset )
	$3->type = type_tristate;
#ifdef STRICT_TYPES
    else if ( $3->type != type_tristate )
#else
    else if ( $3->type != type_tristate && $3->type != type_bool )
#endif
	parser_error( "tristate command re-defines a non-tristate symbol %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_ask_tristate;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = NULL; /* ignore extra arguments even without STRICT_SYNTAX */
    $$->sb_dep_list  = NULL;
}
/*
 * Command lines: DEF_*.
 */
statement: DEF_BOOL symbol word extra_word_list '\n'
{
    if ( $4->first != NULL )
	parser_warning( "define_bool command has extra arguments", $1 );
    if ( $2->type == type_unset )
	$2->type = type_bool;
#ifdef STRICT_TYPES
    else if ( $2->type != type_bool )
#else
    else if ( $2->type != type_bool && $2->type != type_nchoice_slave && $2->type != type_tristate)
#endif
	parser_error( "define_bool command re-defines a non-bool symbol %.*s", $1,
			$2->name.len, $2->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_def_bool;
    $$->sb_prompt    = NULL;
    $$->sb_symbol    = $2;
    $$->sb_def_value = $3;
    $$->sb_dep_list  = NULL;
}
statement: DEF_HEX symbol word extra_word_list '\n'
{
    if ( $4->first != NULL )
	parser_warning( "define_hex command has extra arguments", $1 );
    if ( $2->type == type_unset )
	$2->type = type_hex;
    else if ( $2->type != type_hex )
    	parser_error( "define_hex command redefines %.*s", $1,
		$2->name.len, $2->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_def_hex;
    $$->sb_prompt    = NULL;
    $$->sb_symbol    = $2;
    $$->sb_def_value = $3;
    $$->sb_dep_list  = NULL;
}
statement: DEF_INT symbol word extra_word_list '\n'
{
    if ( $4->first != NULL )
	parser_warning( "define_int command has extra arguments", $1 );
    if ( $2->type == type_unset )
	$2->type = type_int;
    else if ( $2->type != type_int )
    	parser_error( "define_int command redefines %.*s", $1,
			$2->name.len, $2->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_def_int;
    $$->sb_prompt    = NULL;
    $$->sb_symbol    = $2;
    $$->sb_def_value = $3;
    $$->sb_dep_list  = NULL;
}
statement: DEF_STRING symbol word extra_word_list '\n'
{
    if ( $4->first != NULL )
	parser_warning( "define_string command has extra arguments", $1 );
    if ( $2->type == type_unset )
	$2->type = type_string;
    else if ( $2->type != type_string )
    	parser_error( "define_string command redefines %.*s", $1,
			$2->name.len, $2->name.ptr);
    $$                = grab_memory( sizeof(*$$) );
    $$->next          = NULL;
    $$->verb          = verb_def_string;
    $$->sb_prompt     = NULL;
    $$->sb_symbol     = $2;
    $$->sb_def_value  = $3;
    $$->sb_dep_list   = NULL;
}
statement: DEF_TRISTATE symbol word extra_word_list '\n'
{
    if ( $4->first != NULL )
	parser_warning( "define_tristate command has extra arguments", $1 );
    if ( $2->type == type_unset )
	$2->type = type_tristate;
    else if ( $2->type != type_tristate )
    	parser_error( "define_tristate command redefines %.*s", $1,
			$2->name.len, $2->name.ptr);
    $$                = grab_memory( sizeof(*$$) );
    $$->next          = NULL;
    $$->verb          = verb_def_tristate;
    $$->sb_prompt     = NULL;
    $$->sb_symbol     = $2;
    $$->sb_def_value  = $3;
    $$->sb_dep_list   = NULL;
}
/*
 * Command lines: DEP_*.
 */
statement: DEP_BOOL prompt symbol extra_word_list '\n'
{
    if ( $4->first == NULL )
	parser_warning( "dep_bool command has no dependency arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_bool;
#ifdef STRICT_TYPES
    else if ( $3->type != type_bool )
#else
    else if ( $3->type != type_bool && $3->type != type_nchoice_slave)
#endif
	parser_error( "dep_bool command redefines %.*s", $1,
    			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_dep_bool;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = NULL;
    $$->sb_dep_list  = $4;
}
statement: DEP_MBOOL prompt symbol extra_word_list '\n'
{
    if ( $4->first == NULL )
	parser_warning( "dep_mbool command has no dependency arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_bool;
#ifdef STRICT_TYPES
    else if ( $3->type != type_bool )
#else
    else if ( $3->type != type_bool && $3->type != type_nchoice_slave)
#endif
	parser_error( "dep_mbool command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_dep_mbool;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = NULL;
    $$->sb_dep_list  = $4;
}
statement: DEP_HEX prompt symbol word extra_word_list '\n'
{
    if ( $5->first == NULL )
	parser_warning( "dep_hex command has no dependency arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_hex;
    else if ( $3->type != type_hex )
	parser_error( "dep_hex command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_dep_hex;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = $4;
    $$->sb_dep_list  = $5;
}
statement: DEP_INT prompt symbol word extra_word_list '\n'
{
    if ( $5->first == NULL )
	parser_warning( "dep_int command has no dependency arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_int;
    else if ( $3->type != type_int )
	parser_error( "dep_int command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_dep_int;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = $4;
    $$->sb_dep_list  = $5;
}
statement: DEP_STRING prompt symbol word extra_word_list '\n'
{
    if ( $5->first == NULL )
	parser_warning( "warning: dep_string command has no dependency arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_string;
    else if ( $3->type != type_string )
	parser_error( "dep_string command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_dep_string;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = $4;
    $$->sb_dep_list  = $5;
}
statement: DEP_TRISTATE prompt symbol extra_word_list '\n'
{
    if ( $4->first == NULL )
	parser_warning( "dep_tristate command has no dependency arguments", $1 );
    if ( $3->type == type_unset )
	$3->type = type_tristate;
    else if ( $3->type != type_tristate )
	parser_error( "dep_tristate command redefines %.*s", $1,
			$3->name.len, $3->name.ptr);
    $$               = grab_memory( sizeof(*$$) );
    $$->next         = NULL;
    $$->verb         = verb_dep_tristate;
    $$->sb_prompt    = $2;
    $$->sb_symbol    = $3;
    $$->sb_def_value = NULL;
    $$->sb_dep_list  = $4;
}
/*
 * Choice list, classic style.
 * 
 * Example (from 2.3.25-pre2 arch/arm/onfig.in):
 *
 *   $1   choice
 *   $2   'ARM system type'
 *   $3   "Archimedes        CONFIG_ARCH_ARC \
 *         A5000             CONFIG_ARCH_A5K \
 *         RiscPC            CONFIG_ARCH_RPC \
 *         EBSA-110          CONFIG_ARCH_EBSA110 \
 *         Footbridge-based  CONFIG_FOOTBRIDGE"
 *   $4   RiscPC
 *
 * $2 goes into the statement node as is.
 * $3 gets parsed, by hand, into a ps-list.
 * $4 gets translated to a symbol while I am parsing $3.
 */
statement: CHOICE prompt word word extra_word_list '\n'
{
    choice_list_type * choice_list;	/* choice list for $3		*/
    char * choices;			/* choice string from $3	*/
    char * cursor;			/* scanner for choices		*/
    int choice_count  = 0;		/* count of $3			*/
    int default_index = -1;		/* index of $4 in $3		*/
    int match_exact   = 0;		/* match counter for $4		*/
    int match_partial = 0;		/* match counter for $4		*/
    if ( $5->first != NULL )
	parser_warning( "choice command has extra arguments", $1 );
    if ( $4->literal.len < 0 )
	parser_error( "choice command default value must be literal", $1 );
    if ( $3->literal.len < 0 )
	parser_error( "choice command choice-list must be literal", $1 );
    /*
     * Build a new prompt-symbol list.
     */
    choice_list               = grab_memory( sizeof(*choice_list) );
    choice_list->prompt_first = NULL;
    choice_list->prompt_last  = NULL;
    choice_list->symbol_first = NULL;
    choice_list->symbol_last  = NULL;
    /*
     * I need a private copy of the choice string for three reasons:
     *   (1) I need a null-terminated string, not a piece, for strtok
     *   (2) strtok likes to scribble on its input
     *   (3) I may not even *have* a piece (error case)
     */
    if ( $3->literal.len < 0 )
    {
	choices = grab_memory( 1 );
	choices [0] = '\0';
    }
    else
    {
	choices = grab_memory( $3->literal.len+1 );
	memcpy( choices, $3->literal.ptr, $3->literal.len );
	choices [$3->literal.len] = '\0';
    }
    /*
     * Scan off one pair at a time.
     */
    for ( cursor = choices; ; cursor = NULL )
    {
	const char  * ptr;
	prompt_type * prompt;
	symbol_type * symbol;
	/*
	 * Grab the prompt.
	 */
	ptr = strtok( cursor, " \n\t\f\v" );
	if ( ptr == NULL )
	    break;
	prompt            = grab_memory( sizeof(*prompt) );
	prompt->next      = NULL;
	prompt->value.ptr = ptr;
	prompt->value.len = strlen(ptr);
	/*
	 * Grab the symbol.
	 */
	ptr = strtok( NULL, " \n\t\f\v" );
	if ( ptr == NULL )
	{
	    parser_error( "choice list must have an even number of words", $1 );
	    break;
	}
	{
	    piece_type name;
	    name.ptr = ptr;
	    name.len = strlen(ptr);
	    symbol   = symbol_create( name );
	    /* scope_define( symbol ); */
	}
	/*
	 * Choice symbols need to have unique definitions (imagine
	 * someone doing define_bool on an unselected choice outside
	 * the choice list!)
	 */
	if ( symbol->type == type_unset )
	    symbol->type = type_nchoice_slave;
	else
	    parser_error( "choice command re-defines %.*s", $1,
	    			symbol->name.len, symbol->name.ptr);
	/*
	 * Append to choice_list.
	 */
	if ( choice_list->prompt_first == NULL )
	    choice_list->prompt_first = prompt;
	if ( choice_list->prompt_last != NULL )
	    choice_list->prompt_last->next = prompt;
	choice_list->prompt_last = prompt;
	if ( choice_list->symbol_first == NULL )
	    choice_list->symbol_first = symbol;
	if ( choice_list->symbol_last != NULL )
	    choice_list->symbol_last->next = symbol;
	choice_list->symbol_last = symbol;
	/*
	 * Translate default from a value to a symbol name.
	 * E.g., "RiscPC" -> CONFIG_ARCH_RPC
	 * What a *stupid* syntax!
	 */
	if ( $4->literal.len >= 0 && $4->literal.len <= prompt->value.len
	&&   memcmp( $4->literal.ptr, prompt->value.ptr, $4->literal.len ) == 0 )
	{
	    if ( $4->literal.len == prompt->value.len )
	    {
		if ( match_exact++ == 0 )
		    default_index = choice_count;
	    }
	    else
	    {
		if ( match_partial++ == 0 && match_exact == 0 )
		    default_index = choice_count;
	    }
	}
	choice_count++;
    }
    /*
     * Complain about zero-length choices.
     * They aren't useful, and handling them properly is annoying.
     */
    if ( choice_count == 0 )
	parser_error( "choice command has no symbols", $1 );
    /*
     * Complain about funky default values.
     */
    if ( match_exact > 1 || ( match_exact == 0 && match_partial > 1 ) )
	parser_error( "choice command default value matches multiple values", $1 );
    if ( match_exact == 0 && match_partial == 0 )
	parser_error( "choice command default value matches no values", $1 );
    /*
     * Cons up a node.
     */
    $$                   = grab_memory( sizeof(*$$) );
    $$->next             = NULL;
    $$->verb             = verb_nchoice;
    $$->sn_prompt        = $2;
    $$->sn_choice_list   = choice_list;
    $$->sn_choice_count  = choice_count;
    $$->sn_choice_index  = default_index;
    $$->sn_default_index = default_index;
}
/*
 * Here is a better syntax for choice lists:
 *
 *   NCHOICE prompt symdefault prompt symbol prompt symbol prompt symbol ...
 *
 * This is what the ARM processor choice would look like as an nchoice:
 *
 *   $1   nchoice
 *   $2   'ARM system type'
 *   $3   CONFIG_ARCH_RPC
 *   $4   'Archimedes'		CONFIG_ARCH_ARC		\
 *        'A5000'		CONFIG_ARCH_A5K		\
 *        'RiscPC'		CONFIG_ARCH_RPC		\
 *        'EBSA-110'		CONFIG_ARCH_EBSA110	\
 *        'Footbridge-based'	CONFIG_FOOTBRIDGE
 *
 * Note that this allows prompts to have spaces in them, which would make
 * x86 processor type a lot more readable.
 */
statement: NCHOICE prompt symbol choice_list '\n'
{
    symbol_type * symbol;
    int choice_count;
    int default_index;
 
    /* this is true ... */
    parser_warning( "nchoice command is not compatible with old interpreters", $1 );
    for ( symbol  = $4->symbol_first, choice_count = 0, default_index = -1;
	  symbol != NULL;
	  symbol  = symbol->next, ++choice_count )
    {
	if ( symbol->type == type_unset )
	    symbol->type = type_nchoice_slave;
	else
	    parser_error( "nchoice command re-defines %.*s", $1,
	    			symbol->name.len, symbol->name.ptr);
	if ( $3->name.len == symbol->name.len
	&&   memcmp( $3->name.ptr, symbol->name.ptr, symbol->name.len ) == 0 )
	{
	    default_index = choice_count;
	}
    }
    if ( choice_count == 0 )
	parser_error( "nchoice command has no symbols", $1 );
    if ( default_index == -1 )
	parser_error( "nchoice command has bad default symbol", $1 );
    $$                   = grab_memory( sizeof(*$$) );
    $$->next             = NULL;
    $$->verb             = verb_nchoice;
    $$->sn_prompt        = $2;
    $$->sn_choice_list   = $4;
    $$->sn_choice_count  = choice_count;
    $$->sn_choice_index	 = default_index;
    $$->sn_default_index = default_index;
}
/*
 * Expression.
 */
expr: expr_not
{
    $$ = $1;
}
expr_not: expr_or
{
    $$ = $1;
}
expr_not: '!' expr_not
{
    $$             = grab_memory( sizeof(*$$) );
    $$->op         = op_not;
    $$->word_left  = NULL;
    $$->word_right = NULL;
    $$->expr_left  = $2;
    $$->expr_right = NULL;
}
expr_or: expr_and
{
    $$ = $1;
}
expr_or: expr_or DASH_O expr_and
{
    $$             = grab_memory( sizeof(*$$) );
    $$->op         = op_or;
    $$->word_left  = NULL;
    $$->word_right = NULL;
    $$->expr_left  = $1;
    $$->expr_right = $3;
}
expr_and: expr_predicate
{
    $$ = $1;
}
expr_and: expr_and DASH_A expr_predicate
{
    $$             = grab_memory( sizeof(*$$) );
    $$->op         = op_and;
    $$->word_left  = NULL;
    $$->word_right = NULL;
    $$->expr_left  = $1;
    $$->expr_right = $3;
}
expr_predicate: expr_word
{
    $$ = $1;
}
expr_predicate: word '=' word
{
    if ( $1->literal.len >= 0 && $3->literal.len >= 0 )
	parser_warning( "operator '=' has two literal operands", $2 );
    $$             = grab_memory( sizeof(*$$) );
    $$->op         = op_equal;
    $$->word_left  = $1;
    $$->word_right = $3;
    $$->expr_left  = NULL;
    $$->expr_right = NULL;
}
expr_predicate: word '!' '=' word
{
    if ( $1->literal.len >= 0 && $4->literal.len >= 0 )
	parser_warning( "operator '!=' has two literal operands", $2 );
    $$             = grab_memory( sizeof(*$$) );
    $$->op         = op_not_equal;
    $$->word_left  = $1;
    $$->word_right = $4;
    $$->expr_left  = NULL;
    $$->expr_right = NULL;
}
expr_word: word
{
    parser_warning( "naked word in conditional expression", $1->input_point );
    $$             = grab_memory( sizeof(*$$) );
    $$->op         = op_word;
    $$->word_left  = $1;
    $$->word_right = NULL;
    $$->expr_left  = NULL;
    $$->expr_right = NULL;
}
/*
 * Choice list.
 */
choice_list:
{
    $$               = grab_memory( sizeof(*$$) );
    $$->prompt_first = NULL;
    $$->prompt_last  = NULL;
    $$->symbol_first = NULL;
    $$->symbol_last  = NULL;
}
choice_list: choice_list prompt symbol
{
    $$ = $1;
    if ( $$->prompt_first == NULL )
	$$->prompt_first = $2;
    if ( $$->prompt_last != NULL )
	$$->prompt_last->next = $2;
    $$->prompt_last = $2;
    if ( $$->symbol_first == NULL )
	$$->symbol_first = $3;
    if ( $$->symbol_last != NULL )
	$$->symbol_last->next = $3;
    $$->symbol_last = $3;
}
/*
 * Symbol list for unset.
 */
unset_list:
{
    $$               = grab_memory( sizeof(*$$) );
    $$->symbol_first = NULL;
    $$->symbol_last  = NULL;
}
unset_list: unset_list symbol
{
    $$ = $1;
    if ( $$->symbol_first == NULL )
	$$->symbol_first = $2;
    if ( $$->symbol_last != NULL )
	$$->symbol_last->next = $2;
    $$->symbol_last = $2;
}
/*
 * Prompt.
 */
prompt: LEXEME
{
    $$        = grab_memory( sizeof(*$$) );
    $$->next  = NULL;
    $$->value = $1.piece;
}
prompt: word_dquote
{
    if ( $1->literal.len < 0 )
	parser_error( "prompt must have literal value", $1->input_point );
    $$        = grab_memory( sizeof(*$$) );
    $$->next  = NULL;
    $$->value = $1->literal;
}
/*
 * Symbol.
 */
symbol: LEXEME
{
    if ( $1.squote )
    {
	char * buffer = grab_memory( $1.piece.len + 64 );
	sprintf( buffer, "'%.*s': symbol may not be quoted.",
	    $1.piece.len, $1.piece.ptr );
	parser_error( buffer, $1.piece.ptr );
    }
    if ( $1.piece.len < 7 || memcmp( $1.piece.ptr, "CONFIG_", 7 ) != 0 )
    {
	char * buffer = grab_memory( $1.piece.len + 64 );
	sprintf( buffer, "'%.*s': bad symbol name (must be CONFIG_*).",
	    $1.piece.len, $1.piece.ptr );
	parser_warning( buffer, $1.piece.ptr );
    }
    $$ = symbol_create( $1.piece );
    /* scope_define( $$ ); */
}
/*
 * An extra_word_list eats up extra words at the end of a command.
 * These values are not used (in fact I could just leak them away);
 *   but a non-empty extra_word_list warrants a warning.
 */
extra_word_list:
{
    $$        = grab_memory( sizeof(*$$) );
    $$->first = NULL;
    $$->last  = NULL;
}
extra_word_list: extra_word_list word
{
    $$ = $1;
    if ( $$->first == NULL )
	$$->first = $2;
    if ( $$->last != NULL )
	$$->last->next = $2;
    $$->last = $2;
}
/*
 * A word can be a single atom or a double-quoted list of atoms.  If every atom
 * in a word is literal, the word is literal, and I compute the literal value
 * here.  Note that some semantic constructs require literal values (such as
 * the SOURCE command).
 */
word: atom_literal
{
    $$              = grab_memory( sizeof(*$$) );
    $$->next        = NULL;
    $$->input_point = $1->lexeme.piece.ptr;
    $$->dquote      = 0;
    $$->literal     = $1->lexeme.piece;
    $$->atom_first  = NULL;
}
word: atom_variable
{
    $$              = grab_memory( sizeof(*$$) );
    $$->next        = NULL;
    $$->input_point = $1->lexeme.piece.ptr;
    $$->dquote      = 0;
    $$->literal.ptr = NULL;
    $$->literal.len = -1;
    $$->atom_first  = $1;
}
word: word_dquote
{
    $$ = $1;
}
word_dquote: '"' atom_list '"'
{
    $$              = grab_memory( sizeof(*$$) );
    $$->next        = NULL;
    $$->input_point = $1;
    $$->dquote      = 1;
    if ( ! $2->is_literal )
    {
	/* contains a variable word */
	$$->literal.ptr = NULL;
	$$->literal.len = -1;
	$$->atom_first  = $2->first;
    }
    else
    {
	const atom_type * atom;
	int size;
	char * ptr_value;
	char * ptr;
	/* calculate the size */
	for ( atom = $2->first, size = 0; atom != NULL; atom = atom->next )
	    size += atom->lexeme.piece.len;
    
	/* synthesize a value */
	ptr_value = grab_memory( size );
	for ( atom = $2->first, ptr = ptr_value; atom != NULL; atom = atom->next )
	{
	    memcpy( ptr, atom->lexeme.piece.ptr, atom->lexeme.piece.len );
	    ptr += atom->lexeme.piece.len;
	}
	$$->literal.ptr = ptr_value;
	$$->literal.len = size;
	$$->atom_first  = NULL;
    }
}
/*
 * An atom list is a list of atoms.  I track whether the list contains
 * any variable atoms.
 */
atom_list:
{
    $$             = grab_memory( sizeof(*$$) );
    $$->is_literal = 1;
    $$->first      = NULL;
    $$->last       = NULL;
}
atom_list: atom_list atom_literal
{
    $$ = $1;
    if ( $$->first == NULL )
	$$->first = $2;
    if ( $$->last != NULL )
	$$->last->next = $2;
    $$->last = $2;
}
atom_list: atom_list atom_variable
{
    $$ = $1;
    $$->is_literal = 0;
    if ( $$->first == NULL )
	$$->first = $2;
    if ( $$->last != NULL )
	$$->last->next = $2;
    $$->last = $2;
}
/*
 * An atom is either a lexeme or a $lexeme.
 * Lexemes come in two flavors: xxx or 'xxx yyy zzz ...'.
 */
atom_literal: LEXEME
{
    $$         = grab_memory( sizeof(*$$) );
    $$->next   = NULL;
    $$->lexeme = $1;
    $$->dollar = 0;
}
atom_variable: '$' LEXEME
{
    if ( $2.squote )
	parser_error( "$'...' not allowed.", $1 );
    /* yes, people have done this in the corpus! */
    if ( $2.piece.len == 4 && memcmp( $2.piece.ptr, "arch", 4 ) == 0 )
    {
	parser_warning( "'$arch' not supported in old interpreters; use '$ARCH' instead.", $1 );
    }
    $$         = grab_memory( sizeof(*$$) );
    $$->next   = NULL;
    $$->lexeme = $2;
    $$->dollar = 1;
    /* scope_use( $$->value ); */
}
/*
 * The little language in defconfig and .config files.
 * This is a very simple language with just two statements:
 *
 *    # CONFIG_FOO is not set
 *    CONFIG_BAR=word
 *
 * These values are the -old- values of the symbols.  The distinction between
 * -old- and -new- is still a little mystical.
 *
 * But all I have to do is parse.
 */
start_language: COOKIE_DEF_CONFIG def_line_list
{
    parser_def_line_list = $2;
}
def_line_list:
{
    $$        = grab_memory( sizeof(*$$) );
    $$->first = NULL;
    $$->last  = NULL;
}
def_line_list: def_line_list def_line
{
    $$ = $1;
    if ( $$->first == NULL )
	$$->first = $2;
    if ( $$->last != NULL )
	$$->last->next = $2;
    $$->last = $2;
}
def_line_list: def_line_list '\n'
{
    $$ = $1;
}
def_line: symbol '=' word '\n'
{
    if ( $3->literal.len < 0 )
	parser_error( "symbol value must be literal", $2 );
    $$         = grab_memory( sizeof(*$$) );
    $$->symbol = $1;
    $$->value  = $3->literal;
}
def_line: ISNOTSET '\n'
{
    if ( $1.piece.len < 7 || memcmp( $1.piece.ptr, "CONFIG_", 7 ) != 0 )
    {
	char * buffer = grab_memory( $1.piece.len + 64 );
	sprintf( buffer, "'%.*s': bad symbol name (must be CONFIG_*).",
	    $1.piece.len, $1.piece.ptr );
	parser_warning( buffer, $1.piece.ptr );
    }
    $$         = grab_memory( sizeof(*$$) );
    $$->symbol = symbol_create( $1.piece );
    $$->value  = piece_n;
}
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/