tmuck2.4/
tmuck2.4/admin/scripts/
tmuck2.4/docs/
tmuck2.4/minimal-db/
tmuck2.4/minimal-db/data/
tmuck2.4/minimal-db/logs/
tmuck2.4/minimal-db/muf/
tmuck2.4/old/
tmuck2.4/src/
tmuck2.4/src/compile/
tmuck2.4/src/editor/
tmuck2.4/src/game/
tmuck2.4/src/interface/
tmuck2.4/src/scripts/
tmuck2.4/src/utilprogs/
/* Copyright (c) 1992 by Ben Jackson.  All rights reserved. */
/* compile.c,v 2.17 1997/08/30 07:09:30 dmoore Exp */
#include "config.h"

#include <ctype.h>
#include <string.h>
#include <stdarg.h>

#include "db.h"
#include "code.h"
#include "prim_offsets.h"
#include "text.h"
#include "buffer.h"
#include "hashtab.h"
#include "externs.h"

#define TOK_END       -1
#define TOK_UNKNOWN   -2
#define TOK_ANON      -3   /* ': foo ; */
#define TOK_LINENUMBER -4

struct symbol_entry {
    const char *name;
    struct inst data;
};


/*  this is the struct that everything passes around.
 *  Besides making the compiler easier to write, it makes it fully
 *  re-entrant.  If you wanted (and no, I don't know why you'd want
 *  to), you could suspend a compile at any point and resume it later.
 *  (presuming you return to the same place in the code)
 */
typedef struct compile_state {
    const char *str;         /* pointer to pos in current line */
    struct text *text;       /* pointer to our text block */
    const char *ours;        /* pointer to base of str if we own it. */

    int remain;              /* remaining in of current line */
    int where;               /* current line number */
    int apparentline;        /* line that we use to error */
    unsigned int needline : 1;	/* need to emit a line number? */

    dbref who;               /* the compiling person */
    dbref which;             /* the program being compiled */

    unsigned short errors;   /* we must needs count them goofs */
    unsigned short macrosubs;/* how many macro expansions this line? */
    unsigned short anondepth;/* how deep are we in anon lambdas? */

    struct code *code;       /* our result */

    union inst_un temp;      /* for the tokenizer to generate values */
    Buffer buf;              /* for next_word, also used to name 'prim, '.macro */

    unsigned short nextvar;  /* next available variable num */
    hash_tab *symbol_table;  /* funcs and vars */
} compile_state;

/*  notice we return a TAB at the end of a line, since `end of line'
 *  counts as whitespace
 */

/* this is a function now.  it only gets called once a line, not a big hit
#define next_line(x) (clear_our_str(s),\
    (x)->str = text_line((x)->text, ++(x)->where, &(x)->remain))
*/
/*
#define next_char(x) FORCE_RHS((--(x)->remain)? (*(++(x)->str)) :\
    (next_line(x)?  '\t' : '\0'))
*/

/* FIX FIX FIX: this text_line in next_char should probably just
   compare where to text_total_lines to see if it's at the end of
   the input.  Would save much overhead, since text_line is expensive
   and it gets called immediately again in next_line. */
/* FIX FIX FIX: also does it really hurt to return '\t' followed by
   a '\0'?  That'd save a lot of these. */
#define next_char(x) FORCE_RHS((--(x)->remain > 0)? (*(++(x)->str)) :\
    ((x)->remain < 0) ? next_line(x) :\
    ((x)->str++, text_line((x)->text, (x)->where + 1, NULL) ? '\t' : '\0'))
#define look_char(x) FORCE_RHS(((x)->remain > 1) ? (*((x)->str + 1)) :\
    text_line((x)->text, (x)->where + 1, NULL) ? '\t' : '\0')
#define curr_char(x) FORCE_RHS(((x)->remain) ? (*((x)->str)) :\
    text_line((x)->text, (x)->where + 1, NULL) ? '\t' : '\0')

/*  this stack is used to resolve if/else/then and repeat/until.  */

#define NEST_IF        1
#define NEST_ELSE      2
#define NEST_REPEAT    3

struct nest {
    struct nest *next;
    unsigned int type : 2;       /* what flavor is it? */
    unsigned int offset : 30;    /* where is it in the bytecode? */
};

/*  prototypes  */

void init_compiler(void);
static char next_line(compile_state *state);
static const char *next_word(compile_state *state);
static void skip_white(compile_state *state);
static void make_symbol(compile_state *state, const char *name, const int type, union inst_un un);
static struct symbol_entry *lookup_symbol(compile_state *state, const char *name);
static void delete_symbol_entry(void *sym);
struct func_addr *make_func_addr(struct code *code, int num_inst, const char *name);
void clear_func_addr(struct func_addr *victim);
static compile_state *make_compile_state(const dbref player, const dbref prog, struct text *text);
static void clear_compile_state(compile_state *state);
static void compile_error(compile_state *state, const char *fmt, ...);
static void compile_warning(compile_state *state, const char *fmt, ...);
static void push_expansion(compile_state *state, const char *exp);
static int expand_if_macro(compile_state *state);
static int next_token(compile_state *state);
static struct func_addr *compile_func(compile_state *state, const char *faname);

struct func_addr *compile_string(const dbref player, const char *string);
void compile_text(const dbref player, const dbref prog, struct text *text);


/*  next_line:  takes care of steping to the next line.
 */
static char next_line(compile_state *state)
{
    if(state->ours)     /* remove the expansion buffer */
        FREE((char *) state->ours);
    state->macrosubs = 0;  /* no expansions done yet on this new line */
    state->needline = 1;   /* emit a line number into the bytecode */
    do {
        state->str = text_line(state->text, ++state->where, &state->remain);
    } while(state->str && !*state->str);
    if(!state->str)
        return '\0';
    else
	return *state->str;
}

/* next_word: gets you the next whitespace-terminated string in the input.
 * expects the word to start right off, so call skip_white first.
 */
static const char *next_word(compile_state *state)
{
    Buffer *buf = &state->buf;
    char c;

    Bufcpy(buf, "");

    c = curr_char(state);
    while(c && !isspace(c)) {
        Bufcat_char(buf, c);
        c = next_char(state);
    }

    return Buftext(buf);
}

/* skip_white: avoids whitespace and comments.
 */
static void skip_white(compile_state *state)
{
    char c;

    c = curr_char(state);
    while(isspace(c) || c == BEGINCOMMENT) {
        while(isspace(c))
	    c = next_char(state);
        if(c == BEGINCOMMENT) {
            while(c && c != ENDCOMMENT)
                c = next_char(state);
            if(c == ENDCOMMENT)
	        c = next_char(state);
	}
    }
}


/*  init_compiler:  should be called by init_game.
 *  have compiler_inited?  call it automatically if it's not set?
 */
void init_compiler(void)
{
    /* should call fn to init primitive hash table */
}

/* make_symbol:  checks to make sure it's legit and then adds it
 */
static void make_symbol(compile_state *state, const char *name, const int type, union inst_un un)
{
    struct symbol_entry *result;

    MALLOC(result, struct symbol_entry, 1);
    result->name = ALLOC_STRING(name);
    result->data.type = type;
    result->data.un = un;
    add_hash_entry(state->symbol_table, result);
}

/* lookup_symbol:  find it in the table
 */
static struct symbol_entry *lookup_symbol(compile_state *state, const char *name)
{
    struct symbol_entry sym;

    sym.name = name;
    return (struct symbol_entry *) find_hash_entry(state->symbol_table, &sym);
}

/* delete_symbol_entry:  hash internal type fn to delete a symbol
 */
static void delete_symbol_entry(void *entry)
{
    struct symbol_entry *sym = entry;
    FREE_STRING(sym->name);
    FREE(sym);
}

/* make_func_addr:  the PC way to make a new func_addr
 */
struct func_addr *make_func_addr(struct code *code, int num_inst, const char *name)
{
    struct func_addr *result;

    MALLOC(result, struct func_addr, 1);
    result->code = code;
    result->num_inst = num_inst;
    result->name = name;
    if(num_inst) {
        MALLOC(result->bytecode, struct inst, num_inst);
    } else {
	result->bytecode = NULL;
    }
    return result;
}

/*  clear_func_addr:  the PC way to get rid of one
 */
void clear_func_addr(struct func_addr *victim)
{
    int x;

    if(!victim)
        return;

    for(x = 0; x < victim->num_inst; ++x) {
	clear_inst_code(&(victim->bytecode[x]));
    }

    FREE(victim->bytecode);

    if(victim->name)
        FREE_STRING(victim->name);
    FREE(victim);
}


/* make_compile_state:  the PC way to allocate a new one
 */
static compile_state *make_compile_state(const dbref player, const dbref prog, struct text *text)
{
    compile_state *result;
    static const char *reserved_vars[] = { "ME", "LOC", "TRIGGER" };
    int x;
    union inst_un temp_un;

    MALLOC(result, compile_state, 1);

    /*  set up the text block and initialize the first line  */
    result->text = text;
    result->str = text_line(text, 1, &result->remain);

    result->where = 1;
    result->apparentline = 1;
    result->needline = 1;

    /*  which program we are and who is compiling us  */
    result->who = player;
    result->which = prog;

    /*  we haven't done an expand yet  */
    result->ours = NULL;
    result->macrosubs = 0;
    result->anondepth = 0;

    result->errors = 0;

    /*  set up our destination code block  */
    result->code = make_code(prog, 0);

    /*  init our symbol table  */
    result->symbol_table = init_hash_table("Temporary Symbol Table",
					   compare_generic_hash,
					   make_key_generic_hash,
					   delete_symbol_entry,
					   SYMTAB_HASH_SIZE);
    /*  copy in default vars me, loc, trigger  */
    for(x = 0; x < (sizeof(reserved_vars) / sizeof(char *)); x++) {
	temp_un.variable = x;
        make_symbol(result, reserved_vars[x], INST_VARIABLE, temp_un);
    }
    result->nextvar = x;

    return result;
}

/* clear_compile_state:  the PC way to get rid of one when you're done
 */
static void clear_compile_state(compile_state *state)
{
    clear_hash_table(state->symbol_table);
    clear_code(state->code);   /* removes our refcount to it */
    if(state->ours)
	FREE((char *) state->ours);
    FREE(state);
}




/*  compile_error:  this should be called to report all compiler errors
 */
static void compile_error(compile_state *state, const char *fmt, ...)
{
    Buffer buf;
    va_list ap;

    state->errors++;

    if(state->who == NOTHING)
        return;

    va_start(ap, fmt);
    Bufvsprint(&buf, fmt, ap);
    va_end(ap);

    if(state->text)
        notify(state->who, "Error in line %d: %S.", state->apparentline, &buf);
    else
        notify(state->who, "Error: %S.", &buf);
}

/*  compile_warning:  this should be called to report all compiler warnings
 */
static void compile_warning(compile_state *state, const char *fmt, ...)
{
    Buffer buf;
    va_list ap;

    if(state->who == NOTHING)
        return;

    va_start(ap, fmt);
    Bufvsprint(&buf, fmt, ap);
    va_end(ap);

    if(state->text)
        notify(state->who, "Warning line %d: %S.", state->apparentline, &buf);
    else
        notify(state->who, "Warning: %S.", &buf);
}




/* push_expansion:  pushes some expansion into the current line.
 * we expand the current line only when necessary.
 * When there is extra data on the line, state->ours points to
 * the string we allocated to hold all this new string.  state->str
 * then points into this new string, so that next_char can access
 * it the same way.
 *
 * next_line chops this off once we're done with it
 */
#define PUSH_BUF_SIZE 2048  /* the smallest push we'll create */
static void push_expansion(compile_state *state, const char *exp)
{
    int len = strlen(exp);

    /* Special case remain of -1 should be treated like 0 here. */
    if (state->remain == -1)
	state->remain = 0;

    if(state->ours) {
        if(state->str - state->ours >= len) {
	    /* if there's room at the front */
	    /* str is a pointer into the same data that ours points to
	       the memcpy below is used rather than strcpy because we
	       don't want a trailing nul in the stream. */
            state->str -= len;
            state->remain += len;
            memcpy((char *) state->str, exp, len);
        } else {                            /* replace old ->ours with a longer one */
            char *ptr, *temp;

            state->remain += len;
            MALLOC(ptr, char, state->remain + PUSH_BUF_SIZE);
            temp = ptr + PUSH_BUF_SIZE - 1;
            strcpy(temp, exp);
            strcpy(temp + len, state->str);
            FREE((char *) state->ours);
            state->ours = ptr;
            state->str = temp;
        }
    } else {                                   /* first expansion */
        char *ptr, *temp;

        state->remain += len;
        MALLOC(ptr, char, state->remain + PUSH_BUF_SIZE);
        temp = ptr + PUSH_BUF_SIZE - 1;
        strcpy(temp, exp);
        strcpy(temp + len, state->str);
        state->ours = ptr;
        state->str = temp;
    }
}

/* expand_if_macro:  looks at the currect compile position and expands
 * a macro if it's the first thing there.
 */
static int expand_if_macro(compile_state *state)
{
    const char *name, *exp;
    int flag = 0;

    while(curr_char(state) == BEGINMACRO) {
        next_char(state);		/* Skip '.' */
        name = next_word(state);
        exp = macro_expansion(name);

        if(!exp) {
            compile_error(state, "Undefined macro %s", name);
            return -1;
        } else {
            if(++state->macrosubs > MAX_MACRO_SUBS) {
                compile_error(state, "%s", "Macro substitution limit exceeded");
                return -1;
            }
            push_expansion(state, exp);
            flag = 1;
        }
    }
    return flag;
}

/*  next_token:  returns the next token from the source.
 *
 *  ...return normal prims
 *  ...turn '.macro into ': .macro ;
 */
static int next_token(compile_state *state)
{
    char c;
    const char *word;

    skip_white(state);

    if(state->needline) {	/* lets get this line number thing right */
        state->needline = 0;
        state->apparentline = state->where;
        return TOK_LINENUMBER;
    }

    switch(expand_if_macro(state)) {                            /* .<macro> */
        case 1:
            return next_token(state);
        case -1:
            return TOK_UNKNOWN;
        /* case 0: fall through */
    }

    c = curr_char(state);  /* look because skip_white already `saw' it */

    if(!c)                                        /* end of source */
        return TOK_END;

    if(isdigit(c) || ((c == '-' || c == '+')
		      && isdigit(look_char(state)))) { /* [-|+]<0-9...> */
        int num;
        char *end;

        word = next_word(state);
        num = strtol(word, &end, 0);
        if(*end) {
            /*  there were trailing nondigit chars, and the old compiler
             *  treats that like a  word.  so we emulate the stupid
             *  piece of, er, ah...
             */
            push_expansion(state, word); /* stick it back into the text */
            /* notice we fall through here */
        } else {
            state->temp.integer = num;
            return INST_INTEGER;
        }
    }

    if(c == BEGINSTRING) {                                      /* "<string>" */
        Buffer buf;
        char c;

        Bufcpy(&buf, "");

        c = next_char(state);
        while(c != ENDSTRING) {
            if(!c || c == '\t') {   /* tab indicates end of line whitespace */
                compile_error(state, "Unterminated string");
                return TOK_UNKNOWN;
            }
            if(c == '\\') {
                c = next_char(state);  /* take next char as literal */
		if(!c || c == '\t') { /* tab is end of line marker */
		    compile_error(state, "Nothing follows \\");
		    return TOK_UNKNOWN;
		}
	    }
            Bufcat_char(&buf, c);
            c = next_char(state);
        }
	c = next_char(state);  /* skip trailing " */
        state->temp.string = make_shared_string(Buftext(&buf), Buflen(&buf));
        return INST_STRING;
    }

    if(c == '#') {                                              /* #<dbref> */
	/* FIX FIX FIX: does any of this number skipping stuff work? 
	 What is it doing???? */
        dbref obj;
        const char *temp;

        temp = (word = next_word(state)) + 1;

        /*  check to make sure it's a number under the old system */
        while(isspace(*temp)) temp++;
        if(*temp == '-') temp++;
        while(isdigit(*temp)) temp++;

        if(*temp) {
            /*  there were trailing nondigit chars, and the old compiler
             *  treats that like a  word.  so we emulate the stupid
             *  piece of, er, ah...
             */
            push_expansion(state, word); /* stick it back into the text */
            /* notice we fall through here */
        } else {
            obj = parse_dbref(word + 1);
            /*
            if(!Valid(obj)) {
                compile_error(state, "Invalid object #%d", obj);
                return TOK_UNKNOWN;
            }
            */
            state->temp.object = obj;
            return INST_OBJECT;
        }
    }

    if(c == '\'') {						/* '<something> */
        int prim;
        struct symbol_entry *sym;

        next_char(state);					/* skip '\'' */
        word = next_word(state);
        if(!*word) {
            compile_error(state, "%s", "Missing identifier after '");
            return TOK_UNKNOWN;
        }

        sym = lookup_symbol(state, word);
        if(sym) {						/** '<symbol> */
            if(sym->data.type == INST_VARIABLE) {
                compile_error(state, "%s", "Illegal to take address of variable");
                return TOK_UNKNOWN;
            } else {
                /* should always be INST_ADDRESS */
                state->temp = sym->data.un;
                return sym->data.type;
            }
        }

        if(*word == BEGINMACRO) {				/** '.<macro> */
            const char *exp;

            exp = macro_expansion(word + 1);
            if(exp) {
                push_expansion(state, " ;");  /* nifty, huh? */
                push_expansion(state, exp);
		/* name is in the buffer */
                return TOK_ANON;
            } else {
                compile_error(state, "Undefined macro %s", word);
                return TOK_UNKNOWN;
            }
        }

        prim = lookup_primitive(word);

        if(prim == PRIM_word_begin) {                           /** ': <body> ; */
	    Bufcpy(&state->buf, "");  /* no name */
            return TOK_ANON;
        }

        switch(prim) {
            case PRIM_if:
            case PRIM_else:
            case PRIM_then:
            case PRIM_var_decl:
            case PRIM_word_end:
#ifdef MUF_REPEAT_UNTIL
            case PRIM_repeat:
            case PRIM_until:
#endif /* MUF_REPEAT_UNTIL */
                compile_error(state, "Illegal quoted primitive %s", word);
                return TOK_UNKNOWN;
        }
        if(prim >= 0) {                                         /** '<prim>   */
	    Buffer temp;

            push_expansion(state, " ;");
            push_expansion(state, word);
	    Bufcpy(&temp, word);  /* because `word' is really Buftext(&state->buf) */
	    Bufsprint(&state->buf, "prim %S", &temp);
            return TOK_ANON;
        }
        compile_error(state, "Unable to take address of %s", word);
        return TOK_UNKNOWN;
    }

    word = next_word(state);
    if(*word) {
        struct symbol_entry *sym;
        int prim;

        sym = lookup_symbol(state, word);
        if(sym) {                                               /* <symbol> */
            state->temp = sym->data.un;
	    if(sym->data.type == INST_ADDRESS)
	        return INST_EXECUTE;
            return sym->data.type;  /* a variable */
        }

        prim = lookup_primitive(word);
        if(prim >= 0) {                                         /* <prim>  */
	    state->temp.primitive = prim;
	    return INST_PRIMITIVE;
        }
    }
    compile_error(state, "Unrecognized word %s", word);
    return TOK_UNKNOWN;
}



/*  compile_func:  this compiles a func (yep, a word).
 *
 *  ...emit all normal prims and constants
 *  ...resolve if/else/then and repeat/until
 */

#define EMIT(FUNC_ADDR, TYPE, DATA) do {\
        int _offset = (FUNC_ADDR)->num_inst++; \
	(FUNC_ADDR)->bytecode[_offset].type = (TYPE); \
	(FUNC_ADDR)->bytecode[_offset].un = (DATA); \
    } while(0)

/* reemit and last_emit_line are used to make two adjacent linenumber
   commands store as only 1 bytecode. */
#define REEMIT(FUNC_ADDR, TYPE, DATA) do {\
        int _offset = (FUNC_ADDR)->num_inst - 1; \
	clear_inst_code(&((FUNC_ADDR)->bytecode[_offset])); \
	(FUNC_ADDR)->bytecode[_offset].type = (TYPE); \
	(FUNC_ADDR)->bytecode[_offset].un = (DATA); \
    } while(0)
#define LAST_EMIT_LINE(fa) \
    ((fa)->num_inst && (fa)->bytecode[(fa)->num_inst - 1].type == INST_LINENO)

static struct func_addr *compile_func(compile_state *state, const char *faname)
{
    struct nest *if_stack = NULL, *temp;  /* where we keep track of if/then */
    int if_depth = 0;
    struct func_addr *result, *fa;
    int token, bcodesize = 2048;
    union inst_un temp_un;

    /* set up our destination func_addr */
    result = make_func_addr(state->code, bcodesize, NULL);
    result->num_inst = 0; /* this is our current position */

    if(faname && *faname) {
	/* add this fn in as a symbol */
	temp_un.address = result;
        make_symbol(state, faname, INST_ADDRESS, temp_un);
	faname = ALLOC_STRING(faname);  /* turn the static into a copy */
        result->name = faname;
    }

    state->needline = 1;  /* force a line number emit */

    for(;;) {
        if(result->num_inst >= bcodesize) {  /* stretch the result buffer */
            bcodesize += 2048;
	    REALLOC(result->bytecode, struct inst, bcodesize);
        }

        token = next_token(state);
        switch(token) {
            case TOK_UNKNOWN:
                goto ERROR;      /* actual error already printed by next_token */

            case TOK_END:
                compile_error(state, "%s", "Unexpected end of file");
                goto ERROR;

            case TOK_LINENUMBER:
		temp_un.lineno = state->apparentline;
		if(LAST_EMIT_LINE(result))
		    REEMIT(result, INST_LINENO, temp_un);
		else
                    EMIT(result, INST_LINENO, temp_un);
                break;
      
            case TOK_ANON:       /* ahh, an anonymous function */
                if(++state->anondepth > MAX_ANON_DEPTH) {
                    compile_error(state, "%s", "Anonymous function depth limit exceeded");
                    goto ERROR;
                }
                fa = compile_func(state, Buftext(&state->buf));
                state->anondepth--;
                if(!fa) {
                    /* compile_func already generated an error for them */
                    goto ERROR;   /* swap with FAIL to tersify deep errors */
		    goto FAIL;
                } else {
		    /* add it to the list, so we free it later */
		    /* (sorry if you were hoping for mem leaks ;-) */
		    state->code->num_funcs++;
		    if(state->code->funcs)
		        REALLOC(state->code->funcs, struct func_addr *, state->code->num_funcs);
		    else
		        MALLOC(state->code->funcs, struct func_addr *, 1);
		    state->code->funcs[state->code->num_funcs - 1] = fa;
		    temp_un.address = fa;
                    EMIT(result, INST_ADDRESS, temp_un);
                    state->needline = 1;  /* force a line number emit */
                }
                break;

	      
            case INST_PRIMITIVE:
                switch(state->temp.primitive) {
                    case PRIM_word_begin:
                        compile_error(state, "%s", "Illegal to nest words");
                        goto ERROR;

                    case PRIM_word_end:
			temp_un.integer = 0;
                        EMIT(result, INST_EXIT, temp_un);
                        goto DONE;

                    case PRIM_var_decl:
                        compile_error(state, "%s", "Local variables not supported");
                        goto ERROR;

		    /*  prog/self (special `constants')  */

	            case PRIM_self:
			temp_un.address = result;
			EMIT(result, INST_ADDRESS, temp_un);
			break;

		    case PRIM_prog:
			temp_un.object = state->which;
			EMIT(result, INST_OBJECT, temp_un);
			break;

		    /* exit */

		    case PRIM_exit:
		        temp_un.integer = 0;
			EMIT(result, INST_EXIT, temp_un);
			break;

                    /*  if/else/then handling  */
		      
                    case PRIM_if:
                        if(if_depth++ == MAX_IF_STMT_NEST) {
                            compile_error(state, "%s", "Nesting depth limit exceeded");
                            goto ERROR;
                        }
                        MALLOC(temp, struct nest, 1);
                        temp->next = if_stack;
                        temp->type = NEST_IF;
                        temp->offset = result->num_inst;
                        if_stack = temp;

			temp_un.offset = 0;
                        EMIT(result, INST_IF, temp_un);
                        break;

                    case PRIM_else:
                        if(!if_stack || if_stack->type == NEST_REPEAT) {
                            compile_error(state, "%s", "ELSE without IF");
                            goto ERROR;
                        }
                        if(if_stack->type == NEST_ELSE) {
                            compile_error(state, "%s", "Too many ELSEs for one IF");
                            goto ERROR;
                        }

                        /* resolve the if's branch */
			result->bytecode[if_stack->offset].un.offset =
			    result->num_inst + 1;
                        if_stack->type = NEST_ELSE;
                        if_stack->offset = result->num_inst;

			temp_un.offset = 0;
                        EMIT(result, INST_OFFSET, temp_un);
                        state->needline = 1;  /* force a line number emit */
                        break;
		      
                    case PRIM_then:
                        if(!if_stack || if_stack->type == NEST_REPEAT) {
                            compile_error(state, "%s", "THEN without IF");
                            goto ERROR;
                        }

                        /* resolve the if's or else's branch */
			result->bytecode[if_stack->offset].un.offset =
			    result->num_inst - (LAST_EMIT_LINE(result) ? 1 : 0);
                        temp = if_stack->next;
                        FREE(if_stack);
                        if_stack = temp;

                        state->needline = 1;  /* force a line number emit */
                        break;

#ifdef MUF_REPEAT_UNTIL
                    case PRIM_repeat:
                        /* just sets a marker.  repeat, like then, has no code */
		      
                        if(if_depth++ == MAX_IF_STMT_NEST) {
                            compile_error(state, "%s", "Nesting depth limit exceeded");
                            goto ERROR;
                        }
                        MALLOC(temp, struct nest, 1);
                        temp->next = if_nest;
                        temp->type = NEST_REPEAT;
                        temp->offset = result->num_inst;
                        if_nest = temp;
                        break;
                        state->needline = 1;  /* force a line number emit */

                    case PRIM_until:
                        if(!if_stack || if_stack->type == NEST_IF || if_stack->type == NEST_ELSE) {
                            compile_error(state, "%s", "UNTIL without REPEAT");
                            goto ERROR;
                        }

                        /* resolve the if's or else's branch */
			temp_un.offset = if_stack->offset;
                        EMIT(result, INST_NOT_IF, temp_un);
                        temp = if_stack->next;
                        FREE(if_stack);
                        if_stack = temp;

                        break;
#endif /* MUF_REPEAT_UNTIL */

		    /* depricated functions: addprop */
		    case PRIM_addprop:
		        compile_warning(state, "addprop is not recommended.  Use setprop instead");
                        EMIT(result, INST_PRIMITIVE, state->temp);
		        break;

		    /* everything else */
   	            default:
                        EMIT(result, INST_PRIMITIVE, state->temp);
                        break;
		}
                break;  /* end of case INST_PRIMITIVE */

/* lots of stuff we just emit
            case INST_STRING:
            case INST_INTEGER:
            case INST_OBJECT:
	    case INST_ADDRESS:
	    case INST_CONNECTION:
            case INST_VARIABLE:
            case INST_EXECUTE:
            case INST_OFFSET:
            case INST_LINENO:
*/
            default:
                EMIT(result, token, state->temp);
                break;
        }
    }
  
DONE:
    while(if_stack) {
        if(!state->errors) {    /* don't spam them, just give first goof */
            switch(if_stack->type) {
                case NEST_IF:
                    compile_error(state, "%s", "IF without THEN");
                    break;
                case NEST_ELSE:
                    compile_error(state, "%s", "IF-ELSE without THEN");
                    break;
                case NEST_REPEAT:
                    compile_error(state, "%s", "REPEAT without UNTIL");
                    break;
            }
        }
        temp = if_stack->next;
        FREE(if_stack);
        if_stack = temp;
    }

    if(result->num_inst < 3) {  /* has at least a line num and EXIT */
        /* just a warning now, no longer an error */
        compile_warning(state, "Empty definition of %s",
	    result->name ? result->name : "anonymous function");
    }
  
    if(state->errors) {
ERROR:
        compile_error(state, "Error in definition of %s",
            result->name ? result->name : "anonymous function");
FAIL:
	/*  oops, I almost released 2.3 with this leak */
	while(if_stack) {
	    temp = if_stack->next;
	    FREE(if_stack);
	    if_stack = temp;
	}
        clear_func_addr(result);
        return NULL;
    } else {
        /* this realloc chops off extra room in the stretchy buffer */
        REALLOC(result->bytecode, struct inst, result->num_inst);
        return result;
    }
}




/*
 *  compile_string and compile_text provide the external interface to
 *  the compiler.  from the editor, compile_text can be called with
 *  the current struct text.  if you want compile_text to load and free
 *  the text itself, pass text as NULL.  To supress compiler error
 *  messages, pass player as NOTHING.
 *
 */

/*  compile_string:  use this to compile non-files (things without
 *  struct text's)
 */
struct func_addr *compile_string(const dbref player, const char *string)
{
    compile_state *state;
    struct func_addr *result;

    if(!string || !*string)
        return NULL;

    state = make_compile_state(player, NOTHING, NULL);
    state->str = string;
    state->remain = strlen(string);

    result = compile_func(state, NULL);
    clear_compile_state(state);
    return result;
}

/*  compile_text:
 */
void compile_text(const dbref player, const dbref prog, struct text *text)
{
    compile_state *state;
    int ourtext;
    union inst_un temp_un;

    if(text) {            /* make sure we have a text block */
        ourtext = 0;
    } else {
        text = read_program(prog);
        ourtext = 1;
    }

    state = make_compile_state(player, prog, text); /* set up our state */

    if(!text) {            /* error if we never got any text */
	compile_error(state, "%s", "Missing program text");
        goto ERROR;
    }

    for(;;) {
        int token;

        token = next_token(state);

        switch(token) {
            case TOK_END:
                goto DONE;

            case TOK_LINENUMBER:
		continue;

            case INST_PRIMITIVE:
                switch(state->temp.primitive) {
                    struct func_addr *fa;
                    struct symbol_entry *sym;
                    const char *word;
                    int prim, x;

                    case PRIM_var_decl:
                        do {
                            skip_white(state);
                        } while((x = expand_if_macro(state)) > 0);
                        if(x == -1) { /* macro sub bombed */
                            goto ERROR;
                        }
                        word = next_word(state);

                        if(!*word) {
                            compile_error(state, "%s", "Unexpected end of source");
                            goto ERROR;
                        }
                        sym = lookup_symbol(state, word);
                        if(sym) {
			    /* mimic that old compiler */
			    compile_warning(state, "Duplicate identifier %s", word);
                        }
                        if(state->nextvar > MAX_VAR) {
                            compile_error(state, "%s", "Variable limit exceeded");
                            goto ERROR;
                        }
                        prim = lookup_primitive(word);
                        if(prim >= 0) {
                            compile_warning(state, "Variable declaration masks primitive %s",
                                prim2name(prim));
                        }
		    
		        temp_un.variable = state->nextvar++;
                        make_symbol(state, word, INST_VARIABLE, temp_un);
                        break;

                    case PRIM_word_begin:
                        do {
                            skip_white(state);
                        } while((x = expand_if_macro(state)) > 0);
                        if(x == -1) { /* macro sub bombed */
                            goto ERROR;
                        }
                        word = next_word(state);

                        if(!*word) {
                            compile_error(state, "%s", "Unexpected end of source");
                            goto ERROR;
                        }
                        sym = lookup_symbol(state, word);
                        if(sym) {
                            compile_error(state, "Duplicate identifier %s", word);
                            goto ERROR;
                        }
/*
                        do we want to limit funcs?

                        if(state->nextvar > MAX_FUNC) {
                            compile_error(state, "%s", "Function limit exceeded");
                            goto ERROR;
                        }
*/
                        prim = lookup_primitive(word);
                        if(prim >= 0) {
                            compile_warning(state, "Function declaration masks primitive %s",
                                prim2name(prim));
                        }

                        fa = compile_func(state, word); /* makes its own symbol */
                        if(!fa) {
                            /* it's already given an appropriate error */
                            goto ERROR;
                        }

                        /* add it to our list */
                        x = state->code->num_funcs++;
			if (x) {
			    REALLOC(state->code->funcs, struct func_addr *, state->code->num_funcs);
			} else {
			    MALLOC(state->code->funcs, struct func_addr *, 1);
			}
		        state->code->funcs[state->code->num_funcs - 1] = fa;
		        state->code->main_func = fa; /* always the last one! */
                        break;

                    default:
                        compile_error(state, "%s",
                            "Expected variable or word declaration");
                        goto ERROR;
                }
                break;
	      
            default:
                compile_error(state, "%s",
                    "Expected variable or word declaration");
		goto ERROR;
        }
    }
  
DONE:
    if(!state->code->num_funcs)
        compile_error(state, "%s", "No word definitions");

ERROR:
    if(ourtext && text)
        free_text(state->text);  /* we weren't passed it */

    if(!state->errors) {
        optimize_code(player, state->code);
	add_code(prog, state->code);
    }

    clear_compile_state(state);
}