%{
// $Id: parse.in,v 1.26 2003/09/02 04:58:06 vern Exp $
// Copyright (c) 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
%}

%token TOK_ADD TOK_ADD_TO TOK_ADDR TOK_ANY
%token TOK_ATENDIF TOK_ATELSE TOK_ATIF TOK_ATIFDEF TOK_ATIFNDEF
%token TOK_BOOL TOK_BREAK TOK_CASE TOK_CONST
%token TOK_CONSTANT TOK_COUNT TOK_COUNTER TOK_DEFAULT TOK_DELETE
%token TOK_DOUBLE TOK_ELSE TOK_ENUM TOK_EVENT TOK_EXPORT TOK_FILE TOK_FOR
%token TOK_FUNCTION TOK_GLOBAL TOK_GLOBAL_ATTR TOK_HAS_FIELD TOK_HAS_ATTR
%token TOK_ID TOK_IF TOK_IN TOK_INT
%token TOK_INTERVAL TOK_LIST TOK_LOCAL TOK_LOG TOK_MODULE TOK_MATCH TOK_NET
%token TOK_NEXT TOK_NOT_IN TOK_OF TOK_ON TOK_PATTERN TOK_PATTERN_TEXT
%token TOK_PORT TOK_PRINT TOK_RECORD TOK_REDEF
%token TOK_REMOVE_FROM TOK_RETURN TOK_SCHEDULE TOK_SET
%token TOK_STRING TOK_SUBNET TOK_SWITCH TOK_TABLE TOK_THIS
%token TOK_TIME TOK_TIMER TOK_TYPE TOK_UNION

%token TOK_ATTR_ADD_FUNC TOK_ATTR_ATTR TOK_ATTR_DEFAULT TOK_ATTR_OPTIONAL
%token TOK_ATTR_REDEF TOK_ATTR_DEL_FUNC TOK_ATTR_EXPIRE_FUNC
%token TOK_ATTR_EXPIRE_CREATE TOK_ATTR_EXPIRE_READ TOK_ATTR_EXPIRE_WRITE
%token TOK_ATTR_PERSISTENT

%token TOK_DEBUG

%left ',' '|'
%right '='
%right '?' ':'
%left TOK_OR
%left TOK_AND
%nonassoc '<' '>' TOK_LE TOK_GE TOK_EQ TOK_NE
%left TOK_IN TOK_NOT_IN
%left '+' '-'
%left '*' '/' '%'
%left TOK_INCR TOK_DECR
%right '!'
%left '$' '[' ']' '(' ')' TOK_HAS_FIELD TOK_HAS_ATTR

%type <str> TOK_ID TOK_PATTERN_TEXT single_pattern
%type <id> local_id global_id resolve_id begin_func
%type <ic> init_class
%type <expr> opt_init
%type <val> TOK_CONSTANT
%type <re> pattern
%type <expr> expr init anonymous_function
%type <event_expr> event
%type <stmt> stmt stmt_list func_body for_head
%type <type> type opt_type refined_type enum_id_list
%type <func_type> func_hdr func_params
%type <type_l> type_list
%type <type_decl> type_decl formal_args_decl
%type <type_decl_l> type_decl_list formal_args_decl_list opt_attr_attr
%type <record> formal_args
%type <list> expr_list opt_expr_list
%type <c_case> case
%type <case_l> case_list
%type <attr> attr
%type <attr_l> attr_list opt_attr
%type <match_case> match
%type <match_cases> match_list

%{
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "input.h"
#include "Expr.h"
#include "Stmt.h"
#include "Var.h"
#include "DNS.h"
#include "RE.h"
#include "Scope.h"

YYLTYPE GetCurrentLocation();
extern int yyerror(const char[]);
extern int brolex();

#define YYLLOC_DEFAULT(Current, Rhs, N) \
	Current = Rhs[N];

/*
 * Part of the module facility: while parsing, keep track of which
 * module to put things in.
 */
string current_module = GLOBAL_MODULE_NAME;
bool is_export = false; // true if in an export {} block

/*
 * When parsing an expression for the debugger, where to put the result
 * (obviously not reentrant).
 */
extern Expr* g_curr_debug_expr;

#define YYLTYPE yyltype

Expr* bro_this = 0;
int in_init = 0;
bool in_debug = false;

Expr* curr_match_record_expr = 0;

%}

%union {
	char* str;
	ID* id;
	init_class ic;
	Val* val;
	RE_Matcher* re;
	Expr* expr;
	CallExpr* call_expr;
	EventExpr* event_expr;
	Stmt* stmt;
	ListExpr* list;
	BroType* type;
	RecordType* record;
	FuncType* func_type;
	TypeList* type_l;
	TypeDecl* type_decl;
	type_decl_list* type_decl_l;
	Case* c_case;
	case_list* case_l;
	Attr* attr;
	attr_list* attr_l;
	attr_tag attrtag;
	RecordMatchCase* match_case;
	RecordMatchCases* match_cases;
}

%%

bro:
		decl_list stmt_list
			{
			if ( optimize )
				$2 = $2->Simplify();

			if ( stmts )
				stmts->AsStmtList()->Stmts().append($2);
			else
				stmts = $2;

			// Any objects creates from hereon out should not
			// have file positions associated with them.
			set_location(no_location);
			}
	|
		/* Silly way of allowing the debugger to call yyparse()
		 * on an expr rather than a file.
		 */
		TOK_DEBUG { in_debug = true; } expr
			{
			g_curr_debug_expr = $3;
			in_debug = false;
			}
	;

decl_list:
		decl_list decl
	|
	;

expr:
		'(' expr ')'
			{
			set_location(@1, @3);
			$$ = $2; $$->MarkParen();
			}

	|	TOK_INCR expr
			{
			set_location(@1, @2);
			$$ = new IncrExpr(EXPR_INCR, $2);
			}

	|	TOK_DECR expr
			{
			set_location(@1, @2);
			$$ = new IncrExpr(EXPR_DECR, $2);
			}

	|	'!' expr
			{
			set_location(@1, @2);
			$$ = new NotExpr($2);
			}

	|	'-' expr	%prec '!'
			{
			set_location(@1, @2);
			$$ = new NegExpr($2);
			}

	|	'+' expr	%prec '!'
			{
			set_location(@1, @2);
			$$ = new PosExpr($2);
			}

	|	expr '+' expr
			{
			set_location(@1, @3);
			$$ = new AddExpr($1, $3);
			}

	|	expr '-' expr
			{
			set_location(@1, @3);
			$$ = new SubExpr($1, $3);
			}

	|	expr '*' expr
			{
			set_location(@1, @3);
			$$ = new TimesExpr($1, $3);
			}

	|	expr '/' expr
			{
			set_location(@1, @3);
			$$ = new DivideExpr($1, $3);
			}

	|	expr '%' expr
			{
			set_location(@1, @3);
			$$ = new ModExpr($1, $3);
			}

	|	expr TOK_AND expr
			{
			set_location(@1, @3);
			$$ = new BoolExpr(EXPR_AND, $1, $3);
			}

	|	expr TOK_OR expr
			{
			set_location(@1, @3);
			$$ = new BoolExpr(EXPR_OR, $1, $3);
			}

	|	expr TOK_EQ expr
			{
			set_location(@1, @3);
			$$ = new EqExpr(EXPR_EQ, $1, $3);
			}

	|	expr TOK_NE expr
			{
			set_location(@1, @3);
			$$ = new EqExpr(EXPR_NE, $1, $3);
			}

	|	expr '<' expr
			{
			set_location(@1, @3);
			$$ = new RelExpr(EXPR_LT, $1, $3);
			}

	|	expr TOK_LE expr
			{
			set_location(@1, @3);
			$$ = new RelExpr(EXPR_LE, $1, $3);
			}

	|	expr '>' expr
			{
			set_location(@1, @3);
			$$ = new RelExpr(EXPR_GT, $1, $3);
			}

	|	expr TOK_GE expr
			{
			set_location(@1, @3);
			$$ = new RelExpr(EXPR_GE, $1, $3);
			}

	|	expr '?' expr ':' expr
			{
			set_location(@1, @5);
			$$ = new CondExpr($1, $3, $5);
			}

	|	expr '=' expr
			{
			set_location(@1, @3);
			$$ = get_assign_expr($1, $3, in_init);
			}

	|	expr '[' expr_list ']'
			{
			set_location(@1, @4);
			$$ = new IndexExpr($1, $3);
			}

	|	'$' TOK_ID
			{
			set_location(@1, @2);
			if ( ! curr_match_record_expr )
				{
				error("$field syntax may only be used in "
					"a record match expression");
				YYERROR;
				}
			else
				$$ = new FieldExpr(curr_match_record_expr, $2);
			}

	|	expr '$' TOK_ID
			{
			set_location(@1, @3);
			$$ = new FieldExpr($1, $3);
			}

	|	'$' TOK_ID '=' expr
			{
			set_location(@1, @4);
			$$ = new FieldAssignExpr($2, $4);
			}

	|	expr TOK_IN expr
			{
			set_location(@1, @3);
			$$ = new InExpr($1, $3);
			}

	|	expr TOK_NOT_IN expr
			{
			set_location(@1, @3);
			$$ = new NotExpr(new InExpr($1, $3));
			}

	|	'[' expr_list ']'
			{
			// A little crufty: we peek at the type of
			// the first expression in the list.  If it's
			// a record or a field assignment, then this
			// is a record constructor.  If not, then this
			// is a list used for an initializer.

			set_location(@1, @3);

			Expr* e0 = $2->Exprs()[0];
			if ( e0->Tag() == EXPR_FIELD_ASSIGN ||
			     e0->Type()->Tag() == TYPE_RECORD )
				$$ = new RecordConstructorExpr($2);
			else
				$$ = $2;
			}

	|	TOK_MATCH expr
			{
			if ( curr_match_record_expr )
				{
				curr_match_record_expr->Error("Nested match expressions not allowed");
				YYERROR;
				}
			else
				curr_match_record_expr = $2;
			}
		TOK_ON '{' match_list '}'
			{
			curr_match_record_expr = 0;
			set_location(@1, @7);
			$$ = new RecordMatchExpr($6);
			}

	|	expr '(' opt_expr_list ')'
			{
			set_location(@1, @4);
			$$ = new CallExpr($1, $3);
			}

	|	expr TOK_HAS_FIELD TOK_ID
			{
			set_location(@1, @3);
			$$ = new HasFieldExpr($1, $3, false);
			}

	|	expr TOK_HAS_ATTR TOK_ID
			{
			set_location(@1, @3);
			$$ = new HasFieldExpr($1, $3, true);
			}

	|	anonymous_function

	|	TOK_SCHEDULE expr '{' event '}'
			{
			set_location(@1, @5);
			$$ = new ScheduleExpr($2, $4);
			}

	|	TOK_ID
			{
			set_location(@1);

			ID* id = lookup_ID($1, current_module.c_str());
			if ( ! id )
				{
				if ( ! in_debug )
					{
/*	// CHECK THAT THIS IS NOT GLOBAL.
					id = install_ID($1, current_module.c_str(),
							false, is_export);
*/

					yyerror(fmt("unknown identifier %s", $1));
					YYERROR;
					}
				else
					{
					yyerror(fmt("unknown identifier %s", $1));
					YYERROR;
					}
				}
			else
				{
				if ( ! id->Type() )
					{
					id->Error("undeclared variable");
					id->SetType(error_type());
					$$ = new NameExpr(id);
					}

				else if ( id->IsEnumConst() )
					{
					EnumType* t = id->Type()->AsEnumType();
					int intval = t->Lookup(current_module,
							       id->Name());
					if ( intval < 0 )
						internal_error("enum value not found for %s", id->Name());
					$$ = new ConstExpr(new EnumVal(intval, t));
					}
				else
					$$ = new NameExpr(id);
				}
			}

	|	TOK_CONSTANT
			{
			set_location(@1);
			$$ = new ConstExpr($1);
			}

	|	pattern
			{
			set_location(@1);
			$1->Compile();
			$$ = new ConstExpr(new PatternVal($1));
			}

	|	TOK_THIS
			{
			set_location(@1);
			$$ = bro_this->Ref();
			}
	;

expr_list:
		expr_list ',' expr
			{
			set_location(@1, @3);
			$1->Append($3);
			}

	|	expr
			{
			set_location(@1);
			$$ = new ListExpr($1);
			}
	;

opt_expr_list:
		expr_list
	|
		{ $$ = new ListExpr(); }
	;

opt_comma:
		','
	|
	;

match_list:
		match_list ',' match
			{
			set_location(@1, @3);
			$1->insert(make_pair($3->priority, $3));
			}
	|	match
			{
			set_location(@1);
			$$ = new RecordMatchCases();
			$$->insert(make_pair($1->priority, $1));
			}
	;

match:
		'[' TOK_CONSTANT ',' TOK_CONSTANT ',' expr ']'
		    /* priority, classname, match-expr */
			{
			$$ = new RecordMatchCase();
			set_location(@1, @7);
			if ( ! IsIntegral($2->Type()->Tag()) )
				{
				$2->Error("Match case priority must be an integer/count");
				YYERROR;
				}

			$$->priority = $2->CoerceToInt();

			if ( $4->Type()->Tag() != TYPE_STRING )
				{
				$4->Error("Match class name must be a string");
				YYERROR;
				}

			$$->classname = $4;
			$$->match_expr = $6;
			}
	;

pattern:
		pattern '|' single_pattern
			{
			$1->AddPat($3);
			delete [] $3;
			}

	|	single_pattern
			{
			$$ = new RE_Matcher($1);
			delete [] $1;
			}
	;

single_pattern:
		'/' { begin_RE(); } TOK_PATTERN_TEXT { end_RE(); } '/'
			{ $$ = $3; }
	;

enum_id_list:
		TOK_ID
			{
			set_location(@1);

			EnumType* et = new EnumType(is_export);
			if ( et->AddName(current_module, $1) < 0 )
				error("identifier in enumerated type definition already exists");
			$$ = et;
			}

	|	enum_id_list ',' TOK_ID
			{
			set_location(@1, @3);

			if ( $1->AsEnumType()->AddName(current_module, $3) < 1 )
				error("identifier in enumerated type definition already exists");
			$$ = $1;
			}
	;

type:
		TOK_BOOL	{
				set_location(@1);
				$$ = base_type(TYPE_BOOL);
				}

	|	TOK_INT		{
				set_location(@1);
				$$ = base_type(TYPE_INT);
				}

	|	TOK_COUNT	{
				set_location(@1);
				$$ = base_type(TYPE_COUNT);
				}

	|	TOK_COUNTER	{
				set_location(@1);
				$$ = base_type(TYPE_COUNTER);
				}

	|	TOK_DOUBLE	{
				set_location(@1);
				$$ = base_type(TYPE_DOUBLE);
				}

	|	TOK_TIME	{
				set_location(@1);
				$$ = base_type(TYPE_TIME);
				}

	|	TOK_INTERVAL	{
				set_location(@1);
				$$ = base_type(TYPE_INTERVAL);
				}

	|	TOK_STRING	{
				set_location(@1);
				$$ = base_type(TYPE_STRING);
				}

	|	TOK_PATTERN	{
				set_location(@1);
				$$ = base_type(TYPE_PATTERN);
				}

	|	TOK_TIMER	{
				set_location(@1);
				$$ = base_type(TYPE_TIMER);
				}

	|	TOK_PORT	{
				set_location(@1);
				$$ = base_type(TYPE_PORT);
				}

	|	TOK_ADDR	{
				set_location(@1);
				$$ = base_type(TYPE_ADDR);
				}

	|	TOK_NET		{
				set_location(@1);
				$$ = base_type(TYPE_NET);
				}

	|	TOK_SUBNET	{
				set_location(@1);
				$$ = base_type(TYPE_SUBNET);
				}

	|	TOK_ANY		{
				set_location(@1);
				$$ = base_type(TYPE_ANY);
				}

	|	TOK_TABLE '[' type_list ']' TOK_OF type
				{
				set_location(@1, @6);
				$$ = new TableType($3, $6);
				}

	|	TOK_SET '[' type_list ']'
				{
				set_location(@1,@4);
				$$ = new SetType($3, 0);
				}

	|	TOK_SET '{' expr_list '}'
				{
				set_location(@1, @4);
				$$ = new SetType((TypeList*) 0, $3);
				}

	|	TOK_RECORD '{' type_decl_list '}'
				{
				set_location(@1, @4);
				$$ = new RecordType($3);
				}

	|	TOK_UNION '{' type_list '}'
				{
				set_location(@1, @4);
				error("union type not implemented");
				$$ = 0;
				}

	|	TOK_ENUM '{' enum_id_list opt_comma '}'
				{
				set_location(@1, @4);
				$$ = $3;
				}

	|	TOK_LIST
				{
				set_location(@1);
				// $$ = new TypeList();
				error("list type not implemented");
				$$ = 0;
				}

	|	TOK_LIST TOK_OF type
				{
				set_location(@1);
				// $$ = new TypeList($3);
				error("list type not implemented");
				$$ = 0;
				}

	|	TOK_FUNCTION func_params
				{
				set_location(@1, @2);
				$$ = $2;
				}

	|	TOK_EVENT '(' formal_args ')'
				{
				set_location(@1, @3);
				$$ = new FuncType($3, 0, 1);
				}

	|	TOK_FILE TOK_OF type
				{
				set_location(@1, @3);
				$$ = new FileType($3);
				}

	|	TOK_FILE
				{
				set_location(@1);
				$$ = new FileType(base_type(TYPE_STRING));
				}

	|	resolve_id
			{
			if ( ! $1 || ! ($$ = $1->AsType()) )
				{
				if ( $1 )
					$1->Error("not a BRO type");
				$$ = error_type();
				}
			else
				Ref($$);
			}
	;

type_list:
		type_list ',' type
			{ $1->AppendEvenIfNotPure($3); }
	|	type
			{
			$$ = new TypeList($1);
			$$->Append($1);
			}
	;

type_decl_list:
		type_decl_list type_decl
			{ $1->append($2); }
	|
			{ $$ = new type_decl_list(); }
	;

type_decl:
		TOK_ID ':' type opt_attr ';'
			{
			set_location(@1, @5);
			$$ = new TypeDecl($3, $1, $4);
			}
	;

formal_args:	formal_args_decl_list
			{ $$ = new RecordType($1); }
	|	formal_args_decl_list ';'
			{ $$ = new RecordType($1); }
	|
			{ $$ = new RecordType(new type_decl_list()); }
	;

formal_args_decl_list:
		formal_args_decl_list ';' formal_args_decl
			{ $1->append($3); }
	|	formal_args_decl_list ',' formal_args_decl
			{ $1->append($3); }
	|	formal_args_decl
			{ $$ = new type_decl_list(); $$->append($1); }
	;

formal_args_decl:
		TOK_ID ':' type opt_attr
			{
			set_location(@1, @4);
			$$ = new TypeDecl($3, $1, $4);
			}
	;

decl:
		TOK_MODULE TOK_ID ';'
			{ current_module = $2; }

	|	TOK_EXPORT '{' { is_export = true; } decl_list '}'
			{ is_export = false; }

	|	TOK_GLOBAL global_id opt_type init_class opt_init opt_attr ';'
			{ add_global($2, $3, $4, $5, $6, VAR_REGULAR); }

	|	TOK_CONST global_id opt_type init_class opt_init opt_attr ';'
			{ add_global($2, $3, $4, $5, $6, VAR_CONST); }

	|	TOK_REDEF global_id opt_type init_class opt_init opt_attr ';'
			{ add_global($2, $3, $4, $5, $6, VAR_REDEF); }

	|       TOK_REDEF TOK_ENUM global_id TOK_ADD_TO
		'{' enum_id_list opt_comma '}' ';'
			{
			if ( ! $3->Type() )
				$3->Error("unknown identifier");
			else
				{
				EnumType* add_to = $3->Type()->AsEnumType();
				if ( ! add_to )
					$3->Error("not an enum");
				else
					add_to->AddNamesFrom(current_module,
							     $6->AsEnumType());
				}
			}

	|	TOK_TYPE global_id ':' refined_type opt_attr opt_attr_attr';'
			{
			add_type($2, $4, $5, 0);
			if ( $6 )
				$2->AsType()->SetAttributesType($6);
			}

	|	TOK_GLOBAL_ATTR ':' { in_global_attr_decl = true; }
		'{' type_decl_list '}' ';' { in_global_attr_decl = false; }
			{
			global_attributes_type = new RecordType($5);
			}

	|	TOK_EVENT global_id ':' refined_type opt_attr ';'
			{ add_type($2, $4, $5, 1); }

	|	func_hdr func_body
			{ }

	|	conditional
	;

conditional:
		TOK_ATIF '(' expr ')'
			{ do_atif($3); }
	|	TOK_ATIFDEF '(' TOK_ID ')'
			{ do_atifdef($3); }
	|	TOK_ATIFNDEF '(' TOK_ID ')'
			{ do_atifndef($3); }
	|	TOK_ATENDIF
			{ do_atendif(); }
	|	TOK_ATELSE
			{ do_atelse(); }
	;

opt_attr_attr:
		TOK_ATTR_ATTR '=' '{' type_decl_list '}'
			{ $$ = $4; }
	|
			{ $$ = 0; }
	;

func_hdr:
		TOK_FUNCTION global_id func_params
			{
			begin_func($2, current_module.c_str(),
				   FUNC_FLAVOR_FUNCTION, 0, $3);
			$$ = $3;
			}
	|	TOK_EVENT global_id func_params
			{
			begin_func($2, current_module.c_str(),
				   FUNC_FLAVOR_EVENT, 0, $3);
			$$ = $3;
			}
	|	TOK_REDEF TOK_EVENT global_id func_params
			{
			begin_func($3, current_module.c_str(),
				   FUNC_FLAVOR_EVENT, 1, $4);
			$$ = $4;
			}
	;

func_body:
		'{' stmt_list '}'
			{
			if ( optimize )
				$2 = $2->Simplify();

			end_func($2);
			}
	;

anonymous_function:
		TOK_FUNCTION begin_func func_body
			{ $$ = new ConstExpr($2->ID_Val()); }
	;

begin_func:
		func_params
			{
			$$ = current_scope()->GenerateTemporary("anonymous-function");
			begin_func($$, current_module.c_str(),
				   FUNC_FLAVOR_FUNCTION, 0, $1);
			}
	;

func_params:
		'(' formal_args ')' ':' type
			{ $$ = new FuncType($2, $5, 0); }
	|	'(' formal_args ')'
			{ $$ = new FuncType($2, base_type(TYPE_VOID), 0); }
	;

refined_type:
		type_list '{' type_decl_list '}'
			{ $$ = refine_type($1, $3); }
	|	type_list
			{ $$ = refine_type($1, 0); }
	;

opt_type:
		':' type
			{ $$ = $2; }
	|
			{ $$ = 0; }
	;

init_class:
				{ $$ = INIT_NONE; }
	|	'='		{ $$ = INIT_FULL; }
	|	TOK_ADD_TO	{ $$ = INIT_EXTRA; }
	|	TOK_REMOVE_FROM	{ $$ = INIT_REMOVE; }
	;

opt_init:
		{ ++in_init; } init { --in_init; }
			{ $$ = $2; }
	|
			{ $$ = 0; }
	;

init:
		'{' opt_expr_list '}'
			{ $$ = $2; }
	|	'{' expr_list ',' '}'
			{ $$ = $2; }
	|	expr
	;

opt_attr:
		attr_list
	|
			{ $$ = 0; }
	;

attr_list:
		attr_list attr
			{ $1->append($2); }
	|	attr
			{
			$$ = new attr_list;
			$$->append($1);
			}
	;

attr:
		TOK_ATTR_DEFAULT '=' expr
			{ $$ = new Attr(ATTR_DEFAULT, $3); }
	|	TOK_ATTR_OPTIONAL
			{ $$ = new Attr(ATTR_OPTIONAL); }
	|	TOK_ATTR_REDEF
			{ $$ = new Attr(ATTR_REDEF); }
	|	TOK_ATTR_ADD_FUNC '=' expr
			{ $$ = new Attr(ATTR_ADD_FUNC, $3); }
	|	TOK_ATTR_DEL_FUNC '=' expr
			{ $$ = new Attr(ATTR_DEL_FUNC, $3); }
	|	TOK_ATTR_EXPIRE_FUNC '=' expr
			{ $$ = new Attr(ATTR_EXPIRE_FUNC, $3); }
	|	TOK_ATTR_EXPIRE_CREATE '=' expr
			{ $$ = new Attr(ATTR_EXPIRE_CREATE, $3); }
	|	TOK_ATTR_EXPIRE_READ '=' expr
			{ $$ = new Attr(ATTR_EXPIRE_READ, $3); }
	|	TOK_ATTR_EXPIRE_WRITE '=' expr
			{ $$ = new Attr(ATTR_EXPIRE_WRITE, $3); }
	|	TOK_ATTR_PERSISTENT
			{ $$ = new Attr(ATTR_PERSISTENT); }
	;

stmt:
		'{' stmt_list '}'
			{ $$ = $2; }

	|	TOK_LOG expr_list ';'
			{
			set_location(@1, @3);
			$$ = new LogStmt($2);
			}

	|	TOK_PRINT expr_list ';'
			{
			set_location(@1, @3);
			$$ = new PrintStmt($2);
			}

	|	TOK_EVENT event ';'
			{
			set_location(@1, @3);
			$$ = new EventStmt($2);
			}

	|	TOK_IF '(' expr ')' stmt
			{
			set_location(@1, @4);
			$$ = new IfStmt($3, $5, new NullStmt());
			}

	|	TOK_IF '(' expr ')' stmt TOK_ELSE stmt
			{
			set_location(@1, @4);
			$$ = new IfStmt($3, $5, $7);
			}

	|	TOK_SWITCH expr '{' case_list '}'
			{
			set_location(@1, @2);
			$$ = new SwitchStmt($2, $4);
			}

	|	for_head stmt
			{ $1->AsForStmt()->AddBody($2); }

	|	TOK_NEXT ';'
			{
			set_location(@1, @2);
			$$ = new NextStmt;
			}

	|	TOK_BREAK ';'
			{
			set_location(@1, @2);
			$$ = new BreakStmt;
			}

	|	TOK_RETURN ';'
			{
			set_location(@1, @2);
			$$ = new ReturnStmt(0);
			}

	|	TOK_RETURN expr ';'
			{
			set_location(@1, @2);
			$$ = new ReturnStmt($2);
			}

	|	TOK_ADD expr ';'
			{
			set_location(@1, @3);
			$$ = new AddStmt($2);
			}

	|	TOK_DELETE expr ';'
			{
			set_location(@1, @3);
			$$ = new DelStmt($2);
			}

	|	TOK_LOCAL local_id opt_type init_class opt_init opt_attr ';'
			{
			set_location(@1, @7);
			$$ = add_local($2, $3, $4, $5, $6, VAR_REGULAR);
			}

	|	TOK_CONST local_id opt_type init_class opt_init opt_attr ';'
			{
			set_location(@1, @6);
			$$ = add_local($2, $3, $4, $5, $6, VAR_CONST);
			}

	|	expr ';'
			{
			set_location(@1, @2);
			$$ = new ExprStmt($1);
			}

	|	';'
			{
			set_location(@1, @1);
			$$ = new NullStmt;
			}

	|	conditional
			{ $$ = new NullStmt; }
	;

stmt_list:
		stmt_list stmt
			{
			$1->AsStmtList()->Stmts().append($2);
			assert($2->GetLocationInfo()->first_line != 0);
			}
	|
			{ $$ = new StmtList(); }
	;

event:
		TOK_ID '(' opt_expr_list ')'
			{
			set_location(@1, @4);
			$$ = new EventExpr($1, $3);
			}
	;

case_list:
		case_list case
			{ $1->append($2); }
	|
			{ $$ = new case_list; }
	;

case:
		TOK_CASE expr_list ':' stmt_list
			{ $$ = new Case($2, $4); }
	|
		TOK_DEFAULT ':' stmt_list
			{ $$ = new Case(0, $3); }
	;

for_head:
		TOK_FOR '(' TOK_ID TOK_IN expr ')'
			{
			set_location(@1, @6);

			// This rule needs to be separate from the loop
			// body so that we execute these actions - defining
			// the local variable - prior to parsing the body,
			// which might refer to the variable.
			ID* loop_var = lookup_ID($3, current_module.c_str());

			if ( loop_var )
				{
				if ( loop_var->IsGlobal() )
					loop_var->Error("global used in for loop");
				}

			else
				loop_var = install_ID($3, current_module.c_str(),
						      false, false);

			$$ = new ForStmt(loop_var, $5);
			}
		;

local_id:
		TOK_ID
			{
			set_location(@1);

			$$ = lookup_ID($1, current_module.c_str());
			if ( $$ )
				{
				if ( $$->IsGlobal() )
					$$->Error("already a global identifier");
				delete [] $1;
				}

			else
				{
				$$ = install_ID($1, current_module.c_str(),
						false, is_export);
				}
			}
	;

global_id:
		TOK_ID
			{
			set_location(@1);

			$$ = lookup_ID($1, current_module.c_str());
			if ( $$ )
				{
				if ( ! $$->IsGlobal() )
					$$->Error("already a local identifier");

				delete [] $1;
				}

			else
				{
				$$ = install_ID($1, current_module.c_str(),
						true, is_export);
				}
			}
	;

resolve_id:
		TOK_ID
			{
			set_location(@1);
			$$ = lookup_ID($1, current_module.c_str());
			if ( ! $$ )
				error("identifier not defined", $1);
			delete [] $1;
			}
	;

%%

int yyerror(const char msg[])
	{
	char* msgbuf = new char[strlen(msg) + strlen(brotext) + 64];

	if ( brotext[0] == '\n' )
		sprintf(msgbuf, "%s, on previous line", msg);
	else if ( brotext[0] == '\0' )
		sprintf(msgbuf, "%s, at end of file", msg);
	else
		sprintf(msgbuf, "%s, at or near \"%s\"", msg, brotext);

	error(msgbuf);
	curr_match_record_expr = 0;

	return 0;
	}
