/* codegen.c: Generate internal representation for C-- code. */

#define _POSIX_SOURCE

#include <stdio.h>
#include <string.h>
#include "x.tab.h"
#include "codegen.h"
#include "code_prv.h"
#include "object.h"
#include "memory.h"
#include "opcodes.h"
#include "grammar.h"
#include "util.h"
#include "cmstring.h"
#include "config.h"
#include "token.h"
#include "ident.h"

/* We use MALLOC_DELTA to keep instr_buf thirty-two bytes less than a power of
 * two, assuming an Instr and int is four bytes. */
#define MALLOC_DELTA		8
#define INSTR_BUF_START 	(512 - MALLOC_DELTA)
#define JUMP_TABLE_START	(128 - MALLOC_DELTA)
#define MAX_VARS		128

static void compile_stmt_list(Stmt_list *stmt_list, int loop, int catch_level);
static void compile_stmt(Stmt *stmt, int loop, int catch_level);
static void compile_cases(Case_list *cases, int loop, int catch_level,
			  int end_dest);
static void compile_case_values(Expr_list *values, int body_dest);
static void compile_expr_list(Expr_list *expr_list);
static void compile_expr(Expr *expr);
static int find_local_var(char *id);
static void check_instr_buf(int pos);
static void code(long val);
static void code_str(char *str);
static void code_errors(Id_list *errors);
static int new_jump_dest(void);
static void set_jump_dest_here(int dest);
static int id_list_size(Id_list *id_list);
static Method *final_pass(Object *object);

/* Temporary instruction storage. */
static Instr *instr_buf;
static int instr_loc, instr_size;

/* The jump destination table. */
static int *jump_table, jump_loc, jump_size;

/* For convenience.  Set by generate_method(). */
static Prog *the_prog;

/* Keep track of the number of error lists we'll need. */
static int num_error_lists;

Pile *compiler_pile;			/* Temporary storage pile. */

/* Requires: Shouldn't be called twice.
 * Modifies: compiler_pile, instr_buf, instr_size.
 * Effects: Initializes the compiler pile and instruction buffer. */
void init_codegen()
{
    /* Initialize the compiler pile.  We allocate memory from this pile when
     * we compile, and then free it all at once when we're done. */
    compiler_pile = new_pile();

    /* Initialize instruction buffer. */
    instr_buf = EMALLOC(Instr, INSTR_BUF_START);
    instr_size = INSTR_BUF_START;

    /* Initialize jump table. */
    jump_table = EMALLOC(int, JUMP_TABLE_START);
    jump_size = JUMP_TABLE_START;
}

/* All constructors allocate memory from compiler_pile, so their results are
 * only good until the next pfree() of compiler_pile.  Their effects should
 * all be self-explanatory unless otherwise noted. */
Prog *make_prog(int overridable, Arguments *args, Id_list *vars,
		Stmt_list *stmts)
{
    Prog *cnew = PMALLOC(compiler_pile, Prog, 1);

    cnew->overridable = overridable;
    cnew->args = args;
    cnew->vars = vars;
    cnew->stmts = stmts;
    return cnew;
}

Arguments *arguments(Id_list *ids, char *rest)
{
    Arguments *cnew = PMALLOC(compiler_pile, Arguments, 1);

    cnew->ids = ids;
    cnew->rest = rest;
    return cnew;
}

Stmt *comment_stmt(char *comment)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = COMMENT;
    cnew->lineno = cur_lineno();
    cnew->u.comment = comment;
    return cnew;
}

Stmt *noop_stmt(void)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = NOOP;
    cnew->lineno = cur_lineno();
    return cnew;
}

Stmt *expr_stmt(Expr *expr)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = EXPR;
    cnew->lineno = cur_lineno();
    cnew->u.expr = expr;
    return cnew;
}

Stmt *compound_stmt(Stmt_list *stmt_list)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = COMPOUND;
    cnew->lineno = cur_lineno();
    cnew->u.stmt_list = stmt_list;
    return cnew;
}

Stmt *assign_stmt(char *var, Expr *value)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = ASSIGN;
    cnew->lineno = cur_lineno();
    cnew->u.assign.var = var;
    cnew->u.assign.value = value;
    return cnew;
}

Stmt *if_stmt(Expr *cond, Stmt *body)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = IF;
    cnew->lineno = cur_lineno();
    cnew->u.if_.cond = cond;
    cnew->u.if_.true = body;
    return cnew;
}

/* Modifies: if_, to produce the if-else statement. */
Stmt *if_else_stmt(Stmt *if_, Stmt *false)
{
    if_->type = IF_ELSE;
    if_->u.if_.false = false;
    return if_;
}

Stmt *for_range_stmt(char *id, Expr *lower, Expr *upper, Stmt *body)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = FOR_RANGE;
    cnew->lineno = cur_lineno();
    cnew->u.for_range.var = id;
    cnew->u.for_range.lower = lower;
    cnew->u.for_range.upper = upper;
    cnew->u.for_range.body = body;
    return cnew;
}

Stmt *for_list_stmt(char *id, Expr *list, Stmt *body)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = FOR_LIST;
    cnew->lineno = cur_lineno();
    cnew->u.for_list.var = id;
    cnew->u.for_list.list = list;
    cnew->u.for_list.body = body;
    return cnew;
}

Stmt *while_stmt(Expr *cond, Stmt *body)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = WHILE;
    cnew->lineno = cur_lineno();
    cnew->u.while_.cond = cond;
    cnew->u.while_.body = body;
    return cnew;
}

Stmt *switch_stmt(Expr *expr, Case_list *cases)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = SWITCH;
    cnew->lineno = cur_lineno();
    cnew->u.switch_.expr = expr;
    cnew->u.switch_.cases = cases;
    return cnew;
}

Stmt *break_stmt(void)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = BREAK;
    cnew->lineno = cur_lineno();
    return cnew;
}

Stmt *continue_stmt(void)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = CONTINUE;
    cnew->lineno = cur_lineno();
    return cnew;
}

Stmt *return_stmt(void)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = RETURN;
    cnew->lineno = cur_lineno();
    return cnew;
}

Stmt *return_expr_stmt(Expr *expr)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = RETURN_EXPR;
    cnew->lineno = cur_lineno();
    cnew->u.expr = expr;
    return cnew;
}

Stmt *catch_stmt(Id_list *errors, Stmt *body, Stmt *handler)
{
    Stmt *cnew = PMALLOC(compiler_pile, Stmt, 1);

    cnew->type = CATCH;
    cnew->lineno = cur_lineno();
    cnew->u.ccatch.errors = errors;
    cnew->u.ccatch.body = body;
    cnew->u.ccatch.handler = handler;
    return cnew;
}

Expr *integer_expr(long num)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = INTEGER;
    cnew->lineno = cur_lineno();
    cnew->u.num = num;
    return cnew;
}

Expr *string_expr(char *s)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = STRING;
    cnew->lineno = cur_lineno();
    cnew->u.str = s;
    return cnew;
}

Expr *dbref_expr(long dbref)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = DBREF;
    cnew->lineno = cur_lineno();
    cnew->u.dbref = dbref;
    return cnew;
}

Expr *symbol_expr(char *symbol)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = SYMBOL;
    cnew->lineno = cur_lineno();
    cnew->u.symbol = symbol;
    return cnew;
}

Expr *error_expr(char *error)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = ERROR;
    cnew->lineno = cur_lineno();
    cnew->u.error = error;
    return cnew;
}

Expr *name_expr(char *name)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = NAME;
    cnew->lineno = cur_lineno();
    cnew->u.name = name;
    return cnew;
}

Expr *var_expr(char *name)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = VAR;
    cnew->lineno = cur_lineno();
    cnew->u.name = name;
    return cnew;
}

Expr *function_call_expr(char *name, Expr_list *args)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = FUNCTION_CALL;
    cnew->lineno = cur_lineno();
    cnew->u.function.name = name;
    cnew->u.function.args = args;
    return cnew;
}

Expr *pass_expr(Expr_list *args)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = PASS;
    cnew->lineno = cur_lineno();
    cnew->u.args = args;
    return cnew;
}

Expr *message_expr(Expr *to, char *message, Expr_list *args)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = MESSAGE;
    cnew->lineno = cur_lineno();
    cnew->u.message.to = (to) ? to : function_call_expr("this", NULL);
    cnew->u.message.name = message;
    cnew->u.message.args = args;
    return cnew;
}

Expr *expr_message_expr(Expr *to, Expr *message, Expr_list *args)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = EXPR_MESSAGE;
    cnew->lineno = cur_lineno();
    cnew->u.expr_message.to = (to) ? to : function_call_expr("this", NULL);
    cnew->u.expr_message.message = message;
    cnew->u.expr_message.args = args;
    return cnew;
}

Expr *list_expr(Expr_list *args)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = LIST;
    cnew->lineno = cur_lineno();
    cnew->u.args = args;
    return cnew;
}

Expr *dict_expr(Expr_list *args)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = DICT;
    cnew->lineno = cur_lineno();
    cnew->u.args = args;
    return cnew;
}

Expr *buffer_expr(Expr_list *args)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = BUFFER;
    cnew->lineno = cur_lineno();
    cnew->u.args = args;
    return cnew;
}

Expr *frob_expr(Expr *cclass, Expr *rep)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = FROB;
    cnew->lineno = cur_lineno();
    cnew->u.frob.cclass = cclass;
    cnew->u.frob.rep = rep;
    return cnew;
}

Expr *index_expr(Expr *list, Expr *offset)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = INDEX;
    cnew->lineno = cur_lineno();
    cnew->u.index.list = list;
    cnew->u.index.offset = offset;
    return cnew;
}

Expr *unary_expr(int opcode, Expr *expr)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = UNARY;
    cnew->lineno = cur_lineno();
    cnew->u.unary.opcode = opcode;
    cnew->u.unary.expr = expr;
    return cnew;
}

Expr *binary_expr(int opcode, Expr *left, Expr *right)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = BINARY;
    cnew->lineno = cur_lineno();
    cnew->u.binary.opcode = opcode;
    cnew->u.binary.left = left;
    cnew->u.binary.right = right;
    return cnew;
}

Expr *and_expr(Expr *left, Expr *right)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = AND;
    cnew->lineno = cur_lineno();
    cnew->u.and.left = left;
    cnew->u.and.right = right;
    return cnew;
}

Expr *or_expr(Expr *left, Expr *right)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = OR;
    cnew->lineno = cur_lineno();
    cnew->u.or.left = left;
    cnew->u.or.right = right;
    return cnew;
}

Expr *cond_expr(Expr *cond, Expr *true, Expr *false)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = CONDITIONAL;
    cnew->lineno = cur_lineno();
    cnew->u.cond.cond = cond;
    cnew->u.cond.true = true;
    cnew->u.cond.false = false;
    return cnew;
}

Expr *critical_expr(Expr *expr)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = CRITICAL;
    cnew->lineno = cur_lineno();
    cnew->u.expr = expr;
    return cnew;
}

Expr *propagate_expr(Expr *expr)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = PROPAGATE;
    cnew->lineno = cur_lineno();
    cnew->u.expr = expr;
    return cnew;
}

Expr *splice_expr(Expr *expr)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = SPLICE;
    cnew->lineno = cur_lineno();
    cnew->u.expr = expr;
    return cnew;
}

Expr *range_expr(Expr *lower, Expr *upper)
{
    Expr *cnew = PMALLOC(compiler_pile, Expr, 1);

    cnew->type = RANGE;
    cnew->lineno = cur_lineno();
    cnew->u.range.lower = lower;
    cnew->u.range.upper = upper;
    return cnew;
}

Case_entry *case_entry(Expr_list *values, Stmt_list *stmts)
{
    Case_entry *cnew = PMALLOC(compiler_pile, Case_entry, 1);

    cnew->lineno = cur_lineno();
    cnew->values = values;
    cnew->stmts = stmts;
    return cnew;
}

Id_list *id_list(char *ident, Id_list *next)
{
    Id_list *cnew = PMALLOC(compiler_pile, Id_list, 1);

    cnew->lineno = cur_lineno();
    cnew->ident = ident;
    cnew->next = next;
    return cnew;
}

Stmt_list *stmt_list(Stmt *stmt, Stmt_list *next)
{
    Stmt_list *cnew = PMALLOC(compiler_pile, Stmt_list, 1);

    cnew->stmt = stmt;
    cnew->next = next;
    return cnew;
}

Expr_list *expr_list(Expr *expr, Expr_list *next)
{
    Expr_list *cnew = PMALLOC(compiler_pile, Expr_list, 1);

    cnew->expr = expr;
    cnew->next = next;
    return cnew;
}

Case_list *case_list(Case_entry *case_entry, Case_list *next)
{
    Case_list *cnew = PMALLOC(compiler_pile, Case_list, 1);

    cnew->case_entry = case_entry;
    cnew->next = next;
    return cnew;
}

/* Requires: prog is a program generated by the above constructors, and object
 *	     is the object the method will be defined on.
 * Modifies: May add entries to the strings table on object using
 *	     object_add_string().  May call compiler_error(), modifying the
 *	     error list.  Uses the instruction buffer.
 * Effects: Returns a method suitable for adding to an object with
 *	    object_add_method(), or NULL if there were errors. */
Method *generate_method(Prog *prog, Object *object)
{
    /* Reset the error list counter to 0. */
    num_error_lists = 0;

    /* Compile the code into instr_buf. */
    instr_loc = 0;
    jump_loc = 0;
    the_prog = prog;
    compile_stmt_list(prog->stmts, -1, 0);

    /* If we have no errors, call final_pass() to make a method. */
    if (no_errors()) {
	code(RETURN);
	return final_pass(object);
    }

    return NULL;
}

/* Requires: Same as compile_stmt() below.
 * Modifies: Uses the instruction buffer and may call compiler_error().
 * Effects: Compiles the statements in stmt_list, in reverse order. */
static void compile_stmt_list(Stmt_list *stmt_list, int loop, int catch_level)
{
    if (stmt_list) {
	compile_stmt_list(stmt_list->next, loop, catch_level);
	compile_stmt(stmt_list->stmt, loop, catch_level);
    }
}

/* Requires:	loop is a destination number for the current loop, or -1 if
 *			we're not in a loop.
 *		catch_level is the number of catch statements we're inside in
 *			the current loop, if we're in a loop.
 * Modifies:	Uses the instruction buffer and may call compiler_error().
 * Effects:	compiles stmt into instr_buf. */
static void compile_stmt(Stmt *stmt, int loop, int catch_level)
{
    switch (stmt->type) {

      case COMMENT:

	/* Add a comment instruction and argument.  The interpreter will ignore
	 * this, but the decompiler will use it. */
	code(COMMENT);
	code_str(stmt->u.comment);

	break;

      case NOOP:

	/* Do nothing.  This is a dummy statement caused by a ';' character
	 * with no preceding expression.  Since the interpreter doesn't count
	 * statements, there's no need for a dummy instruction. */
	break;

      case EXPR:

	/* Compile the expression and code a POP opcode to discard its
	 * value. */
	compile_expr(stmt->u.expr);
	code(POP);

	break;

      case COMPOUND:

	/* Compile each statement in the statement list. */
	compile_stmt_list(stmt->u.stmt_list, loop, catch_level);

	break;

      case ASSIGN: {
	  Expr *value = stmt->u.assign.value;
	  int n;

	  /* Compile the expression we're assigning or adding. */
	  compile_expr(value);

	  n = find_local_var(stmt->u.assign.var);
	  if (n != -1) {
	      /* This is a local variable.  Code a SET_LOCAL opcode with a
	       * variable number argument. */
	      code(SET_LOCAL);
	      code(n);
	  } else {
	      /* This is an object variable.  Code a SET_OBJ_VAR opcode with
	       * an identifier argument. */
	      code(SET_OBJ_VAR);
	      code_str(stmt->u.assign.var);
	  }
	  break;
      }

     case IF: {
	  int end_dest = new_jump_dest();

	  /* Compile the condition expression. */
	  compile_expr(stmt->u.if_.cond);

	  /* Code an IF opcode with a jump argument pointing to the end of the
	   * true statmeent. */
	  code(IF);
	  code(end_dest);

	  /* Compile the true statement and set end_dest to the end of the
	   * false statement. */
	  compile_stmt(stmt->u.if_.true, loop, catch_level);
	  set_jump_dest_here(end_dest);
	  break;
      }

      case IF_ELSE: {
	  int false_stmt_dest = new_jump_dest(), end_dest = new_jump_dest();

	  /* Compile the condition expression. */
	  compile_expr(stmt->u.if_.cond);

	  /* Code an IF_ELSE opcode with a jump argument pointing to the false
	   * statement. */
	  code(IF_ELSE);
	  code(false_stmt_dest);

	  /* Compile the true statement. */
	  compile_stmt(stmt->u.if_.true, loop, catch_level);

	  /* Code an ELSE opcode with a jump argument pointing to the end of
	   * the false statement. */
	  code(ELSE);
	  code(end_dest);

	  /* Set false_stmt_dest to here, and compile the false statement. */
	  set_jump_dest_here(false_stmt_dest);
	  compile_stmt(stmt->u.if_.false, loop, catch_level);

	  /* Set end_dest to the end of the false statement. */
	  set_jump_dest_here(end_dest);

	  break;
      }

      case FOR_RANGE: {
	  int n, begin_dest = new_jump_dest(), end_dest = new_jump_dest();

	  /* Find the variable in the method's local variables. */
	  n = find_local_var(stmt->u.for_range.var);
	  if (n == -1) {
	      compiler_error(stmt->lineno, "%s is not a local variable",
			     stmt->u.for_range.var);
	      break;
	  }

	  /* Compile the lower and upper bounds of the range. */
	  compile_expr(stmt->u.for_range.lower);
	  compile_expr(stmt->u.for_range.upper);

	  /* Set begin_dest to here, and begin the loop with a FOR_RANGE
	   * opcode, with a jump argument pointing to the end of the loop. */
	  set_jump_dest_here(begin_dest);
	  code(FOR_RANGE);
	  code(end_dest);
	  code(n);

	  /* Compile the loop body. */
	  compile_stmt(stmt->u.for_range.body, begin_dest, 0);

	  /* Code an END opcode with a jump argument pointing to the beginning
	   * of the loop, and set end_dest. */
	  code(END);
	  code(begin_dest);
	  set_jump_dest_here(end_dest);

	  break;
      }

      case FOR_LIST: {
	  int n, begin_dest = new_jump_dest(), end_dest = new_jump_dest();

	  /* Find the variable in the method's local variables. */
	  n = find_local_var(stmt->u.for_list.var);
	  if (n == -1) {
	      compiler_error(stmt->lineno, "%s is not a local variable.",
			     stmt->u.for_list.var);
	      break;
	  }

	  /* Compile the list expression, and code a ZERO opcode to push a zero
	   * value onto the stack.  This will serve as the loop index. */
	  compile_expr(stmt->u.for_list.list);
	  code(ZERO);

	  /* Set begin_dest to here, and begin the loop with a FOR_LIST opcode,
	   * with a jump argument pointing to the end of the loop. */
	  set_jump_dest_here(begin_dest);
	  code(FOR_LIST);
	  code(end_dest);
	  code(n);

	  /* Compile the loop body. */
	  compile_stmt(stmt->u.for_list.body, begin_dest, 0);

	  /* Code an END opcode with a jump argument pointing to the beginning
	   * of the loop, and set end_dest. */
	  code(END);
	  code(begin_dest);
	  set_jump_dest_here(end_dest);

	  break;
      }

      case WHILE: {
	  int cond_dest = new_jump_dest(), begin_dest = new_jump_dest();
	  int end_dest = new_jump_dest();

	  /* Set begin_dest to here, and compile the loop condition. */
	  set_jump_dest_here(cond_dest);
	  compile_expr(stmt->u.while_.cond);

	  /* Code a WHILE opcode with jump arguments pointing to the end of
	   * the loop and the beginning of the condition expression. */
	  set_jump_dest_here(begin_dest);
	  code(WHILE);
	  code(end_dest);
	  code(cond_dest);

	  /* Compile the loop body. */
	  compile_stmt(stmt->u.while_.body, begin_dest, 0);

	  /* Code an END opcode with a jump argument pointing to the beginning
	   * of the condition, and set end_dest. */
	  code(END);
	  code(cond_dest);
	  set_jump_dest_here(end_dest);
	  break;
      }

      case SWITCH: {
	  int end_dest = new_jump_dest();
	  Case_list *cases;
	  Stmt_list *default_case_stmts = NULL;

	  /* Compile the switch expression. */
	  compile_expr(stmt->u.switch_.expr);

	  /* Set switch_dest to here, and code a SWITCH opcode with a jump
	   * argument pointing to the end of the switch statement.  The
	   * interpreter won't actually do anything with this instruction. */
	  code(SWITCH);
	  code(end_dest);

	  /* Pull out the default entry if there is one. */
	  cases = stmt->u.switch_.cases;
	  if (cases && !cases->case_entry->values) {
	      default_case_stmts = cases->case_entry->stmts;
	      cases = cases->next;
	  }

	  /* Compile the cases. */
	  compile_cases(cases, loop, catch_level, end_dest);

	  /* Code the final default case and set end_dest to the end of the
	   * switch statement.  We don't need a break opcode here under the
	   * current design of the switch statement. */
	  code(DEFAULT);
	  if (default_case_stmts)
	      compile_stmt_list(default_case_stmts, loop, catch_level);
	  set_jump_dest_here(end_dest);

	  break;
      }

      case BREAK:

	if (loop == -1) {
	    compiler_error(stmt->lineno, "break statement outside loop.");
	    break;
	}

	/* Code a BREAK opcode, with a jump argument pointing to the loop
	 * instruction, and a cont of the number of catch statements we need to
	 * break out of.  The instruction after the loop instruction is always
	 * the end of the loop, and the interpreter can use the loop
	 * instruction to see if it needs to pop anything off the stack. */
	code(BREAK);
	code(loop);
	code(catch_level);
	break;

      case CONTINUE:

	if (loop == -1) {
	    compiler_error(stmt->lineno, "continue statement outside loop.");
	    break;
	}

	/* Code a CONTINUE opcode to bring us to the end of the loop.  The
	 * first argument to the opcode is the catch level, the number of catch
	 * statements we are breaking out of.  The second argument is the
	 * location of the loop opcode for the current loop.  The interpreter
	 * can use that to find the location of the end of the loop. */
	code(CONTINUE);
	code(loop);
	code(catch_level);
	break;

      case RETURN:

	/* Code a RETURN opcode. */
	code(RETURN);

	break;

      case RETURN_EXPR:

	/* Compile the expression to return and code a RETURN_EXPR opcode. */
	compile_expr(stmt->u.expr);
	code(RETURN_EXPR);

	break;

      case CATCH: {
	  int handler_dest = new_jump_dest(), end_dest = new_jump_dest();

	  /* Increment the number of error lists we'll need, if there was an
	   * error list.  (No list is needed for a 'catch any'.) */
	  if (stmt->u.ccatch.errors)
	      num_error_lists++;

	  /* Code a CATCH opcode, with an error list argument, and a jump
	   * argument pointing to the handler. */
	  code(CATCH);
	  code(handler_dest);
	  code_errors(stmt->u.ccatch.errors);

	  /* Compile the body, incrementing catch_level. */
	  compile_stmt(stmt->u.ccatch.body, loop, catch_level + 1);

	  /* Code a CATCH_END opcode to end the catch statement, with a jump
	   * argument pointing to after the handler. */
	  code(CATCH_END);
	  code(end_dest);

	  /* Set handler_dest to here and compile the handler, if there is
	   * one. */
	  set_jump_dest_here(handler_dest);
	  if (stmt->u.ccatch.handler)
	      compile_stmt(stmt->u.ccatch.handler, loop, catch_level);

	  /* Code a HANDLER_END to signify that the handler is done. */
	  code(HANDLER_END);

	  /* Set end_dest to here, so the CATCH_END opcode can find it. */
	  set_jump_dest_here(end_dest);

	  break;
      }

    }
}

static void compile_cases(Case_list *cases, int loop, int catch_level,
			  int end_dest)
{
    int body_dest, next_dest;
    Case_entry *entry;
    Expr *expr;

    if (!cases)
	return;

    entry = cases->case_entry;
    if (!entry->values) {
	compiler_error(entry->lineno, "Default case not last in list.");
	return;
    }

    /* Compile previous cases. */
    compile_cases(cases->next, loop, catch_level, end_dest);

    /* It's a list of values.  Make a destination for the body and next
     * case. */
    body_dest = new_jump_dest();
    next_dest = new_jump_dest();

    /* Compile the list of values except for the last one. */
    compile_case_values(entry->values->next, body_dest);

    /* Compile the last value.  Code a LAST_CASE_RANGE or
     * LAST_CASE_VALUE instead of a CASE_RANGE or CASE_VALUE, and use
     * next_dest instead of body_dest for the jump argument. */
    expr = entry->values->expr;
    compile_expr(expr);
    code((expr->type == RANGE) ? LAST_CASE_RANGE : LAST_CASE_VALUE);
    code(next_dest);

    /* Set the body destination to here and compile the body. */
    set_jump_dest_here(body_dest);
    compile_stmt_list(entry->stmts, loop, catch_level);

    /* Code an END_SWITCH opcode with a jump argument pointing to the end of
     * the switch statement. */
    code(END_CASE);
    code(end_dest);

    /* Set next_dest here, which is where the next case will be
     * compiled. */
    set_jump_dest_here(next_dest);
}

static void compile_case_values(Expr_list *values, int body_dest)
{
    if (values) {
	compile_case_values(values->next, body_dest);
	compile_expr(values->expr);
	code((values->expr->type == RANGE) ? CASE_RANGE : CASE_VALUE);
	code(body_dest);
    }
}

/* Modifies: Uses the instruction buffer and may call compiler_error().
 * Effects: Compiles the expressions in expr_list into instr_buf in reverse
 *	    order. */
static void compile_expr_list(Expr_list *expr_list)
{
    if (expr_list) {
	compile_expr_list(expr_list->next);
	compile_expr(expr_list->expr);
    }
}

/* Modifies: Uses the instruction buffer and may call compiler_error().
 * Effects: Compiles expr into instr_buf. */
static void compile_expr(Expr *expr)
{
    switch(expr->type) {

      case INTEGER:

	/* Special-case zero and one to save space. */
	if (expr->u.num == 0) {
	    code(ZERO);
	} else if (expr->u.num == 1) {
	    code(ONE);
	} else {
	    code(INTEGER);
	    code(expr->u.num);
	}

	break;
	
      case STRING:

	code(STRING);
	code_str(expr->u.str);

	break;

      case DBREF:

	code(DBREF);
	code(expr->u.dbref);

	break;

      case SYMBOL:

	code(SYMBOL);
	code_str(expr->u.symbol);

	break;

      case ERROR:

	code(ERROR);
	code_str(expr->u.error);

	break;

      case NAME:

	code(NAME);
	code_str(expr->u.name);

	break;

      case VAR: {
	  int n;

	  /* Determine whether the variable is a local or global variable, and
	   * code the appropriate retrieval opcode. */
	  n = find_local_var(expr->u.name);
	  if (n != -1) {
	      code(GET_LOCAL);
	      code(n);
	  } else {
	      code(GET_OBJ_VAR);
	      code_str(expr->u.name);
	  }

	  break;
      }

      case FUNCTION_CALL: {
	  int n;

	  code(START_ARGS);
	  compile_expr_list(expr->u.function.args);
	  n = find_function(expr->u.function.name);
	  if (n != -1) {
	      code(n);
	  } else {
	      compiler_error(expr->lineno, "Unknown function %s.",
			     expr->u.function.name);
	  }

	  break;
      }

      case PASS:

	code(START_ARGS);
	compile_expr_list(expr->u.args);
	code(PASS);

	break;

      case MESSAGE:

	compile_expr(expr->u.message.to);
	code(START_ARGS);
	compile_expr_list(expr->u.message.args);
	code(MESSAGE);
	code_str(expr->u.message.name);

	break;

      case EXPR_MESSAGE:

	compile_expr(expr->u.expr_message.to);
	compile_expr(expr->u.expr_message.message);
	code(START_ARGS);
	compile_expr_list(expr->u.expr_message.args);
	code(EXPR_MESSAGE);

	break;

      case LIST: {
	  Expr_list **elistp = &expr->u.args, *elist;

	  /* [@foo, ...] --> foo [...] SPLICE_ADD */
	  while (*elistp && (*elistp)->next)
	      elistp = &(*elistp)->next;
	  if (*elistp && (*elistp)->expr->type == SPLICE) {
	      /* Compile the spliced expression. */
	      compile_expr((*elistp)->expr->u.expr);

	      /* Make a list out of the remaining expressions. */
	      code(START_ARGS);
	      elist = *elistp;
	      *elistp = NULL;
	      compile_expr_list(expr->u.args);
	      *elistp = elist;
	      code(LIST);

	      /* Add them together with SPLICE_ADD. */
	      code(SPLICE_ADD);
	      break;
	  }

	  /* The general case; just code up the arguments and make a list. */
	  code(START_ARGS);
	  compile_expr_list(expr->u.args);
	  code(LIST);
	  break;
      }

      case DICT:

	code(START_ARGS);
	compile_expr_list(expr->u.args);
	code(DICT);

	break;

      case BUFFER:

	code(START_ARGS);
	compile_expr_list(expr->u.args);
	code(BUFFER);

	break;

      case FROB:

	compile_expr(expr->u.frob.cclass);
	compile_expr(expr->u.frob.rep);
	code(FROB);

	break;

      case INDEX:

	compile_expr(expr->u.index.list);
	compile_expr(expr->u.index.offset);
	code(INDEX);

	break;

      case UNARY:

	compile_expr(expr->u.unary.expr);
	code(expr->u.unary.opcode);

	break;

      case BINARY:

	compile_expr(expr->u.binary.left);
	compile_expr(expr->u.binary.right);
	code(expr->u.binary.opcode);

	break;

      case AND: {
	  int end_dest = new_jump_dest();

	  /* Compile ther left-hand expression. */
	  compile_expr(expr->u.and.left);

	  /* Code an AND opcode with a jump argument pointing to the end of the
	   * expression. */
	  code(AND);
	  code(end_dest);

	  /* Compile the right-hand expression and set end_dest. */
	  compile_expr(expr->u.and.right);
	  set_jump_dest_here(end_dest);

	  break;
      }

      case OR: {
	  int end_dest = new_jump_dest();

	  /* Compile the left-hand expression. */
	  compile_expr(expr->u.and.left);

	  /* Code an OR opcode with a jump argument pointing to the end of the
	   * expression. */
	  code(OR);
	  code(end_dest);

	  /* Compile the right-hand expression and set end_dest. */
	  compile_expr(expr->u.and.right);
	  set_jump_dest_here(end_dest);

	  break;
      }

      case CONDITIONAL: {
	  int false_dest = new_jump_dest(), end_dest = new_jump_dest();

	  /* Compile the condition expression. */
	  compile_expr(expr->u.cond.cond);

	  /* Code a CONDITIONAL opcode with a jump argument pointing to the
	   * false expression. */
	  code(CONDITIONAL);
	  code(false_dest);

	  /* Compile the true expression. */
	  compile_expr(expr->u.cond.true);

	  /* Code an ELSE opcode with a jump argument pointing to the end of
	   * the expression. */
	  code(ELSE);
	  code(end_dest);

	  /* Set false_dest to here and compile the false expression. */
	  set_jump_dest_here(false_dest);
	  compile_expr(expr->u.cond.false);

	  /* Set end_dest to here. */
	  set_jump_dest_here(end_dest);

	  break;
      }

      case CRITICAL: {
	  int end_dest = new_jump_dest();

	  /* Code a CRITICAL opcode with a jump argument pointing to the end of
	   * the critical expression. */
	  code(CRITICAL);
	  code(end_dest);

	  /* Compile the critical expression. */
	  compile_expr(expr->u.expr);

	  /* Code a CRITICAL_END opcode, and set end_dest. */
	  code(CRITICAL_END);
	  set_jump_dest_here(end_dest);

	  break;
      }

      case PROPAGATE: {
	  int end_dest = new_jump_dest();

	  /* Code a PROPAGATE opcode with a jump argument pointing to the end
	   * of the critical expression. */
	  code(PROPAGATE);
	  code(end_dest);

	  /* Compile the critical expression. */
	  compile_expr(expr->u.expr);

	  /* Code a PROPAGATE_END opcode, and set end_dest. */
	  code(PROPAGATE_END);
	  set_jump_dest_here(end_dest);

	  break;
      }

      case SPLICE:

	compile_expr(expr->u.expr);
	code(SPLICE);

	break;

      case RANGE:

	compile_expr(expr->u.range.lower);
	compile_expr(expr->u.range.upper);

	break;
    }
}

/* Effects: Returns the number of id as a local variable, or -1 if it doesn't
 *	    match any of the local variable names. */
static int find_local_var(char *id)
{
    int count = 0;
    Id_list *id_list;

    /* Check arguments. */
    for (id_list = the_prog->args->ids; id_list; id_list = id_list->next) {
	count++;
	if (strcmp(id_list->ident, id) == 0)
	    return id_list_size(the_prog->args->ids) - count;
    }

    /* Check variable argument container, if it exists. */
    if (the_prog->args->rest) {
	if (strcmp(the_prog->args->rest, id) == 0)
	    return count;
	count++;
    }

    /* Check local variables. */
    for (id_list = the_prog->vars; id_list; id_list = id_list->next) {
	if (strcmp(id_list->ident, id) == 0)
	    return count;
	count++;
    }

    /* No match. */
    return -1;
}

/* Requires: Works inefficiently if pos is much larger than instr_size.
 * Modifies: instr_buf, instr_size.
 * Effects: Roughly doubles the size of the instruction buffer until it can
 *	    hold an instruction at the location specified by pos. */
static void check_instr_buf(int pos)
{
    while (pos >= instr_size) {
	instr_size = instr_size * 2 + MALLOC_DELTA;
	instr_buf = EREALLOC(instr_buf, Instr, instr_size);
    }
}

/* Modifies: instr_buf, instr_loc, maybe instr_size.
 * Effects: Appends the long integer val to the instruction buffer. */
static void code(long val)
{
    check_instr_buf(instr_loc);
    instr_buf[instr_loc++].val = val;
}

/* Requires: str should be good until the next pfree() of compiler_pile, at
 *	     least.  Putting it in the instruction buffer doesn't cause it to
 *	     be freed.
 * Modifies: instr_buf, instr_loc, maybe instr_size.
 * Effects: Appends the string str to the instruction buffer. */
static void code_str(char *str)
{
    check_instr_buf(instr_loc);
    instr_buf[instr_loc++].str = str;
}

/* Requires: errors should be good until the next pfree() of compiler_pile, at
 *	     least.  Putting it in the instruction buffer doesn't cause it to
 *	     be freed.
 * Modifies: instr_buf, instr_loc, maybe instr_size.
 * Effects: Appends errors to the instruction buffer. */
static void code_errors(Id_list *errors)
{
    check_instr_buf(instr_loc);
    instr_buf[instr_loc++].errors = errors;
}

/* Modifies: jump_loc, maybe jump_size and jump_table.
 * Effects: Returns a location in the jump table for use as a destination. */
static int new_jump_dest(void)
{
    /* Resize jump table if we need to. */
    if (jump_loc == jump_size) {
	jump_size = jump_size * 2 + MALLOC_DELTA;
	jump_table = EREALLOC(jump_table, int, jump_size);
    }

    /* Increment jump_loc, and return the old value. */
    return jump_loc++;
}

/* Requires: dest was obtained with new_jump_dest() during this compile.
 *	     pos is a valid instruction in the instruction stream.
 * Modifies: Contents of jump_table.
 * Effects: Sets the destination numbered by dest to the current location. */
static void set_jump_dest_here(int dest)
{
    jump_table[dest] = instr_loc;
}

static int id_list_size(Id_list *id_list)
{
    int count = 0;

    for (; id_list; id_list = id_list->next)
	count++;

    return count;
}

/* Requires: The instruction buffer is full of code.  The method did not have
 *	     any errors.  the_prog->vars is what it originally was.
 * Modifies: Adds global identifiers, adds strings to object.
 * Effects: Converts the data in the instruction buffer into a method. */
static Method *final_pass(Object *object)
{
    Method *method;
    Id_list *idl;
    Op_info *info;
    String *string;
    int i, j, opcode, arg_type, cur_error_list;

    method = EMALLOC(Method, 1);

    method->overridable = the_prog->overridable;

    /* Set argument names. */
    method->num_args = id_list_size(the_prog->args->ids);
    if (method->num_args) {
	method->argnames = TMALLOC(int, method->num_args);
	i = 0;
	for (idl = the_prog->args->ids; idl; idl = idl->next)
	    method->argnames[i++] = object_add_ident(object, idl->ident);
    }

    /* Set rest. */
    if (the_prog->args->rest)
	method->rest = object_add_ident(object, the_prog->args->rest);
    else
	method->rest = -1;

    /* Set variable names. */
    method->num_vars = id_list_size(the_prog->vars);
    if (method->num_vars) {
	method->varnames = TMALLOC(int, method->num_vars);
	i = 0;
	for (idl = the_prog->vars; idl; idl = idl->next)
	    method->varnames[i++] = object_add_ident(object, idl->ident);
    }

    /* Allocate space for error lists, and initialize cur_error_list. */
    method->num_error_lists = num_error_lists;
    if (num_error_lists)
	method->error_lists = TMALLOC(Error_list, num_error_lists);
    cur_error_list = 0;

    /* Copy the opcodes, translating from intermediate instruction forms. */
    method->opcodes = TMALLOC(long, instr_loc);
    method->num_opcodes = instr_loc;
    i = 0;
    while (i < instr_loc) {
	opcode = method->opcodes[i] = instr_buf[i].val;

	/* Use opcode info table for anything else. */
	info = &op_table[opcode];
	for (j = 0; j < 2; j++) {
	    arg_type = (j == 0) ? info->arg1 : info->arg2;
	    if (arg_type) {
		i++;
		switch (arg_type) {
		  case INTEGER:
		  case VAR:
		    method->opcodes[i] = instr_buf[i].val;
		    break;

		  case STRING:
		    string = string_from_chars(instr_buf[i].str,
					       strlen(instr_buf[i].str));
		    method->opcodes[i] = object_add_string(object, string);
		    string_discard(string);
		    break;

		  case IDENT:
		    method->opcodes[i] = object_add_ident(object,
							  instr_buf[i].str);
		    break;

		  case JUMP:
		    method->opcodes[i] = jump_table[instr_buf[i].val];
		    break;

		  case ERROR: {
		      int count;
		      int *ids;

		      if (!instr_buf[i].errors) {
			  /* This is a 'catch any'.  Just code a -1. */
			  method->opcodes[i] = -1;
			  break;
		      }

		      /* Count the number of error codes to catch. */
		      count = 0;
		      for (idl = instr_buf[i].errors; idl; idl = idl->next)
			  count++;

		      /* Allocate space in an error list. */
		      ids = TMALLOC(int, count);
		      method->error_lists[cur_error_list].num_errors = count;
		      method->error_lists[cur_error_list].error_ids = ids;

		      /* Store the error ids. */
		      count = 0;
		      for (idl = instr_buf[i].errors; idl; idl = idl->next)
			  ids[count++] = ident_get(idl->ident);

		      method->opcodes[i] = cur_error_list++;
		      break;
		  }
		}
	    }
	}
	i++;
    }

    method->refs = 1;
    return method;
}