/* $Id: grammar.y,v 2.0.1.15 1994/05/19 01:15:15 greyham Exp $
 *
 * yacc grammar for C manual page generator
 * This was derived from the grammar given in Appendix A of
 * "The C Programming Language" by Kernighan and Ritchie.
 */

/* identifiers that are not reserved words */
%token T_IDENTIFIER T_TYPEDEF_NAME

/* storage class */
%token T_AUTO T_EXTERN T_REGISTER T_STATIC T_TYPEDEF
/* This keyword included for compatibility with C++. */
%token T_INLINE

/* type specifiers */
%token T_CHAR T_DOUBLE T_FLOAT T_INT T_VOID
%token T_LONG T_SHORT T_SIGNED T_UNSIGNED
%token T_ENUM T_STRUCT T_UNION

/* type qualifiers */
%token T_CONST T_VOLATILE
/* These keywords included for compatibility with MSDOS C compilers. */
%token T_CDECL T_FAR T_HUGE T_INTERRUPT T_NEAR T_PASCAL

/* paired braces and everything between them: { ... } */
%token T_BRACES

/* paired square brackets and everything between them: [ ... ] */
%token T_BRACKETS

/* three periods */
%token T_ELLIPSIS

/* equal sign followed by constant expression or stuff between braces */
%token T_INITIALIZER

/* string literal */
%token T_STRING_LITERAL

/* text inside a regular comment, and one at the end of a non-empty line */
%token T_COMMENT T_EOLCOMMENT

/* whenever we change include files */
%token T_BASEFILE

%type <declaration> declaration
%type <parameter> function_definition
%type <decl_spec> declaration_specifiers declaration_specifier
%type <decl_spec> storage_class type_specifier type_qualifier
%type <decl_spec> struct_or_union_specifier enum_specifier
%type <decl_list> declarator_list init_declarator_list
%type <declarator> init_declarator declarator direct_declarator
%type <declarator> abstract_declarator direct_abstract_declarator
%type <param_list> parameter_type_list parameter_list
%type <parameter> parameter_declaration
%type <param_list> opt_identifier_list identifier_list
%type <enumerator> enumerator
%type <enum_list> enumerator_list
%type <identifier> identifier
%type <text> struct_or_union
%type <text> pointer type_qualifier_list
%type <text> opt_comment opt_eolcomment
%type <text> any_id T_IDENTIFIER T_TYPEDEF_NAME
%type <text> T_BRACKETS
%type <text> T_COMMENT T_EOLCOMMENT T_STRING_LITERAL
%type <boolean> T_BASEFILE

%{
#include "c2man.h"
#include "semantic.h"
#include "strconcat.h"
#include "strappend.h"
#include "manpage.h"
#include "enum.h"

#ifdef I_STDARG
#include <stdarg.h>
#endif
#ifdef I_VARARGS
#include <varargs.h>
#endif

int yylex();

#define YYMAXDEPTH 150

/* where are we up to scanning through an enum? */
static enum { NOENUM, KEYWORD, BRACES } enum_state = NOENUM;

/* Pointer to parameter list for the current function definition. */
static ParameterList *func_params;

/* Table of typedef names */
SymbolTable *typedef_names;

boolean first_comment;	/* are we still looking for the first comment? */
%}
%%

program
	: /* empty */
	| translation_unit
	;

translation_unit
	: external_declaration
	| translation_unit external_declaration
	;

external_declaration
	: declaration opt_eolcomment
	{
	    remember_declarations(NULL, &$1.decl_spec, &$1.decl_list, $2);
	}
	| T_COMMENT declaration opt_eolcomment
	{
	    remember_declarations($1, &$2.decl_spec, &$2.decl_list, $3);
	}
	| function_definition opt_eolcomment
	{
	    free_declarator($1.declarator);
	    free_decl_spec(&$1.decl_spec);
	    safe_free($2);
	}
	| T_COMMENT function_definition opt_eolcomment
	{
	    new_manual_page($1,&$2.decl_spec,$2.declarator);
	    safe_free($3);
	}
	| function_definition ';' opt_eolcomment
	{
	    free_declarator($1.declarator);
	    free_decl_spec(&$1.decl_spec);
	    safe_free($3);
	}
	| T_COMMENT function_definition ';' opt_eolcomment
	{
	    new_manual_page($1,&$2.decl_spec,$2.declarator);
	    safe_free($4);
	}
	| linkage_specification
	| T_COMMENT
	{
	    if (inbasefile && first_comment)
	    {
		remember_terse($1);
		first_comment = FALSE;
	    }
	    free($1);
	}
	| T_EOLCOMMENT
	{
	    free($1);
	}
	| T_BASEFILE
	{
	    inbasefile = $1;
	}
	| error ';'
	{
	    yyerrok;
	}
	;

linkage_specification
	: T_EXTERN T_STRING_LITERAL T_BRACES
	{
	    /* Provide an empty action here so bison will not complain about
	     * incompatible types in the default action it normally would
	     * have generated.
	     */
	}
	| T_EXTERN T_STRING_LITERAL declaration
	{
	    /* empty */
	}
	;

function_definition
	: declaration_specifiers declarator opt_eolcomment
	{
	    if ($2->type != DECL_FUNCTION) {
		yyerror("syntax error");
		YYERROR;
	    }
	    func_params = &($2->head->params);
            if ($3)	comment_last_parameter(&$2->head->params, $3);
	}
	  opt_declaration_list T_BRACES
	{
	    func_params = NULL;
	    $2->type = DECL_FUNCDEF;

	    $$.decl_spec = $1;
	    $$.declarator = $2;
	}
	| declarator opt_eolcomment
	{
	    if ($1->type != DECL_FUNCTION) {
		yyerror("syntax error");
		YYERROR;
	    }
	    func_params = &($1->head->params);
            if ($2)	comment_last_parameter(&$1->head->params, $2);
	}
	  opt_declaration_list T_BRACES
	{
	    DeclSpec	decl_spec;

	    func_params = NULL;
	    $1->type = DECL_FUNCDEF;

	    new_decl_spec(&$$.decl_spec, "int", DS_NONE);
	    $$.declarator = $1;
	}
	;

declaration
	: declaration_specifiers ';'
	{
	    $$.decl_spec = $1;
	    $$.decl_list.first = NULL;
	}
	| declaration_specifiers init_declarator_list ';'
	{
	    $$.decl_spec = $1;
	    $$.decl_list = $2;
	}
	| T_TYPEDEF declaration_specifiers declarator_list ';'
	{
	    new_typedef_symbols(&$2,&$3);
	    $$.decl_spec = $2;
	    $$.decl_list = $3;
	}
	;

declarator_list
	: declarator
	{
	    new_decl_list(&$$, $1);
	}
        | declarator_list ',' opt_eolcomment declarator
        {
            if ($3)	comment_last_decl(&$1, $3);
            add_decl_list(&$$, &$1, $4);
        }
        | declarator_list opt_eolcomment
        {
            $$ = $1;
            if ($2)	comment_last_decl(&$1, $2);
        }
	;

opt_declaration_list
	: /* empty */
	| declaration_list
	| declaration_list T_ELLIPSIS
	;

declaration_list
	: opt_comment declaration opt_eolcomment
	{
	    set_param_types(func_params, &$2.decl_spec, &$2.decl_list, $1, $3);
	}
	| declaration_list opt_comment declaration opt_eolcomment
	{
	    set_param_types(func_params, &$3.decl_spec, &$3.decl_list, $2, $4);
	}
	;

declaration_specifiers
	: declaration_specifier
	| declaration_specifiers declaration_specifier
	{
	    join_decl_specs(&$$, &$1, &$2);
	}
	;

declaration_specifier
	: storage_class
	| type_specifier
	| type_qualifier
	;

storage_class
	: T_AUTO
	{
	    new_decl_spec(&$$, "auto", DS_NONE);
	}
	| T_EXTERN
	{
	    new_decl_spec(&$$, "extern", DS_EXTERN);
	}
	| T_REGISTER
	{
	    new_decl_spec(&$$, "register", DS_NONE);
	}
	| T_STATIC
	{
	    new_decl_spec(&$$, "static", DS_STATIC);
	}
	| T_INLINE
	{
	    new_decl_spec(&$$, "inline", DS_INLINE);
	}
	;

type_specifier
	: T_CHAR
	{
	    new_decl_spec(&$$, "char", DS_CHAR);
	}
	| T_DOUBLE
	{
	    new_decl_spec(&$$, "double", DS_NONE);
	}
	| T_FLOAT
	{
	    new_decl_spec(&$$, "float", DS_FLOAT);
	}
	| T_INT
	{
	    new_decl_spec(&$$, "int", DS_NONE);
	}
	| T_LONG
	{
	    new_decl_spec(&$$, "long", DS_NONE);
	}
	| T_SHORT
	{
	    new_decl_spec(&$$, "short", DS_SHORT);
	}
	| T_SIGNED
	{
	    new_decl_spec(&$$, "signed", DS_NONE);
	}
	| T_UNSIGNED
	{
	    new_decl_spec(&$$, "unsigned", DS_NONE);
	}
	| T_VOID
	{
	    new_decl_spec(&$$, "void", DS_NONE);
	}
	| struct_or_union_specifier
	| enum_specifier
	| T_TYPEDEF_NAME
	{
	    Symbol *s = find_symbol(typedef_names, $1);
	   
	    new_enum_decl_spec(&$$, $1, s->flags,
		s->valtype == SYMVAL_ENUM ? s->value.enum_list
					  : (EnumeratorList *)NULL);
	}
	;

type_qualifier
	: T_CONST
	{
	    new_decl_spec(&$$, "const", DS_NONE);
	}
	| T_VOLATILE
	{
	    new_decl_spec(&$$, "volatile", DS_NONE);
	}
	| T_CDECL
	{
	    new_decl_spec(&$$, "cdecl", DS_NONE);
	}
	| T_INTERRUPT
	{
	    new_decl_spec(&$$, "interrupt", DS_NONE);
	}
	| T_FAR
	{
	    new_decl_spec(&$$, "far", DS_NONE);
	}
	| T_HUGE
	{
	    new_decl_spec(&$$, "huge", DS_NONE);
	}
	| T_NEAR
	{
	    new_decl_spec(&$$, "near", DS_NONE);
	}
	| T_PASCAL
	{
	    new_decl_spec(&$$, "pascal", DS_NONE);
	}
	;

struct_or_union_specifier
	: struct_or_union any_id T_BRACES
	{
	    dyn_decl_spec(&$$, strconcat($1, " ",$2," {}",NULLCP), DS_NONE);
	    free($2);
	}
	| struct_or_union T_BRACES
	{
	    dyn_decl_spec(&$$, strconcat($1," {}",NULLCP), DS_NONE);
	}
	| struct_or_union any_id
	{
	    dyn_decl_spec(&$$, strconcat($1, " ",$2,NULLCP), DS_NONE);
	    free($2);
	}
	;

struct_or_union
	: T_STRUCT
	{
	    $$ = "struct";
	}
	| T_UNION
	{
	    $$ = "union";
	}
	;

init_declarator_list
	: init_declarator
	{
	    new_decl_list(&$$, $1);
	}
        | init_declarator_list ',' opt_eolcomment init_declarator
        {
            if ($3)	comment_last_decl(&$1, $3);
            add_decl_list(&$$, &$1, $4);
        }
        | init_declarator_list opt_eolcomment
        {
            $$ = $1;
            if ($2)	comment_last_decl(&$1, $2);
        }
	;

init_declarator
	: declarator
	| declarator T_INITIALIZER
	;

enum_specifier
	: T_ENUM any_id '{' opt_eolcomment enumerator_list '}'
	{
	    add_enum_symbol($2, $5);
	    new_enum_decl_spec(&$$, strconcat("enum ",$2," {}",NULLCP),
		   DS_NONE, $5);
	    free($2);
	    safe_free($4);
	    enum_state = NOENUM;
	}
	| T_ENUM '{' opt_eolcomment enumerator_list '}'
	{
	    new_enum_decl_spec(&$$, strduplicate("enum {}"), DS_NONE, $4);
	    safe_free($3);
	    enum_state = NOENUM;
	}
	| T_ENUM any_id
	{
	    new_enum_decl_spec(&$$, strconcat("enum ",$2,NULLCP), DS_NONE,
	    	find_enum_symbol($2));
	    free($2);
	    enum_state = NOENUM;
	}
	;

enumerator_list
	: enumerator
	{
	    $$ = new_enumerator_list(&$1);
	}
	| enumerator_list ',' opt_eolcomment enumerator
	{
	    $$ = $1;
	    if ($3)	comment_last_enumerator($$, $3);
	    add_enumerator_list($$, &$4);
	}
	| enumerator_list opt_eolcomment
	{
	    $$ = $1;
	    if ($2)	comment_last_enumerator($$, $2);
	}
	| enumerator_list ',' opt_eolcomment
	{
	    $$ = $1;
	    if ($3)     comment_last_enumerator($$, $3);
	}
	;
	
enumerator
	: identifier
	{
	    new_enumerator(&$$,$1.name,$1.comment_before,$1.comment_after);
	}
	;
	
any_id
	: T_IDENTIFIER
	| T_TYPEDEF_NAME
	| any_id T_COMMENT
	{
	    $$ = $1;
	    free($2);
	}
	| any_id T_EOLCOMMENT
	{
	    $$ = $1;
	    free($2);
	}
	;

declarator
	: pointer direct_declarator
	{
	    char *newtext = strappend($1,$2->text,NULLCP);
	    free($2->text);
	    $$ = $2;
	    $$->text = newtext;
	    if ($$->type == DECL_SIMPLE)
		$$->type = DECL_COMPOUND;
	}
	| direct_declarator
	;

direct_declarator
	: T_IDENTIFIER
	{
	    $$ = new_declarator($1, strduplicate($1));
	}
	| '(' declarator ')'
	{
	    char *newtext = strconcat("(",$2->text,")",NULLCP);
	    free($2->text);
	    $$ = $2;
	    $$->text = newtext;
	}
	| direct_declarator T_BRACKETS
	{
	    $$ = $1;
	    $$->text = strappend($1->text,$2,NULLCP);
	    free($2);
	}
	| direct_declarator '(' parameter_type_list ')'
	{
	    $$ = new_declarator(strduplicate("%s()"), strduplicate($1->name));
	    $$->params = $3;
	    $$->func_stack = $1;
	    $$->head = ($1->func_stack == NULL) ? $$ : $1->head;
	    $$->type = ($1->type == DECL_SIMPLE) ? DECL_FUNCTION : $1->type;
	}
	| direct_declarator '(' opt_identifier_list ')'
	{
	    $$ = new_declarator(strduplicate("%s()"), strduplicate($1->name));
	    $$->params = $3;
	    $$->func_stack = $1;
	    $$->head = ($1->func_stack == NULL) ? $$ : $1->head;
	    $$->type = ($1->type == DECL_SIMPLE) ? DECL_FUNCTION : $1->type;
	}
	;

pointer
	: '*' type_qualifier_list
	{
	    $$ = strconcat("*",$2, NULLCP);
	    safe_free($2);
	}
	| '*' type_qualifier_list pointer
	{
	    $$ = $2 ? strconcat("*",$2, $3, NULLCP)
		    : strconcat("*", $3, NULLCP);
	    safe_free($2);
	    free($3);
	}
	;

type_qualifier_list
	: /* empty */
	{
	    $$ = NULL;
	}
	| type_qualifier_list type_qualifier
	{
	    $$ = $1 ? strconcat($1," ",$2.text," ",NULLCP)
		    : strconcat($2.text," ",NULLCP);
	    safe_free($1);
	    free_decl_spec(&$2);
	}
	;

parameter_type_list
	: parameter_list opt_eolcomment
	{
	    $$ = $1;
	    if ($2)	comment_last_parameter(&$1, $2);
	}
	| parameter_list ',' opt_eolcomment
		opt_comment T_ELLIPSIS opt_comment opt_eolcomment
	{
	    Identifier ellipsis;

	    if ($3)	comment_last_parameter(&$1, $3);
	    ellipsis.name = strduplicate("...");

	    if ($4 && $6 && $7)
	    {
		yyerror("ellipsis parameter has multiple comments");
		free($7);
		free($6);
		free($4);
		ellipsis.comment_before = ellipsis.comment_after = NULL;
	    }
	    else
	    {
		ellipsis.comment_before = $4;
		ellipsis.comment_after = $6 ? $6 : $7;
	    }

	    add_ident_list(&$$, &$1, &ellipsis);
	}
	;

parameter_list
	: parameter_declaration
	{
	    new_param_list(&$$, &$1);
	}
	| parameter_list ',' opt_eolcomment parameter_declaration
	{
	    if ($3)	comment_last_parameter(&$1, $3);
	    add_param_list(&$$, &$1, &$4);
	}
	;

parameter_declaration
	: opt_comment declaration_specifiers declarator opt_comment
	{
	    new_parameter(&$$, &$2, $3, $1, $4);
	}
	| opt_comment declaration_specifiers abstract_declarator opt_comment
	{
	    new_parameter(&$$, &$2, $3, $1, $4);
	}
	| opt_comment declaration_specifiers opt_comment
	{
	    new_parameter(&$$, &$2, (Declarator *)NULL, $1, $3);
	}
	;

opt_identifier_list
	: /* empty */
	{
	    new_ident_list(&$$);
	}
	| identifier_list opt_eolcomment
	{
	    $$ = $1;
	    if ($2)	comment_last_parameter(&$1, $2);
	}
	;

identifier_list
	: identifier
	{
	    new_ident_list(&$$);
	    add_ident_list(&$$, &$$, &$1);
	}
	| identifier_list ',' opt_eolcomment identifier
	{
	    if ($3)	comment_last_parameter(&$1, $3);
	    add_ident_list(&$$, &$1, &$4);
	}
	;

identifier
	: opt_comment T_IDENTIFIER opt_comment
	{
	    $$.comment_before = $1;
	    $$.comment_after = $3;
	    $$.name = $2;
	}
	
abstract_declarator
	: pointer
	{
	    $$ = new_declarator($1, NULLCP);
	}
	| pointer direct_abstract_declarator
	{
	    char *newtext = strappend($1,$2->text,NULLCP);
	    free($2->text);
	    $$ = $2;
	    $$->text = newtext;
	    if ($$->type == DECL_SIMPLE)
		$$->type = DECL_COMPOUND;
	}
	| direct_abstract_declarator
	;

direct_abstract_declarator
	: '(' abstract_declarator ')'
	{
	    char *newtext = strconcat("(",$2->text,")",NULLCP);
	    free($2->text);
	    $$ = $2;
	    $$->text = newtext;
	}
	| direct_abstract_declarator T_BRACKETS
	{
	    $$ = $1;
	    $$->text = strappend($1->text,$2,NULLCP);
	    free($2);
	}
	| T_BRACKETS
	{
	    $$ = new_declarator($1, NULLCP);
	}
	| direct_abstract_declarator '(' parameter_type_list ')'
	{
	    $$ = new_declarator(strduplicate("%s()"), NULLCP);
	    $$->params = $3;
	    $$->func_stack = $1;
	    $$->head = ($1->func_stack == NULL) ? $$ : $1->head;
	    $$->type = ($1->type == DECL_SIMPLE) ? DECL_FUNCTION : $1->type;
	}
	| direct_abstract_declarator '(' ')'
	{
	    $$ = new_declarator(strduplicate("%s()"), NULLCP);
	    $$->func_stack = $1;
	    $$->head = ($1->func_stack == NULL) ? $$ : $1->head;
	    $$->type = ($1->type == DECL_SIMPLE) ? DECL_FUNCTION : $1->type;
	}
	| '(' parameter_type_list ')'
	{
	    Declarator *d;
	    
	    d = new_declarator(NULL, NULL);
	    $$ = new_declarator(strduplicate("%s()"), NULLCP);
	    $$->params = $2;
	    $$->func_stack = d;
	    $$->head = $$;
	}
	| '(' ')'
	{
	    Declarator *d;
	    
	    d = new_declarator(NULL, NULL);
	    $$ = new_declarator(strduplicate("%s()"), NULLCP);
	    $$->func_stack = d;
	    $$->head = $$;
	}
	;

opt_comment
	: /* empty */
	{
	    $$ = NULL;
	}
	| T_COMMENT
	;

opt_eolcomment
	: /* empty */
	{
	    $$ = NULL;
	}
	| T_EOLCOMMENT
	;

%%
#ifdef MSDOS
#include "lex_yy.c"
#else
#ifdef VMS
#include "lexyy.c"
#else
#include "lex.yy.c"
#endif /* !VMS   */
#endif /* !MSDOS */

#ifdef I_STDARG
void yyerror(const char *format, ...)
#else
void yyerror(va_alist)
    va_dcl
#endif
{
#ifndef I_STDARG
    const char *format;
#endif
    va_list args;

    output_error();

#ifdef I_STDARG
    va_start(args, format);
#else
    va_start(args);
    format = va_arg(args, char *);
#endif

    vfprintf(stderr, format, args);
    va_end(args);
    putc('.',stderr);
    putc('\n',stderr);
}

void
parse_file (start_file)
const char *start_file;
{
    const char *s;
#ifdef FLEX_SCANNER
    static boolean restart = FALSE;
#endif

    cur_file = start_file ? strduplicate(start_file) : NULL;

    if (basefile && strlen(basefile) > 2) {
	s = basefile + strlen(basefile) - 2;
	if (strcmp(s, ".l") == 0 || strcmp(s, ".y") == 0)
	    BEGIN LEXYACC;
    }

    typedef_names = create_symbol_table();
    enum_table = create_symbol_table();

    line_num = 1;
    ly_count = 0;
    first_comment = group_together && !terse_specified;

    /* flex needs a yyrestart before every file but the first */
#ifdef FLEX_SCANNER
    if (restart)	yyrestart(yyin);
    restart = TRUE;
#endif

    yyparse();

    destroy_symbol_table(enum_table);
    destroy_symbol_table(typedef_names);

    safe_free(cur_file);
}
