%{
/* This file is part of Mailfromd.
   Copyright (C) 2005-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <fnmatch.h>
#include <netdb.h>
#include <limits.h>
#include "mailfromd.h"
#include <mailutils/yyloc.h>
#include "global.h"
#include "prog.h"
#include "optab.h"

static void free_node(NODE *node); 
static void set_poll_arg(struct poll_data *poll, int kw, NODE *expr);
static int codegen(prog_counter_t *pc, NODE *node, struct exmask *exmask,
		   size_t nautos);
static void mark(NODE *node);
static void dataseg_layout(void);
static void regex_layout(void);
static int optimize_tree(NODE *node);
static void compile_tree(NODE *node);
static NODE *reverse(NODE *in);
static size_t nodelistlength(NODE *list);
static NODE *function_call(struct function *function, size_t count,
                           NODE *subtree);
static NODE *declare_function(struct function *func,
		              struct mu_locus_range const *loc,
                              size_t nautos);
static data_type_t node_type(NODE *node);
static NODE *cast_arg_list(NODE *args, size_t parmc, data_type_t *parmtype,
			   data_type_t vartype);
void add_xref(struct variable *var, struct mu_locus_range const *locus);
static struct variable *vardecl(const char *name, data_type_t type,
                                storage_class_t sc,
		                struct mu_locus_range const *loc);
static struct variable *externdecl(const char *name, struct value *value,
		                   struct mu_locus_range const *loc);
static int initialize_variable(struct variable *var, struct value *val,
                               struct mu_locus_range const *locus);
static void apply_deferred_init(void);
static NODE *create_asgn_node(struct variable *var, NODE *expr,
		              struct mu_locus_range const *loc);

NODE *defined_parm(struct variable *var, struct mu_locus_range const *locus);
 
static void register_auto(struct variable *var);
static void unregister_auto(struct variable *var);
static size_t forget_autos(size_t nparam, size_t prev, size_t hidden_arg);
static void optimize(NODE *node);

static NODE *root_node[smtp_state_count];
prog_counter_t entry_point[smtp_state_count];

int regex_flags;        /* Should default to REG_NOSUB ? */
unsigned error_count;   /* Number of detected errors */
size_t variable_count = 0;  /* Number of variables in the data segment. */
size_t precious_count = 0;

static enum smtp_state state_tag; /* State tag of the currently processed
                                      PROG */

static struct function *func;   /* The currently compiled function */
static prog_counter_t jump_pc;  /* Pointer to the chain of jmp instructions */
static prog_counter_t action_hook_pc; /* Chain of action hook addresses */
/* Outermost context decides how to code exits from catches.
   Innermost context is used to access parameters. */
enum lexical_context outer_context, inner_context;

size_t catch_nesting;  /* Nesting level for catch statements */

static struct stmtlist genstmt;     /* List of generated statements */

/* State handlers and their positional parameters */ 
struct state_parms {
        int cnt;                /* Number or positional parameters */
        data_type_t types[6];   /* Their data types */
} state_parms[smtp_state_count] = {
        [smtp_state_connect] = { 4, { dtype_string, dtype_number, dtype_number, dtype_string } },
        [smtp_state_helo] = { 1, { dtype_string } },
        [smtp_state_envfrom] = { 2, { dtype_string, dtype_string } },
        [smtp_state_envrcpt] = { 2, { dtype_string, dtype_string } },
        [smtp_state_header] = { 2, { dtype_string, dtype_string } },
        [smtp_state_body] = { 2, { dtype_pointer, dtype_number } },
	[smtp_state_action] = { 6, { dtype_number, dtype_string, dtype_string, dtype_string, dtype_string, dtype_number } },
};

struct parmtype {
        struct parmtype *next;
        data_type_t type;
};

static int
parmcount_none()
{
        return 0;
}

static data_type_t
parmtype_none(int n)
{
        return dtype_unspecified;
}

static int
parmcount_handler()
{
        return state_parms[state_tag].cnt;
}

static data_type_t
parmtype_handler(int n)
{
        return state_parms[state_tag].types[n-1];
}

static int
parmcount_catch()
{
        return 3;
}

static data_type_t
parmtype_catch(int n)
{
        switch (n) {
        case 1:
                return dtype_number;
        case 2:
                return dtype_string;
	case 3:
		return dtype_number;
        }
        abort();
}

static int
parmcount_function()
{
        return func->parmcount;
}

static data_type_t
parmtype_function(int n)
{
        if (func->varargs && n > func->parmcount)
                return func->vartype;
        return func->parmtype[n-1];
}

struct parminfo {
        int (*parmcount)(void);
        data_type_t (*parmtype)(int n);
} parminfo[] = {
        { parmcount_none, parmtype_none },
        { parmcount_handler, parmtype_handler },
        { parmcount_catch, parmtype_catch },
        { parmcount_function, parmtype_function },
};

#define PARMCOUNT() parminfo[inner_context].parmcount()
#define PARMTYPE(n) parminfo[inner_context].parmtype(n)
#define FUNC_HIDDEN_ARGS(f) (((f)->optcount || (f)->varargs) ? 1 : 0)


data_type_t
string_to_type(const char *s)
{
        if (strcmp(s, "n") == 0)
                return dtype_number;
        else if (strcmp(s, "s") == 0)
                return dtype_string;
        else
                return dtype_unspecified;
}

const char *
type_to_string(data_type_t t)
{
        switch (t) {
        case dtype_number:
                return "number";
        case dtype_string:
                return "string";
        case dtype_unspecified:
                return "unspecified";
        case dtype_pointer:
                return "pointer";
	case dtype_any:
		return "any";
        default:
                abort();
        }
}

static int
check_func_usage(struct function *fp, struct mu_locus_range const *locus)
{
	switch (outer_context) {
	case context_handler:
		if (fp->statemask && !(STATMASK(state_tag) & fp->statemask)) {
                        parse_error_locus(locus,
                                          _("function `%s' cannot be used in "
					    "prog `%s'"),
				    fp->sym.name,
				    xstate_to_string(state_tag));
			return 1;
		}
		break;
		
	case context_function:
		func->statemask |= fp->statemask;
		break;

	default:
		break;
	}
	return 0;
}

static int
check_builtin_usage(const struct builtin *bp,
		    struct mu_locus_range const *locus)
{
	switch (outer_context) {
	case context_handler:
		if (bp->statemask && !(STATMASK(state_tag) & bp->statemask)) {
			parse_error_locus(locus,
					  _("built-in function `%s' cannot be used in "
					    "prog `%s'"),
					  bp->name,
					  xstate_to_string(state_tag));
			return 1;
		}
		break;
		
	case context_function:
		func->statemask |= bp->statemask;
		break;
		
	default:
		break;
	}
			
	if (bp->flags & MFD_BUILTIN_CAPTURE)
		capture_on();
	return 0;
}

static void jump_fixup(prog_counter_t pos, prog_counter_t endpos);

#define LITERAL_TEXT(lit) ((lit) ? (lit)->text : NULL)
#define LITERAL_OFF(lit) ((lit) ? (lit)->off : 0)

static int
_create_alias(void *item, void *data)
{
        struct literal *lit = item;
        struct function *fun = data;
        /* FIXME: Ideally we should pass a locus of `lit' instead of
           fun->locus. However, its only purpose is for use in redefinition
           diagnostic messages and these are never produced, because the
           grammar catches these errors earlier. This might change in
           the future.  2008-09-14 */
        install_alias(lit->text, fun, &fun->sym.locus);
        return 0;
}
%}

%error-verbose
%locations
%expect 15
%code requires {
#define YYLTYPE struct mu_locus_range
}

%union {
	struct literal *literal;
	struct stmtlist stmtlist;
	NODE *node;
	struct return_node ret;
	struct poll_data poll;
	struct pollarg {
		int kw;
		NODE *expr;
	} pollarg;
	struct arglist {
		NODE *head;
		NODE *tail;
		size_t count;
	} arglist;
	long number;
	const struct builtin *builtin;
	struct variable *var;
	enum smtp_state state;
	struct {
		int qualifier;
	} matchtype;
	data_type_t type;
	struct parmtype *parm;
	struct parmlist {
		struct parmtype *head, *tail;
		size_t count;
		size_t optcount;
		int varargs;
		data_type_t vartype;
	} parmlist;
	enum lexical_context tie_in;
	struct function *function;
	struct {
		struct valist *head, *tail;
	} valist_list;
	struct {
		int all;
		struct valist *valist;
	} catchlist;
	struct valist *valist;
	struct value value;
	struct {
		struct case_stmt *head, *tail;
	} case_list ;
	struct case_stmt *case_stmt;
	struct loop_node loop;
	struct enumlist {
		struct constant *cv;
		struct enumlist *prev;
	} enumlist;
	mu_list_t list;
	struct import_rule_list import_rule_list;
	char *string;
};

%token T_ACCEPT "accept"
       T_REJECT "reject"
       T_TEMPFAIL "tempfail"
       T_CONTINUE "continue"
       T_DISCARD "discard"
       T_ADD "add" T_REPLACE "replace" T_DELETE "delete"
       T_PROG "prog"
       T_IF "if"
       T_FI "fi"
       T_ELSE "else"
       T_ELIF "elif"
       T_ON "on"
       T_HOST "host"
       T_FROM "from"
       T_AS "as"
       T_DO "do" T_DONE "done"
       T_POLL "poll"
       T_MATCHES "matches"
       T_FNMATCHES "fnmatches"
       T_MXMATCHES "mx matches"
       T_MXFNMATCHES "mx fnmatches"
       T_WHEN "when"
       T_PASS "pass"
       T_SET "set"
       T_CATCH "catch"
       T_TRY "try"
       T_THROW "throw"
       T_ECHO "echo"
       T_RETURNS "returns"
       T_RETURN "return"
       T_FUNC "func"
       T_SWITCH "switch"
       T_CASE "case"
       T_DEFAULT "default"
       T_CONST "const"
       T_FOR "for"
       T_LOOP "loop"
       T_WHILE "while"
       T_BREAK "break"
       T_NEXT "next"
       T_ARGCOUNT "$#"
       T_ALIAS "alias"
       T_DOTS "..."
       T_ARGX "$(n)"
       T_VAPTR "vaptr"
       T_PRECIOUS "precious"
       T_OR "or"
       T_AND "and"
       T_EQ "=="
       T_NE "!="
       T_LT "<"
       T_LE "<="
       T_GT ">"
       T_GE ">="
       T_NOT "!"
       T_LOGAND "&"
       T_LOGOR "|"
       T_LOGXOR "^"
       T_LOGNOT "~"
       T_REQUIRE "require"
       T_IMPORT "import"
       T_STATIC "static"
       T_PUBLIC "public"
       T_MODULE "module"
       T_BYE "bye"
       T_DCLEX "dclex"
       T_SHL "<<"
       T_SHR ">>"
       T_SED "sed"
       T_ARGV "$@"
       T_VOID "void"

%token <node> T_COMPOSE "composed string"
%token T_MODBEG T_MODEND
%token <literal> T_STRING "string"
%token <literal> T_SYMBOL "MTA macro" T_IDENTIFIER "identifier"
%token <number> T_ARG "$n" T_NUMBER "number" T_BACKREF "back reference"
%token <builtin> T_BUILTIN "builtin function"
%token <function> T_FUNCTION "function"
%token <type> T_TYPE "data type" T_TYPECAST "typecast"
%token <var> T_VARIABLE "variable"
%token T_BOGUS

%left '.'
%left T_OR
%left T_AND
%left T_NOT
%left T_LOGOR
%left T_LOGXOR
%left T_LOGAND
%nonassoc T_EQ T_NE T_MATCHES T_FNMATCHES T_MXMATCHES T_MXFNMATCHES
%nonassoc T_LT T_LE T_GT T_GE
%left T_SHL T_SHR
%left '+' '-'
%left '*' '/' '%'
%left T_UMINUS

%type <node> decl stmt condition action sendmail_action header_action
             if_cond else_cond on_cond atom argref paren_argref
             funcall void_funcall expr bool_expr maybe_expr maybe_xcode_expr
             simp_expr atom_expr
             asgn catch simple_catch try_block throw return
             case_cond autodcl constdecl
             loopstmt opt_while jumpstmt strval strcat argv argvarg
%type <stmtlist> program stmtlist decllist modcntl
%type <ret> triplet maybe_triplet
%type <poll> pollstmt pollarglist
%type <pollarg> pollarg loop_parm
%type <loop> opt_loop_parms loop_parm_list
%type <arglist> arglist funcarglist
%type <var> variable
%type <literal> literal string opt_ident loop_ident alias code xcode varname
%type <state> state_ident
%type <matchtype> matches fnmatches
%type <type> retdecl dots
%type <parmlist> params opt_parmlist parmlist fparmlist parmdecl
%type <parm> parm
%type <function> fundecl
%type <value> value
%type <valist_list> valist
%type <catchlist> catchlist
%type <case_list> cond_branches branches 
%type <case_stmt> cond_branch branch
%type <locus> on
%type <list> aliases aliasdecl
%type <import_rule_list> imports
%type <number> qualifiers qualifier qualconst
%type <enumlist> enumlist constdefn
%%

input      : program
             {
                     if (error_count == 0) {
                             if (genstmt.head) {
                                     genstmt.tail->next = $1.head;
                                     genstmt.tail = $1.tail;
                             } else
                                     genstmt = $1;
                             optimize_tree(genstmt.head);
                             if (error_count)
                                     YYERROR;
                             mark(genstmt.head);
                             if (error_count)
                                     YYERROR;
                             apply_deferred_init();
			     fixup_exceptions();
                             dataseg_layout();
                             if (optimization_level)
                                     regex_layout();
                             compile_tree(genstmt.head);
                             if (!optimization_level)
                                     regex_layout();
                     }
             }
           ;

program    : decllist
           | moddecl decllist bye
             {
                     $$ = $2;
             }
           ;

decllist   : decl
             {
                     $$.head = $$.tail = $1;
             }
           | modcntl
           | decllist decl
             {
                     if ($2) {
                             if ($1.tail) 
                                     $1.tail->next = $2;
                             else
                                     $1.head = $2;
                             $1.tail = $2;
                     }
                     $$ = $1;
             }
           | decllist modcntl
             {
                     if ($2.tail) {
                             if ($1.tail) {
                                     $$.head = $1.head;
                                     $1.tail->next = $2.head;
                                     $$.tail = $2.tail;
                             } else
                                     $$ = $2;
                     } 
             }
           ;

modcntl    : require
             {
                     $$.head = $$.tail = NULL;
             }
           | require T_MODBEG opt_moddecl decllist bye T_MODEND
             {
                     $$ = $4;
                     pop_top_module();
             }
           ;

opt_moddecl: /* empty */
             {
                     parse_warning(_("missing module declaration"));
             }
           | moddecl
           ;

moddecl    : T_MODULE literal '.'
             {
		     if (top_module->dclname) {
			     struct mu_locus_range lr;
			     lr.beg = @1.beg;
			     lr.end = @3.end;
                             parse_error_locus(&lr,
                                               _("duplicate module declaration"));
		     } else
			     top_module->dclname = $2->text;
	     }
           | T_MODULE literal qualifier '.'
             {
		     if (top_module->dclname) {
			     struct mu_locus_range lr;
			     lr.beg = @1.beg;
			     lr.end = @4.end;
                             parse_error_locus(&lr,
                                               _("duplicate module declaration"));
		     } else {
			     if (($3 & (SYM_STATIC|SYM_PUBLIC)) == 0)
                                     parse_error_locus(&@3,
                                                       _("invalid module declaration"));
			     top_module->dclname = $2->text;
			     top_module->flags = $3;
		     }
	     }
           ;

require    : T_REQUIRE literal
             {
                     require_module($2->text, NULL);
             }
           | T_FROM literal T_IMPORT imports '.'
             {
                     require_module($2->text, $4.head);
             }
           ;

bye        : /* empty */
           | T_BYE
             {
                     lex_bye();
             }
           ;

imports    : literal
             {
                     struct import_rule *rule = import_rule_create($1);
                     $$.head = $$.tail = rule;
             }
           | imports ',' literal
             {
                     struct import_rule *rule = import_rule_create($3);
                     $$.tail->next = rule;
                     $$.tail = rule;
             }
           ;

literal    : T_STRING
           | T_IDENTIFIER
           ;

decl       : T_PROG state_ident T_DO stmtlist T_DONE
             {
		     mf_yyltype_t loc;

		     loc.beg = @1.beg;
		     loc.end = @5.end;

		     if (root_node[state_tag]) {
			     root_node[state_tag]->v.progdecl.tree.tail->next = $4.head;
			     root_node[state_tag]->v.progdecl.tree.tail = $4.tail;
			     $$ = NULL;
		     } else {
			     $$ = alloc_node(node_type_progdecl, &loc);
			     $$->v.progdecl.tag = state_tag;
			     $$->v.progdecl.tree = $4;
			     $$->v.progdecl.auto_count = 0;
			     root_node[state_tag] = $$;
			     $$->v.progdecl.auto_count =
				     forget_autos(PARMCOUNT(), 0, 0);
		     }
		     outer_context = inner_context = context_none;
		     state_tag = smtp_state_none;
	     }
           | qualifiers T_FUNC fundecl T_DO stmtlist T_DONE
             {
		     if ($1 & SYM_PRECIOUS)
                             parse_error_locus(&@2,
					       _("`precious' used with func"));
		     if (($1 & (SYM_STATIC|SYM_PUBLIC)) ==
			 (SYM_STATIC|SYM_PUBLIC))
                             parse_error_locus(&@2,
					       _("`static' and `public' "
						 "used together"));
		     func->sym.flags = $1;
		     func->node = $5.head;
                     $$ = declare_function(func, &@2,
			   forget_autos(PARMCOUNT() + FUNC_HIDDEN_ARGS(func),
					0, FUNC_HIDDEN_ARGS(func)));
		     outer_context = inner_context = context_none;
		     func = NULL;
	     }
           | vardecl
	     {
		     $$ = NULL;
	     }
	   | constdecl
	     {
		     $$ = NULL;
	     }
           | exdecl
             {
		     $$ = NULL;
	     }
           ;

varname    : T_IDENTIFIER
           | T_VARIABLE
             {
		     $$ = literal_lookup($1->sym.name);
	     }
           ;

vardecl    : qualifiers T_TYPE varname
             {
		     struct variable *var;
		     
		     if (($1 & (SYM_STATIC|SYM_PUBLIC))
			 == (SYM_STATIC|SYM_PUBLIC))
                             parse_error_locus(&@1,
                                               _("`static' and `public' "
						 "used together"));
		     var = vardecl($3->text, $2, storage_extern, &@3);
		     if (!var) 
			     YYERROR;
		     var->sym.flags |= $1;
	     }
           | qualifiers T_TYPE varname expr
             {
                     struct value value;
                     struct variable *var;

		     if (($1 & (SYM_STATIC|SYM_PUBLIC))
			 == (SYM_STATIC|SYM_PUBLIC))
                             parse_error_locus(&@1,
                                               _("`static' and `public' "
						 "used together"));
		     
                     var = vardecl($3->text, $2, storage_extern, &@2);
                     if (!var)
                             YYERROR;
                     var->sym.flags |= $1;

                     if (optimization_level)
                             optimize($4);
                     value.type = node_type($4);

                     switch ($4->type) {
                     case node_type_string:
                             value.v.literal = $4->v.literal;
                             break;

                     case node_type_number:
                             value.v.number = $4->v.number;
                             break;

                     default:
                             yyerror("initializer element is not constant");
                             YYERROR;

		     }
		     if (initialize_variable(var, &value, &@3))
			     YYERROR;
	     }
           | T_SET varname expr
	     /* FIXME: Optimize if varname: T_VARIABLE */
             {
		     struct value value;
		     
		     if (optimization_level)
			     optimize($3);
		     value.type = node_type($3);
		     
		     switch ($3->type) {
		     case node_type_string:
			     value.v.literal = $3->v.literal;
			     break;

		     case node_type_number:
			     value.v.number = $3->v.number;
			     break;

		     default:
			     yyerror("initializer element is not constant");
			     YYERROR;
		     }
                     if (!externdecl($2->text, &value, &@2))
			     YYERROR;
	     }
           ;

qualifiers : /* empty */
             {
                     $$ = 0;
             }
           | qualifiers qualifier
             {
                     if ($1 & $2)
                             parse_warning(_("duplicate `%s'"),
                                           symbit_to_qualifier($2));
                     $$ = $1 | $2;
             }
           ;

qualifier  : T_PRECIOUS
             {
                     $$ = SYM_PRECIOUS;
             }
           | T_STATIC
             {
                     $$ = SYM_STATIC;
             }
           | T_PUBLIC
             {
                     $$ = SYM_PUBLIC;
             }
           ;


constdecl  : qualconst constdefn
             {
		     $2.cv->sym.flags = $1;
		     $$ = NULL;
	     }
           | qualconst T_DO enumlist T_DONE
	     {
		     struct enumlist *elist;
		     while ((elist = $3.prev)) {
			     elist->cv->sym.flags = $1;
			     elist = elist->prev;
			     free($3.prev);
			     $3.prev = elist;
		     }
		     $$ = NULL;
	     }
           ;

qualconst  : qualifiers T_CONST
             {
		     if ($1 & SYM_PRECIOUS)
                             parse_error_locus(&@1,
					       _("`precious' used with const"));
		     if (($1 & (SYM_STATIC|SYM_PUBLIC)) ==
			 (SYM_STATIC|SYM_PUBLIC))
                             parse_error_locus(&@1,
					       _("`static' and `public' "
						 "used together"));
		     $$ = $1;
	     }
           ;

constdefn  : varname expr
             /* FIXME: Optimize if varname: T_IDENTIFIER */
             {
		     struct value value;
		     struct variable *pvar;

		     /* FIXME: This is necessary because constants can be
			referred to the same way as variables. */
		     if (pvar = variable_lookup($1->text)) {
			     parse_warning(_("constant name `%s' clashes with a variable name"),
					   $1->text);
			     parse_warning_locus(&pvar->sym.locus,
						 _("this is the location of the "
						   "previous definition"));
		     }
			     
		     if (optimization_level)
			     optimize($2);
		     
		     switch ($2->type) {
		     case node_type_string:
			     value.type = dtype_string;
			     value.v.literal = $2->v.literal;
			     break;

		     case node_type_number:
			     value.type = dtype_number;
			     value.v.number = $2->v.number;
			     break;
			     
		     default:
			     yyerror(_("initializer element is not constant"));
			     YYERROR;
		     }

		     $$.cv = define_constant($1->text, &value, 0, &@1);
		     $$.prev =  NULL;
	     }
           ;

enumlist   : varname
             {
		     struct enumlist *elist;
		     struct value value;
		     struct variable *pvar;

		     if (pvar = variable_lookup($1->text)) {
			     parse_warning(_("constant name `%s' clashes with a variable name"),
					   $1->text);
			     parse_warning_locus(&pvar->sym.locus,
						 _("this is the location of the "
						   "previous definition"));
		     }

		     value.type = dtype_number;
		     value.v.number = 0;

		     elist = mu_alloc(sizeof(*elist));
		     
		     elist->cv = define_constant($1->text, &value, 0, &@1);
		     elist->prev = NULL;
		     $$.cv = NULL;
		     $$.prev = elist;
	     }
           | constdefn
             {
		     struct enumlist *elist = mu_alloc(sizeof(*elist));
		     elist->cv = $1.cv;
		     elist->prev = NULL;
		     $$.cv = NULL;
		     $$.prev = elist;
	     }
           | enumlist varname
	     {
		     if ($1.prev->cv->value.type == dtype_number) {
			     struct enumlist *elist = mu_alloc(sizeof(*elist));
			     struct value value;

			     value.type = dtype_number;
			     value.v.number = $1.prev->cv->value.v.number + 1;
			     
			     elist->cv = define_constant($2->text, &value, 0,
							 &@2);
			     elist->prev = $1.prev;
			     $$.prev = elist;
		     } else {
			     yyerror(_("initializer element is not numeric"));
			     YYERROR;
		     }
	     }
           | enumlist constdefn
	     {
		     struct enumlist *elist = mu_alloc(sizeof(*elist));
		     elist->cv = $2.cv;
		     elist->prev = $1.prev;
		     $1.prev = elist;
		     $$ = $1;
	     }
           ;

exdecl     : T_DCLEX T_IDENTIFIER
             {
		     define_exception($2, &@1);
	     }
           ;

fundecl    : varname '(' parmdecl ')' aliasdecl retdecl
 	     {
		     data_type_t *ptypes = NULL;
		     
		     if ($3.count) {
			     int i;
			     struct parmtype *p;
			     
			     ptypes = mu_alloc($3.count * sizeof *ptypes);
			     for (i = 0, p = $3.head; p; i++) {
				     struct parmtype *next = p->next;
				     ptypes[i] = p->type;
				     free(p);
				     p = next;
			     }
		     }

		     $$ = func = function_install($1->text,
						  $3.count, $3.optcount,
						  $3.varargs,
						  $3.vartype,
						  ptypes, $6,
                                                  &@1);
		     if ($5) {
			     mu_list_foreach($5, _create_alias, $$);
			     mu_list_destroy(&$5);
		     }
		     outer_context = inner_context = context_function;
	     }
           ;

parmdecl   : /* empty */
             {
                     $$.count = $$.optcount = 0;
                     $$.varargs = 0;
             }
           | params
           ;

params     : fparmlist
           | dots
             {
                     $$.count = $$.optcount = 0;
                     $$.varargs = 1;
		     $$.vartype = $1;
             }
           | opt_parmlist ';' fparmlist
             {
                     $1.count += $3.count;
                     $1.optcount = $3.count;
		     $1.varargs = $3.varargs;
		     $1.vartype = $3.vartype;
                     if ($1.tail) 
                             $1.tail->next = $3.head;
                     else
                             $1.head = $3.head;
                     $1.tail = $3.tail;
                     $$ = $1;
             }
           ;

opt_parmlist: /* empty */
             {
                     $$.count = 0;
                     $$.varargs = 0;
                     $$.head = $$.tail = NULL;
             }
           | parmlist
           ;

parmlist   : parm
             {
		     $$.count = 1;
		     $$.varargs = 0;
                     $$.optcount = 0;
		     $$.head = $$.tail = $1;
	     }
           | parmlist ',' parm
             {
                     $1.count++;
                     $1.tail->next = $3;
                     $1.tail = $3;
                     $$ = $1;
             }
           ;

fparmlist  : parmlist
           | parmlist ',' dots
             {
                     $1.varargs = 1;
		     $1.vartype = $3;
                     $$ = $1;
             }
           ;

dots       : T_DOTS
             {
		     $$ = dtype_string;
	     }
           | T_TYPE T_DOTS
	     {
		     $$ = $1;
	     }
           ;

parm       : T_TYPE varname
             {
		     if (!vardecl($2->text, $1, storage_param, &@2))
			     YYERROR;
		     $$ = mu_alloc(sizeof *$$);
		     $$->next = NULL;
		     $$->type = $1;
	     }
           ;

aliasdecl  : /* empty */
             {
                     $$ = NULL;
             }
           | aliases
           ;

aliases    : alias
             {
                     mu_list_create(&$$);
                     mu_list_append($$, $1);
             }
           | aliases alias
             {
                     mu_list_append($1, $2);
                     $$ = $1;
             }
           ;

alias      : T_ALIAS varname
             {
                     $$ = $2;
             }
           ;

retdecl    : /* empty */
             {
                     $$ = dtype_unspecified;
             }
           | T_RETURNS T_TYPE
             {
		     $$ = $2;
	     }
           ;

state_ident: T_IDENTIFIER
             {
		     $$ = string_to_state($1->text);
		     if ($$ == smtp_state_none)
                             parse_error_locus(&@1,
					       _("unknown smtp state tag: %s"),
					       $1->text);
		     state_tag = $$;
		     outer_context = inner_context = context_handler;
	     }
           ;

stmtlist   : stmt
             {
                     if ($1) 
                             $1->next = NULL;
                     $$.head = $$.tail = $1;
             }
           | stmtlist stmt
             {
                     if ($2) {
                             if ($1.tail) 
                                     $1.tail->next = $2;
                             else
                                     $1.head = $2;
                             $1.tail = $2;
                     }
                     $$ = $1;
             }
           ;

stmt       : condition
           | action
           | asgn
           | autodcl
           | catch
           | throw
           | return
           | funcall
             {
		     if (node_type($1) != dtype_unspecified) {
			     parse_warning(_("return from %s is ignored"),
					   $1->type == node_type_builtin ?
					   $1->v.builtin.builtin->name :
					   $1->v.call.func->sym.name);
			     $1->value_ignored = 1;
		     }
	     }
           | void_funcall
           | constdecl
           | loopstmt
           | jumpstmt
           ;

void_funcall: T_VOID '(' funcall ')'
             {
		     $3->value_ignored = 1;
		     $$ = $3;
	     }
           ;

asgn       : T_SET varname expr
             {
		     struct variable *var;
		     data_type_t t = node_type($3);

		     if (t == dtype_unspecified) {
                             parse_error_locus(&@3,
					   _("unspecified value not ignored as "
					     "it should be"));
			     YYERROR;
		     }

		     var = variable_lookup($2->text);
		     if (!var) {
                             var = vardecl($2->text, t, storage_auto, &@2);
			     if (!var) 
				     YYERROR;
		     }
                     $$ = create_asgn_node(var, $3, &@1);
		     if (!$$)
			     YYERROR;
	     }
           ;

autodcl    : T_TYPE varname
             {
		     if (!vardecl($2->text, $1, storage_auto, &@2))
			     YYERROR;
		     $$ = NULL;
	     }
           | T_TYPE varname expr
             {
		     struct variable *var = vardecl($2->text, $1,
						    storage_auto, &@2);
		     if (!var)
			     YYERROR;
                     $$ = create_asgn_node(var, $3, &@1);
		     if (!$$)
			     YYERROR;
	     }
           ;

action     : sendmail_action
             {
		     if (inner_context == context_handler) {
			     if (state_tag == smtp_state_begin)
                                     parse_error_locus(&@1,
                                                _("Sendmail action is not "
						   "allowed in begin block"));
			     else if (state_tag == smtp_state_end)
                                     parse_error_locus(&@1,
                                               _("Sendmail action is not "
						   "allowed in end block"));
		     }
	     }
           | header_action
             {
		     if (inner_context == context_handler
			 && state_tag == smtp_state_end)
                            parse_error_locus(&@1,
                                          _("header action is not allowed "
					   "in end block"));
	     }
           | T_PASS
             {
                    $$ = alloc_node(node_type_noop, &@1);
	     }
           | T_ECHO expr
             {
                    $$ = alloc_node(node_type_echo, &@1);
		    $$->v.node = cast_to(dtype_string, $2);
	     }
           ;

sendmail_action:
             T_ACCEPT maybe_triplet
             {
		     if ($2.code || $2.xcode || $2.message)
			     parse_warning(_("arguments are ignored for accept"));
                     $$ = alloc_node(node_type_result, &@1);
		     $$->v.ret = $2;
		     $$->v.ret.stat = SMFIS_ACCEPT;
	     }
           | T_REJECT maybe_triplet
             {
                     $$ = alloc_node(node_type_result, &@1);
		     $$->v.ret = $2;
		     if (!$$->v.ret.code)
			     $$->v.ret.code = create_node_string(DEFAULT_REJECT_CODE, &@1);
		     $$->v.ret.stat = SMFIS_REJECT;
	     }
           | T_REJECT '(' maybe_expr ',' maybe_xcode_expr ',' maybe_expr ')'
             {
                     $$ = alloc_node(node_type_result, &@1);
		     $$->v.ret.stat = SMFIS_REJECT;
		     $$->v.ret.code = $3 ? cast_to(dtype_string, $3) : create_node_string(DEFAULT_REJECT_CODE, &@1);;
		     $$->v.ret.xcode = $5 ? cast_to(dtype_string, $5) : NULL;
		     $$->v.ret.message = $7 ? cast_to(dtype_string, $7) : NULL;
	     }
           | T_TEMPFAIL maybe_triplet
             {
                     $$ = alloc_node(node_type_result, &@1);
		     $$->v.ret = $2;
		     if (!$$->v.ret.code)
			     $$->v.ret.code = create_node_string(DEFAULT_TEMPFAIL_CODE, &@1);
		     $$->v.ret.stat = SMFIS_TEMPFAIL;
	     }
           | T_TEMPFAIL '(' maybe_expr ',' maybe_xcode_expr ',' maybe_expr ')'
             {
                     $$ = alloc_node(node_type_result, &@1);
		     $$->v.ret.stat = SMFIS_TEMPFAIL;
		     $$->v.ret.code = $3 ? cast_to(dtype_string, $3) : create_node_string(DEFAULT_TEMPFAIL_CODE, &@1);
		     $$->v.ret.xcode = $5 ? cast_to(dtype_string, $5) : NULL;
		     $$->v.ret.message = $7 ? cast_to(dtype_string, $7) : NULL;
	     }
           | T_CONTINUE
             {
                     $$ = alloc_node(node_type_result, &@1);
		     memset(&$$->v.ret, 0, sizeof $$->v.ret);
		     $$->v.ret.stat = SMFIS_CONTINUE;
	     }
           | T_DISCARD
             {
                     $$ = alloc_node(node_type_result, &@1);
		     memset(&$$->v.ret, 0, sizeof $$->v.ret);
		     $$->v.ret.stat = SMFIS_DISCARD;
	     }
           ;

maybe_xcode_expr: maybe_expr
           | xcode
             {
                     $$ = alloc_node(node_type_string, &@1);
		     $$->v.literal = $1;
	     }
           ;
           

header_action:
             T_ADD string expr
             {
                     $$ = alloc_node(node_type_header, &@1);
		     $$->v.hdr.opcode = header_add;
		     $$->v.hdr.name = $2;
		     $$->v.hdr.value = cast_to(dtype_string, $3);
	     }
           | T_REPLACE string expr
             {
                     $$ = alloc_node(node_type_header, &@1);
		     $$->v.hdr.opcode = header_replace;
		     $$->v.hdr.name = $2;
		     $$->v.hdr.value = cast_to(dtype_string, $3);
	     }
           | T_DELETE string
             {
                     $$ = alloc_node(node_type_header, &@1);
		     $$->v.hdr.opcode = header_delete;
		     $$->v.hdr.name = $2;
		     $$->v.hdr.value = NULL;
	     }
           ;	   

maybe_triplet: /* empty */
             {
                     memset(&$$, 0, sizeof $$);
             }
           | triplet
           ;

triplet    : code
             {
                     $$.code = alloc_node(node_type_string, &@1);
		     $$.code->v.literal = $1;
		     $$.xcode = NULL;
		     $$.message = NULL;
	     }
           | code xcode
             {
                     $$.code = alloc_node(node_type_string, &@1);
		     $$.code->v.literal = $1;
                     $$.xcode = alloc_node(node_type_string, &@2);
		     $$.xcode->v.literal = $2;
		     $$.message = NULL;
	     }
           | code xcode expr
             {
                     $$.code = alloc_node(node_type_string, &@1);
		     $$.code->v.literal = $1;
                     $$.xcode = alloc_node(node_type_string, &@2);
		     $$.xcode->v.literal = $2;
		     $$.message = cast_to(dtype_string, $3);
	     }
           | code expr
             {
                     $$.code = alloc_node(node_type_string, &@1);
		     $$.code->v.literal = $1;
		     $$.xcode = NULL;
		     $$.message = cast_to(dtype_string, $2);
	     }
           ;

code       : T_NUMBER
             {
                     char buf[4];

                     if ($1 < 200 || $1 > 599) {
                             yyerror(_("invalid SMTP reply code"));
                             buf[0] = 0;
                     } else
                             snprintf(buf, sizeof(buf), "%lu", $1);
                     $$ = string_alloc(buf, strlen(buf));
             }
           ;

xcode      : T_NUMBER '.' T_NUMBER '.' T_NUMBER
             {
                     char buf[sizeof("5.999.999")];

                     /* RFC 1893:
                        The syntax of the new status codes is defined as:

                        status-code = class "." subject "." detail
                        class = "2"/"4"/"5"
                        subject = 1*3digit
                        detail = 1*3digit
                     */                        
                     if (($1 != 2 && $1 != 4 && $1 !=5)
                         || $3 > 999 || $5 > 999) {
                             yyerror(_("invalid extended reply code"));
                             buf[0] = 0;
                     } else
                             snprintf(buf, sizeof(buf), "%lu.%lu.%lu",
                                      $1, $3, $5);
                     $$ = string_alloc(buf, strlen(buf));
             }
           ;

condition  : if_cond
           | case_cond 
           | on_cond
           ;

if_cond    : T_IF bool_expr stmtlist else_cond T_FI
             {
                     $$ = alloc_node(node_type_if, &@1);
		     $$->v.cond.cond = $2;
		     $$->v.cond.if_true = $3.head;
		     $$->v.cond.if_false = $4;
	     }
           ;

else_cond  : /* empty */
             {
                     $$ = NULL;
             }
           | T_ELIF bool_expr stmtlist else_cond
             {
                     $$ = alloc_node(node_type_if, &@1);
		     $$->v.cond.cond = $2;
		     $$->v.cond.if_true = $3.head;
		     $$->v.cond.if_false = $4;
	     }
           | T_ELSE stmtlist
             {
                     $$ = $2.head;
             }
           ;

case_cond  : T_SWITCH expr T_DO cond_branches T_DONE
             {
		     struct case_stmt *defcase = NULL, *pcase, *prev;
		     
                     $$ = alloc_node(node_type_switch, &@1);
		     $$->v.switch_stmt.node = $2;
		     
		     /* Make sure there is only one default case and
			place it at the beginning of the list */
		     pcase = $4.head;
		     if (!pcase->valist) {
			     defcase = pcase;
			     $4.head = $4.head->next;
		     }
		     prev = pcase;
		     pcase = pcase->next;
		     while (pcase) {
			     if (!pcase->valist) {
				     if (defcase) {
					     parse_error_locus(&pcase->locus,
					      _("duplicate default statement"));
					     parse_error_locus(&defcase->locus,
					      _("previously defined here"));
					     YYERROR;
				     }
				     defcase = pcase;
				     prev->next = pcase->next;
			     } else
				     prev = pcase;
			     pcase = pcase->next;
		     }

		     if (!defcase) {
			     defcase = mu_alloc(sizeof *defcase);
			     mu_locus_range_init(&defcase->locus);
                             mu_locus_range_copy(&defcase->locus, &@5);
			     defcase->valist = NULL;
			     defcase->node = alloc_node(node_type_noop,
							&defcase->locus);
		     }
		     defcase->next = $4.head;
		     $$->v.switch_stmt.cases = defcase;
	     }
           ;

cond_branches: cond_branch
               {
                       $$.head = $$.tail = $1;
               }
           | cond_branches cond_branch
               {
                       $$.tail->next = $2;
                       $$.tail = $2;
               }
           ;

cond_branch: T_CASE valist ':' stmtlist 
             {
		     $$ = mu_alloc(sizeof *$$);
		     $$->next = NULL;
		     mu_locus_range_init (&$$->locus);
                     mu_locus_range_copy (&$$->locus, &@1);
		     $$->valist = $2.head;
		     $$->node = $4.head;
	     }
           | T_DEFAULT ':' stmtlist
             {
		     $$ = mu_alloc(sizeof *$$);
		     $$->next = NULL;
		     mu_locus_range_init (&$$->locus);
                     mu_locus_range_copy (&$$->locus, &@1);
		     $$->valist = NULL;
		     $$->node = $3.head;
	     }		     
           ;

valist     : value
             {
                     $$.head = $$.tail = mu_alloc(sizeof($$.head[0]));
                     $$.head->next = NULL;
                     $$.head->value = $1;
             }
           | valist T_OR value
             {
                     struct valist *p = mu_alloc(sizeof(*p));
                     p->value = $3;
                     p->next = NULL;
                     $1.tail->next = p;
                     $1.tail = p;
                     $$ = $1;
             }
           ;

value      : T_STRING
             {
		     $$.type = dtype_string;
		     $$.v.literal = $1;
	     }
           | T_NUMBER
             {
                     $$.type = dtype_number;
                     $$.v.number = $1;
             }
           ;

string     : value
             {
		     if ($1.type != dtype_string) {
                             parse_error_locus(&@1,
                                         _("expected string, but found %s"),
					 type_to_string($1.type));
			     /* Make sure we return something usable: */
			     $$ = string_alloc("ERROR", 5);
		     } else
			     $$ = $1.v.literal; 
	     }
           ;

matches    : T_MATCHES
             {
		     $$.qualifier = 0;
	     }
           | T_MXMATCHES
             {
		     $$.qualifier = QUALIFIER_MX;
	     }
           ;

fnmatches  : T_FNMATCHES
             {
		     $$.qualifier = 0;
	     }
           | T_MXFNMATCHES
             {
		     $$.qualifier = QUALIFIER_MX;
	     }
           ;


/* Loop statements */

loopstmt   : T_LOOP loop_ident opt_loop_parms T_DO stmtlist T_DONE opt_while
             {
		     leave_loop();
		     $3.end_while = $7;
                     $$ = alloc_node(node_type_loop, &@1);
		     $3.body = $5.head;
		     $3.ident = $2;
		     $$->v.loop = $3;
	     }
           ;

loop_ident : opt_ident
             {
                     enter_loop($1, NULL, NULL);
             }
           ;

opt_ident  : /* empty */
             {
		     $$ = NULL;
	     }
           | varname
           ;

opt_loop_parms: /* empty */
             {
                     memset(&$$, 0, sizeof $$);
             }
           | loop_parm_list
           ;

loop_parm_list: loop_parm
             {
                     memset(&$$, 0, sizeof $$);
                     switch ($1.kw) {
                     case 0:
                             $$.stmt = $1.expr;
                             break;

                     case T_FOR:
                             $$.for_stmt = $1.expr;
                             break;

                     case T_WHILE:
                             $$.beg_while = $1.expr;
                             break;

                     default:
                             abort();
                     }
             }
           | loop_parm_list ',' loop_parm
             {
		     switch ($3.kw) {
		     case 0:
			     if ($$.stmt) 
                                     parse_error_locus(&@3,
                                                _("duplicate loop increment"));
			     $$.stmt = $3.expr;
			     break;

		     case T_FOR:
			     if ($$.for_stmt) 
                                     parse_error_locus(&@3,
                                                _("duplicate for statement"));
			     $$.for_stmt = $3.expr;
			     break;

		     case T_WHILE:
			     if ($$.beg_while) 
                                     parse_error_locus(&@3,
                                              _("duplicate while statement"));
			     $$.beg_while = $3.expr;
			     break;

		     default:
			     abort();
		     }
	     }
           ;

loop_parm  : stmtlist
             {
                     $$.kw = 0;
                     $$.expr = $1.head;
             }
           | T_FOR stmtlist
             {
                     $$.kw = T_FOR;
                     $$.expr = $2.head;
             }
           | T_WHILE expr
             {
                     $$.kw = T_WHILE;
                     $$.expr = $2;
             }
           ;

opt_while  : /* empty */
             {
                     $$ = NULL;
             }
           | T_WHILE expr
             {
                     $$ = $2;
             }
           ;

jumpstmt   : T_BREAK opt_ident
             {
		     if (!within_loop($2)) {
			     if ($2)
                                     parse_error_locus(&@2,
                                                       _("no such loop: %s"), 
				                 $2->text);
                             parse_error_locus(&@1,
                                        _("`break' used outside of `loop'"));
			     YYERROR;
		     }
                     $$ = alloc_node(node_type_break, &@1);
		     $$->v.literal = $2;
	     }
           | T_NEXT opt_ident
             {
		     if (!within_loop($2)) {
			     if ($2) {
                                     parse_error_locus(&@2,
                                                       _("no such loop: %s"),
                                                       $2->text);
                                     parse_error_locus(&@1,
                                                       _("`next' used outside `loop'"));
				     YYERROR;
			     } else {
                                     parse_error_locus(&@1,
                                           _("`next' is used outside `loop'; "
                                             "did you mean `pass'?"));
				     YYERROR;
			     }
		     } else {
                             $$ = alloc_node(node_type_next, &@1);
			     $$->v.literal = $2;
		     }
	     }
           ;


/* Expressions */
expr       : T_NOT expr
             {
                     $$ = alloc_node(node_type_un, &@1);
		     $$->v.un.opcode = unary_not;
		     $$->v.un.arg = cast_to(dtype_number, $2);
	     }
           | expr T_EQ expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_eq;
		     $$->v.bin.arg[0] = $1;
		     $$->v.bin.arg[1] = cast_to(node_type($1), $3);
	     }
           | expr T_NE expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_ne;
		     $$->v.bin.arg[0] = $1;
		     $$->v.bin.arg[1] = cast_to(node_type($1), $3);
	     }
           | expr T_LT expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_lt;
		     $$->v.bin.arg[0] = $1;
		     $$->v.bin.arg[1] = cast_to(node_type($1), $3);
	     }
	   | expr T_LE expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_le;
		     $$->v.bin.arg[0] = $1;
		     $$->v.bin.arg[1] = cast_to(node_type($1), $3);
	     }
	   | expr T_GT expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_gt;
		     $$->v.bin.arg[0] = $1;
		     $$->v.bin.arg[1] = cast_to(node_type($1), $3);
	     }
	   | expr T_GE expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_ge;
		     $$->v.bin.arg[0] = $1;
		     $$->v.bin.arg[1] = cast_to(node_type($1), $3);
	     }
           | expr matches expr %prec T_MATCHES 
             {
		     NODE *p;
		     
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_match;
		     $$->v.bin.qualifier = $2.qualifier;
		     $$->v.bin.arg[0] = cast_to(dtype_string, $1);
                     $$->v.bin.arg[1] = p = alloc_node(node_type_regcomp,
                                                       &@2);
		     p->v.regcomp_data.expr = cast_to(dtype_string, $3);
		     p->v.regcomp_data.flags = regex_flags;
		     p->v.regcomp_data.regind = -1;
	     }
           | expr fnmatches expr %prec T_MATCHES
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_fnmatch;
		     $$->v.bin.qualifier = $2.qualifier;
		     $$->v.bin.arg[0] = cast_to(dtype_string, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_string, $3);
	     }
           | expr T_OR expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_or;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | expr T_AND expr
             {
                     $$ = alloc_node(node_type_bin, &@2);
		     $$->v.bin.opcode = bin_and;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | simp_expr
	   ;

maybe_expr : /* empty */
             {
                     $$ = NULL;
             }
           | expr
           ;

simp_expr  : atom_expr
           | simp_expr '+' simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_add;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | simp_expr '.' simp_expr
             {
                     $$ = alloc_node(node_type_concat, &@2);
		     $$->v.concat.arg[0] = cast_to(dtype_string, $1);
		     $$->v.concat.arg[1] = cast_to(dtype_string, $3);
	     }
	   | simp_expr '-' simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_sub;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }		     
	   | simp_expr '*' simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_mul;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }		     
	   | simp_expr '/' simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_div;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
	   | simp_expr '%' simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_mod;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | simp_expr T_LOGAND simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_logand;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | simp_expr T_LOGOR simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_logor;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | simp_expr T_LOGXOR simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_logxor;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | simp_expr T_SHL simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_shl;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           | simp_expr T_SHR simp_expr
             {
		     $$ = alloc_node(node_type_bin, &@1);
		     $$->v.bin.opcode = bin_shr;
		     $$->v.bin.arg[0] = cast_to(dtype_number, $1);
		     $$->v.bin.arg[1] = cast_to(dtype_number, $3);
	     }
           ;

atom_expr  : funcall
             {
		     if (node_type($1) == dtype_unspecified)
			     parse_error(_("unspecified value not ignored as it should be"));
	     }
           | '(' expr ')'
             {
                     $$ = $2;
             }
           | T_TYPECAST '(' expr ')'
             {
                     $$ = cast_to($1, $3);
             }
           | atom
           | '-' simp_expr %prec T_UMINUS
             {
                     $$ = alloc_node(node_type_un, &@1);
		     $$->v.un.opcode = unary_minus;
		     $$->v.un.arg = cast_to(dtype_number, $2);
	     }
           | '+' simp_expr %prec T_UMINUS
             {
                     $$ = $2;
             }
           | T_LOGNOT simp_expr %prec T_UMINUS
             {
                     $$ = alloc_node(node_type_un, &@1);
		     $$->v.un.opcode = unary_lognot;
		     $$->v.un.arg = cast_to(dtype_number, $2);
	     }
           ;

atom       : T_SYMBOL
             {
		     $$ = create_node_symbol($1, &@1);
	     }
           | T_NUMBER
             {
		     $$ = alloc_node(node_type_number, &@1);
		     $$->v.number = $1;
	     }
           | T_BACKREF
             {
		     $$ = create_node_backref($1, &@1);
	     }
           | argref
           | T_ARGCOUNT
             {
		     $$ = create_node_argcount(&@1);
	     }
           | '@' T_VARIABLE
             {
		     $$ = alloc_node(node_type_offset, &@1);
		     $$->v.var_ref.variable = $2;
		     $$->v.var_ref.nframes = catch_nesting;
	     }
           | T_VAPTR paren_argref
             {
		     $$ = alloc_node(node_type_vaptr, &@1);
		     $$->v.node = $2;
	     }
           | strcat
 	   ;

strcat     : strval
           | strcat strval
             {
		     $$ = alloc_node(node_type_concat, &@2);
		     $$->v.concat.arg[0] = $1;
		     $$->v.concat.arg[1] = $2;
	     }
           ;

strval     : T_STRING
             {
		     $$ = alloc_node(node_type_string, &@1);
		     $$->v.literal = $1;
	     }
           | T_COMPOSE
           ;

argref     : variable
             {
                     $$ = create_node_variable($1, &@1);
	     }
           | T_ARG
             {
		     $$ = create_node_arg($1, &@1);
 	     }
           | T_ARGX '(' expr ')'
             {
		     if (inner_context == context_function && func->varargs) {
			     $$ = alloc_node(node_type_argx, &@1);
			     $$->v.argx.nargs = PARMCOUNT() +
				                 FUNC_HIDDEN_ARGS(func);
			     $$->v.argx.node = $3;
			     $$->v.argx.dtype = func->vartype;
		     } else {
                             $$ = alloc_node(node_type_noop, &@1);
                             parse_error_locus(&@1,
                                         _("$(expr) is allowed only "
					   "in a function with "
					   "variable number of "
					   "arguments"));
		     }
	     }
           ;

paren_argref: argref
           | '(' argref ')'
             {
                     $$ = $2;
             }
           ;

funcall    : T_BUILTIN '(' funcarglist ')' 
             {
		     if (check_builtin_usage($1, &@1))
			     YYERROR;

		     if ($1->flags & MFD_BUILTIN_REGEX_FLAGS) {
			     NODE *p = alloc_node(node_type_number, &@1);
			     p->v.number = regex_flags;
			     p->next = $3.head;
			     $3.head = p;
			     $3.count++;
		     }

		     if ($3.count < $1->parmcount - $1->optcount) {
                             parse_error_locus(&@1,
                                       _("too few arguments in call to `%s'"),
					 $1->name);
			     YYERROR;
		     } else if ($3.count > $1->parmcount
				&& !($1->flags & MFD_BUILTIN_VARIADIC)) {
                             parse_error_locus(&@1,
                                      _("too many arguments in call to `%s'"),
					 $1->name);
			     YYERROR;
		     } else {
                             $$ = alloc_node(node_type_builtin, &@1);
			     $$->v.builtin.builtin = $1;
			     $$->v.builtin.args =
					     reverse(cast_arg_list($3.head,
 							       $1->parmcount,
							       $1->parmtype,
							       ($$->v.builtin.builtin->flags & MFD_BUILTIN_NO_PROMOTE) ? dtype_any : dtype_string));
		     }
	     }
	   | T_BUILTIN '(' ')' 
             {
		     if (check_builtin_usage($1, &@1))
			     YYERROR;
		     if ($1->parmcount - $1->optcount) {
                             parse_error_locus(&@1,
                                       _("too few arguments in call to `%s'"),
					 $1->name);
			     YYERROR;
		     } else {
                             $$ = alloc_node(node_type_builtin, &@1);
			     $$->v.builtin.builtin = $1;
			     $$->v.builtin.args = NULL;
		     }
	     }
	   | T_SED '(' arglist ')'
	     {
		     NODE *arg, *expr;

		     if ($3.count < 2) {
			     parse_error_locus(&@1,
					       _("too few arguments in call to `%s'"),
					       "sed");
			     YYERROR;
		     }

		     arg = cast_to(dtype_string, $3.head);
		     expr = $3.head->next;
		     /* Break the link between the args lest any eventual
			optimizations cause grief later in mark phase. */
		     $3.head->next = NULL;

		     do {
			     NODE *comp, *next;
			     struct mu_locus_range lr;

			     comp = alloc_node(node_type_sedcomp, &expr->locus);
			     comp->v.sedcomp.index = next_transform_index();
			     comp->v.sedcomp.expr = cast_to(dtype_string, expr);
			     comp->v.sedcomp.flags = regex_flags;

			     lr.beg = @1.beg;
			     lr.end = expr->locus.end;

			     $$ = alloc_node(node_type_sed, &lr);
			     $$->v.sed.comp = comp;
			     $$->v.sed.arg = arg;

			     next = expr->next;
			     expr->next = NULL;
			     expr = next;

			     arg = $$;
		     } while (expr);
	     }
           | T_FUNCTION '(' funcarglist ')' 
             {
                     if (check_func_usage($1, &@1))
			     YYERROR;
		     $$ = function_call($1, $3.count, $3.head);
		     if (!$$)
			     YYERROR;
	     }
           | T_FUNCTION '(' ')' 
             {
                     if (check_func_usage($1, &@1))
			     YYERROR;
		     $$ = function_call($1, 0, NULL);
		     if (!$$)
			     YYERROR;
	     }
           ;

funcarglist: argv
             {
                     $$.head = $$.tail = $1;
                     $$.count = 1;
	     }
           | arglist
           | arglist ',' argv
             {
                     $1.tail->next = $3;
                     $1.tail = $3;
                     $1.count++;
                     $$ = $1;
	     }
           ;

argv       : T_ARGV argvarg
             {
		     if (inner_context == context_function && func->varargs) {
			     $$ = alloc_node(node_type_argv, &@1);
			     $$->v.argv.nargs = PARMCOUNT();
			     $$->v.argv.node = $2;
		     } else {
			     $$ = alloc_node(node_type_noop, &@1);
			     parse_error_locus(&@1,
					       _("$@ is allowed only in variadic functions"));
			     
		     }
	     }
           ;

argvarg    : /* empty */
             {
                    $$ = NULL;
             }
           | '(' expr ')'
             {
                    $$ = $2;
             }
           ;

arglist    : expr
             {
                     $1->next = NULL;
                     $$.head = $$.tail = $1;
                     $$.count = 1;
             }
           | arglist ',' expr
             {
                     $1.tail->next = $3;
                     $1.tail = $3;
                     $1.count++;
                     $$ = $1;
             }
           ;

variable   : T_VARIABLE
             {
		     add_xref($1, &@1);
	     }
           | T_BOGUS
             {
                     YYERROR;
             }
           ;

catch      : simple_catch
             {
		     if (outer_context == context_function) {
			     func->exmask->all |= $$->v.catch.exmask->all;
			     bitmask_merge(&func->exmask->bm,
					   &$$->v.catch.exmask->bm);
		     }
	     }
           | try_block simple_catch
             {
		     $$ = alloc_node(node_type_try, &@1);
		     $$->v.try.node = $1;
		     $$->v.try.catch = $2;
	     }
           ;

try_block  : T_TRY T_DO stmtlist T_DONE
             {
		     $$ = $3.head;
	     }
           ;

simple_catch
           : T_CATCH catchlist T_DO
                 { $<tie_in>$ = inner_context;
                   inner_context = context_catch; 
                   catch_nesting++; }
                 stmtlist T_DONE
              {
		      int i;
		      struct valist *p;

		      inner_context = $<tie_in>4;
		      catch_nesting--;
                      $$ = alloc_node(node_type_catch, &@1);
		      $$->v.catch.exmask = exmask_create();
		      $$->v.catch.context = outer_context;/*??*/
		      $$->v.catch.exmask->all = $2.all;
		      if (!$2.all) {
			      for (i = 0, p = $2.valist; p; p = p->next, i++) {
				      if (p->value.type != dtype_number) {
					      parse_error_locus(&@1,
								_("expected numeric value, but found `%s'"),
								p->value.v.literal->text);
					      continue;
				      }
				      bitmask_set(&$$->v.catch.exmask->bm,
						  p->value.v.number);
			      }
		      }
		      $$->v.catch.node = $5.head;
	      }
            ;

catchlist   : '*'
              {
		      $$.all = 1;
	      }
            | valist
              {
		      $$.all = 0;
		      $$.valist = $1.head;
	      }
            ; 

throw       : T_THROW value expr
              {
                      $$ = alloc_node(node_type_throw, &@1);
		      if ($2.type != dtype_number) 
                              parse_error_locus(&@2,
                                           _("exception code not a number"));
		      else if ($2.v.number > exception_count)
                              parse_error_locus(&@2,
                                           _("invalid exception number: %lu"),
					  $2.v.number);

		      $$->v.throw.code = $2.v.number;
		      $$->v.throw.expr = cast_to(dtype_string, $3);
	      }
            ;

return      : T_RETURN
              {
		      if (!func) 
			      parse_error_locus(&@1,
				      _("`return' outside of a function"));
		      else if (func->rettype != dtype_unspecified) 
			      parse_error_locus(&@1,
				      _("`return' with no value, in function "
				      "returning non-void"));
		      $$ = alloc_node(node_type_return, &@1);
		      $$->v.node = NULL;
	      }
            | T_RETURN expr
              {
		      if (!func) 
			      parse_error_locus(&@1,
					_("`return' outside of a function"));
		      else {
			      $$ = alloc_node(node_type_return, &@1);
			      if (func->rettype == dtype_unspecified) { 
				      parse_error_locus(&@1,
				       _("`return' with a value, in function "
				       "returning void"));
				      $$->v.node = NULL;
			      }
			      else
				      $$->v.node = cast_to(func->rettype, $2);
		      }
	      }
            ;

bool_expr   : expr
              {
		      switch (node_type($1)) {
		      case dtype_string:
			      $$ = alloc_node(node_type_bin, &@1);
			      $$->v.bin.opcode = bin_ne;
			      $$->v.bin.arg[0] = $1;
			      $$->v.bin.arg[1] = alloc_node(node_type_string, &@1);
			      $$->v.bin.arg[1]->v.literal = literal_lookup("");
			      break;
			      
		      case dtype_number:
		      case dtype_pointer:
			      $$ = $1;
			      break;
			      
		      default:
			      parse_error_locus(&@1,
						_("unspecified data type in conditional expression: please, report"));
			      YYERROR;
		      }
	      }
            ;

/* *************************** */
/*         ON statement        */
/* *************************** */

on_cond    : on pollstmt do branches T_DONE
             {
		     NODE *sel, *np;
		     NODE *head = NULL, *tail;
		     struct function *fp;
		     
		     fp = function_lookup($2.client_addr ?
					  "strictpoll" : "stdpoll");
		     if (!fp) {
			     parse_error_locus(&@1,
					       _("`on poll' used without prior `require poll'"));
			     YYERROR;
		     }

		     /* Build argument list */
		     if ($2.client_addr) {
			     head = tail = $2.client_addr;
			     tail = $2.email;
			     if (!tail) {
                                     parse_error_locus(&@2,
                                          _("recipient address not specified "
                                            "in `on poll' construct"));
				     YYERROR;
			     }
			     tail->next = NULL;
			     head->next = tail;
		     } else
			     head = tail = $2.email;

		     if ($2.ehlo)
			     np = $2.ehlo;
		     else {
			     /* FIXME: Pass NULL? */
                             np = alloc_node(node_type_variable, &@2);
			     np->v.var_ref.variable =
				     variable_lookup("ehlo_domain");
			     np->v.var_ref.nframes = 0;
		     }
		     tail->next = np;
		     tail = np;

		     if ($2.mailfrom)
			     np = $2.mailfrom;
		     else {
			     /* FIXME: Pass NULL? */
                             np = alloc_node(node_type_variable, &@2);
			     np->v.var_ref.variable =
				     variable_lookup("mailfrom_address");
			     np->v.var_ref.nframes = 0;
		     }
		     tail->next = np;
		     tail = np;

		     sel = function_call(fp, nodelistlength(head), head);

		     $$ = alloc_node(node_type_switch, &@1);
		     $$->v.switch_stmt.node = sel;
		     $$->v.switch_stmt.cases = $4.head;
	     }
           | on funcall do branches T_DONE
             {
		     $$ = alloc_node(node_type_switch, &@1);
		     $$->v.switch_stmt.node = $2;
		     $$->v.switch_stmt.cases = $4.head;
             }
           ;

on         : T_ON
             {
                     tie_in_onblock(1);
             }
           ;

do         : T_DO
             {
                     tie_in_onblock(0);
             }
           ;

pollstmt   : T_POLL expr
             {
                     struct pollarg arg;
                     
                     arg.kw = T_FOR;
                     arg.expr = $2;
                     memset(&$$, 0, sizeof $$);
                     set_poll_arg(&$$, arg.kw, arg.expr);
             }
           | T_POLL expr pollarglist
             {
                     struct pollarg arg;
                     
                     arg.kw = T_FOR;
                     arg.expr = $2;
                     set_poll_arg(&$3, arg.kw, arg.expr);
                     $$ = $3;
             }
           | T_POLL pollarglist
             {
                     $$ = $2;
             }
           ;

pollarglist: pollarg
             {
                     memset(&$$, 0, sizeof $$);
                     set_poll_arg(&$$, $1.kw, $1.expr);
             }
           | pollarglist pollarg
             {
                     set_poll_arg(&$1, $2.kw, $2.expr);
                     $$ = $1;
             }
           ;

pollarg    : T_FOR expr
             {
                     $$.kw = T_FOR;
                     $$.expr = $2;
             }
           | T_HOST expr
             {
                     $$.kw = T_HOST;
                     $$.expr = $2;
             }
           | T_AS expr
             {
                     $$.kw = T_AS;
                     $$.expr = $2;
             }
           | T_FROM expr
             {
                     $$.kw = T_FROM;
                     $$.expr = $2;
             }
           ;


branches   : branch
             {
                     $$.head = $$.tail = $1;
             }
           | branches branch
             {
                     $1.tail->next = $2;
                     $1.tail = $2;
                     $$ = $1;
             }
           ;

branch     : T_WHEN valist ':' stmtlist
             {
		     struct valist *p;

		     for (p = $2.head; p; p = p->next) {
			     if (p->value.type == dtype_string) {
                                     parse_error_locus(&@2,
                                                       _("invalid data type, "
                                                         "expected number"));
				     /* Try to continue */
				     p->value.type = dtype_number;
				     p->value.v.number = 0;
			     }
		     }
		     
		     $$ = mu_alloc(sizeof *$$);
		     $$->next = NULL;
		     mu_locus_range_init (&$$->locus);
                     mu_locus_range_copy (&$$->locus, &@1);
		     $$->valist = $2.head;
		     $$->node = $4.head;
	     }
           ;

%%

int
yyerror(char const *s)
{
        parse_error("%s", s);
        return 0;
}

struct stream_state
{
	int mode;
	struct mu_locus_range loc;
	int sevmask;
};

void
stream_state_save(mu_stream_t str, struct stream_state *st)
{
	mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM,
			MU_IOCTL_LOGSTREAM_GET_MODE, &st->mode);
	mu_locus_range_init(&st->loc);
	mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM,
			MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE, &st->loc);
	mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM,
			MU_IOCTL_LOGSTREAM_GET_SEVERITY_MASK,
			&st->sevmask);
}

void
stream_state_restore(mu_stream_t str, struct stream_state *st)
{
	mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM,
			MU_IOCTL_LOGSTREAM_SET_MODE, &st->mode);
	mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM,
			MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE, &st->loc);
	mu_stream_ioctl(mu_strerr, MU_IOCTL_LOGSTREAM,
			MU_IOCTL_LOGSTREAM_SET_SEVERITY_MASK,
			&st->sevmask);
	mu_locus_range_deinit(&st->loc);
}

static void define_action_ids(void);

int
parse_program(char *name, int ydebug)
{
        int rc;
	struct stream_state st;
	int mode;

	stream_state_save(mu_strerr, &st);
	define_action_ids();

	mode = st.mode | MU_LOGMODE_LOCUS | MU_LOGMODE_SEVERITY;
	mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
			 MU_IOCTL_LOGSTREAM_SET_MODE, &mode);
	mode = MU_DEBUG_LEVEL_MASK (MU_DIAG_ERROR);
	mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
			 MU_IOCTL_LOGSTREAM_SET_SEVERITY_MASK,
			 &mode);
	
        yydebug = ydebug;
        if (lex_new_source(name, 0))
                return -1;
	/* Top module was initialized with NULL filename in init_symbols. */
	top_module->file = mf_strdup(name);
        outer_context = inner_context = context_none;
        catch_nesting = 0;
        rc = yyparse() + error_count;

	stream_state_restore(mu_strerr, &st);
	
        return rc;
}

static void
alloc_locus(struct mu_locus_range const *locus)
{
	struct literal *lit;
	if (locus->beg.mu_file) {
		lit = string_alloc(locus->beg.mu_file,
				   strlen(locus->beg.mu_file));
		lit->flags |= SYM_REFERENCED;
	}
	if (!mu_locus_point_same_file(&locus->beg, &locus->end)) {
		lit = string_alloc(locus->end.mu_file,
				   strlen(locus->end.mu_file));
		lit->flags |= SYM_REFERENCED;
	}
}

NODE *
alloc_node(enum node_type type, struct mu_locus_range const *locus)
{
        NODE *node = malloc(sizeof(*node));
        if (!node) {
                yyerror("Not enough memory");
                abort();
        }
        node->type = type;
        mu_locus_range_init (&node->locus);
        mu_locus_range_copy (&node->locus, locus);
	alloc_locus(locus);
        node->next = NULL;
	node->value_ignored = 0;
        return node;
}

void
free_node(NODE *node)
{
	mu_locus_range_deinit(&node->locus);
        free(node);
}

void
copy_node(NODE *dest, NODE *src)
{
        dest->type = src->type;
        mu_locus_range_copy (&dest->locus, &src->locus);
        dest->v = src->v;
}

void
free_subtree(NODE *node)
{
        /*FIXME*/
}

void
free_parser_data()
{
	/*FIXME*/
}


/* Print parse tree */

static void print_node_list(NODE *node, int indent);
static void print_node_list_reverse(NODE *node, int level);
static void print_node(NODE *node, int indent);
void print_stat(sfsistat stat);
static int dbg_setreply(void *data, char *code, char *xcode, char *message);
static void dbg_msgmod(void *data, struct msgmod_closure *clos);

static void
print_level(int level)
{
        level *= 2;
        printf("%*.*s", level, level, "");
}

static void
print_bin_op(enum bin_opcode opcode)
{
        char *p;
        switch (opcode) {
        case bin_and:
                p = "AND";
                break;
                
        case bin_or:
                p = "OR";
                break;
                
        case bin_eq:
                p = "EQ";
                break;
                
        case bin_ne:
                p = "NE";
                break;
                
        case bin_lt:
                p = "LT";
                break;
                
        case bin_le:
                p = "LE";
                break;
                
        case bin_gt:
                p = "GT";
                break;
                
        case bin_ge:
                p = "GE";
                break;
                
        case bin_match:
                p = "MATCH";
                break;
                
        case bin_fnmatch:
                p = "FNMATCH";
                break;

        case bin_add:
                p = "ADD";
                break;
                
        case bin_sub:
                p = "SUB";
                break;
                
        case bin_mul:
                p = "MUL";
                break;
                
        case bin_div:
                p = "DIV";
                break;

        case bin_mod:
                p = "MOD";
                break;

        case bin_logand:
                p = "LOGAND";
                break;
                
        case bin_logor:
                p = "LOGOR";
                break;
                
        case bin_logxor:
                p = "LOGXOR";
                break;

	case bin_shl:
		p = "SHL";
		break;

	case bin_shr:
		p = "SHR";
		break;
		
        default:
                p = "UNKNOWN_OP";
        }
        printf("%s", p);
}

static void
print_quoted_string(const char *str)
{
        for (; *str; str++) {
                if (mu_isprint(*str))
                        putchar(*str);
                else {
                        putchar('\\');
                        switch (*str) {
                        case '\a':
                                putchar('a');
                                break;
                        case '\b':
                                putchar('b');
                                break;
                        case '\f':
                                putchar('f');
                                break;
                        case '\n':
                                putchar('n');
                                break;
                        case '\r':
                                putchar('r');
                                break;
                        case '\t':
                                putchar('t');
                                break;
                        default:
                                printf("%03o", *str);
                        }
                }
        }
}

struct node_drv {
        void (*print) (NODE *, int);
        void (*mark) (NODE *);
        void (*code) (NODE *, struct mu_locus_range const **);
        void (*optimize) (NODE *);
};

static void traverse_tree(NODE *node);
static void code_node(NODE *node);
static void code_node(NODE *node);
static void optimize_node(NODE *node);
static void optimize(NODE *node);
static void record_switch(struct switch_stmt *sw);

#include "drivers.c"
#include "node-tab.c"

struct node_drv *
find_node_drv(enum node_type type)
{
        if (type >= NELEMS(nodetab)) {
                parse_error(_("INTERNAL ERROR at %s:%d, "
                              "unexpected node type %d"),
                            __FILE__, __LINE__,
                            type);
                abort();
        }
        return nodetab + type;
}

static void
print_node(NODE *node, int level)
{
        struct node_drv *nd = find_node_drv(node->type);
        if (nd->print)
                nd->print(node, level);
}       

static void
print_node_list(NODE *node, int level)
{
        for (; node; node = node->next)
                print_node(node, level);
}

static void
print_node_list_reverse(NODE *node, int level)
{
        if (node) {
                print_node_list_reverse(node->next, level);
                print_node(node, level);
        }
}

int
function_enumerator(void *sym, void *data)
{
        int i;
        struct function *fsym = sym;
        struct function *f =
                (struct function *)symbol_resolve_alias(&fsym->sym);
        struct module *mod = data;

        if (f->sym.module != mod)
                return 0;
        printf("function %s (", f->sym.name);
        for (i = 0; i < f->parmcount; i++) {
                printf("%s", type_to_string(f->parmtype[i]));
                if (i < f->parmcount-1)
                        putchar(',');
        }
        putchar(')');
        if (f->rettype != dtype_unspecified)
                printf(" returns %s", type_to_string(f->rettype));
        printf(":\n");
        print_node_list(f->node, 0);
        printf("END function %s\n", f->sym.name);
        return 0;
}
                
void
print_syntax_tree()
{
        struct module **modv;
        size_t i, modc;

        enum smtp_state tag;

        printf("State handlers:\n");
        printf("---------------\n");
        for (tag = smtp_state_startup; tag < smtp_state_count; tag++) {
                if (root_node[tag]) {
                        printf("%s:\n", xstate_to_string(tag));
                        print_node_list(root_node[tag]->v.progdecl.tree.head, 0);
                        putchar('\n');
                }
        }
        printf("User functions:\n");
        printf("---------------\n");
        
        collect_modules(&modv, &modc);
        for (i = 0; i < modc; i++) {
                size_t n;

                n = printf("Module %s (%s)\n", modv[i]->name,
                           modv[i]->file);
                while (n--)
                        putchar('-');
                putchar('\n');
                symtab_enumerate(MODULE_SYMTAB(modv[i], namespace_function),
                                 function_enumerator, modv[i]);
                putchar('\n');
        }
        free(modv);
}


/* Cross-reference support */

struct collect_data {
	mu_opool_t pool;
        size_t count;
};

static int
variable_enumerator(void *item, void *data)
{
        struct variable *var = item;
        struct collect_data *p = data;
        if (var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED)) {
                mu_opool_append(p->pool, &var, sizeof var);
                p->count++;
        }
        return 0;
}

int
print_locus(void *item, void *data)
{
        struct mu_locus_range *loc = item;
        struct mu_locus_range **prev = data;
        int c;

        if (!*prev) {
                *prev = loc;
                printf("%s", loc->beg.mu_file);
                c = ':';
        } else if (mu_locus_point_same_file (&(*prev)->beg, &loc->beg)) {
                *prev = loc;
                printf(", %s", loc->beg.mu_file);
                c = ':';
        } else
                c = ',';
        printf("%c%u", c, loc->beg.mu_line);
        return 0;
}

void
print_xref_var(struct variable *var)
{
        struct mu_locus_range *prev = NULL;
        var = (struct variable *)symbol_resolve_alias(&var->sym);
        printf("%-32.32s %6s %lu ",
               var->sym.name, type_to_string(var->type),
               (unsigned long)var->off);
        mu_list_foreach(var->xref, print_locus, &prev);
        printf("\n");
}

int
vp_comp(const void *a, const void *b)
{
        struct variable * const *va = a, * const *vb = b;
        return strcmp((*va)->sym.name, (*vb)->sym.name);
}

void
print_xref()
{
        struct collect_data cd;
        struct variable **vp;
        size_t i;
        
        mu_opool_create(&cd.pool, MU_OPOOL_ENOMEMABRT);
        cd.count = 0;
        symtab_enumerate(TOP_MODULE_SYMTAB(namespace_variable),
                         variable_enumerator, &cd);
        printf("Cross-references:\n");
        printf("-----------------\n");
        
        vp = mu_opool_finish(cd.pool, NULL);
        qsort(vp, cd.count, sizeof *vp, vp_comp);
        for (i = 0; i < cd.count; i++, vp++) 
                print_xref_var(*vp);
        mu_opool_destroy(&cd.pool);
}


static mu_list_t smtp_macro_list[gacopyz_stage_max];

static enum gacopyz_stage 
smtp_to_gacopyz_stage(enum smtp_state tag)
{
        switch (tag) {
        case smtp_state_connect:
                return gacopyz_stage_conn;
        case smtp_state_helo:
                return gacopyz_stage_helo;
        case smtp_state_envfrom:
                return gacopyz_stage_mail;
        case smtp_state_envrcpt:
                return gacopyz_stage_rcpt;
        case smtp_state_data:
        case smtp_state_header:
        case smtp_state_body:
                return gacopyz_stage_data;
        case smtp_state_eoh:
                return gacopyz_stage_eoh;
        case smtp_state_eom:
                return gacopyz_stage_eom;
        default:
                break;
        }
        return gacopyz_stage_none;
}

static int
compare_macro_names (const void *item, const void *data)
{
	const char *elt;
	size_t elen;
	const char *arg;
	size_t alen;

	elt = item;
	elen = strlen (elt);
	if (elt[0] == '{') {
		elt++;
		elen -= 2;
	}

	arg = data;
	alen = strlen (arg);
	if (arg[0] == '{') {
		arg++;
		alen -= 2;
	}

	if (alen != elen)
		return 1;
	return memcmp (elt, arg, alen);
}

void
register_macro(enum smtp_state state, const char *macro)
{
        enum gacopyz_stage ind = smtp_to_gacopyz_stage(state);
	
        if (ind == gacopyz_stage_none)
                return;
        if (!smtp_macro_list[ind]) {
                mu_list_create(&smtp_macro_list[ind]);
                mu_list_set_comparator(smtp_macro_list[ind],
				       compare_macro_names);
        }
        /* FIXME: MU: 2nd arg should be const? */
        if (mu_list_locate(smtp_macro_list[ind], (void*) macro, NULL)) {
		char *cmacro;
		if (macro[1] == 0 || macro[0] == '{')
			cmacro = mu_strdup (macro);
		else {
			size_t mlen = strlen (macro);
			cmacro = mu_alloc (mlen + 3);
			cmacro[0] = '{';
			memcpy (cmacro + 1, macro, mlen);
			cmacro[mlen + 1] = '}';
			cmacro[mlen + 2] = 0;
		}
                mu_list_append(smtp_macro_list[ind], cmacro);
	}
}

static int
print_macro(void *item, void *data)
{
        int *p = data;
        if (*p) {
                printf(" ");
                *p = 0;
        } else
                printf(", ");
        printf("%s", (char*) item);
        return 0;
}

void
print_used_macros()
{
        enum gacopyz_stage i;
        
        for (i = 0; i < gacopyz_stage_max; i++) {
                if (smtp_macro_list[i]) {
                        int n = 1;
                        printf("%s", gacopyz_stage_name[i]);
                        mu_list_foreach(smtp_macro_list[i], print_macro, &n);
                        printf("\n");
                }
        }
}

struct macro_acc {
        size_t size;
        char *buf;
};

static int
add_macro_size(void *item, void *data)
{
        char *macro = (char*) item;
        struct macro_acc *mp = data;
        mp->size += strlen(macro) + 1;
        return 0;
}

static int
concat_macro(void *item, void *data)
{
        char *macro = (char*) item;
        struct macro_acc *mp = data;
        size_t len = strlen(macro);
        char *pbuf = mp->buf + mp->size;
        memcpy(pbuf, macro, len);
        pbuf[len++] = ' ';
        mp->size += len;
        return 0;
}

char *
get_stage_macro_string(enum gacopyz_stage i)
{
        struct macro_acc acc;
        size_t size;
        mu_list_t list = smtp_macro_list[i];
        if (!list)
                return NULL;
                
        acc.size = 0;
        mu_list_foreach(list, add_macro_size, &acc);
        if (!acc.size)
                return NULL;
        size = acc.size;
                
        acc.size = 0;
        acc.buf = mu_alloc (size);
        mu_list_foreach(list, concat_macro, &acc);
        acc.buf[size-1] = 0;
        return acc.buf;
}


/* Code generation */

static void
code_node(NODE *node)
{
        if (!node) 
                error_count++;
        else {
                static struct mu_locus_range const *old_locus;
                struct node_drv *nd = find_node_drv(node->type);
                if (nd->code)
                        nd->code(node, &old_locus);
        }
}

static void
traverse_tree(NODE *node)
{
        for (; node; node = node->next)
                code_node(node);
}

static void
optimize_node(NODE *node)
{
        if (!node) 
                error_count++;
        else {
                struct node_drv *nd = find_node_drv(node->type);
                if (nd->optimize)
                        nd->optimize(node);
        }
}

static void
optimize(NODE *node)
{
        for (; node; node = node->next)
                optimize_node(node);
}

static int
optimize_tree(NODE *node)
{
        if (optimization_level)
                optimize(node);
        return error_count;
}


static struct switch_stmt *switch_root;

static void
record_switch(struct switch_stmt *sw)
{
        sw->next = switch_root;
        switch_root = sw;
}


static struct exmask *exmask_root;

struct exmask *
exmask_create()
{
	struct exmask *p = mu_alloc(sizeof(*p));
	p->next = exmask_root;
	p->off = 0;
	p->all = 0;
	bitmask_init(&p->bm);
	exmask_root = p;
	return p;
}



static void
mark_node(NODE *node)
{
        if (!node) 
                error_count++;
        else {
                struct node_drv *nd = find_node_drv(node->type);
                if (nd->mark)
                        nd->mark(node);
        }
}

static void
mark(NODE *node)
{
        for (; node; node = node->next)
                mark_node(node);
}
        

static int
codegen(prog_counter_t *pc, NODE *node, struct exmask *exmask, size_t nautos)
{
	int save_mask;
	
	if (error_count)
		return 1;
	
	*pc = code_get_counter();
	jump_pc = 0;
	if (nautos) {
		code_op(opcode_stkalloc);
		code_immediate(nautos, uint);
	}
	save_mask = exmask && bitmask_nset(&exmask->bm);
	if (save_mask) {
		code_op(opcode_saveex);
		code_exmask(exmask);
	}
	traverse_tree(node);

	jump_fixup(jump_pc, code_get_counter());
	if (save_mask) 
		code_op(opcode_restex);
	
	code_op(opcode_return);
		
	return 0;
}

static void
action_hook_fixup(void)
{
	prog_counter_t hook_pc = entry_point[smtp_state_action];

	if (hook_pc != 0) {
		while (action_hook_pc) {
			prog_counter_t next = (prog_counter_t)mf_cell_c_value(code_peek(action_hook_pc), size);
			code_put(action_hook_pc, hook_pc, size);
			action_hook_pc = next;
		}
	}
}

static void
compile_tree(NODE *node)
{
        struct node_drv *nd;
        struct mu_locus_range const *plocus;

	/* slot 0 - emergency return point */
	code_op(opcode_nil);
	/* slot 1 - return point for main in --run mode */
	code_op(opcode_nil);
	/* traverse the tree producing code for each node */
        for (; node; node = node->next) {
                switch (node->type) {
                case node_type_progdecl:
                case node_type_funcdecl:
                        nd = find_node_drv(node->type);
                        if (!nd->code)
                                abort();
                        nd->code(node, &plocus);
                        break;
                        
                default:
                        parse_error_locus(&node->locus,
                                          _("INTERNAL ERROR at %s:%d, "
                                            "unexpected node type %d"),
                                          __FILE__, __LINE__,
                                          node->type);
                        break;
                }
        }
	
	action_hook_fixup();
}


enum regex_mode { regex_enable, regex_disable, regex_set };

static mf_stack_t regex_stack;

void
regex_push()
{
        if (!regex_stack)
                regex_stack = mf_stack_create(sizeof regex_flags, 0);
        mf_stack_push(regex_stack, &regex_flags);
}

void
regex_pop()
{
        if (!regex_stack || mf_stack_pop(regex_stack, &regex_flags))
                parse_error(_("nothing to pop"));
}

static void
pragma_regex(int argc, char **argv, const char *text)
{
        enum regex_mode mode = regex_set;
        int i = 1;

        if (strcmp(argv[i], "push") == 0) {
                regex_push();
                i++;
        } else if (strcmp(argv[i], "pop") == 0) {
                regex_pop();
                i++;
        }

        for (; i < argc; i++) {
                int bit;
                char *p = argv[i];
                switch (p[0]) {
                case '+':
                        mode = regex_enable;
                        p++;
                        break;

                case '-':
                        mode = regex_disable;
                        p++;
                        break;

                case '=':
                        mode = regex_set;
                        p++;
                        break;
                }

                if (strcmp (p, REG_EXTENDED_NAME) == 0)
                        bit = REG_EXTENDED;
                else if (strcmp (p, REG_ICASE_NAME) == 0)
                        bit = REG_ICASE;
                else if (strcmp (p, REG_NEWLINE_NAME) == 0)
                        bit = REG_NEWLINE;
                else {
                        parse_error(_("unknown regexp flag: %s"), p);
                        return;
                }

                switch (mode) {
                case regex_disable:
                        regex_flags &= ~bit;
                        break;
                case regex_enable:
                        regex_flags |= bit;
                        break;
                case regex_set:
                        regex_flags = bit;
                        break;
                }
        }
}       

static int
strtosize(const char *text, size_t *psize)
{
        unsigned long size;
	size_t factor = 1;
        char *p;

        size = strtoul(text, &p, 0);
	if (size == ULONG_MAX && errno == ERANGE) {
                parse_error(_("invalid size: numeric overflow occurred"));
                return 2;
	}
	
        switch (*p) {
        case 't':
        case 'T':
                factor = 1024;
        case 'g':
        case 'G':
                factor *= 1024;
        case 'm':
        case 'M':
                factor *= 1024;
        case 'k':
        case 'K':
                factor *= 1024;
                p++;
                if (*p && (*p == 'b' || *p == 'B'))
                        p++;
                break;

        case 0:
		factor = 1;
                break;
                
        default:
                parse_error(_("invalid size suffix (near %s)"), p);
                return 1;
        }

	if (((size_t)-1) / factor < size) {
                parse_error(_("invalid size: numeric overflow occurred"));
                return 2;
        }
        
        *psize = size * factor;

        return 0;
}

static void
pragma_stacksize(int argc, char **argv, const char *text)
{
        size_t size, incr = stack_expand_incr, max_size = stack_max_size;
        enum stack_expand_policy policy = stack_expand_policy;

        switch (argc) {
        case 4:
                if (strtosize(argv[3], &max_size))
                        return;
        case 3:
                if (strcmp(argv[2], "twice") == 0) 
                        policy = stack_expand_twice;
                else {
                        policy = stack_expand_add;
                                
                        if (strtosize(argv[2], &incr))
                                return;
                }
        case 2:
                if (strtosize(argv[1], &size))
			return;
        }

        stack_size = size;
        stack_expand_incr = incr;
        stack_expand_policy = policy;
        stack_max_size = max_size;
}

void
pragma_setup()
{
	install_pragma("regex", 2, 0, pragma_regex);
	install_pragma("stacksize", 2, 4, pragma_stacksize);
}



/* Test run */
struct sfsistat_tab {
        char *name;
	char *constname;
        sfsistat stat;
} sfsistat_tab[] = {
        { "accept",   "ACCEPT_ACTION",   SMFIS_ACCEPT },
        { "continue", "CONTINUE_ACTION", SMFIS_CONTINUE },
        { "discard",  "DISCARD_ACTION",  SMFIS_DISCARD },
        { "reject",   "REJECT_ACTION",   SMFIS_REJECT },
        { "tempfail", "TEMPFAIL_ACTION", SMFIS_TEMPFAIL },
        { NULL }
};

const char *
sfsistat_str(sfsistat stat)
{
        struct sfsistat_tab *p;
        for (p = sfsistat_tab; p->name; p++)
                if (p->stat == stat) 
                        return p->name;
        return NULL;
}

int
sfsistat_code(char const *stat)
{
        struct sfsistat_tab *p;
        for (p = sfsistat_tab; p->name; p++)
		if (strcmp(p->name, stat) == 0)
			return p->stat;
	return -1;
}
	
void
print_stat(sfsistat stat)
{
        struct sfsistat_tab *p;
        for (p = sfsistat_tab; p->name; p++)
                if (p->stat == stat) {
                        printf("%s", p->name);
                        return;
                }
        printf("%d", stat);
}

static void
define_action_ids(void)
{
        struct sfsistat_tab *p;
	struct mu_locus_range r = {
		{ __FILE__, __LINE__, 0 },
		{ __FILE__, __LINE__, 0 }
	};
	
        for (p = sfsistat_tab; p->name; p++) {
		struct value val = { .type = dtype_number };
		val.v.number = p->stat;
		define_constant(p->constname, &val, 0, &r);
	}
}

const char *
msgmod_opcode_str(enum msgmod_opcode opcode)
{
        switch (opcode) {
        case header_add:
                return "ADD HEADER";
                
        case header_replace:
                return "REPLACE HEADER";

        case header_delete:
                return "DELETE HEADER";

        case header_insert:
                return "INSERT HEADER";
                
        case rcpt_add:
                return "ADD RECIPIENT";

        case rcpt_delete:
                return "DELETE RECIPIENT";

        case quarantine:
                return "QUARANTINE";

        case body_repl:
                return "REPLACE BODY";

	case body_repl_fd:
		return "REPLACE BODY FROM FILE";
		
	case set_from:
		return "SET FROM";
		
        }
        return "UNKNOWN HEADER COMMAND";
}

static int
dbg_setreply(void *data, char *code, char *xcode, char *message)
{
        if (code) {
                printf("SET REPLY %s", code);
                if (xcode)
                        printf(" %s", xcode);
                if (message && message[0])
                        printf(" %s", message);
                printf("\n");
        }
        return 0;
}

static void
dbg_msgmod(void *data, struct msgmod_closure *clos)
{
	if (!clos)
		printf("clearing msgmod list\n");
	else
		printf("%s %s: %s %u\n", msgmod_opcode_str(clos->opcode),
		       SP(clos->name), SP(clos->value), clos->idx);
}

static const char *
dbg_dict_getsym (void *data, const char *str)
{
        return dict_getsym ((mu_assoc_t)data, str);
}

void
mailfromd_test(int argc, char **argv)
{
	int i;
	mu_assoc_t dict = NULL;
	eval_environ_t env;
	char *p, *end;
	long n;
	sfsistat status;
	char *args[9] = {0,0,0,0,0,0,0,0,0};
		
	dict_init(&dict);
	env = create_environment(NULL,
				 dbg_dict_getsym, dbg_setreply, dbg_msgmod,
				 dict);
	env_init(env);
	xeval_begin(env);
	
	for (i = 0; i < argc; i++) {
		if (p = strchr(argv[i], '=')) {
			char *ident = argv[i];
			*p++ = 0;
			if (mu_isdigit(*ident) && *ident != 0
				 && ident[1] == 0) 
				args[*ident - '0' - 1] = p;
			else {
				dict_install(dict, argv[i], p);
				if (strcmp(argv[i], "client_addr") == 0) {
					/* Deduce connection family. */
					if (dns_str_is_ipv6(p))
						env_save_socket_family(env, AF_INET6);
				}
			}
		}
	}

	env_init(env);
	for (i = state_parms[test_state].cnt; i--; ) {
		switch (state_parms[test_state].types[i]) {
		case dtype_string:
			env_push_string(env, args[i] ? args[i] : "");
			break;
		case dtype_number:
			if (args[i]) {
				n = strtol(args[i], &end, 0);
				if (*end) 
					mu_error(_("$%d is not a number"),
						 i+1);
			} else
				n = 0;
			env_push_number(env, n);
			break;
		default:
			abort();
		}
	}

	test_message_data_init(env);
	
	xeval(env, test_state);
	env_final_gc(env);

	status = environment_get_status(env);

	env_init(env);
	xeval(env, smtp_state_end);

	printf("State %s: ", xstate_to_string(test_state));
	print_stat(status);
	printf("\n");
	destroy_environment(env);
}

void
mailfromd_run(prog_counter_t entry_point, int argc, char **argv,
	      int macc, char **macv)
{
	int rc, i;
	mu_assoc_t dict = NULL;
	eval_environ_t env;

	dict_init(&dict);
	env = create_environment(NULL,
				 dbg_dict_getsym, dbg_setreply, dbg_msgmod,
				 dict);

	env_init(env);
	test_message_data_init(env);

	xeval_begin(env);

	for (i = 0; i < macc; i++) {
		char *p;
		if (p = strchr(macv[i], '=')) {
			*p++ = 0;
			dict_install(dict, macv[i], p);
			if (strcmp(macv[i], "client_addr") == 0) {
				/* Deduce connection family. */
				if (dns_str_is_ipv6(p))
					env_save_socket_family(env, AF_INET6);
			}
		}
	}
	
	env_init(env);
	env_push_number(env, 0);

	for (i = argc - 1; i >= 0; i--)
		env_push_string(env, argv[i]);
	env_push_number(env, argc);
	
	env_make_frame(env);
	set_milter_state(env, smtp_state_none);
	rc = eval_environment(env, entry_point);
	env_final_gc(env);
	rc = mf_c_val(env_get_reg(env), long);

	env_init(env);
	xeval(env, smtp_state_end);

	destroy_environment(env);
	exit(rc);
}

static char const *statenames[] ={
        [smtp_state_none]     = "none",
	[smtp_state_startup]  = "startup",
	[smtp_state_shutdown] = "shutdown",
        [smtp_state_begin]    = "begin",
	[smtp_state_end]      = "end",
        [smtp_state_connect]  = "connect",
        [smtp_state_helo]     = "helo",
        [smtp_state_envfrom]  = "envfrom",
        [smtp_state_envrcpt]  = "envrcpt",
        [smtp_state_data]     = "data",
        [smtp_state_header]   = "header",
        [smtp_state_eoh]      = "eoh",
        [smtp_state_body]     = "body",
        [smtp_state_eom]      = "eom",
	[smtp_state_action]   = "action",
};

enum smtp_state
string_to_state(const char *name)
{
	int i;

        for (i = 0; i < sizeof(statenames)/sizeof(statenames[0]); i++)
                if (strcasecmp(statenames[i], name) == 0)
                        return i;

        return smtp_state_none;
}

const char *
state_to_string(enum smtp_state state)
{
        if (state < sizeof(statenames)/sizeof(statenames[0]))
                return statenames[state];
        return NULL;
}

const char *
xstate_to_string(enum smtp_state state)
{
	const char *ret = state_to_string(state);
	if (!ret)
		abort();
	return ret;
}

static NODE *
_reverse(NODE *list, NODE **root)
{
        NODE *next;

        if (list->next == NULL) {
                *root = list;
                return list;
        }
        next = _reverse(list->next, root);
        next->next = list;
        list->next = NULL;
        return list;
}               

NODE *
reverse(NODE *in)
{
        NODE *root;
        if (!in)
                return in;
        _reverse(in, &root);
        return root;
}

size_t
nodelistlength(NODE *p)
{
	size_t len = 0;
	for (; p; p = p->next)
		len++;
	return len;
}

static NODE *
create_asgn_node(struct variable *var, NODE *expr,
		 struct mu_locus_range const *loc)
{
        NODE *node;
        data_type_t t = node_type(expr);

        if (t == dtype_unspecified) {
                parse_error(_("unspecified value not ignored as it should be"));
                return NULL;
        }
        node = alloc_node(node_type_asgn, loc);
        node->v.asgn.var = var;
        node->v.asgn.nframes = catch_nesting;
        node->v.asgn.node = cast_to(var->type, expr);
	var->initialized = 1;
        return node;
}


NODE *
function_call(struct function *function, size_t count, NODE *subtree)
{
        NODE *np = NULL;
        if (count < function->parmcount - function->optcount) {
                parse_error(_("too few arguments in call to `%s'"),
                            function->sym.name);
        } else if (count > function->parmcount && !function->varargs) {
                parse_error(_("too many arguments in call to `%s'"),
                            function->sym.name);
        } else {
                np = alloc_node(node_type_call, &yylloc);
                np->v.call.func = function;
                np->v.call.args = reverse(cast_arg_list(subtree,
                                                        function->parmcount,
                                                        function->parmtype,
                                                        function->vartype));
        }
        return np;
}       

data_type_t
node_type(NODE *node)
{
	switch (node->type) {
	case node_type_string:
	case node_type_symbol:
	case node_type_sed:
	case node_type_concat:
	case node_type_backref:
		return dtype_string;

	case node_type_argx:
		return node->v.argx.dtype;

	case node_type_number:
	case node_type_bin:
	case node_type_un:
	case node_type_sedcomp:
	case node_type_offset:
	case node_type_vaptr:
		return dtype_number;
		
	case node_type_if:
		return dtype_unspecified;
		
	case node_type_builtin:
		return node->v.builtin.builtin->rettype;
		
	case node_type_variable:
		return node->v.var_ref.variable->type;
		
	case node_type_arg:
		return node->v.arg.data_type;

	case node_type_call:
		return node->v.call.func->rettype;
		
	case node_type_return:
		if (node->v.node)
			return node_type(node->v.node);
		break;

	case node_type_cast:
		return node->v.cast.data_type;

	case node_type_noop:
		/* Nodes of this type are generated as placeholders in case
		   of various compilation errors and thus can represent any
		   data type. */
		return dtype_any;

	case node_type_result:
	case node_type_header:
	case node_type_asgn:
	case node_type_regex:
	case node_type_regcomp:
	case node_type_catch:
	case node_type_try:
	case node_type_throw:
	case node_type_echo:
	case node_type_switch:
	case node_type_funcdecl:
	case node_type_progdecl:
	case node_type_next:
	case node_type_break:
	case node_type_loop:
	case node_type_argv:
	case max_node_type:
		break;
	}
	return dtype_unspecified;
}

NODE *
cast_to(data_type_t type, NODE *node)
{
        NODE *np;
        data_type_t ntype = node_type(node);
        
        switch (ntype) {
        case dtype_string:
        case dtype_number:
        case dtype_pointer:
	case dtype_any:
                if (type == ntype)
                        return node;
                break;
                
        case dtype_unspecified:
                parse_error(_("cannot convert %s to %s"), type_to_string(ntype),
                            type_to_string(type));
                return NULL;

        default:
                abort();
        }
        np = alloc_node(node_type_cast, &yylloc);
        np->v.cast.data_type = type;
        np->v.cast.node = node;
        node->next = NULL;
        return np;
}

NODE *
cast_arg_list(NODE *args, size_t parmc, data_type_t *parmtype, data_type_t vartype)
{
        NODE *head = NULL, *tail = NULL;
        
        while (args) {
                NODE *next = args->next;
                NODE *p;
                data_type_t type;
                
		if (args->type == node_type_argv) {
			if (parmc) {
				parse_error_locus(&args->locus,
						  _("$@ cannnot be used to supply actual arguments for mandatory or optional parameters"));
				return NULL;
			} else {
				p = args;
			}
		} else {
			if (parmc) {
				type = *parmtype++;
				parmc--;
			} else if (vartype == dtype_any)
				type = node_type(args);
			else
				type = vartype;

			p = cast_to(type, args);
		}
		
                if (head)
                        tail->next = p;
                else
                        head = p;
                tail = p;
                args = next;
        }
        return head;
}

void
add_xref(struct variable *var, struct mu_locus_range const *locus)
{
        if (script_dump_xref) {
		/* FIXME: either change type to mu_locus_point, or
		   change print_locus above */
                struct mu_locus_range *elt = mu_zalloc(sizeof *elt);
                if (!var->xref)
                        mu_list_create(&var->xref);
                mu_locus_range_copy(elt, locus);
                mu_list_append(var->xref, elt);
        }
}

struct variable *
vardecl(const char *name, data_type_t type, storage_class_t sc,
        struct mu_locus_range const *loc)
{
        struct variable *var;
        const struct constant *cptr;
	
	if (!loc)
		loc = &yylloc;
        if (type == dtype_unspecified) {
                parse_error(_("cannot define variable of unspecified type"));
                return NULL;
        }
        var = variable_install(name);
        if (var->type == dtype_unspecified) {
                /* the variable has just been added: go straight to
                   initializing it */;
        } else if (sc != var->storage_class) {
                struct variable *vp;
                
                switch (sc) {
                case storage_extern:
                        parse_error(_("INTERNAL ERROR at %s:%d, declaring %s %s"),
                                    __FILE__, __LINE__,
                                    storage_class_str(sc), name);
                        abort();

                case storage_auto:
                        if (var->storage_class == storage_param) {
                                parse_warning_locus(loc,
						    _("automatic variable `%s' "
						      "is shadowing a parameter"),
						    var->sym.name);
                        } else
                                parse_warning_locus(loc,
						    _("automatic variable `%s' "
						      "is shadowing a global"), 
                                              var->sym.name);
                        unregister_auto(var);
                        break;

                case storage_param:
                        parse_warning_locus(loc,
					    _("parameter `%s' is shadowing a "
					      "global"),
					    name);
                }

                /* Do the shadowing */
                vp = variable_replace(var->sym.name, NULL);
                vp->shadowed = var;
                var = vp;
        } else {
                switch (sc) {
                case storage_extern:
                        if (var->type != type) {
                                parse_error_locus(loc,
						  _("redeclaring `%s' as different "
						    "data type"),
						  name);
                                parse_error_locus(&var->sym.locus,
                                            _("this is the location of the "
                                              "previous definition"));
                                return NULL;
                        }
                        break;

                case storage_auto:
                        if (var->type != type) {
                                parse_error_locus(loc,
						  _("redeclaring `%s' as different "
						    "data type"),
						  name);
                                parse_error_locus(&var->sym.locus,
						  _("this is the location of the "
						    "previous definition"));
                                return NULL;
                        } else {
                                parse_error_locus(loc,
						  _("duplicate variable: %s"),
						  name);
                                return NULL;
                        }
                        break;

                case storage_param:
                        parse_error_locus(loc, _("duplicate parameter: %s"),
					  name);
                        return NULL;
                }
        }

        /* FIXME: This is necessary because constants can be
           referred to the same way as variables. */
        if (cptr = constant_lookup(name)) {
                parse_warning_locus(loc,
				    _("variable name `%s' clashes with a constant name"),
                              name);
                parse_warning_locus(&cptr->sym.locus,
                                    _("this is the location of the "
                                      "previous definition"));
        }
        
        var->type = type;
        var->storage_class = sc;
        switch (sc) {
        case storage_extern:
                add_xref(var, loc);
                break;
        case storage_auto:
        case storage_param:
                register_auto(var);
        }
        mu_locus_range_copy(&var->sym.locus, loc);
        return var;
}

static int
cast_value(data_type_t type, struct value *value)
{
        if (type != value->type) {
                char buf[NUMERIC_BUFSIZE_BOUND];
                char *p;
                
                switch (type) {
                default:
                        abort();
                        
                case dtype_string:
                        snprintf(buf, sizeof buf, "%ld", value->v.number);
                        value->v.literal = string_alloc(buf, strlen(buf));
                        break;

                case dtype_number:
                        value->v.number = strtol(value->v.literal->text,
                                                 &p, 10);
                        if (*p) {
                                parse_error(_("cannot convert `%s' to number"),
                                            value->v.literal->text);
                                return 1;
                        }
                        break;
                }
                value->type = type;
        }
        return 0;
}

static struct variable *
externdecl(const char *name, struct value *value,
	   struct mu_locus_range const *loc)
{
        struct variable *var = vardecl(name, value->type, storage_extern, loc);
        if (!var)
                return NULL;
        if (initialize_variable(var, value, loc))
                return NULL;
        return var;
}


struct deferred_decl {
        struct deferred_decl *next;
        struct literal *name;
        struct value value;
	struct mu_locus_range locus;
};

struct deferred_decl *deferred_decl;

void
defer_initialize_variable(const char *arg, const char *val,
			  struct mu_locus_range const *ploc)
{
        struct deferred_decl *p;
        struct literal *name = string_alloc(arg, strlen(arg));
        for (p = deferred_decl; p; p = p->next)
                if (p->name == name) {
                        parse_warning_locus(NULL, _("redefining variable %s"),
                                            name->text);
                        p->value.type = dtype_string;
                        p->value.v.literal = string_alloc(val, strlen(val));
			mu_locus_range_copy (&p->locus, ploc);
                        return;
                }
        p = mu_alloc(sizeof *p);
        p->name = name;
        p->value.type = dtype_string;
        p->value.v.literal = string_alloc(val, strlen(val));
	mu_locus_range_init (&p->locus);
	mu_locus_range_copy (&p->locus, ploc);
        p->next = deferred_decl;
        deferred_decl = p;
}

static void
apply_deferred_init()
{
        struct deferred_decl *p;
        for (p = deferred_decl; p; p = p->next) {
                struct variable *var = variable_lookup(p->name->text);
                if (!var) {
			mu_diag_at_locus_range(MU_DIAG_ERROR,
					       &p->locus,
					       _("warning: no such variable: %s"),
					       p->name->text);
                } else if (initialize_variable(var, &p->value, &p->locus))
                        parse_error_locus(&p->locus,
					  _("error initialising variable %s: incompatible types"),
					  p->name->text);
        }
}


struct declvar {
        struct declvar *next;
        struct mu_locus_range locus;
        struct variable *var;
        struct value val;
};

static struct declvar *declvar;

void
set_poll_arg(struct poll_data *poll, int kw, NODE *expr)
{
        switch (kw) {
        case T_FOR:
                poll->email = expr;
                break;
                
        case T_HOST:
                poll->client_addr = expr;
                break;
                
        case T_AS:
                poll->mailfrom = expr;
                break;
                
        case T_FROM:
                poll->ehlo = expr;
                break;
                
        default:
                abort();
        }
}
                
int
initialize_variable(struct variable *var, struct value *val,
                    struct mu_locus_range const *locus)
{
        struct declvar *dv;

        if (cast_value(var->type, val))
                return 1;
        for (dv = declvar; dv; dv = dv->next)
                if (dv->var == var) {
                        if (dv->locus.beg.mu_file) {
                                parse_warning_locus(locus,
                                     _("variable `%s' already initialized"),
                                                    var->sym.name);
                                parse_warning_locus(&dv->locus,
                                     _("this is the location of the "
                                       "previous initialization"));
                        }
			
                        if (locus)
				mu_locus_range_copy (&dv->locus, locus);
                        else
				mu_locus_range_deinit (&dv->locus);
                        dv->val = *val;
                        return 0;
                }

        dv = mu_alloc(sizeof *dv);
        dv->next = declvar;
        dv->var = var;
	mu_locus_range_init (&dv->locus);
	if (locus)
		mu_locus_range_copy (&dv->locus, locus);
        dv->val = *val;
        declvar = dv;
	var->sym.flags |= SYM_INITIALIZED;
        return 0;
}

void
ds_init_variable(const char *name, void *data)
{
        struct declvar *dv;     
        struct variable *var = variable_lookup(name);

        if (!var) {
                mu_error(_("INTERNAL ERROR at %s:%d: variable to be "
                           "initialized is not declared"),
                         __FILE__, __LINE__);
                abort();
        }

        for (dv = declvar; dv; dv = dv->next)
                if (dv->var == var)
                        return;

        dv = mu_alloc(sizeof *dv);
	mu_locus_range_init(&dv->locus);
        dv->var = var;
        dv->next = declvar;
        declvar = dv;

	switch (var->type) {
	case dtype_string:
		dv->val.v.literal = string_alloc(data, strlen(data));
		break;
		
	case dtype_number:
		dv->val.v.number = *(long*)data;
		break;

	default:
                mu_error(_("INTERNAL ERROR at %s:%d: variable to be "
                           "initialized has wrong type"),
                         __FILE__, __LINE__);
		abort ();
	}
	dv->val.type = var->type;
}	

static int
_ds_variable_count_fun(void *sym, void *data)
{
        struct variable *var = sym;

        if ((var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED))
	    && !(var->sym.flags & SYM_PASSTOGGLE)) {
		var->sym.flags |= SYM_PASSTOGGLE;
                variable_count++;
                if (var->type == dtype_string) 
                        dataseg_reloc_count++;
                if (var->sym.flags & SYM_PRECIOUS)
                        precious_count++;
        }
        return 0;
}

static int
_ds_variable_fill_fun(void *sym, void *data)
{
        struct variable *var = sym;
        
        if (var->sym.flags & SYM_PASSTOGGLE) {
		var->sym.flags &= ~SYM_PASSTOGGLE;
                struct variable ***vtabptr = data;
                **vtabptr = var;
                ++*vtabptr;
        }
        return 0;
}

static int
_ds_reloc_fun(void *sym, void *data)
{
        struct variable *var = sym;
        size_t *pi = data;
        
        if ((var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED))
	    && !(var->sym.flags & SYM_PASSTOGGLE)
	    && var->type == dtype_string) {
		var->sym.flags |= SYM_PASSTOGGLE;
                dataseg_reloc[(*pi)++] = var->off;
	}
        return 0;
}

static int
_ds_literal_count_fun(void *sym, void *data)
{
        struct literal *lit = sym;
        size_t *offset = data;
        if (!(lit->flags & SYM_VOLATILE) && (lit->flags & SYM_REFERENCED)) {
                lit->off = *offset;
                *offset += B2STACK(strlen(lit->text) + 1);
        }
        return 0;
}

static int
_ds_literal_copy_fun(void *sym, void *data)
{
        struct literal *lit = sym;
        if (!(lit->flags & SYM_VOLATILE) && (lit->flags & SYM_REFERENCED)) 
                strcpy((char*)(dataseg + lit->off), lit->text);
        return 0;
}

static int
vtab_comp(const void *a, const void *b)
{
        const struct variable *vp1 = *(const struct variable **)a;
        const struct variable *vp2 = *(const struct variable **)b;

        if ((vp1->sym.flags & SYM_PRECIOUS)
            && !(vp2->sym.flags & SYM_PRECIOUS))
                return 1;
        else if ((vp2->sym.flags & SYM_PRECIOUS) 
                 && !(vp1->sym.flags & SYM_PRECIOUS))
                return -1;
        return 0;
}

static int
place_exc(const struct constant *cp, const struct literal *lit, void *data)
{
	STKVAL *tab = data;
	tab[cp->value.v.number] = (STKVAL) lit->off;
	return 0;
}

static void
dataseg_layout()
{
	struct declvar *dv;
	size_t i;
	struct switch_stmt *sw;
	struct variable **vtab, **pvtab;
	struct exmask *exmask;

	if (root_node[smtp_state_action]) {
		/* Make sure PROG_ACTION_NAME is registered before code
		   generation.  It will be used by code_type_result in
		   drivers.c */
		literal_lookup(PROG_ACTION_NAME)->flags |= SYM_REFERENCED;
	}
	
	/* Count used variables and estimate the number of relocations
	   needed */
	dataseg_reloc_count = 0;
	module_symtab_enumerate(namespace_variable,
				_ds_variable_count_fun,
				NULL);

	/* Fill variable pointer array and make sure precious variables
	   occupy its bottom part */
	vtab = mu_calloc(variable_count, sizeof(vtab[0]));
	pvtab = vtab;
	module_symtab_enumerate(namespace_variable,
				_ds_variable_fill_fun,
				&pvtab);
	qsort(vtab, variable_count, sizeof(vtab[0]), vtab_comp);

	/* Compute variable offsets. Offset 0 is reserved for NULL symbol */
	for (i = 0; i < variable_count; i++) {
		vtab[i]->off = i + 1;
		if (vtab[i]->addrptr)
			*vtab[i]->addrptr = vtab[i]->off;
	}

	/* Free the array */
	free(vtab);
	
	/* Mark literals used to initialize variables as referenced */
	for (dv = declvar; dv; dv = dv->next) {
		if ((dv->var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED))
		    && dv->var->type == dtype_string) {
			dv->val.v.literal->flags |= SYM_REFERENCED;
		}
	}

	datasize = variable_count + 1;
	dvarsize = datasize - precious_count;
	
	/* Count referenced literals and adjust the data size */
	symtab_enumerate(stab_literal, _ds_literal_count_fun, &datasize);

	/* Account for switch translation tables */
	for (sw = switch_root; sw; sw = sw->next) {
		sw->off = datasize;
		datasize += sw->tabsize;
	}

	/* Account for exception masks */
	for (exmask = exmask_root; exmask; exmask = exmask->next) {
		exmask->off = datasize;
		if (exmask->all) {
			size_t i;
			for (i = 0; i < exception_count; i++)
				bitmask_set(&exmask->bm, i);
		}
		datasize += exmask->bm.bm_size + 1;
	}

	/* Account for exception name table */
	datasize += exception_count;
	
	/* Allocate data segment and relocation table */
	dataseg = mu_calloc(datasize, sizeof(STKVAL));
	dataseg_reloc = mu_calloc(dataseg_reloc_count, sizeof *dataseg_reloc);

	/* Fill relocation table */
	i = 0;
	module_symtab_enumerate(namespace_variable, _ds_reloc_fun, &i);
	
	/* Initialize variables */
	for (dv = declvar; dv; dv = dv->next) {
		if (dv->var->sym.flags & (SYM_VOLATILE | SYM_REFERENCED)) {
			switch (dv->var->type) {
			case dtype_string:
				dataseg[dv->var->off] =
					(STKVAL) dv->val.v.literal->off;
				break;

			case dtype_number:
				dataseg[dv->var->off] =
					(STKVAL) dv->val.v.number;
				break;

			default:
				abort();
			}
		}
	}	

	/* Place literals */
	symtab_enumerate(stab_literal, _ds_literal_copy_fun, NULL);

	/* Initialize exception masks */
	for (exmask = exmask_root; exmask; exmask = exmask->next) {
		size_t i, off = exmask->off;
		
		dataseg[off++] = (STKVAL) exmask->bm.bm_size;
		for (i = 0; i < exmask->bm.bm_size; i++)
			dataseg[off++] = (STKVAL) exmask->bm.bm_bits[i++];
	}

	/* Initialize exception name table */
	enumerate_exceptions(place_exc, dataseg + EXTABIND);
}


static int
_regex_compile_fun(void *sym, void *data)
{
        struct literal *lit = sym;
        if (lit->regex) {
                struct sym_regex *rp;
                for (rp = lit->regex; rp; rp = rp->next) 
                        register_regex(rp);
        }
        return 0;
}

void
regex_layout()
{
        symtab_enumerate(stab_literal, _regex_compile_fun, NULL);
        finalize_regex();
}


static struct variable *auto_list;

static void
register_auto(struct variable *var)
{
        var->next = auto_list;
        auto_list = var;
}

static void
unregister_auto(struct variable *var)
{
        struct variable *p = auto_list, *prev = NULL;
        while (p) {
                struct variable *next = p->next;
                if (p == var) {
                        if (prev)
                                prev->next = next;
                        else
                                auto_list = next;
                        p->next = NULL;
                        return;
                }
                prev = p;
                p = next;
        }
}

/* FIXME: Redo shadowing via a separate table? */
static size_t
forget_autos(size_t nparam, size_t auto_count, size_t hidden_arg)
{
        size_t param_count = 0;
        struct variable *var = auto_list;
        while (var) {
                struct variable *next = var->next;
                switch (var->storage_class) {
                case storage_auto:
                        var->off = auto_count++;
                        break;
                case storage_param:
                        var->off = nparam - param_count++;
                        var->ord = var->off - (hidden_arg ? 1 : 0) - 1;
                        break;
                default:
                        abort();
                }
                while (var->storage_class != storage_extern) {
                        struct variable *shadowed = var->shadowed;
                        if (!shadowed) {
                                symtab_remove(TOP_MODULE_SYMTAB(namespace_variable),
                                              var->sym.name);
                                break;
                        }
                        var->shadowed = NULL;
                        var = variable_replace(var->sym.name, shadowed);
                }
                var = next;
        }
        auto_list = NULL;
        return auto_count;
}

const char *
storage_class_str(storage_class_t sc)
{
        switch (sc) {
        case storage_extern:
                return "extern";
        case storage_auto:
                return "auto";
        case storage_param:
                return "param";
        }
        return "unknown?";
}


const char *
function_name()
{
        switch (outer_context) {
        case context_function:
                return func->sym.name;
        case context_handler:
                return xstate_to_string(state_tag);
        default:
                return "";
        }
}

NODE *
declare_function(struct function *func, struct mu_locus_range const *loc,
		 size_t nautos)
{
        NODE *node = alloc_node(node_type_funcdecl, loc);
        node->v.funcdecl.func = func;
        node->v.funcdecl.auto_count = nautos;
        node->v.funcdecl.tree = func->node; 
        return node;
}


NODE *
create_node_variable(struct variable *var, struct mu_locus_range const *locus)
{
        NODE *node;

	variable_check_initialized(var, locus);
	node = alloc_node(node_type_variable, locus);
        node->v.var_ref.variable = var;
        node->v.var_ref.nframes = catch_nesting;
        return node;
}

NODE *
create_node_argcount(struct mu_locus_range const *locus)
{
        NODE *node;
        
        if (outer_context == context_function) {
                if (func->optcount || func->varargs) {
                        node = alloc_node(node_type_arg, locus);
                        node->v.arg.data_type = dtype_number;
                        node->v.arg.number = 1;
                } else {
                        node = alloc_node(node_type_number, locus);
                        node->v.number = parminfo[outer_context].parmcount();
                }
        } else {
                node = alloc_node(node_type_number, locus);
                node->v.number = parminfo[outer_context].parmcount();
        }
        return node;
}

NODE *
create_node_arg(long num, struct mu_locus_range const *locus)
{
        NODE *node = alloc_node(node_type_arg, locus);
	long argnum = num;
	data_type_t type;

	if (inner_context == context_function) {
		if (func->varargs) {
			argnum += PARMCOUNT() + FUNC_HIDDEN_ARGS(func);
			type = PARMTYPE(num);
		} else {
			parse_error_locus(locus,
					  _("$N is allowed only in functions "
					    "with variable number of arguments"));
			argnum = 0;
			type = dtype_string;
		}
        } else if (num > PARMCOUNT()) {
		parse_error_locus(locus, _("argument number too high"));
		num = argnum = 0;
		type = dtype_string;
	} else
		type = PARMTYPE(num);

	node->v.arg.data_type = type;
	node->v.arg.number = argnum;
        return node;
}

NODE *
create_node_string(char const *str, struct mu_locus_range const *locus)
{
        NODE *node = alloc_node(node_type_string, locus);
        node->v.literal = literal_lookup(str);
        return node;
}

NODE *
create_node_symbol(struct literal *lit, struct mu_locus_range const *locus)
{
        NODE *node;
        register_macro(state_tag, lit->text);
        node = alloc_node(node_type_symbol, locus);
        node->v.literal = lit;
        return node;
}

NODE *
create_node_backref(long num, struct mu_locus_range const *locus)
{
        NODE *node = alloc_node(node_type_backref, locus);
        node->v.number = num;
        return node;
}

static inline int
variable_is_initialized(struct variable *var)
{
	return var->storage_class != storage_auto || var->initialized;
}

void
variable_check_initialized(struct variable *var,
			   struct mu_locus_range const *loc)
{
	if (!variable_is_initialized(var)) {
		parse_warning_locus(loc,
				    _("use of uninitialized variable '%s'"),
				    var->sym.name);
		var->initialized = 1;
	}
}
