/* 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/>. */

static inline void
mark_locus(NODE *node, struct mu_locus_range const **old_locus)
{
	if (!*old_locus
	    || !mu_locus_point_same_line (&(*old_locus)->beg, &node->locus.beg)) {
                struct literal *lit = literal_lookup(node->locus.beg.mu_file);
                *old_locus = &node->locus;
		code_op(opcode_locus);
		code_immediate(lit->off, ulong);
		code_immediate(node->locus.beg.mu_line, uint);
	}
}

static void code_trycatch_exit(unsigned id);
static unsigned trycatch_last_id(void);



void
code_memref(NODE *node)
{
	switch (node->v.var_ref.variable->storage_class) {
	case storage_extern:
		code_op(opcode_push);
		code_immediate(node->v.var_ref.variable->off, long);
		break;

	case storage_auto:
		code_op(opcode_memstk);
		code_immediate(node->v.var_ref.nframes, size);
		code_immediate((-node->v.var_ref.variable->off), long);
		break;

	case storage_param:
		code_op(opcode_memstk);
		code_immediate(node->v.var_ref.nframes, size);
		code_immediate((node->v.var_ref.variable->off + 2), long);
		break;
	}
}


/* type noop */
/* Empty node, nothing to print, mark, optimize or code */


/* type string */

void
print_type_string(NODE *node, int level)
{
	print_level(level);
	printf("STRING: \"");
	print_quoted_string(node->v.literal->text);
	printf("\"\n");
}

void
mark_type_string(NODE *node)
{
	node->v.literal->flags |= SYM_REFERENCED;
}

void
code_type_string(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	code_op(opcode_push);
	code_immediate(node->v.literal->off, size);
}


/* type symbol */

void
print_type_symbol(NODE *node, int level)
{
	print_level(level);
	printf("SYMBOL: %s\n", node->v.literal->text);
}

void
code_type_symbol(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	code_op(opcode_symbol);
	code_immediate(node->v.literal->off, size);
}

void
mark_type_symbol(NODE *node)
{
	node->v.literal->flags |= SYM_REFERENCED;
}


/* type number */
void
print_type_number(NODE *node, int level)
{
	print_level(level);
	printf("NUMBER: %ld\n", node->v.number);
}

void
code_type_number(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	code_op(opcode_push);
	code_immediate(node->v.number, long);
}


/* type if */

void
print_type_if(NODE *node, int level)
{
	print_level(level);
	printf("COND: \n");
	print_node(node->v.cond.cond, level);
	print_level(level);
	printf("IFTRUE\n");
	print_node_list(node->v.cond.if_true, level+1);
	print_level(level);
	printf("IFFALSE\n");
	print_node_list(node->v.cond.if_false, level+1);
}

void
mark_type_if(NODE *node)
{
	mark(node->v.cond.cond);
	mark(node->v.cond.if_true);
	mark(node->v.cond.if_false);
}

void
optimize_type_if(NODE *node)
{
	NODE *p = node->v.cond.cond;
	optimize(p);
	optimize(node->v.cond.if_true);
	optimize(node->v.cond.if_false);
	if (p->type == node_type_number) {
		NODE *head;
		NODE *tail = node->next;
		if (p->v.number) {
			head = node->v.cond.if_true;
			free_subtree(node->v.cond.if_false);
		} else {
			head = node->v.cond.if_false;
			free_subtree(node->v.cond.if_true);
		}
		
		if (head) { 
			*node = *head;
			free_node(head);
			
			for (; node->next; node = node->next)
				;
			node->next = tail;
		} else
			node->type = node_type_noop;
	} else if (p->type == node_type_string) {
		NODE *head;
		NODE *tail = p->next;
		if (p->v.literal->text[0]) {
			head = node->v.cond.if_true;
			free_subtree(node->v.cond.if_false);
		} else {
			head = node->v.cond.if_false;
			free_subtree(node->v.cond.if_true);
		}
		
		if (head) {
			*node = *head;
			free_node(head);
			
			for (; node->next; node = node->next)
				;
			node->next = tail;
		} else
			node->type = node_type_noop;
	}
}

void
code_type_if(NODE *node, struct mu_locus_range const **old_locus)
{
	prog_counter_t pos1, pos2, endpos;

	code_node(node->v.cond.cond);
	mark_locus(node, old_locus);
	code_op(opcode_bz);
	pos1 = code_immediate(NULL, ptr);
	traverse_tree(node->v.cond.if_true);
	if (node->v.cond.if_false) {
		code_op(opcode_jmp);
		pos2 = code_immediate(NULL, ptr);
		traverse_tree(node->v.cond.if_false);
		endpos = code_get_counter ();
		code_put(pos1, (pos2 - pos1), long);
		code_put(pos2, (endpos - pos2 - 1), long);
	} else 
		code_put(pos1, (code_get_counter () - pos1 - 1), long);
}


/* type bin */

void
print_type_bin(NODE *node, int level)
{
	print_level(level);
	print_bin_op(node->v.bin.opcode);
	if (node->v.bin.opcode == bin_match
		    || node->v.bin.opcode == bin_fnmatch) {
		if (node->v.bin.qualifier & QUALIFIER_MX)
			printf(",MX");
	}
	printf("\n");
	print_node(node->v.bin.arg[0], level+1);
	print_node(node->v.bin.arg[1], level+1);
}

void
mark_type_bin(NODE *node)
{
	mark(node->v.bin.arg[0]);
	mark(node->v.bin.arg[1]);
}

static void
optimize_arith(NODE *node)
{
	NODE *arg0 = node->v.bin.arg[0];
	NODE *arg1 = node->v.bin.arg[1];

	if (arg0->type == node_type_number                                     
	    && arg1->type == node_type_number) {
		switch (node->v.bin.opcode) {
		case bin_add:
			node->v.number = arg0->v.number + arg1->v.number;
			break;
			
		case bin_sub:
			node->v.number = arg0->v.number - arg1->v.number;
			break;
			
		case bin_mul:
			node->v.number = arg0->v.number * arg1->v.number;
			break;
				
		case bin_div:
			if (arg1->v.number == 0) {
				parse_error_locus(&node->locus,
						  _("division by zero"));
				break;
			}
			node->v.number = arg0->v.number  / arg1->v.number;
			break;

		case bin_mod:
			if (arg1->v.number == 0) {
				parse_error_locus(&node->locus,
						  _("division by zero"));
				break;
			}
			node->v.number = arg0->v.number % arg1->v.number;
			break;

		case bin_logand:
			node->v.number = arg0->v.number & arg1->v.number;
			break;
			
		case bin_logor:
			node->v.number = arg0->v.number | arg1->v.number;
			break;
			
		case bin_logxor:
			node->v.number = arg0->v.number ^ arg1->v.number;
			break;

		case bin_shl:
			node->v.number = arg0->v.number << (unsigned long) arg1->v.number;
			break;
			
		case bin_shr:
			node->v.number = arg0->v.number >> (unsigned long) arg1->v.number;
			break;
			
		default:
			return;
		}
		node->type = node_type_number;
		free_node(arg0);
		free_node(arg1);
	} else if (node_type(arg0) != dtype_number) {
		parse_error_locus(&arg0->locus,
				  _("left-hand side argument to the "
				    "arithmetical operation is "
                                    "of wrong data type"));
	} else if (node_type(arg1) != dtype_number) {
		parse_error_locus(&arg1->locus,
				  _("right-hand side argument to the "
				    "arithmetical operation is "
                                    "of wrong data type"));
	} else if (arg0->type == node_type_number) {
		switch (node->v.bin.opcode) {
		case bin_add:
			if (arg0->v.number == 0) {
				copy_node(node, arg1);
				free_node(arg0);
				free(arg1);
			}
			break;
			
		case bin_sub:
			if (arg0->v.number == 0) {
				NODE *n = alloc_node(node_type_un,
						     &node->locus);
				n->v.un.opcode = unary_minus;
				n->v.un.arg = arg1;
				copy_node(node, n);
				free_node(arg0);
				free(n);
			}
			break;
			
		case bin_mul:
			if (arg0->v.number == 0) {
				node->type = node_type_number;
				node->v.number = 0;
				free_node(arg0);
				free_node(arg1);
			} else if (arg0->v.number == 1) {
				copy_node(node, arg1);
				free_node(arg0);
			}
			break;
				
		case bin_div:
		case bin_mod:
			if (arg0->v.number == 0) {
				node->type = node_type_number;
				node->v.number = 0;
				free_node(arg0);
				free_node(arg1);
			}
			break;

		case bin_logand:
			if (arg0->v.number == 0) {
				node->type = node_type_number;
				node->v.number = 0;
				free_node(arg0);
				free_node(arg1);
			} else if (arg0->v.number == ~(unsigned long)0) {
				copy_node(node, arg1);
				free_node(arg0);
			}
			break;
			
		case bin_logor:
			if (arg0->v.number == 0) {
				copy_node(node, arg1);
				free_node(arg0);
			} else if (arg0->v.number == ~(unsigned long)0) {
				node->type = node_type_number;
				node->v.number = ~(unsigned long)0;
				free_node(arg0);
				free_node(arg1);
			}
			break;
			
		case bin_logxor:
			if (arg0->v.number == 0) {
				copy_node(node, arg1);
				free_node(arg0);
			} 
			break;

		case bin_shl:
		case bin_shr:
			if (arg0->v.number == 0) {
				node->type = node_type_number;
				node->v.number = 0;
				free_node(arg0);
				free_node(arg1);
			}
			break;
			
		default:
			return;
		}
	} else if (arg1->type == node_type_number) {
		switch (node->v.bin.opcode) {
		case bin_add:
		case bin_sub:
			if (arg1->v.number == 0) {
				copy_node(node, arg0);
				free_node(arg1);
				free(arg0);
			}
			break;
			
		case bin_mul:
			if (arg1->v.number == 0) {
				node->type = node_type_number;
				node->v.number = 0;
				free_node(arg0);
				free_node(arg1);
			} else if (arg1->v.number == 1) {
				copy_node(node, arg0);
				free_node(arg1);
			}
			break;
				
		case bin_div:
			if (arg1->v.number == 0) {
				parse_error_locus(&node->locus,
						  _("division by zero"));
			} else if (arg1->v.number == 1) {
				copy_node(node, arg0);
				free_node(arg1);
			}
			break;

		case bin_mod:
			if (arg1->v.number == 0) {
				parse_error_locus(&node->locus,
						  _("division by zero"));
			} else if (arg1->v.number == 1) {
				node->type = node_type_number;
				node->v.number = 0;
				free_node(arg0);
				free_node(arg1);
			}
			break;
			
		case bin_logand:
			if (arg1->v.number == 0) {
				node->type = node_type_number;
				node->v.number = 0;
				free_node(arg0);
				free_node(arg1);
			} else if (arg1->v.number == ~(unsigned long)0) {
				copy_node(node, arg0);
				free_node(arg1);
			}
			break;
			
		case bin_logor:
			if (arg1->v.number == 0) {
				copy_node(node, arg0);
				free_node(arg1);
			} else if (arg1->v.number == ~(unsigned long)0) {
				node->type = node_type_number;
				node->v.number = ~(unsigned long)0;
				free_node(arg0);
				free_node(arg1);
			}
			break;
			
		case bin_logxor:
			if (arg1->v.number == 0) {
				copy_node(node, arg0);
				free_node(arg0);
			} 
			break;
			
		case bin_shl:
		case bin_shr:
			if (arg1->v.number == 0) {
				copy_node(node, arg0);
				free_node(arg1);
				free(arg0);
			}
			break;
			
		default:
			return;
		}
	}
}

static int
node_boolean_value(NODE *node, int *pval)
{
	switch (node->type) {
	case node_type_number:
		*pval = !!node->v.number;
		break;
		
	case node_type_string:
		*pval = !!node->v.literal->text[0];
		break;
		
	default:
		return 1;
	}
	return 0;
}

static int
_optimize_shortcut(NODE *node, NODE *arg)
{
	int val;
	if (node_boolean_value(arg, &val))
		return 1;
	switch (node->v.bin.opcode) {
	case bin_and:
		if (val)
			return 1;
		node->v.number = 0;
		break;
		
	case bin_or:
		if (!val)
			return 1;
		node->v.number = 1;
		break;
			
	default:
		return 1;
	}

	node->type = node_type_number;
	free_node(node->v.bin.arg[0]);
	free_node(node->v.bin.arg[1]);
	return 0;
}

void
optimize_relational(NODE *node)
{
	NODE *arg0 = node->v.bin.arg[0];
	NODE *arg1 = node->v.bin.arg[1];

	if (_optimize_shortcut(node, arg0) == 0
	    || _optimize_shortcut(node, arg1) == 0)
		return;
	
	if (arg0->type == node_type_number
	    && arg1->type == node_type_number) {
		switch (node->v.bin.opcode) {
		case bin_and:
			node->v.number = arg0->v.number && arg1->v.number;
			break;
		
		case bin_or:
			node->v.number = arg0->v.number || arg1->v.number;
			break;
			
		case bin_eq:
			node->v.number = arg0->v.number == arg1->v.number;
			break;
			
		case bin_ne:
			node->v.number = arg0->v.number != arg1->v.number;
			break;
				
		case bin_lt:
			node->v.number = arg0->v.number < arg1->v.number;
			break;
			
		case bin_le:
			node->v.number = arg0->v.number <= arg1->v.number;
			break;
			
		case bin_gt:
			node->v.number = arg0->v.number  > arg1->v.number;
			break;
			
		case bin_ge:
			node->v.number = arg0->v.number  >= arg1->v.number;
			break;

		default:
			return;
		}
	} else if (arg0->type == node_type_string
		   && arg1->type == node_type_string) {
		switch (node->v.bin.opcode) {
		case bin_and:
			node->v.number = arg0->v.literal->text[0] != 0
				              && arg1->v.literal->text[0] != 0;
			break;
		
		case bin_or:
			node->v.number = arg0->v.literal->text[0] != 0
				              || arg1->v.literal->text[0] != 0;
			break;
			
		case bin_eq:
			/* NOTE: This case and the one below make use of the
			   fact that no two entries in the symbol table can
			   contain lexicographically equal literals */
			node->v.number = arg0->v.literal == arg1->v.literal;
			break;
			
		case bin_ne:
			node->v.number = arg0->v.literal != arg1->v.literal;
			break;
				
		case bin_lt:
			node->v.number = strcmp(arg0->v.literal->text,
						arg1->v.literal->text) < 0;
			break;
			
		case bin_le:
			node->v.number = strcmp(arg0->v.literal->text,
						arg1->v.literal->text) <= 0;
			break;
			
		case bin_gt:
			node->v.number = strcmp(arg0->v.literal->text,
						arg1->v.literal->text) > 0;
			break;
			
		case bin_ge:
			node->v.number = strcmp(arg0->v.literal->text,
						arg1->v.literal->text) >= 0;
			break;

		default:
			return;
		}
	} else
		return;
	    
	node->type = node_type_number;
	free_node(arg0);
	free_node(arg1);
}

static int
node_regmatch(NODE *node, struct literal *lit)
{
	regex_t re;
	struct sym_regex *sym = node->v.regex;
	int rc;
	
	rc = regcomp(&re, sym->lit->text, sym->regflags);
	if (rc)	{
		char errbuf[512];
		regerror(rc, &re, errbuf, sizeof(errbuf));
		parse_error_locus(&node->locus,
				  _("cannot compile regex: %s"),
				  errbuf);
		return 1;
	}
	rc = regexec(&re, lit->text, 0, NULL, 0);
	regfree(&re);
	return rc;
}

void
optimize_type_bin(NODE *node)
{
	NODE *arg0, *arg1;

	arg0 = node->v.bin.arg[0];
	arg1 = node->v.bin.arg[1];
	optimize(arg0);
	optimize(arg1);

	switch (node->v.bin.opcode) {
	case bin_and:
	case bin_or:
	case bin_eq:
	case bin_ne:
	case bin_lt:
	case bin_le:
	case bin_gt:
	case bin_ge:
		optimize_relational(node);
		break;
		
	case bin_add:
	case bin_sub:
	case bin_mul:
	case bin_div:
	case bin_mod:
	case bin_logand:
	case bin_logor:
	case bin_logxor:
	case bin_shl:
	case bin_shr:
		optimize_arith(node);
		break;

	case bin_match:
		if (node_type(arg0) != dtype_string) {
			parse_error_locus(&arg0->locus,
					  _("left-hand side argument "
					    "to match is "
					    "of wrong data type"));
		} else if (arg1->type == node_type_regex) {
			if (arg0->type == node_type_string) {
				node->v.number =
					node_regmatch(arg1,
						      arg0->v.literal) == 0;
				node->type = node_type_number;
				free_node(arg0);
				free_node(arg1);
			}  
		} else if (arg1->type != node_type_regcomp) {
			parse_error_locus(&arg1->locus,
					  _("right-hand side argument "
					    "to match is "
					    "of wrong data type "
					    "(should not happen)"));
		}
		break;
		
	case bin_fnmatch:
		if (arg0->type == node_type_string
		    && arg1->type == node_type_string) {
			node->v.number = fnmatch(arg1->v.literal->text,
						 arg0->v.literal->text,
						 0) == 0;
			node->type = node_type_number;
			free_node(arg0);
			free_node(arg1);
		} else if (node_type(arg0) != dtype_string) {
			parse_error_locus(&arg0->locus,
					  _("left-hand side argument "
					    "to fnmatch is of wrong data type"));
		} else if (node_type(arg1) != dtype_string) {
			parse_error_locus(&arg1->locus,
					  _("right-hand side argument "
					    "to fnmatch is of wrong data type"));
		}
		break;
	}
}

#define __code_cat3__(a,b,c) a ## b ## c

#define CODE_BINARY(op, node) do {                                            \
	switch (node_type(node->v.bin.arg[0])) {                              \
	case dtype_number:                                                    \
		code_op(__code_cat3__(opcode_,op,n));                         \
		break;                                                        \
	case dtype_string:                                                    \
		code_op(__code_cat3__(opcode_,op,s));                         \
		break;                                                        \
	default:                                                              \
		parse_error_locus(&node->locus,                               \
			_("invalid argument type in binary operation"));      \
		break;                                                        \
	}                                                                     \
} while (0)

void
code_type_bin(NODE *node, struct mu_locus_range const **old_locus)
{
	prog_counter_t pos1, pos2;

	code_node(node->v.bin.arg[0]);

	switch (node->v.bin.opcode) {
	case bin_and:
		mark_locus(node, old_locus);
		/* cond1                        
		   if not true goto X
		   cond2
		   if true goto Y
		   X: push 0
		   goto Z
		   Y: push 1
		   Z: */
		code_op(opcode_bz);
		pos1 = code_immediate(NULL, ptr);
		code_node(node->v.bin.arg[1]);
		code_op(opcode_bnz);
		pos2 = code_immediate(4, long);
		code_op(opcode_push);
		code_immediate(0, long);
		code_op(opcode_jmp);
		code_immediate(2, long);
		code_op(opcode_push);
		code_immediate(1, long);
		code_put(pos1, (pos2 - pos1), long);
		break;
			
	case bin_or:
		mark_locus(node, old_locus);
		/* cond1                        
		   if true goto X
		   cond2
		   if not true goto Y
		   X: push 1
		   goto Z
		   Y: push 0
		   Z: */
		
		code_op(opcode_bnz);
		pos1 = code_immediate(NULL, ptr);
		code_node(node->v.bin.arg[1]);
		code_op(opcode_bz);
		pos2 = code_immediate(4, long);
		code_op(opcode_push);
		code_immediate(1, long);
		code_op(opcode_jmp);
		code_immediate(2, long);
		code_op(opcode_push);
		code_immediate(0, long);
		code_put(pos1, (pos2 - pos1), long);
		break;
		
	case bin_eq:
		code_node(node->v.bin.arg[1]);
		CODE_BINARY(eq, node);
		break;
		
	case bin_ne:
		code_node(node->v.bin.arg[1]);
		CODE_BINARY(ne, node);
		break;
		
	case bin_lt:
		code_node(node->v.bin.arg[1]);
		CODE_BINARY(lt, node);
		break;
		
	case bin_le:
		code_node(node->v.bin.arg[1]);
		CODE_BINARY(le, node);
		break;
		
	case bin_gt:
		code_node(node->v.bin.arg[1]);
		CODE_BINARY(gt, node);
		break;
		
	case bin_ge:
		code_node(node->v.bin.arg[1]);
		CODE_BINARY(ge, node);
		break;
		
	case bin_match:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		if (node->v.bin.qualifier & QUALIFIER_MX)
			code_op(opcode_regmatch_mx);
		else
			code_op(opcode_regmatch);
		break;
		
	case bin_fnmatch:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		if (node->v.bin.qualifier & QUALIFIER_MX)
			code_op(opcode_fnmatch_mx);
		else
			code_op(opcode_fnmatch);
		break;
		
	case bin_add:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_add);
		break;
		
	case bin_sub:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_sub);
		break;
		
	case bin_mul:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_mul);
		break;
		
	case bin_div:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_div);
		break;
		
	case bin_mod:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_mod);
		break;
		
	case bin_logand:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_logand);
		break;
		
	case bin_logor:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_logor);
		break;
		
	case bin_logxor:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_logxor);
		break;
		
	case bin_shl:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_shl);
		break;
		
	case bin_shr:
		code_node(node->v.bin.arg[1]);
		mark_locus(node, old_locus);
		code_op(opcode_shr);
		break;
		
	default:
		break;
	}
}


/* type un */

void
print_type_un(NODE *node, int level)
{
	print_level(level);
	switch (node->v.un.opcode) {
	case unary_not:
		printf("NOT\n");
		break;
	case unary_minus:
		printf("NEG\n");
		break;
	default:
		abort();
	}
	print_node(node->v.un.arg, level+1);
}

void
mark_type_un(NODE *node)
{
	mark(node->v.un.arg);
}

void
optimize_type_un(NODE *node)
{
	NODE *p = node->v.un.arg;
	optimize(p);
	if (p->type == node_type_number) {
		switch (node->v.un.opcode) {
		case unary_not:
			node->v.number = !p->v.number;
			break;
			
		case unary_minus:
			node->v.number = -p->v.number;
			break;
			
		case unary_lognot:
			node->v.number = ~p->v.number;
			break;
		}
		node->type = node_type_number;
		free_node(p);			
	}
}

void
code_type_un(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.un.arg);
	mark_locus(node, old_locus);
	switch (node->v.un.opcode) {
	case unary_not:
		code_op(opcode_not);
		break;
			
	case unary_minus:
		code_op(opcode_neg);
		break;

	case unary_lognot:
		code_op(opcode_lognot);
		break;
			
	default:
		abort();
	}
}


/* type result */

void
print_type_result(NODE *node, int level)
{
	NODE *code, *xcode;
	
	code = node->v.ret.code;
	xcode = node->v.ret.xcode;

	print_level(level);
	printf("SET REPLY ");
	print_stat(node->v.ret.stat);
	printf("\n");
	print_level(level);
	printf("CODE:\n");
	if (code)
		print_node(code, level+1);
	print_level(level);
	printf("XCODE:\n");
	if (xcode)
		print_node(xcode, level+1);
	print_level(level);
	printf("MESSAGE:\n");
	if (node->v.ret.message)
		print_node(node->v.ret.message, level+1);
	printf("\n");
}

void
mark_type_result(NODE *node)
{
	mark(node->v.ret.code);
	mark(node->v.ret.xcode);
	mark(node->v.ret.message);
}

void
optimize_type_result(NODE *node)
{
	optimize(node->v.ret.code);
	optimize(node->v.ret.xcode);
	optimize(node->v.ret.message);
}

static void
code_result_arg(NODE *node)
{
	if (node)
		code_node(node);
	else {
		code_op(opcode_push);
		code_immediate(NULL, ptr);
	}
}

static NODE *
result_argptr(NODE *arg)
{
	if (arg && arg->type == node_type_string
	    && arg->v.literal->text[0] == 0)
		arg = NULL;
	return arg;
}

void
code_type_result(NODE *node, struct mu_locus_range const **old_locus)
{
	NODE *code, *xcode;
	
	mark_locus(node, old_locus);
	code = result_argptr(node->v.ret.code);
	xcode = result_argptr(node->v.ret.xcode);

	switch (node->v.ret.stat) {
	case SMFIS_REJECT:
		if (code && code->type == node_type_string
		    && code->v.literal->text[0] != '5')
			parse_error_locus(&node->locus,
     			          _("reject code should be 5xx"));
		if (xcode && xcode->type == node_type_string
		    && xcode->v.literal->text[0] != '5')
			parse_error_locus(&node->locus,
				  _("reject extended code should be 5.x.x"));
		break;

	case SMFIS_TEMPFAIL:
		if (code && code->type == node_type_string
		    && code->v.literal->text[0] != '4')
			parse_error_locus(&node->locus,
					  _("tempfail code should be 4xx"));
		if (xcode && xcode->type == node_type_string
		    && xcode->v.literal->text[0] != '4')
			parse_error_locus(&node->locus,
				  _("tempfail extended code should be 4.x.x"));
		break;
	default:
		break;
	}		

	if (root_node[smtp_state_action]) {
		code_op(opcode_push);
		code_immediate(node->locus.beg.mu_line, long);

		struct literal *lit = literal_lookup(node->locus.beg.mu_file);
		code_op(opcode_push);
		code_immediate(lit->off, ulong);

		code_result_arg(node->v.ret.message);
		code_result_arg(xcode);
		code_result_arg(code);
		code_op(opcode_push);
		code_immediate(node->v.ret.stat, long);
		code_op(opcode_funcall);
		code_immediate(literal_lookup(PROG_ACTION_NAME)->off, size);
		action_hook_pc = code_immediate(action_hook_pc, size);
		*old_locus = NULL;
	}
	
	code_result_arg(node->v.ret.message);
	code_result_arg(xcode);
	code_result_arg(code);
			
	mark_locus(node, old_locus);
	code_op(opcode_result);
	code_immediate(node->v.ret.stat, int);
	code_op(opcode_nil);
}


/* type header */

void
print_type_header(NODE *node, int level)
{
	print_level(level);
	printf("%s %s: \n", msgmod_opcode_str(node->v.hdr.opcode),
	       node->v.hdr.name->text);
	print_node_list(node->v.hdr.value, level+1);
}

void
mark_type_header(NODE *node)
{
	node->v.hdr.name->flags |= SYM_REFERENCED;
	mark(node->v.hdr.value);
}

void
optimize_type_header(NODE *node)
{
	optimize(node->v.hdr.value);
}

void
code_type_header(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	if (node->v.hdr.value)
		code_node(node->v.hdr.value);
	else {
		code_op(opcode_push);
		code_immediate(0, size);
	}
	code_op(opcode_header);
	code_immediate(node->v.hdr.opcode, int);
	code_immediate(node->v.hdr.name->off, size);
}


/*
 * Generate code for passing actual number of arguments when calling a
 * variadic function or function with optional arguments.
 *
 * Arguments:
 *   n    - computed number of arguments
 *   argv - if argument list ends with $@ - a node_type_argv node describing
 *          the $@ context, otherwise NULL.
 *
 * Calling sequence:
 *   The function must be called right after the code for passing arguments
 *   has been generated.  In particular this ensures that code for $@ is
 *   already generated, so that we can assume that the machine register is
 *   initialized with shift count (see code_type_argv).
 *
 * The code for passing the argument count is as follows:
 *
 *  ; Push the count of mandatory and optional arguments
 *  push n
 *  ; The code below is generated only if argv != NULL
 *  ; A. Push the actual number of arguments to the calling function
 *  memstk 0(3)
 *  deref
 *  add
 *  ; B. Push the number of formal arguments to the calling function
 *  push pargs
 *  ; subtract: A - B.  This leaves on stack number of variadic arguments
 *  ; passed to the calling function.
 *  sub
 *  ; Register keeps shift count - N from $@(N)
 *  ; Push it on stack
 *  pushreg
 *  ; Subtract this value from argument count
 *  sub
 */
static void
code_hidden_argcount(long n, NODE *argv)
{
	code_op(opcode_push);
	code_immediate(n, long);
	if (argv) {
		/*
		 * Add number of arguments passed to the calling
		 * function.
		 */
		code_op(opcode_memstk);
		code_immediate(0, size);
		code_immediate(3, long);
		code_op(opcode_deref);
		code_op(opcode_add);
		/*
		 * Minus number of non-variable arguments.
		 */
		if (argv->v.argv.nargs) {
			code_op(opcode_push);
			code_immediate(argv->v.argv.nargs, long);
			code_op(opcode_sub);
		}

		/*
		 * Minus shift count, if any.
		 */
		if (argv->v.argv.node) {
			code_op(opcode_pushreg);
			code_op(opcode_sub);
		}
	}
}

/* type builtin */

void
print_type_builtin(NODE *node, int level)
{
	print_level(level);
	printf("BUILTIN %s\n", node->v.builtin.builtin->name);
	print_node_list_reverse(node->v.builtin.args, level+1);
}

void
mark_type_builtin(NODE *node)
{
	unsigned i;
	NODE *p;
	struct literal *s = literal_lookup(node->v.builtin.builtin->name);

	s->flags |= SYM_REFERENCED;
	for (i = 0, p = node->v.builtin.args; p; i++, p = p->next) 
		mark(p);
}
	
void
optimize_type_builtin(NODE *node)
{
	optimize(node->v.builtin.args);
	if (strcmp(node->v.builtin.builtin->name, "interval") == 0) {
		if (node->v.builtin.args->type == node_type_string) {
			time_t t;
			const char *endp;
				
			if (parse_time_interval(
				      node->v.builtin.args->v.literal->text,
				      &t, &endp)) {
				parse_error_locus(&node->locus,
				      _("unrecognized time format (near `%s')"),
						  endp);
				return;
			}
			/* Replace this node */
			node->type = node_type_number;
			node->v.number = t;
		}
	}
}

/*
 * Produce code for passing argument list P, whose first argument represents
 * a shifted arglist $@(N).  The caller should ensure that this is the case
 * before calling this function.
 *
 * Return value: number of arguments (words) pushed on stack, not
 * counting the ones generated by $@(N) instruction.
 *
 * This function is called by code_type_call and code_type_builtin.
 *
 * Note: The algorithm below is suboptimal, but it seems to be the only way
 * given scarcity of registers.  I need to implement at least one more
 * register in order to do that efficiently.  So, for now:
 */
static size_t
code_args_shifted(NODE *p)
{
	size_t i;
	
	/*
	 * First, process the $@(N) node.  This will, among others, generate
	 * code for leaving shift count in the register.
	 */
	code_node(p);
	i = 0;
	if ((p = p->next) != NULL) {
		/*
		 * Code the rest of arguments.  Eventual function calls
		 * appearing in these will clobber the reg, so push it on
		 * on stack first and keep exchanging with tos after each
		 * subsequent argument is coded, so that shift count always
		 * remains on top.
		 */
		code_op(opcode_pushreg);
		do {
			code_node(p);
			code_op(opcode_xchg);
			i++;
		} while ((p = p->next) != NULL);
		/* Finally, pop it back to the register. */
		code_op(opcode_popreg);
	}
	return i;
}

void
code_type_builtin(NODE *node, struct mu_locus_range const **old_locus)
{
	NODE *p;
	int i;
	const struct builtin *bp = node->v.builtin.builtin;
	struct literal *s;
	NODE *argv = (node->v.builtin.args &&
		      node->v.builtin.args->type == node_type_argv) ?
		      node->v.builtin.args : NULL;
	
	/* Pass arguments */
	if (argv && argv->v.argv.node) {
		/* Shift count given: $@(N).  */
		i = code_args_shifted(argv);
	} else {
		for (p = node->v.builtin.args, i = 0; p; p = p->next, i++) {
			code_node(p);
		}
		if (argv) i--;
	}
		    
	if (bp->optcount || (bp->flags & MFD_BUILTIN_VARIADIC)) {
		/* Pass the number of actual arguments in a hidden arg */
		code_hidden_argcount(i, argv);
	}
	
	mark_locus(node, old_locus);
	code_op(opcode_builtin);
	s = literal_lookup(node->v.builtin.builtin->name);
	code_immediate(s->off, size);
	code_immediate(node->v.builtin.builtin->handler, ptr);/*FIXME*/

	if (node->value_ignored) {
		code_op(opcode_adjust);
		code_immediate(1, ulong);
	}
}


/* type concat */

void
print_type_concat(NODE *node, int level)
{
	print_level(level);
	printf("CONCAT:\n");
	print_node(node->v.concat.arg[0], level+1);
	print_node(node->v.concat.arg[1], level+1);
}

void
mark_type_concat(NODE *node)
{
	mark(node->v.concat.arg[0]);
	mark(node->v.concat.arg[1]);
}

void
optimize_type_concat(NODE *node)
{
	NODE *arg0, *arg1;
	
	optimize(node->v.concat.arg[0]);
	optimize(node->v.concat.arg[1]);
	arg0 = node->v.concat.arg[0];
	arg1 = node->v.concat.arg[1];
	if (arg0->type == node_type_string
	    && arg1->type == node_type_string) {
		string_begin();
		string_add(arg0->v.literal->text,
			   strlen(arg0->v.literal->text));
		string_add(arg1->v.literal->text,
			   strlen(arg1->v.literal->text));
		node->v.literal = string_finish();
		node->type = node_type_string;
		free_node(arg0);
		free_node(arg1);
	} else if (arg0->type == node_type_string
		   && arg0->v.literal->text[0] == 0) {
		copy_node(node, arg1);
		free_node(arg0);
	} else if (arg1->type == node_type_string
		   && arg1->v.literal->text[0] == 0) {
		copy_node(node, arg0);
		free_node(arg1);
	}
}

void
code_type_concat(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.concat.arg[0]);
	code_node(node->v.concat.arg[1]);
	code_op(opcode_concat);
}

/* type variable */

void
print_type_variable(NODE *node, int level)
{
	print_level(level);
	printf("VARIABLE %s %s %lu(%lu)\n",
	       storage_class_str(node->v.var_ref.variable->storage_class),
	       node->v.var_ref.variable->sym.name,
	       (unsigned long) node->v.var_ref.nframes,
	       (unsigned long) node->v.var_ref.variable->off);
}

void
mark_type_variable(NODE *node)
{
	if (node->v.var_ref.variable->storage_class == storage_extern)
		node->v.var_ref.variable->sym.flags |= SYM_REFERENCED;
}

void
code_type_variable(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	code_memref(node);
	code_op(opcode_deref);
}


/* type asgn */

void
print_type_asgn(NODE *node, int level)
{
	print_level(level);
	printf("SET %s %s %lu(%lu)\n",
	       storage_class_str(node->v.asgn.var->storage_class),
	       node->v.asgn.var->sym.name,
	       (unsigned long) node->v.asgn.nframes,
	       (unsigned long) node->v.asgn.var->off);
	print_node(node->v.asgn.node, level + 1);
}

void
mark_type_asgn(NODE *node)
{
	/* FIXME: This is overly conservative.  First of all, `referenced'
	   does not mean `used', so this can create useless assignments,
	   even if the variable in question is not referenced elsewhere: */
	node->v.asgn.var->sym.flags |= SYM_REFERENCED;
        /* Secondly, the node should be marked only if var is referenced
	   or volatile, but determining this would probably require an
	   extra pass. */
	mark(node->v.asgn.node);
}

void
optimize_type_asgn(NODE *node)
{
	optimize(node->v.asgn.node);
}

void
code_type_asgn(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.asgn.node);
	node->v.asgn.var->type = node_type(node->v.asgn.node);
	code_memref(node);
	code_op(opcode_asgn);
}
			

/* type arg */

void
print_type_arg(NODE *node, int level)
{
	print_level(level);
	printf("ARG %u\n", node->v.arg.number);
}

void
code_argref(NODE *node)
{
	code_op(opcode_memstk);
	code_immediate(0, size);
	code_immediate((node->v.arg.number + 2), long);
}

void
code_type_arg(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	code_argref(node);
	code_op(opcode_deref);
}


/* type argx */

void
print_type_argx(NODE *node, int level)
{
	print_level(level);
	printf("ARGX\n");
	print_node(node->v.argx.node, level + 1);
}

void
mark_type_argx(NODE *node)
{
	mark(node->v.argx.node);
}

void
code_argxref(NODE *node)
{
	code_op(opcode_push);
	code_immediate(0, size);
	code_node(node->v.argx.node);

	code_op(opcode_push);
	code_immediate((node->v.argx.nargs + 2), size);
	code_op(opcode_add);

	code_op(opcode_xmemstk);
}

void
code_type_argx(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	code_argxref(node);
	code_op(opcode_deref);
}

void
optimize_type_argx(NODE *node)
{
	NODE *argx = node->v.argx.node;
	int nargs = node->v.argx.nargs;
	optimize(argx);
	if (argx->type == node_type_number) {
		node->type = node_type_arg;
		node->v.arg.number = argx->v.number + nargs;
	}
}

/* type argv */
void
print_type_argv(NODE *node, int level)
{
	print_level(level);
	printf("ARGV\n");
	if (node->v.argv.node)
		print_node(node->v.argv.node, level + 1);
}

void
mark_type_argv(NODE *node)
{
       mark(node->v.argv.node);
}

void
optimize_type_argv(NODE *node)
{
       optimize(node->v.argv.node);
}

void
code_type_argv(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);

	/*
	 * If shift count is given, generate code for it.  Duplicate it
	 * on stack and leave a copy of it in register.  This will later
	 * be used in code generated by code_hidden_argcount.
	 */
	if (node->v.argv.node) {
		code_node(node->v.argv.node);
		code_op(opcode_dup);
		code_op(opcode_dup);
		code_op(opcode_popreg);
	}

	/*
	 * Second argument: number of stack words to push
	 */
	code_op(opcode_memstk);
	code_immediate(0, size);
	code_immediate(3, long);
	code_op(opcode_deref);
	/* Subtract number of formal arguments. */
	if (node->v.argv.nargs) {
		code_op(opcode_push);
		code_immediate(node->v.argv.nargs, long);
		code_op(opcode_sub);
	}
	/*
	 * If shift count is given, tos+1 is the count computed so far,
	 * tos+2 and tos+3 keep the shift count.
	 * Swap two topmost entries and subtract.  Swap the result with
	 * the shift count so it becomes the topmost entry.
	 */
	if (node->v.argv.node) {
		code_op(opcode_xchg);
		code_op(opcode_sub);
		code_op(opcode_xchg);
	}

        /* First argument: parameter offset */
	code_op(opcode_push);
	code_immediate(node->v.argv.nargs + 4, long);

	/*
	 * If shift count is given, add it to the offset.
	 */
	if (node->v.argv.node)
		code_op(opcode_add);

	/*
	 * Finally, push the selected stack entries on stack again.
	 */
	code_op(opcode_dupn);
	code_immediate(0, size);
}

/* type vaptr */
void
print_type_vaptr(NODE *node, int level)
{
	print_level(level);
	printf("VAPTR\n");
	print_node(node->v.node, level + 1);
}

void
mark_type_vaptr(NODE *node)
{
	mark(node->v.node);
}

void
code_type_vaptr(NODE *node, struct mu_locus_range const **old_locus)
{
	NODE *arg = node->v.node;
	mark_locus(node, old_locus);
	switch (arg->type) {
	case node_type_variable:
		code_memref(arg);
		break;
		
	case node_type_arg:
		code_argref(arg);
		break;
		
	case node_type_argx:
		code_argxref(arg);
		break;
		
	default:
		abort();
	}
}

void
optimize_type_vaptr(NODE *node)
{
	optimize(node->v.node);
}



char *
regex_flags_to_string(int flags, char *buf, size_t size)
{
	static struct {
		unsigned flag;
		char *name;
	} regflg[] = {
		{ REG_EXTENDED, REG_EXTENDED_NAME },
		{ REG_ICASE, REG_ICASE_NAME },
		{ REG_NEWLINE, REG_NEWLINE_NAME }
	};
	char *p;
	int i;
	
	p = buf;
	size--;
	for (i = 0; i < NELEMS(regflg) && size > 0; i++) {
		if (regflg[i].flag & flags) {
			size_t len = strlen(regflg[i].name);
			if (p > buf)
				len++;
			if (len > size)
				len = size;
			if (p > buf) {
				*p++ = ',';
				len--;
			}
			if (len > 0) {
				memcpy(p, regflg[i].name, len);
				p += len;
			}
			size -= len;
		}
	}
	*p = 0;
	return buf;
}	

/* type regex */
void
print_type_regex(NODE *node, int level)
{
	char buffer[REGEX_STRING_BUFSIZE];
	print_level(level);
	printf("REGEX (%s) %s\n",
	       regex_flags_to_string(node->v.regex->regflags, buffer,
				     sizeof buffer),
	       node->v.regex->lit->text);
}

void
mark_type_regex(NODE *node)
{
	node->v.regex->lit->flags |= SYM_REFERENCED;
}

void
code_type_regex(NODE *node, struct mu_locus_range const **old_locus)
{
	code_op(opcode_regex);
	code_immediate(node->v.regex->index, size);
}
		

/* type regcomp */
void
print_type_regcomp(NODE *node, int level)
{
	char buffer[REGEX_STRING_BUFSIZE];
	print_level(level);
	printf("REGCOMP %s:\n",
	       regex_flags_to_string(node->v.regcomp_data.flags,
				     buffer, sizeof buffer));
	print_node(node->v.regcomp_data.expr, level+1);
}

void
mark_type_regcomp(NODE *node)
{
	mark(node->v.regcomp_data.expr);
}

static void
save_regexp(NODE *node)
{
	struct sym_regex symreg;
	memset(&symreg, 0, sizeof symreg);
	symreg.regflags = node->v.regcomp_data.flags;
	symreg.index = 0;
	register_regex(&symreg);
	node->v.regcomp_data.regind = symreg.index;
}

void
optimize_type_regcomp(NODE *node)
{
	int flags = node->v.regcomp_data.flags;
	NODE *arg0 = node->v.regcomp_data.expr;
	optimize(arg0);
	if (arg0->type == node_type_string) {
		node->type = node_type_regex;
		node->locus = arg0->locus;
		node->v.regex = install_regex(arg0->v.literal, flags);
	} else {
		save_regexp(node);
	}
}

void
code_type_regcomp(NODE *node, struct mu_locus_range const **old_locus)
{
	if (node->v.regcomp_data.regind == -1)
		save_regexp(node);
	code_node(node->v.regcomp_data.expr);
	code_op(opcode_regcomp);
	code_immediate(node->v.regcomp_data.regind, size);
}


struct trycatch_stack_entry {
	int istry;
	struct mu_locus_range locus;
	unsigned id;
};

static mf_stack_t trycatch_stack;

static unsigned
trycatch_last_id(void)
{
	struct trycatch_stack_entry ent;
	if (!trycatch_stack || mf_stack_peek(trycatch_stack, 0, &ent))
		return 0;
	return ent.id;
}

static void
enter_trycatch(int istry, struct mu_locus_range const *locus)
{
	struct trycatch_stack_entry ent;
	
	if (!trycatch_stack)
		trycatch_stack = mf_stack_create(sizeof(ent), 0);
	ent.istry = istry;
	mu_locus_range_init(&ent.locus);
	mu_locus_range_copy(&ent.locus, locus);
	ent.id = trycatch_last_id() + 1;
	mf_stack_push(trycatch_stack, &ent);
}

static void
leave_trycatch()
{
	struct trycatch_stack_entry ent;
	if (mf_stack_pop(trycatch_stack, &ent) == 0)
		mu_locus_range_deinit(&ent.locus);
	
}

static int
_code_trycatch_exit(void *item, void *data)
{
	struct trycatch_stack_entry *ent = item;
	unsigned id = *(unsigned*)data;

	if (ent->id <= id)
		return 1;
	if (!ent->istry)
		code_op(opcode_retcatch);
	code_op(opcode_restex);
	return 0;
}

static void
code_trycatch_exit(unsigned id)
{	
	if (trycatch_stack)
		mf_stack_enumerate_desc(trycatch_stack, _code_trycatch_exit,
					&id);
}

/* type catch */
void
print_type_catch(NODE *node, int level)
{
	size_t i;
	struct bitmask *bm = &node->v.catch.exmask->bm;
	size_t nmax = bitmask_max(bm);
	
	print_level(level);
	printf("CATCH ");
	if (node->v.catch.exmask->all)
		printf("ALL");
	else
		for (i = 0; i < nmax; i++) 
			if (bitmask_isset(bm, i)) 
				printf("%s ", mf_exception_str(i));
	printf("\n");
	print_node_list(node->v.catch.node, level+1);
	print_level(level);
	printf("END CATCH\n");
}

void
mark_type_catch(NODE *node)
{
	mark(node->v.catch.node);
}

void
optimize_type_catch(NODE *node)
{
	optimize(node->v.catch.node);
}

void
code_type_catch(NODE *node, struct mu_locus_range const **old_locus)
{
	prog_counter_t pos1, endpos;
	prog_counter_t ctr;

	mark_locus(node, old_locus);
	code_op(opcode_catch);
	pos1 = code_immediate(NULL, ptr);
	code_exmask(node->v.catch.exmask);

	ctr = jump_pc;
	jump_pc = 0;

	enter_trycatch(0, &node->locus);
	traverse_tree(node->v.catch.node);
	leave_trycatch();
	jump_fixup(jump_pc, code_get_counter());
	
	jump_pc = ctr;
	
	code_op(opcode_retcatch);
	if (node->v.catch.context == context_function) {
		code_op(opcode_jmp);
		jump_pc = code_immediate(jump_pc, long);
	} else {
		code_result_arg(NULL);
		code_result_arg(NULL);
		code_result_arg(NULL);
		code_op(opcode_result);
		code_immediate(SMFIS_CONTINUE, int);
	}
	
	endpos = code_get_counter ();
	code_put(pos1, (endpos - pos1), long);
	
}


/* type try */
void
print_type_try(NODE *node, int level)
{
	print_level(level);
	printf("TRY:\n");
	print_node_list(node->v.try.node, level+1);
	print_type_catch(node->v.try.catch, level+1);
	print_level(level);
	printf("END TRY\n");
}

void
mark_type_try(NODE *node)
{
	mark(node->v.try.node);
	mark(node->v.try.catch);
}

void
optimize_type_try(NODE *node)
{
	optimize(node->v.try.node);
	optimize(node->v.try.catch);
}

void
code_type_try(NODE *node, struct mu_locus_range const **old_locus)
{
	prog_counter_t pos, ctr, pos1;
	prog_counter_t try_jmp_pc, catch_jmp_pc;
	prog_counter_t try_ret_pc, catch_ret_pc;
	struct catch_node *catch = &node->v.try.catch->v.catch;

	ctr = jump_pc;

	code_op(opcode_saveex);
	code_exmask(catch->exmask);
	
	mark_locus(node->v.try.catch, old_locus);
	code_op(opcode_catch);
	pos1 = code_immediate(NULL, ptr);
	code_exmask(catch->exmask);
	
	/* Compile `catch' part */
	jump_pc = 0;
	enter_trycatch(0, &catch->node->locus);
	traverse_tree(catch->node);
	leave_trycatch();
	
	/* Normal exit from catch */
	code_op(opcode_retcatch);
	code_op(opcode_jmp);
	catch_jmp_pc = code_immediate(NULL, ptr);

	/* Target point for returns from catch block */
	jump_fixup(jump_pc, code_get_counter());
	code_op(opcode_retcatch);

	code_op(opcode_jmp);
	catch_ret_pc = code_immediate(NULL, ptr);

	pos = code_get_counter ();
	code_put(pos1, (pos - pos1), long);
	
	/* Compile `try' part */
	mark_locus(node, old_locus);
	jump_pc = 0;
	enter_trycatch(1, &node->v.try.node->locus);
	traverse_tree(node->v.try.node);
	leave_trycatch();
	try_ret_pc = jump_pc;
	code_op(opcode_jmp);
	try_jmp_pc = code_immediate(NULL, ptr);
	
	/* Restore jump_pc */
	jump_pc = ctr;

	/* Return part */
	/* 1. Fixup return chains */
	pos = code_get_counter();
	jump_fixup(try_ret_pc, pos);
	/* 2. Fixup the jump address for catch returns */
	code_put(catch_ret_pc, (pos - catch_ret_pc - 1), long);
	/* 3. Code return proper */
	/* Restore exceptions */
	code_op(opcode_restex);
	/* Push result back on stack */
	code_op(opcode_pushreg);

	code_op(opcode_jmp);
	jump_pc = code_immediate(jump_pc, long);

	/* Continue part */
	/* 1. Fixup jump addresses */
	pos = code_get_counter();
	code_put(try_jmp_pc, (pos - try_jmp_pc - 1), long);
	code_put(catch_jmp_pc, (pos - catch_jmp_pc - 1), long);
	code_op(opcode_restex);
	/* FIN */
}


/* type throw */
void
print_type_throw(NODE *node, int level)
{
	print_level(level);
	printf("THROW %s\n", mf_exception_str(node->v.throw.code));
	print_node(node->v.throw.expr, level+1);
}

void
mark_type_throw(NODE *node)
{
	mark(node->v.throw.expr);
}

void
optimize_type_throw(NODE *node)
{
	optimize(node->v.throw.expr);
}
		
void
code_type_throw(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.throw.expr);
	mark_locus(node, old_locus);
	code_op(opcode_throw);
	code_immediate(node->v.throw.code, ulong);
}


/* type echo */
void
print_type_echo(NODE *node, int level)
{
	print_level(level);
	printf("ECHO:\n");
	print_node(node->v.node, level+1);
}

void
mark_type_echo(NODE *node)
{
	mark(node->v.node);
}

void
optimize_type_echo(NODE *node)
{
	optimize(node->v.node);
}

void
code_type_echo(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.node);
	mark_locus(node, old_locus);
	code_op(opcode_echo);
}


/* type return */
void
print_type_return(NODE *node, int level)
{
	print_level(level);
	if (node->v.node) {
		printf("RETURN:\n");
		print_node(node->v.node, level+1);
	}
}

void
mark_type_return(NODE *node)
{
	mark(node->v.node);
}

void
optimize_type_return(NODE *node)
{
	optimize(node->v.node);
}

void
code_type_return(NODE *node, struct mu_locus_range const **old_locus)
{
	if (func->rettype == dtype_unspecified) {
		mark_locus(node, old_locus);
		code_op(opcode_jmp);
		jump_pc = code_immediate(jump_pc, long);
	} else {
		code_node(node->v.node);
		mark_locus(node, old_locus);
		code_op(opcode_popreg);
		code_op(opcode_jmp);
		jump_pc = code_immediate(jump_pc, long);
	}
}


/* type call */
void
print_type_call(NODE *node, int level)
{
	print_level(level);
	printf("CALL %s\n", node->v.call.func->sym.name);
	print_node_list_reverse(node->v.call.args, level+1);
}

void
mark_type_call(NODE *node)
{
	NODE *p;
	struct literal *s = literal_lookup(node->v.call.func->sym.name);
	s->flags |= SYM_REFERENCED;
	for (p = node->v.call.args; p; p = p->next) 
		mark(p);
}
	
void
optimize_type_call(NODE *node)
{
	NODE *p;
	
	for (p = node->v.call.args; p; p = p->next) 
		optimize_node(p);
}

void
code_type_call(NODE *node, struct mu_locus_range const **old_locus)
{
	NODE *p;
	struct literal *s;
	struct function *func = node->v.call.func;
	int i;

	if (func->optcount || func->varargs) {
		int j;
		NODE *argv = (node->v.call.args &&
			      node->v.call.args->type == node_type_argv) ?
			      node->v.call.args : NULL;
		
		/* Count actual arguments */
		for (p = node->v.call.args, i = 0; p; p = p->next, i++)
			;

		if (argv) i--;
		
		/* Provide placeholders for the missing ones.  This is
		   necessary in order to make assignments to optional
		   arguments possible. */
		for (j = i; j < func->parmcount; j++) {
			code_op(opcode_push);
			code_immediate(0, ulong);
		}

		/* Pass actual arguments */
		if (argv && argv->v.argv.node) {
			/* Shift count given: $@(N).  */
			code_args_shifted(argv);
		} else {
			/* No shifts involved: everything is straightforward */
			for (p = node->v.call.args; p; p = p->next) {
				code_node(p);
			}
		}

		code_hidden_argcount(i, argv);
	} else {
		/*
		 * Function takes fixed number of parameters: everything is
		 * straightforward as well.
		 */
		for (p = node->v.call.args, i = 0; p; p = p->next, i++) 
			code_node(p);
	}
	
	mark_locus(node, old_locus);
	code_op(opcode_funcall);
	s = literal_lookup(func->sym.name);
	code_immediate(s->off, size);
	if (func->entry == 0) {
		func->refstart = code_immediate(func->refstart, size);
	} else {
		code_immediate(func->entry, size);
	}

	if (func->varargs) {
		code_op(opcode_adjustx);
		code_immediate(func->parmcount, ulong);
	} else {
		code_op(opcode_adjust);
		code_immediate((func->parmcount + (func->optcount ? 1 : 0)), ulong);
	}
	if (func->rettype != dtype_unspecified && !node->value_ignored)
		code_op(opcode_pushreg);

	/* Force emission of LOCUS when coding next instruction. */
	*old_locus = NULL;
}


/* type switch */

void
print_type_switch(NODE *node, int level)
{
	struct case_stmt *pcase;
		
	print_level(level);
	printf("SWITCH: \n");
	print_node(node->v.switch_stmt.node, level+1);
	for (pcase = node->v.switch_stmt.cases;
	     pcase; pcase = pcase->next) {
		
		print_level(level+1);
		if (pcase->valist) {
			struct valist *vp;
			
			printf("CASE ");
			for (vp = pcase->valist; vp; vp = vp->next)
				switch (vp->value.type) {
				case dtype_string:
					printf("\"%s\" ",
					       vp->value.v.literal->text);
					break;
					
				case dtype_number:
					printf("%ld ",
					       vp->value.v.number);
					break;
					
				default:
					abort();
				}
		} else {
			printf("DEFAULT");
		}
		putchar('\n');
		
		print_level(level+1);
		printf("ACTION\n");
		print_node_list(pcase->node, level+2);
	}
}

void
mark_type_switch(NODE *node)
{
	struct case_stmt *cs;
	data_type_t type = node_type(node->v.switch_stmt.node);
	size_t tabsize = 0;

	record_switch(&node->v.switch_stmt);
	mark(node->v.switch_stmt.node);
	for (cs = node->v.switch_stmt.cases; cs; cs = cs->next) {
		struct valist *vp;

		mark(cs->node);
		for (vp = cs->valist; vp; vp = vp->next) {
			tabsize += 2;
			if (vp->value.type != type) {
				char buf[NUMERIC_BUFSIZE_BOUND];
				long v;
				char *p;

				/* FIXME: The following code is very crude */
				switch (type) {
				case dtype_number:
					v = strtol(vp->value.v.literal->text,
						   &p, 0);
					if (*p) {
						parse_error_locus(&cs->locus,
					    _("cannot convert %s to number"),
					    vp->value.v.literal->text);
						return;
					}
					vp->value.v.number = v;
					break;

				case dtype_string:
					snprintf(buf, sizeof buf, "%ld",
						 vp->value.v.number);
					vp->value.v.literal = string_alloc(buf,
								  strlen(buf));
					break;

				default:
					abort();
				}
				vp->value.type = type;
			}
			if (vp->value.type == dtype_string)
				vp->value.v.literal->flags |= SYM_REFERENCED;
		}
	}
	node->v.switch_stmt.tabsize = tabsize;
}
		
void
optimize_type_switch(NODE *node)
{
	struct case_stmt *pcase;
	data_type_t type;
	NODE *p, *bp = NULL;
	
	p = node->v.switch_stmt.node;
	optimize(p);
	type = node_type(p);
		
	for (pcase = node->v.switch_stmt.cases; pcase; pcase = pcase->next) {
		struct valist *vp;
		long v;
		char *s;

		for (vp = pcase->valist; vp; vp = vp->next) {
			if (vp->value.type != type) {
				char buf[NUMERIC_BUFSIZE_BOUND];
				
				/* FIXME: The following code needs
				   generalization */
				switch (type) {
				case dtype_number:
					v = strtol(vp->value.v.literal->text,
						   &s, 0);
					if (*s) {
					       parse_error_locus(&pcase->locus,
					       _("cannot convert %s to number"),
						    vp->value.v.literal->text);
					       return;
					}
					vp->value.v.number = v;
					break;

				case dtype_string:
					snprintf(buf, sizeof buf, "%ld",
						 vp->value.v.number);
					vp->value.v.literal = string_alloc(buf,
								  strlen(buf));
					break;

				default:
					abort();
				}
			}
			vp->value.type = type;
		}
		optimize(pcase->node);
	}

	if (p->type == node_type_number || p->type == node_type_string) {
		for (pcase = node->v.switch_stmt.cases; pcase;
		     pcase = pcase->next) {
			struct valist *vp;

			for (vp = pcase->valist; vp; vp = vp->next) {
				if (p->type == node_type_number ?
				    (vp->value.v.number == p->v.number)
				    /* FIXME: Make sure this works. It is
				       instead of comparing literal string
				       values: */
				     : (vp->value.v.literal == p->v.literal)) {
					bp = pcase->node;
					pcase->node = NULL;
					break;
				}
			}
		}

		/* If no node found, use the default one */
		if (!bp) {
			bp = node->v.switch_stmt.cases->node;
			node->v.switch_stmt.cases->node = NULL;
		}

		if (bp) {
			NODE *tail = node->next;

			*node = *bp;
			free_node(bp);

			for (; node->next; node = node->next)
				;
			node->next = tail;
			/* FIXME : free branches */
		}
	}
}

static void
jump_fixup(prog_counter_t pos, prog_counter_t endpos)
{
	while (pos) {
		prog_counter_t next = (prog_counter_t)mf_cell_c_value(code_peek(pos), size); /*FIXME*/
		code_put(pos, (endpos - pos - 1), long);
		pos = next;
	}
}

/* The following code is generated for switch statements:
   
	popreg      ; Pop the result of the previous instruction into the reg
	xlat        ; Look up in the table and replace reg with the new value
	            ; If the switch selector is of string type, xlats is
		    ; coded instead
	N           ; Number of elements in the xlat table
	OFF         ; Offset of the table in the data segment
	bnz L1      ; If xlat failed, jump to the default case
L0:	jreg        ; Jump to the selected branch 
L1:	
	...         ; Default case
L0+off1:
        ...         ; First branch
	jmp L2
L0+off2:
        ...         ; Second branch
	jmp L2
	.
	.
	.
L0+offN:
        ...         ; Nth branch
L2:	
*/
static void
code_switch_branches(NODE *node, data_type_t type)
{
	prog_counter_t refpos, jmppos;
	struct case_stmt *pcase;
	size_t data_off = node->v.switch_stmt.off;
	
	code_op(opcode_popreg);
	code_op(type == dtype_number ? opcode_xlat : opcode_xlats);
	code_immediate(node->v.switch_stmt.tabsize, size);
	code_immediate(data_off, size);
	code_op(opcode_bnz);
	code_immediate(1, long);
	refpos = code_op(opcode_jreg);
		
	/* Generate code for the branches */
	jmppos = 0;
	for (pcase = node->v.switch_stmt.cases;
	     pcase;
	     pcase = pcase->next) {
		struct valist *vp;
		prog_counter_t pos;
		
		pos = code_get_counter() - refpos;
		traverse_tree(pcase->node);
		if (pcase->next) {
			code_op(opcode_jmp);
			jmppos = code_immediate(jmppos, long);
		}
		for (vp = pcase->valist; vp; vp = vp->next) {
			switch (type) {
			case dtype_number:
				dataseg[data_off] =
					(STKVAL) vp->value.v.number;
				break;
			case dtype_string:
				dataseg[data_off] = 
					(STKVAL) vp->value.v.literal->off;
				break;
			default:
				abort();
			}
			dataseg[data_off + 1] = (STKVAL) pos;
			data_off += 2;
		}
	}

	/* Fix up jump offsets */
	jump_fixup(jmppos, code_get_counter());
}
	
void
code_type_switch(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.switch_stmt.node);
	code_switch_branches(node, node_type(node->v.switch_stmt.node));
}


/* Loop nesting stack */
struct loop_stack {
	struct literal *ident;
	prog_counter_t *endjmp;
	prog_counter_t *nxtjmp;
	unsigned trycatch_id;
};

static mf_stack_t loop_stack;

static int
_find_loop(void *item, void *data)
{
	struct loop_stack *ent = item;
	struct loop_stack *s = data;
	if (ent->ident == s->ident) {
		*s = *ent;
		return 1;
	}
	return 0;
}

static int
find_loop(struct literal *lit, struct loop_stack *pret)
{
	if (!loop_stack)
		return 0;
	else if (!lit)
		return mf_stack_peek(loop_stack, 0, pret) == 0;
	else {
		struct loop_stack ent;
		if (!pret) 
			pret = &ent;
		pret->ident = lit;
		return mf_stack_enumerate_desc(loop_stack, _find_loop, pret);
	}
}

int
within_loop(struct literal *lit)
{
	return find_loop(lit, NULL);
}

void
enter_loop(struct literal *lit, prog_counter_t *endptr, prog_counter_t *nxtptr)
{
	struct loop_stack ent;
	if (!loop_stack)
		loop_stack = mf_stack_create(sizeof(struct loop_stack), 0);
	ent.endjmp = endptr;
	ent.nxtjmp = nxtptr;
	ent.ident = lit;
	ent.trycatch_id = trycatch_last_id();
	mf_stack_push(loop_stack, &ent);
}

void
leave_loop()
{
	mf_stack_pop(loop_stack, NULL);
}


/* type next */
void
print_type_next(NODE *node, int level)
{
	print_level(level);
	printf("NEXT");
	if (node->v.literal)
		printf(" %s", node->v.literal->text);
	printf("\n");
}

void
code_type_next(NODE *node, struct mu_locus_range const **old_locus)
{
	struct loop_stack ent;
	
	mark_locus(node, old_locus);

	if (find_loop(node->v.literal, &ent) == 0) {
		parse_error_locus(&node->locus,
				 _("INTERNAL ERROR at %s:%d: cannot find loop"),
				  __FILE__, __LINE__);
		abort();
	}

	code_trycatch_exit(ent.trycatch_id);
	
	code_op(opcode_jmp);
	*ent.nxtjmp = code_immediate(*ent.nxtjmp, long);
}


/* type break */
void
print_type_break(NODE *node, int level)
{
	print_level(level);
	printf("BREAK");
	if (node->v.literal)
		printf(" %s", node->v.literal->text);
	printf("\n");
}

void
code_type_break(NODE *node, struct mu_locus_range const **old_locus)
{
	struct loop_stack ent;
	
	mark_locus(node, old_locus);

	if (find_loop(node->v.literal, &ent) == 0) {
		parse_error_locus(&node->locus,
				 _("INTERNAL ERROR at %s:%d: cannot find loop"),
				  __FILE__, __LINE__);
		abort();
	}

	code_trycatch_exit(ent.trycatch_id);
	code_op(opcode_jmp);
	*ent.endjmp = code_immediate(*ent.endjmp, long);
}


/* type loop */
void
print_type_loop(NODE *node, int level)
{
	print_level(level);
	printf("LOOP");
	if (node->v.loop.ident)
		printf(" %s", node->v.loop.ident->text);
	printf(":\n");
	if (node->v.loop.for_stmt) {
		print_level(level);
		printf("FOR ");
		print_node_list(node->v.loop.for_stmt, level + 1);
	}
	if (node->v.loop.beg_while) {
		print_level(level);
		printf("WHILE ");
		print_node_list(node->v.loop.beg_while, level + 1);
	}
	if (node->v.loop.end_while) {
		print_level(level);
		printf("END_WHILE ");
		print_node_list(node->v.loop.end_while, level + 1);
	}
	if (node->v.loop.stmt) {
		print_level(level);
		printf("BY ");
		print_node_list(node->v.loop.stmt, level + 1);
	}
	
	print_level(level);
	print_node_list(node->v.loop.body, level + 1);
}

void
mark_type_loop(NODE *node)
{
	mark(node->v.loop.beg_while);
	mark(node->v.loop.stmt);
	mark(node->v.loop.for_stmt);
	mark(node->v.loop.end_while);
	mark(node->v.loop.body);
}

void
optimize_type_loop(NODE *node)
{
	optimize(node->v.loop.beg_while);
	if (node->v.loop.beg_while) {
		if (node->v.loop.beg_while->type == node_type_number) {
			if (node->v.loop.beg_while->v.number)
				node->v.loop.beg_while = NULL;
			else
				node->type = node_type_noop;
			/* FIXME: free subtrees?? */
			return;
		} else if (node->v.loop.beg_while->type == node_type_string) {
			if (node->v.loop.beg_while->v.literal->text[0])
				node->v.loop.beg_while = NULL;
			else
				node->type = node_type_noop;
			return;
		}
	}
			
	optimize(node->v.loop.stmt);
	optimize(node->v.loop.for_stmt);
	optimize(node->v.loop.end_while);
	optimize(node->v.loop.body);
}

void
code_type_loop(NODE *node, struct mu_locus_range const **old_locus)
{
	/* FIXME */
	/*
	    .
	    . <for_stmt>
	    .
	  L_begin:
	     [<beg_while>
	      bz L_end]
              <body>
	     [<end_while>
	      bz L_end]
	     [<stmt>]
	     jmp L_begin
	  L_end:
	*/
	prog_counter_t begin, end, stmt, endjmp = 0, nxtjmp = 0;

	mark_locus(node, old_locus);

	enter_loop(node->v.loop.ident, &endjmp, &nxtjmp);
	
	traverse_tree(node->v.loop.for_stmt);
	begin = code_get_counter();
	if (node->v.loop.beg_while) {
		code_node(node->v.loop.beg_while);
		code_op(opcode_bz);
		endjmp = code_immediate(endjmp, long);
	}

	traverse_tree(node->v.loop.body);

	stmt = code_get_counter();

	if (node->v.loop.end_while) {
		code_node(node->v.loop.end_while);
		code_op(opcode_bz);
		endjmp = code_immediate(endjmp, long);
	}

	traverse_tree(node->v.loop.stmt);
	code_op(opcode_jmp);
	code_immediate((begin - code_get_counter() - 1), long);
	end = code_get_counter();

	jump_fixup(endjmp, end);
	jump_fixup(nxtjmp, stmt);

	leave_loop();
}    


/* type backref */
void
print_type_backref(NODE *node, int level)
{
	print_level(level);
	printf("BACKREF: %ld\n", node->v.number);
}

void
code_type_backref(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	code_op(opcode_backref);
	code_immediate(node->v.number, uint);
}


/* type cast */
void
print_type_cast(NODE *node, int level)
{
	print_level(level);
	printf("CAST %s\n", type_to_string(node->v.cast.data_type));
	print_node_list(node->v.cast.node, level+1);
}
		
void
mark_type_cast(NODE *node)
{
	mark(node->v.cast.node);
}

void
optimize_type_cast(NODE *node)
{
	NODE *p;

	optimize(node->v.cast.node);
	p = node->v.cast.node;
	if (node->v.cast.data_type == node_type(p)) {
		node->type = p->type;
		node->locus = p->locus;
		node->v = p->v;
		free_node(p);
	} else if (node->v.cast.data_type == dtype_string &&
		   p->type == node_type_number) {
		char buf[NUMERIC_BUFSIZE_BOUND];
		snprintf(buf, sizeof buf, "%ld", p->v.number);
		node->v.literal = string_alloc(buf, strlen(buf));
		node->type = node_type_string;
	} else if (node->v.cast.data_type == dtype_number &&
		   p->type == node_type_string) {
		long val;
		char *endp;
		errno = 0;
		val  = strtol(p->v.literal->text, &endp, 0);
		if (errno || *endp) {
			parse_error_locus(&node->locus,
					  _("cannot convert literal to number"));
			return;
		}

		node->type = node_type_number;
		node->locus = p->locus;
		node->v.number = val;
		free_node(p);
	}
}

int
code_cast(struct mu_locus_range const *locus, data_type_t fromtype, data_type_t totype)
{
	if (fromtype == totype) 
		return 0;
	
	switch (fromtype) {
	case dtype_unspecified:
		parse_error_locus(locus,
			    _("expression evaluates to unspecified data type"));
		return 1;

	case dtype_string:
		code_op(opcode_ston);
		break;
		
	case dtype_number:
	case dtype_pointer:
		code_op(opcode_ntos);
		break;

	case dtype_any:
		break;
	}
	return 0;
}

void
code_type_cast(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.cast.node);
	code_cast(&node->v.cast.node->locus,
		  node_type(node->v.cast.node),
		  node->v.cast.data_type);
}


/* type funcdecl */
void
mark_type_funcdecl(NODE *node)
{
	mark(node->v.funcdecl.tree);
}
		
void
optimize_type_funcdecl(NODE *node)
{
	optimize(node->v.funcdecl.tree);
}

void
code_type_funcdecl(NODE *node, struct mu_locus_range const **old_locus)
{
	prog_counter_t pc;

	func = node->v.funcdecl.func;
	/* Assign the entry point early to properly
	   handle recursive functions */
	func->entry = code_get_counter();
			
	codegen(&pc, node->v.funcdecl.tree, func->exmask,
		node->v.funcdecl.auto_count);

	/*
	 * Fix-up unresolved references.
	 */
	pc = func->refstart;
	while (pc) {
		prog_counter_t next = (prog_counter_t)mf_cell_c_value(code_peek(pc), size);
		code_put(pc, func->entry, size);
		pc = next;
	}
	func->refstart = 0;
}
	

/* type progdecl */

void
mark_type_progdecl(NODE *node)
{
	mark(node->v.progdecl.tree.head);
}
		
void
optimize_type_progdecl(NODE *node)
{
	optimize(node->v.progdecl.tree.head);
}

void
code_type_progdecl(NODE *node, struct mu_locus_range const **old_locus)
{
	enum smtp_state tag = node->v.progdecl.tag;
	if (codegen(&entry_point[tag],
		    node->v.progdecl.tree.head, 0,
		    node->v.progdecl.auto_count) == 0)
		milter_enable_state(tag);
}


/* type offset */
void
mark_type_offset(NODE *node)
{
	if (node->v.var_ref.variable->storage_class == storage_extern)
		node->v.var_ref.variable->sym.flags |= SYM_REFERENCED;
}

void
print_type_offset(NODE *node, int level)
{
	print_level(level);
	printf("OFFSET OF VARIABLE %s %s %lu(%lu)\n",
	       storage_class_str(node->v.var_ref.variable->storage_class),
	       node->v.var_ref.variable->sym.name,
	       (unsigned long) node->v.var_ref.nframes,
	       (unsigned long) node->v.var_ref.variable->off);
}

void
optimize_type_offset(NODE *node)
{
	node->type = node_type_number;
	switch (node->v.var_ref.variable->storage_class) {
	case storage_extern:
		node->v.number = node->v.var_ref.variable->off;
		break;

	case storage_auto:
		node->v.number = 0;
		break;

	case storage_param:
		node->v.number = node->v.var_ref.variable->ord;
	}
}

void
code_type_offset(NODE *node, struct mu_locus_range const **old_locus)
{
	mark_locus(node, old_locus);
	switch (node->v.var_ref.variable->storage_class) {
	case storage_extern:
		code_op(opcode_push);
		code_immediate(node->v.var_ref.variable->off, size);
		break;

	case storage_auto:
		code_op(opcode_push);
		code_immediate(0, size);
		break;

	case storage_param:
		code_op(opcode_push);
		code_immediate(node->v.var_ref.variable->ord, size);
	}
}

/* type sedcomp */
void
mark_type_sedcomp(NODE *node)
{
	mark(node->v.sedcomp.expr);
}

void
print_type_sedcomp(NODE *node, int level)
{
	print_level(level);
	printf("SEDCOMP %zu %d\n", node->v.sedcomp.index, node->v.sedcomp.flags);
	print_node(node->v.sedcomp.expr, level + 1);
}

void
optimize_type_sedcomp(NODE *node)
{
	NODE *arg = node->v.sedcomp.expr;
	optimize(arg);
	if (arg->type == node_type_string) {
		transform_t t = transform_compile(arg->v.literal->text,
						  node->v.sedcomp.flags);
		if (t) {
			install_transform(node->v.sedcomp.index, t);
			node->type = node_type_number;
			node->v.number = node->v.sedcomp.index;
			free_node(arg);
		} else
			parse_error_locus(&arg->locus,
					  _("invalid transform string: %s"),
					  transform_error_string());
	}
}

void
code_type_sedcomp(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.sedcomp.expr);
	mark_locus(node, old_locus);
	code_op(opcode_sedcomp);
	code_immediate(node->v.sedcomp.index, size);
	code_immediate(node->v.sedcomp.flags, int);
}

/* type sed */

void
mark_type_sed(NODE *node)
{
	mark(node->v.sed.arg);
	mark(node->v.sed.comp);
}

void
print_type_sed(NODE *node, int level)
{
	print_level(level);
	printf("SED\n");
	print_node(node->v.sed.arg, level + 1);
	print_node(node->v.sed.comp, level + 1);
}

void
optimize_type_sed(NODE *node)
{
	optimize(node->v.sed.arg);
	optimize(node->v.sed.comp);
	if (node->v.sed.comp->type == node_type_number &&
	    node->v.sed.arg->type == node_type_string) {
		transform_t t = get_transform(node->v.sed.comp->v.number);
		char *res = transform_string(t, node->v.sed.arg->v.literal->text);
		free_subtree(node->v.sed.arg);
		free_subtree(node->v.sed.comp);

		node->type = node_type_string;
		string_begin();
		string_add(res, strlen(res));
		node->v.literal = string_finish();
		free(res);
	} else if (node->v.sed.comp->type == node_type_number &&
		   node->v.sed.arg->type == node_type_sed &&
		   node->v.sed.arg->v.sed.comp->type == node_type_number) {
		NODE *arg = node->v.sed.arg;

		transform_join(get_transform(arg->v.sed.comp->v.number),
			       get_transform(node->v.sed.comp->v.number));
		/* FIXME: transform expression at index
		   node->v.sed.comp->v.number remains unused */
		node->v.sed.comp->v.number = arg->v.sed.comp->v.number;
		free_subtree(arg->v.sed.comp);
		node->v.sed.arg = arg->v.sed.arg;
	}
}

void
code_type_sed(NODE *node, struct mu_locus_range const **old_locus)
{
	code_node(node->v.sed.arg);
	code_node(node->v.sed.comp);
	mark_locus(node, old_locus);
	code_op(opcode_sed);
}
