tmi2/
tmi2/bin/
tmi2/etc/
tmi2/lib/
tmi2/lib/adm/
tmi2/lib/adm/daemons/languages/
tmi2/lib/adm/daemons/network/I3/
tmi2/lib/adm/daemons/virtual/template/
tmi2/lib/adm/obj/master/
tmi2/lib/adm/priv/
tmi2/lib/adm/shell/
tmi2/lib/adm/tmp/
tmi2/lib/cmds/
tmi2/lib/d/
tmi2/lib/d/Conf/
tmi2/lib/d/Conf/adm/
tmi2/lib/d/Conf/boards/
tmi2/lib/d/Conf/cmds/
tmi2/lib/d/Conf/data/
tmi2/lib/d/Conf/logs/
tmi2/lib/d/Conf/obj/
tmi2/lib/d/Conf/text/help/
tmi2/lib/d/Fooland/adm/
tmi2/lib/d/Fooland/data/
tmi2/lib/d/Fooland/data/attic/
tmi2/lib/d/Fooland/items/
tmi2/lib/d/TMI/
tmi2/lib/d/TMI/adm/
tmi2/lib/d/TMI/boards/
tmi2/lib/d/TMI/data/
tmi2/lib/d/TMI/rooms/
tmi2/lib/d/grid/
tmi2/lib/d/grid/adm/
tmi2/lib/d/grid/data/
tmi2/lib/d/std/
tmi2/lib/d/std/adm/
tmi2/lib/data/adm/
tmi2/lib/data/adm/daemons/
tmi2/lib/data/adm/daemons/doc_d/
tmi2/lib/data/adm/daemons/emoted/
tmi2/lib/data/adm/daemons/network/http/
tmi2/lib/data/adm/daemons/network/services/mail_q/
tmi2/lib/data/adm/daemons/network/smtp/
tmi2/lib/data/adm/daemons/news/archives/
tmi2/lib/data/attic/connection/
tmi2/lib/data/attic/user/
tmi2/lib/data/std/connection/b/
tmi2/lib/data/std/connection/l/
tmi2/lib/data/std/user/a/
tmi2/lib/data/std/user/b/
tmi2/lib/data/std/user/d/
tmi2/lib/data/std/user/f/
tmi2/lib/data/std/user/l/
tmi2/lib/data/std/user/x/
tmi2/lib/data/u/d/dm/working/doc_d/
tmi2/lib/data/u/l/leto/doc_d/
tmi2/lib/data/u/l/leto/smtp/
tmi2/lib/doc/
tmi2/lib/doc/driverdoc/applies/
tmi2/lib/doc/driverdoc/concepts/
tmi2/lib/doc/driverdoc/driver/
tmi2/lib/doc/driverdoc/efuns/arrays/
tmi2/lib/doc/driverdoc/efuns/buffers/
tmi2/lib/doc/driverdoc/efuns/compile/
tmi2/lib/doc/driverdoc/efuns/ed/
tmi2/lib/doc/driverdoc/efuns/floats/
tmi2/lib/doc/driverdoc/efuns/functions/
tmi2/lib/doc/driverdoc/efuns/general/
tmi2/lib/doc/driverdoc/efuns/numbers/
tmi2/lib/doc/driverdoc/efuns/parsing/
tmi2/lib/doc/driverdoc/lpc/constructs/
tmi2/lib/doc/driverdoc/lpc/preprocessor/
tmi2/lib/doc/driverdoc/lpc/types/
tmi2/lib/doc/driverdoc/platforms/
tmi2/lib/doc/mudlib/
tmi2/lib/ftp/
tmi2/lib/log/
tmi2/lib/obj/net/
tmi2/lib/obj/shells/
tmi2/lib/std/board/
tmi2/lib/std/body/
tmi2/lib/std/fun/
tmi2/lib/std/living/
tmi2/lib/std/object/
tmi2/lib/std/shop/
tmi2/lib/std/socket/
tmi2/lib/std/virtual/
tmi2/lib/student/
tmi2/lib/student/kalypso/
tmi2/lib/student/kalypso/armor/
tmi2/lib/student/kalypso/rooms/
tmi2/lib/student/kalypso/weapons/
tmi2/lib/u/l/leto/
tmi2/lib/u/l/leto/cmds/
tmi2/lib/www/errors/
tmi2/lib/www/gateways/
tmi2/lib/www/images/
tmi2/old/
tmi2/v21.7a10/
tmi2/v21.7a10/ChangeLog.old/
tmi2/v21.7a10/compat/simuls/
tmi2/v21.7a10/include/
tmi2/v21.7a10/testsuite/
tmi2/v21.7a10/testsuite/clone/
tmi2/v21.7a10/testsuite/command/
tmi2/v21.7a10/testsuite/data/
tmi2/v21.7a10/testsuite/etc/
tmi2/v21.7a10/testsuite/include/
tmi2/v21.7a10/testsuite/inherit/
tmi2/v21.7a10/testsuite/inherit/master/
tmi2/v21.7a10/testsuite/log/
tmi2/v21.7a10/testsuite/u/
tmi2/v21.7a10/tmp/
/*
 *  Parsing efuns.  Many of the concepts and algorithms here are stolen from
 *  an earlier LPC parser written by Rust@ZorkMUD.
 */
/*
 * TODO:
 * . he, she, it, him, her, them -> "look at tempress.  get sword.  kill her with it"
 * . compound input -> "n. e then s."
 * . OBS: and, all and everything (all [of] X, X except [for] Y, X and Y)
 * . OBS in OBS
 * . possesive: my his her its their Beek's
 * . first, second, etc
 * . one, two, ...
 * . questions.  'Take what?'
 * . oops
 * . the 'her' ambiguity
 * . foo, who is ...   foo, where is ...   foo, what is ...   foo, go south
 * . where is ... "where is sword" -> "In the bag on the table"
 * . > Which button do you mean, the camera button or the recorder button?
 * . Zifnab's crasher
 *
 */

/* from Ohara:
> GET BALL
(from the box)
You pick up the small red ball.

> PUT KEY IN BOX
Which key do you mean? The gold key or the skull key?

> SKULL
You put the skull key in the box.

> EXAMINE THE KEY
(The gold key)
The gold key glitters in the light.

*/

#include "std.h"
#include "lpc_incl.h"
#include "parser.h"
#include "md.h"

char *pluralize PROT((char *));

static parse_info_t *pi = 0;
static hash_entry_t *hash_table[HASH_SIZE];
static verb_t *verbs[VERB_HASH_SIZE];
static int objects_loaded = 0;
static int num_objects, num_people, me_object;
static struct object_s *loaded_objects[MAX_NUM_OBJECTS];
static int object_flags[MAX_NUM_OBJECTS];
static int num_literals = -1;
static char **literals;
static word_t words[256];
static int num_words = 0;
static verb_node_t *parse_vn;
static verb_t *parse_verb_entry;
static object_t *parse_restricted;
static object_t *parse_user;
static unsigned int cur_livings[NUM_BITVEC_INTS];
static unsigned int cur_accessible[NUM_BITVEC_INTS];
static int best_match;
static int best_error_match;
static int best_num_errors;
static parse_result_t *best_result = 0;
static match_t matches[10];
static char *current_error = 0;
static char *the_word, *a_word, *me_word;
#ifdef DEBUG
static int debug_parse_depth = 0;
static int debug_parse_verbose = 0;

#define DEBUG_PP(x) if (debug_parse_depth && debug_parse_verbose) debug_parse x
#define DEBUG_P(x) if (debug_parse_depth) debug_parse x
#define DEBUG_INC  if (debug_parse_depth) debug_parse_depth++
#define DEBUG_DEC  if (debug_parse_depth) debug_parse_depth--
#else
#define DEBUG_PP(x)
#define DEBUG_P(x)
#define DEBUG_INC
#define DEBUG_DEC
#endif

static void parse_rule PROT((parse_state_t *));

#define isignore(x) (!isascii(x) || !isprint(x) || x == '\'')
#define iskeep(x) (isalnum(x) || x == '*')

/* parse_init() - setup the object
 * parse_refresh() - refresh an object's parse data
 * parse_add_rule(verb, rule, handler) - add a rule for a verb
 * parse_sentence(sent) - do the parsing :)
 */

#ifdef DEBUGMALLOC_EXTENSIONS
void parser_mark_verbs() {
    int i;

    for (i = 0; i < VERB_HASH_SIZE; i++) {
	verb_t *verb_entry = verbs[i];
	
	while (verb_entry) {
	    EXTRA_REF(BLOCK(verb_entry->name))++;
	    verb_entry = verb_entry->next;
	}
    }
    
    for (i = 0; i < num_literals; i++) {
	EXTRA_REF(BLOCK(literals[i]))++;
    }

    EXTRA_REF(BLOCK(the_word))++;
    EXTRA_REF(BLOCK(a_word))++;
    EXTRA_REF(BLOCK(me_word))++;
}
    
void parser_mark P1(parse_info_t *, pinfo) {
    int i;

    if (!(pinfo->flags & PI_SETUP))
	return;
    
    for (i = 0; i < pinfo->num_ids; i++) {
	EXTRA_REF(BLOCK(pinfo->ids[i]))++;
    }
    for (i = 0; i < pinfo->num_adjs; i++) {
	EXTRA_REF(BLOCK(pinfo->adjs[i]))++;
    }
    for (i = 0; i < pinfo->num_plurals; i++) {
	EXTRA_REF(BLOCK(pinfo->plurals[i]))++;
    }
}
#endif

#ifdef DEBUG
/* Usage:  DEBUG_P(("foo: %s:%i", str, i)); */
void debug_parse P1V(char *, fmt) {
    va_list args;
    char buf[2048];
    char *p = buf;
    int n = debug_parse_depth - 1;
    V_DCL(char *fmt);
    
    while (n--) {
	*p++ = ' ';
	*p++ = ' ';
    }
    
    V_START(args, fmt);
    V_VAR(char *, fmt, args);
    vsprintf(p, fmt, args);
    va_end(args);

    tell_object(command_giver, buf);
    tell_object(command_giver, "\n");
}
#endif

INLINE static void bitvec_copy P2(unsigned int *, b1, unsigned int *, b2) {
    int i;
    for (i = 0; i < NUM_BITVEC_INTS; i++)
	b1[i] = b2[i];
}

INLINE static void bitvec_zero P1(unsigned int *, bv) {
    int i;
    for (i = 0; i < NUM_BITVEC_INTS; i++)
	bv[i] = 0;
}

INLINE static int intersect P2(unsigned int *, bv1, unsigned int *, bv2) {
    int i, found = 0;

    for (i = 0; i < NUM_BITVEC_INTS; i++)
	if (bv1[i] &= bv2[i]) found = 1;
    
    return found;
}

void all_objects P2(unsigned int *, bv, int, remote_flag) {
    int i;
    int num = (remote_flag ? num_objects : num_objects - num_people);
    int last = BV_WHICH(num);
    
    i = last;
    while (i--)
	bv[i] = ~0;
    if (BV_WHICH(num + 1) != last)
	bv[last] = ~0;
    else
	bv[last] = 2 * BV_BIT(num) - 1;
    for (i = last + 1; i < NUM_BITVEC_INTS; i++)
	bv[i] = 0;
}

/* Note:
 * For efficiency reasons, there is very little memory allocation in the
 * code.  We take advantage of the fact that the algorithm is recursive,
 * and a parse_* routine doesn't return until that branch has been completely
 * evaluated.  This allows us to safely keep pointers to temporary buffers
 * on the stack.
 *
 * alloca() would be better for this, but MudOS doesn't currently use it.
 */

INLINE static match_t *add_match P4(parse_state_t *, state, int, token, 
				    int, start, int, end) {
    match_t *ret;

    DEBUG_PP(("Adding match: tok = %i start = %i end = %i", token, start, end));
    ret = &matches[state->num_matches++];
    ret->first = start;
    ret->last = end;
    ret->token = token;
    switch (token) {
    default: /* Some kind of object; no literals here */
	state->match_level += 2;
	/* This next one is b/c in the presence of a singular and plural match,
	   we want the singular.  Consider 'take fish' with >1 fish. */
	if ((-token) & PLURAL_MODIFIER)
	    state->match_level--;
	if ((-token) & LIV_MODIFIER)
	    state->match_level++;
	if (!((-token) & VIS_ONLY_MODIFIER))
	    state->match_level++;
	break;
    case STR_TOKEN:
    case WRD_TOKEN:
	state->match_level++;
	break;
    case ERROR_TOKEN:
	state->num_errors++;
	break;
    }
    DEBUG_PP(("State is: %x, match_level: %i num_errors: %i\n", state, state->match_level, state->num_errors));
    return ret;
}

static void parse_copy_array P2(array_t *, arr, char ***, sarrp) {
    char **table;
    int j;

    if (!arr->size) {
	*sarrp = 0;
	return;
    }
    
    table = *sarrp = CALLOCATE(arr->size,char *, 
			       TAG_PARSER, "parse_copy_array");
    for (j = 0; j < arr->size; j++) {
	if (arr->item[j].type == T_STRING) {
	    DEBUG_PP(("Got: %s", "arr->item[j].u.string"));
	    if (arr->item[j].subtype == STRING_SHARED) {
		table[j] = ref_string(arr->item[j].u.string);
	    } else {
		table[j] = make_shared_string(arr->item[j].u.string);
	    }
	} else {
	    table[j] = 0;
	}
    }
}

static void interrogate_master PROT((void)) {
    svalue_t *ret;

    DEBUG_PP(("[master::parse_command_prepos_list]"));
    ret = apply_master_ob("parse_command_prepos_list", 0);
    if (ret && ret->type == T_ARRAY) {
	num_literals = ret->u.arr->size;
	parse_copy_array(ret->u.arr, &literals);
    }
    the_word = make_shared_string("the");
    a_word = make_shared_string("a");
    me_word = make_shared_string("me");
}

void f_parse_init PROT((void)) {
    parse_info_t *pi;

    if (num_literals == -1) {
	num_literals = 0;
	interrogate_master();
    }
    
    if (current_object->pinfo)
	return;

    pi = current_object->pinfo = ALLOCATE(parse_info_t, TAG_PARSER, "parse_init");
    pi->ob = current_object;
    pi->flags = 0;
}

static void remove_ids P1(parse_info_t *, pinfo) {
    int i;
    
    if (pinfo->flags & PI_SETUP) {
	if (pinfo->num_ids) {
	    for (i = 0; i < pinfo->num_ids; i++)
		free_string(pinfo->ids[i]);
	    FREE(pinfo->ids);
	}
	if (pinfo->num_plurals) {
	    for (i = 0; i < pinfo->num_plurals; i++)
		free_string(pinfo->plurals[i]);
	    FREE(pinfo->plurals);
	}
	if (pinfo->num_adjs) {
	    for (i = 0; i < pinfo->num_adjs; i++)
		free_string(pinfo->adjs[i]);
	    FREE(pinfo->adjs);
	}
    }
}

void f_parse_refresh PROT((void)) {
    if (!(current_object->pinfo))
	error("%s is not known by the parser.  Call parse_init() first.\n",
	      current_object->name);
    remove_ids(current_object->pinfo);
    current_object->pinfo->flags &= PI_VERB_HANDLER;
}

/* called from free_object() */
void parse_free P1(parse_info_t *, pinfo) {
    int i;

    if (pinfo->flags & PI_VERB_HANDLER) {
	for (i = 0; i < VERB_HASH_SIZE; i++) {
	    verb_t *v = verbs[i];
	    while (v) {
		verb_node_t **vn = &(v->node), *old;
		while (*vn) {
		    if ((*vn)->handler == pinfo->ob) {
			old = *vn;
			*vn = (*vn)->next;
			FREE(old);
		    } else vn = &((*vn)->next);
		}
		v = v->next;
	    }
	}
    }
    remove_ids(pinfo);
    FREE(pinfo);
}

static void hash_clean PROT((void)) {
    int i;
    hash_entry_t **nodep, *next;

    for (i = 0; i < HASH_SIZE; i++) {
	for (nodep = &hash_table[i]; *nodep && ((*nodep)->flags & HV_PERM);
	     nodep = &((*nodep)->next))
	    ;
	while (*nodep) {
	    next = (*nodep)->next;
	    free_string((*nodep)->name);
	    FREE((*nodep));
	    *nodep = next;
	}
    }
}

static void free_parse_result P1(parse_result_t *, pr) {
    int i, j;

    if (pr->ob)
	free_object(pr->ob, "free_parse_result");
    
    for (i = 0; i < 4; i++) {
	if (pr->res[i].func) FREE_MSTR(pr->res[i].func);
	if (pr->res[i].args) {
    	    for (j = 0; j < pr->res[i].num; j++)
	        free_svalue(((svalue_t *)pr->res[i].args) + j, "free_parse_result");
	    FREE(pr->res[i].args);
	}
    }
    FREE(pr);
}

static void clear_result P1(parse_result_t *, pr) {
    int i;

    pr->ob = 0;
    
    for (i = 0; i < 4; i++) {
	pr->res[i].func = 0;
	pr->res[i].args = 0;
    }
}

static void free_parse_globals PROT((void)) {
    int i;

    pi = 0;
    hash_clean();
    if (objects_loaded) {
	for (i = 0; i < num_objects; i++)
	    free_object(loaded_objects[i], "free_parse_globals");
	objects_loaded = 0;
    }
#ifdef DEBUG
    debug_parse_depth = 0;
#endif
}

token_def_t tokens[] = {
    { "OBJ", OBJ_A_TOKEN, 1 },
    { "STR", STR_TOKEN, 0 },
    { "WRD", WRD_TOKEN, 0 },
    { "LIV", LIV_A_TOKEN, 1 },
    { "OBS", OBS_TOKEN, 1 },
    { "LVS", LVS_TOKEN, 1 },
    { 0, 0 }
};

#define STR3CMP(x, y) (x[0] == y[0] && x[1] == y[1] && x[2] == y[2])

static int tokenize P1(char **, rule) {
    char *start = *rule;
    int i, n;
    token_def_t *td;
    
    while (*start == ' ') start++;

    if (!*start)
	return 0; /* at the end */

    *rule = strchr(start, ' ');

    if (!*rule)
	*rule = start + strlen(start);
    
    n = *rule - start;

    if (n == 3 || (n > 4 && start[3] == ':')) {
	td = tokens;
	while (td->name) {
	    if (STR3CMP(td->name, start)) {
		i = td->token;
		if (n != 3) {
		    if (!td->mod_legal)
			error("Illegal to have modifiers to '%s'\n", td->name);
		    /* modifiers */
		    start += 4;
		    n -= 4;
		    while (n--) {
			switch(*start++) {
			case 'l':
			    i = -((-i) | LIV_MODIFIER);
			    break;
			case 'v':
			    i = -((-i) | VIS_ONLY_MODIFIER);
			    break;
			case 'p':
			    i = -((-i) | PLURAL_MODIFIER);
			    break;
			default:
			    error("Unknown modifier '%c'\n", start[-1]);
			}
		    }
		}
		return i;
	    }
	    td++;
	}
    }

    /* it's not a standard token.  Check the literals */
    for (i = 0; i < num_literals; i++) {
	if ((literals[i][n] == 0) && (strncmp(literals[i], start, n) == 0))
	    return i + 1;
    }

    {
	char buf[256];

	if (n > 50) {
	    strncpy(buf, start, 50);
	    strcpy(buf + 50, "...");
	} else {
	    strncpy(buf, start, n);
	    buf[n] = 0;
	}

	error("Unknown token '%s'\n", buf);
    }
    return 0;
}

static void make_rule P2(char *, rule, int *, tokens) {
    int idx = 0;

    while (idx < 10) {
	if (!(tokens[idx++] = tokenize(&rule)))
	    return; /* we got to the end */
    }
    error("Only 10 tokens permitted per rule!\n");
}

static void free_words PROT((void)) {
    int i;

    for (i = 0; i < num_words; i++)
	if (words[i].type == WORD_ALLOCATED)
	    FREE_MSTR(words[i].string);
    num_words = 0;
}

static void interrogate_object P1(object_t *, ob) {
    svalue_t *ret;

    DEBUG_P(("Interogating %s.", ob->name));
    
    DEBUG_PP(("[parse_command_id_list]"));
    ret = apply("parse_command_id_list", ob, 0, ORIGIN_DRIVER);
    if (ret && ret->type == T_ARRAY) {
	ob->pinfo->num_ids = ret->u.arr->size;
	parse_copy_array(ret->u.arr, &ob->pinfo->ids);
    } else ob->pinfo->num_ids = 0;
    if (ob->flags & O_DESTRUCTED) return;
    ob->pinfo->flags |= PI_SETUP;

    DEBUG_PP(("[parse_command_plural_id_list]"));
    ret = apply("parse_command_plural_id_list", ob, 0, ORIGIN_DRIVER);
    if (ret && ret->type == T_ARRAY) {
	ob->pinfo->num_plurals = ret->u.arr->size;
	parse_copy_array(ret->u.arr, &ob->pinfo->plurals);
    } else ob->pinfo->num_plurals = 0;
    if (ob->flags & O_DESTRUCTED) return;

    DEBUG_PP(("[parse_command_adjectiv_id_list]"));
    ret = apply("parse_command_adjectiv_id_list", ob, 0, ORIGIN_DRIVER);
    if (ret && ret->type == T_ARRAY) {
	ob->pinfo->num_adjs = ret->u.arr->size;
	parse_copy_array(ret->u.arr, &ob->pinfo->adjs);
    } else ob->pinfo->num_adjs = 0;
    if (ob->flags & O_DESTRUCTED) return;

    DEBUG_PP(("[is_living]"));
    ret = apply("is_living", ob, 0, ORIGIN_DRIVER);
    if (!IS_ZERO(ret)) {
	ob->pinfo->flags |= PI_LIVING;
	DEBUG_PP(("(yes)"));
    }
    if (ob->flags & O_DESTRUCTED) return;

    DEBUG_PP(("[inventory_accessible]"));
    ret = apply("inventory_accessible", ob, 0, ORIGIN_DRIVER);
    if (!IS_ZERO(ret)) {
	ob->pinfo->flags |= PI_INV_ACCESSIBLE;
	DEBUG_PP(("(yes)"));
    }
    if (ob->flags & O_DESTRUCTED) return;

    DEBUG_PP(("[inventory_visible]"));
    ret = apply("inventory_visible", ob, 0, ORIGIN_DRIVER);
    if (!IS_ZERO(ret)) {
	ob->pinfo->flags |= PI_INV_VISIBLE;
	DEBUG_PP(("(yes)"));
    }
}

static void rec_add_object P2(object_t *, ob, int, inreach) {
    object_t *o;

    if (!ob) return;
    if (ob->flags & O_DESTRUCTED) return;
    if (ob->pinfo) {
	if (ob == parse_user)
	    me_object = num_objects;
	object_flags[num_objects] = inreach;
	loaded_objects[num_objects++] = ob;
	add_ref(ob, "rec_add_object");
	if (!(ob->pinfo->flags & PI_INV_VISIBLE))
	    return;
	if (!(ob->pinfo->flags & PI_INV_ACCESSIBLE))
	    inreach = 0;
    }
    o = ob->contains;
    while (o) {
	rec_add_object(o, inreach);
	o = o->next_inv;
    }
}

static void find_uninited_objects P1(object_t *, ob) {
    object_t *o;
    
    if (!ob) return;
    if (ob->flags & O_DESTRUCTED) return;
    if (ob->pinfo && !(ob->pinfo->flags & PI_SETUP)) {
	loaded_objects[num_objects++] = ob;
	add_ref(ob, "find_united_objects");
    }
    o = ob->contains;
    while (o) {
	find_uninited_objects(o);
	o = o->next_inv;
    }
}    

static hash_entry_t *add_hash_entry P1(char *, str) {
    int h = DO_HASH(str, HASH_SIZE);
    hash_entry_t *he;

    DEBUG_PP(("add_hash_entry: %s", str));
    he = hash_table[h];
    while (he) {
	if (he->name == str)
	    return he;
	he = he->next;
    }

    he = ALLOCATE(hash_entry_t, TAG_PARSER, "add_hash_entry");
    he->name = ref_string(str);
    bitvec_zero(he->pv.noun);
    bitvec_zero(he->pv.plural);
    bitvec_zero(he->pv.adj);
    he->next = hash_table[h];
    he->flags = 0;
    hash_table[h] = he;
    return he;
}

static void add_to_hash_table P2(object_t *, ob, int, index) {
    int i;
    parse_info_t *pi = ob->pinfo;
    hash_entry_t *he;

    DEBUG_PP(("add_to_hash_table: %s", ob->name));
    for (i = 0; i < pi->num_ids; i++) {
	he = add_hash_entry(pi->ids[i]);
	he->flags |= HV_NOUN;
	he->pv.noun[BV_WHICH(index)] |= BV_BIT(index);
    }
    for (i = 0; i < pi->num_plurals; i++) {
	he = add_hash_entry(pi->plurals[i]);
	he->flags |= HV_PLURAL;
	he->pv.plural[BV_WHICH(index)] |= BV_BIT(index);
    }
    for (i = 0; i < pi->num_adjs; i++) {
	he = add_hash_entry(pi->adjs[i]);
	he->flags |= HV_ADJ;
	he->pv.adj[BV_WHICH(index)] |= BV_BIT(index);
    }

    if (pi->flags & PI_LIVING)
	cur_livings[BV_WHICH(index)] |= BV_BIT(index);
    
    if (object_flags[index])
	cur_accessible[BV_WHICH(index)] |= BV_BIT(index);
}

static void init_users() {
    svalue_t *ret;
    int i;
    object_t *ob;
    
    /* this should be cached */
    DEBUG_PP(("[master::parse_command_users]"));
    ret = apply_master_ob("parse_command_users", 0);
    if (!ret || ret->type != T_ARRAY) return;
    
    for (i = 0; i < ret->u.arr->size; i++) {
	if (ret->u.arr->item[i].type == T_OBJECT
	    && (ob = ret->u.arr->item[i].u.ob)->pinfo
	    && !(ob->pinfo->flags & PI_SETUP)) {
	    DEBUG_PP(("adding: %s", ob->name));
	    loaded_objects[num_objects++] = ob;
	    add_ref(ob, "init_users");
	}
    }
}

static void load_objects PROT((void)) {
    int i;
    svalue_t *ret;
    object_t *ob, *env;

    /* 1. Find things that need to be interrogated
     * 2. interrogate them
     * 3. build the object list
     *
     * If some of this looks suboptimal, consider:
     * 1. LPC code can error, and we don't want to leak, so we must be
     *    careful that all structures are consistent when we call LPC code
     * 2. LPC code can move objects, so we don't want to call LPC code
     *    while walking inventories.
     */
    bitvec_zero(cur_livings);
    bitvec_zero(cur_accessible);
    num_objects = 0;
    if (!parse_user || parse_user->flags & O_DESTRUCTED)
	error("No this_player()!\n");

    find_uninited_objects(parse_user->super);
    init_users();
    objects_loaded = 1;
    for (i = 0; i < num_objects; i++)
	interrogate_object(loaded_objects[i]);
    for (i = 0; i < num_objects; i++)
	free_object(loaded_objects[i], "free_parse_globals");
    num_objects = 0;
    
    rec_add_object(parse_user->super, 1);
    /* this should be cached */
    DEBUG_PP(("[master::parse_command_users]"));
    ret = apply_master_ob("parse_command_users", 0);
    num_people = 0;
    if (ret && ret->type == T_ARRAY) {
	for (i = 0; i < ret->u.arr->size; i++) {
	    if (ret->u.arr->item[i].type != T_OBJECT) continue;
	    /* check if we got them already */
	    ob = ret->u.arr->item[i].u.ob;
	    env = ob->super;
	    while (env) {
		if (env == parse_user->super)
		    break;
		env = env->super;
	    }
	    if (env) continue;
	    object_flags[num_objects + num_people] = 1;
	    loaded_objects[num_objects + num_people++] = ob;
	    add_ref(ob, "load_objects");
	}
    }
    num_objects += num_people;

    for (i = 0; i < num_objects; i++)
	add_to_hash_table(loaded_objects[i], i);
}

static int get_single P1(unsigned int *, bv) {
    int i, res = -1;

    DEBUG_PP(("get_single: %x %x %x %x", bv[0], bv[1], bv[2], bv[3]));
    for (i = 0; i < NUM_BITVEC_INTS; i++) {
	if (bv[i]) {
	    if (res != -1) return -1;
	    res = i;
	}
    }
    if (res < 0) return -1;

    i = bv[res];
    res *= 32;
    /* Binary search for the set bit, unrolled for speed. */
    if (i & 0x0000ffff) {
	if (i & 0xffff0000)
	    return -1;
    } else {
	i >>= 16;
	res += 16;
    }

    if (i & 0x00ff) {
	if (i & 0xff00)
	    return -1;
    } else {
	i >>= 8;
	res += 8;
    }
    
    if (i & 0x0f) {
	if (i & 0xf0)
	    return -1;
    } else {
	i >>= 4;
	res += 4;
    }
    
    if (i & 0x3) {
	if (i & 0xc)
	    return -1;
    } else {
	i >>= 2;
	res += 2;
    }
    
    if (i & 0x1) {
	if (i & 0x2)
	    return -1;
	DEBUG_PP((" -> %i", res));
	return res;
    } else {
	DEBUG_PP((" -> %i", res));
	return res + 1;
    }
}

/* equivalent to strcpy(x, y); return x + strlen(y), but faster */
static char *strput P2(char *, x, char *, y) {
    while ((*x++ = *y++))
	;
    return x - 1;
}

static char *query_the_short P2(char *, start, object_t *, ob) {
    svalue_t *ret;
    
    if (ob->flags & O_DESTRUCTED || 
	!(ret = apply("the_short", ob, 0, ORIGIN_DRIVER))
	|| ret->type != T_STRING) {
	return strput(start, "the thing");
    }
    return strput(start, ret->u.string);
}

static void error_is_not P4(char *, buf, hash_entry_t *, adj, 
			    int, multiple, hash_entry_t *, noun) {
    unsigned int *objects = noun->pv.noun;
    int ob;

    /* first case.  If the multiple flag is set, there was more than one
     * adjective.  We don't bother to figure out which adjectives do and
     * do not apply.  For example: 'sharp red sword'
     * There could be sharp swords, and red swords, but no sharp red ones.
     * We have a adjective they specified at this point, however, so we
     * use it to make a decent error message
     * We don't count swords in this case, since we can't do any better for
     * one object, unless we want to go through all the adjectives figuring
     * out which one (or more) is the bad one.
     */
    if (multiple) {
	sprintf(buf, "There is no such %s %s here.\n", 
		adj->name, noun->name);
	return;
    }

    /* If adj is zero, the failure was due to refering to a remote living */
    if (!adj) {
	sprintf(buf, "%s isn't here.\n", noun->name);
	if (islower(*buf))
	    *buf = toupper(*buf);
	return;
    }
    /* check if there is more than one of that noun.  In this case, we've
       only been called with zero or one adjectives */
    if (get_single(objects) == -1) {
	/* 'the red sword' with many swords, none of
	   which are red */
	char *pl = pluralize(noun->name);
	
	sprintf(buf, "None of the %s are %s.\n", pl,
		adj->name);
	FREE_MSTR(pl);
	return;
    } else {
	/* Easy case.  'the red sword', one sword, which isn't red. */
	char *p;
	
	p = query_the_short(buf, loaded_objects[ob]);
	sprintf(p, " is not %s.\n", adj->name);
	if (islower(*buf))
	    *buf = toupper(*buf);
	return;
    }
}

static char *strput_words P3(char *, str, int, first, int, last) {
    char *p = words[first].start;
    char *end = words[last].end;

    /* can these happen any more? */
    while (isspace(p[0]))
	p++;
    while (isspace(end[0]))
	end--;
    while (p <= end)
	*str++ = *p++;
    *str = 0;
    return str;
}

static void error_there_is_no P3(char *, buf, int, start, int, end) {
    char *p;
    
    p = strput(buf, "There is no ");
    p = strput_words(p, start, end);
    strput(p, " here.\n");
}

static void error_not_living P4(char *, buf, unsigned int *, objects,
				hash_entry_t *, noun, hash_entry_t *, adj) {
    /* The X isn't alive. (if only 1 of noun)
       The X you refered to isn't alive.
       None of the X are alive.
       None of the X you refered to are alive */
    if (get_single(objects) != -1) {
	if (adj)
	    sprintf(buf, "The %s you refered to isn't alive.\n", noun->name);
	else
	    sprintf(buf, "The %s isn't alive.\n", noun->name);
    } else {
	char *pl = pluralize(noun->name);
	if (adj)
	    sprintf(buf, "None of the %s you refered to are alive.\n", pl);
	else
	    sprintf(buf, "None of the %s are alive.\n", pl);
	FREE_MSTR(pl);
    }
}

static void error_not_accessible P4(char *, buf, unsigned int *, objects,
				   hash_entry_t *, noun, hash_entry_t *, adj) {
    if (get_single(objects) != -1) {
	if (adj)
	    sprintf(buf, "The %s you refered to isn't within reach.\n", noun->name);
	else
	    sprintf(buf, "The %s isn't within reach.\n", noun->name);
    } else {
	char *pl = pluralize(noun->name);
	if (adj)
	    sprintf(buf, "None of the %s you refered to are within reach.\n", pl);
	else
	    sprintf(buf, "None of the %s are within reach.\n", pl);
	FREE_MSTR(pl);
    }
}

static void error_ambig P3(char *, buf, hash_entry_t *, noun,
			   unsigned int *, objects) {
    /* This should be more sophisticated */
    sprintf(buf, "Which %s do you mean?\n", noun->name);
}

static void parse_obj P2(int, tok, parse_state_t *, state) {
    parse_state_t local_state;
    unsigned int objects[NUM_BITVEC_INTS];
    unsigned int save_obs[NUM_BITVEC_INTS];
    unsigned int err_obs[NUM_BITVEC_INTS];
    int start = state->word_index;
    char buf[1024];
    char *str;
    hash_entry_t *hnode, *last_adj = 0;
    int multiple_adj = 0;
    int tmp;
    match_t *mp;

    DEBUG_INC;
    DEBUG_P(("parse_obj:"));
    
    all_objects(objects, parse_vn->handler->pinfo->flags & PI_REMOTE_LIVINGS);

    while (1) {
	str = words[state->word_index++].string;
	DEBUG_PP(("Word is %s", str));
	if (str == the_word || str == a_word)
	    continue;
	if (str == me_word) {
	    local_state = *state;
	    mp = add_match(&local_state, tok,
			   start, state->word_index - 1);
	    mp->val.number = me_object;
	    parse_rule(&local_state);
	}
	hnode = hash_table[DO_HASH(str, HASH_SIZE)];
	while (hnode) {
	    if (hnode->name == str) {
		if (hnode->flags & HV_NOUN) {
		    int explore_errors = !best_match &&
			state->num_errors < best_num_errors;
		    DEBUG_P(("Found noun: %s", str));
		    local_state = *state;
		    bitvec_copy(save_obs, objects);

		    /* Sigh, I want to throw exceptions */
		    if (!intersect(objects, hnode->pv.noun)) {
			if (!explore_errors) goto skip_it;
			
			error_is_not(buf, last_adj, multiple_adj, hnode);
			goto we_have_an_error;
		    }
		    if ((-tok) & LIV_MODIFIER) {
			if (explore_errors)
			    bitvec_copy(err_obs, objects);
			if (!intersect(objects, cur_livings)) {
			    if (!explore_errors) goto skip_it;
			
			    error_not_living(buf, err_obs, hnode, last_adj);
			    goto we_have_an_error;
			}
		    }
		    if (!((-tok) & VIS_ONLY_MODIFIER)) {
			if (explore_errors)
			    bitvec_copy(err_obs, objects);
			if (!intersect(objects, cur_accessible)) {
			    if (!explore_errors) goto skip_it;
			
			    error_not_accessible(buf, err_obs, hnode, last_adj);
			    goto we_have_an_error;
			}
		    }
		    if ((tmp = get_single(objects)) == -1) {
			if (!explore_errors) goto skip_it;
			
			error_ambig(buf, hnode, objects);
			goto we_have_an_error;
		    }
		    DEBUG_P(("Found object: %s", loaded_objects[tmp]->name));
		    mp = add_match(&local_state, tok,
				   start, state->word_index - 1);
		    mp->val.number = tmp;
		    goto do_the_parse;

		we_have_an_error:
		    DEBUG_P((buf));
		    mp = add_match(&local_state, ERROR_TOKEN,
				   start, state->word_index - 1);
		    mp->val.string = buf;

		do_the_parse:
		    parse_rule(&local_state);

		skip_it:
		    bitvec_copy(objects, save_obs);
		}
		if (hnode->flags & HV_ADJ) {
		    DEBUG_P(("Found adj: %s", str));
		    intersect(objects, hnode->pv.adj);
		    if (last_adj) multiple_adj = 1;
		    last_adj = hnode;
		} else {
		    DEBUG_DEC;
		    return;
		}
		break;
	    }
	    hnode = hnode->next;
	}
	if (!hnode) break;
    }
    DEBUG_PP(("exiting ..."));
    DEBUG_DEC;
}

/* misnomer; this func is not recursive */
static verb_t *parse_verb P1(char *, str) {
    char *vb = findstring(str);
    verb_t *ret;

    if (!vb) return 0;

    ret = verbs[DO_HASH(vb, VERB_HASH_SIZE)];
    while (ret) {
	if (ret->name == vb) {
	    words[0].string = vb;
	    words[0].type = 0;
	    return ret;
	}
	ret = ret->next;
    }
    return 0;
}

static char *make_error_message P1(int, which) {
    char buf[1024];
    char *p;
    int cnt = 0;
    int tok;
    int index = 0;
    
    p = strput(buf, "You can't ");
    p = strput(p, words[0].string);
    *p++ = ' ';
    while ((tok = parse_vn->token[index++])) {
	switch (tok) {
	case STR_TOKEN:
	    if (cnt == which - 1) {
		p = strput(p, "that ");
		cnt++;
		break;
	    }
	    /* FALLTHRU */
	case WRD_TOKEN:
	    p = strput_words(p, matches[cnt].first, matches[cnt].last);
	    *p++ = ' ';
	    cnt++;
	    break;
	default:
	    if (tok >= 0) {
		p = strput(p, literals[tok - 1]);
		*p++ = ' ';
	    } else {
		if (cnt == which - 1) {
		    p = strput(p, "that ");
		} else {
		    p = query_the_short(p, loaded_objects[matches[cnt].val.number]);
		    *p++ = ' ';
		}
		cnt++;
	    }
	    break;
	}
    }
    p--;
    strcpy(p, ".\n");
    DEBUG_P((buf));
    return string_copy(buf, "make_error_message");
}

static char *current_possible_error = 0;

static int process_answer P3(parse_state_t *, state, svalue_t *, sv,
			     int, which) {
    if (!sv) return 0;
    if (sv->type == T_NUMBER) {
	DEBUG_P(("Return value was: %i", sv->u.number));
	if (sv->u.number)
	    return 1;
	if (state->num_errors == best_num_errors) {
	    DEBUG_P(("Have a better match; aborting ..."));
	    return -2;
	}
	if (state->num_errors++ == 0) {
	    if (current_possible_error) FREE_MSTR(current_possible_error);
	    current_possible_error = make_error_message(which);
	}
	return -1;
    }
    if (sv->type != T_STRING) {
	DEBUG_P(("Return value was not a string or number.", sv->u.number));
	return 0;
    }
    DEBUG_P(("Returned string was: %s", sv->u.string));
    if (state->num_errors == best_num_errors) {
	DEBUG_P(("Have a better match; aborting ..."));
	return -2;
    }
    if (state->num_errors++ == 0) {
	if (current_possible_error) FREE_MSTR(current_possible_error);
	current_possible_error = string_copy(sv->u.string, "process_answer");
    }
    return -1;
}

static int push_real_names P1(int, try) {
    int index = 0, match = 0;
    int tok;

    if (try >= 2) {
	char tmpbuf[1024];
	strput_words(tmpbuf, 0, 0);
	push_string(tmpbuf, STRING_SHARED);
    }
    
    while ((tok = parse_vn->token[index++])) {
	if (tok < 0) {
	    char tmp[1024];

	    strput_words(tmp, matches[match].first, matches[match].last);
	    push_malloced_string(string_copy(tmp, "push_real_names"));
	    match++;
	}
    }
    return match + (try >= 2);
}

char *rule_string P1(verb_node_t *, vn) {
    int index = 0;
    int tok;
    static char buf[1024];
    char *p;

    p = buf;
    
    while (1) {
	switch (tok = vn->token[index++]) {
	case OBJ_A_TOKEN:
	case OBJ_TOKEN:
	    p = strput(p, "OBJ ");
	    break;
	case LIV_A_TOKEN:
	case LIV_TOKEN:
	    p = strput(p, "LIV ");
	    break;
	case OBS_TOKEN:
	case ADD_MOD(OBS_TOKEN, VIS_ONLY_MODIFIER):
	    p = strput(p, "OBS ");
	    break;
	case LVS_TOKEN:
	case ADD_MOD(LVS_TOKEN, VIS_ONLY_MODIFIER):
	    p = strput(p, "LVS ");
	    break;
	case STR_TOKEN:
	    p = strput(p, "STR ");
	    break;
	case WRD_TOKEN:
	    p = strput(p, "WRD ");
	    break;
	case 0:
	    if (p == buf) {
		*buf = 0;
	    } else {
		*(p-1) = 0; /* nuke last space */
	    }
            return buf;
	default:
	    p = strput(p, literals[tok - 1]);
	    *p++ = ' ';
	    break;
	}
    }
}

static int make_function P4(char *, buf, char *, pre, 
			    parse_state_t *, state, int, try) {
    int index = 0, match = 0;
    int on_stack = 0;
    int tok;
    /* try = 0: "read_about_str_from_obj"
     * try = 1: "read_word_str_word_obj"
     * try = 2: "verb_word_str_word_obj"
     * try = 3: "verb_rule"
     */

    buf = strput(buf, pre);
    if (try < 2) {
	buf = strput(buf, words[0].string);
    } else {
	buf = strput(buf, "verb");
	push_string(words[0].string, STRING_SHARED);
	on_stack++;
    }

    if (try == 3) {
	buf = strput(buf, "_rule");
	/* leave the 0; this effectively truncates the string. */
	buf++;
	push_constant_string(rule_string(parse_vn));
	on_stack++;
    }
    while ((tok = parse_vn->token[index++])) {
	*buf++ = '_';
	switch (tok) {
	case OBJ_TOKEN:
	case OBJ_A_TOKEN:
	    buf = strput(buf, "obj");
	    goto put_obj_value;

	case OBS_TOKEN:
	case ADD_MOD(OBS_TOKEN, VIS_ONLY_MODIFIER):
	    buf = strput(buf, "obs");
	    goto put_obj_value;
	    
	case LVS_TOKEN:
	case ADD_MOD(LVS_TOKEN, VIS_ONLY_MODIFIER):
	    buf = strput(buf, "lvs");
	    goto put_obj_value;

	case LIV_TOKEN:
	case LIV_A_TOKEN:
	    buf = strput(buf, "liv");

	put_obj_value:
	    if (matches[match].token == ERROR_TOKEN || 
		(loaded_objects[matches[match].val.number]->flags & O_DESTRUCTED)) {
		push_number(0);
	    } else {
		push_object(loaded_objects[matches[match].val.number]);
	    }
	    match++;
	    on_stack++;
	    break;
	case STR_TOKEN:
	    {
		char tmp[1024];
		buf = strput(buf, "str");
		strput_words(tmp, matches[match].first, matches[match].last);
		push_malloced_string(string_copy(tmp, "push_real_names"));
		match++;
		on_stack++;
	    }
	    break;
	case WRD_TOKEN:
	    {
		char tmp[1024];
		buf = strput(buf, "wrd");
		strput_words(tmp, matches[match].first, matches[match].last);
		push_malloced_string(string_copy(tmp, "push_real_names"));
		match++;
		on_stack++;
	    }
	    break;
	default:
	    if (!try) {
		buf = strput(buf, literals[tok - 1]);
	    } else if (try < 3) {
		buf = strput(buf, "word");
		push_string(literals[tok - 1], STRING_SHARED);
		on_stack++;
	    }
	}
    }
    return on_stack;
}

static char *prefixes[] = { "can_", "direct_", "indirect_" };

#define SET_OB(x) ob = (x); if (ob->flags & O_DESTRUCTED) return;

static void we_are_finished P1(parse_state_t *, state) {
    char func[256];
    char *p;
    int ret;
    int which, try, args, mtch = 0;
    object_t *ob;
    int local_error;

    DEBUG_INC;
    DEBUG_P(("we_are_finished"));
    
    /* ignore it if we already have somethign better */
    if (best_match >= state->match_level) {
	DEBUG_P(("Have a better match; aborting ..."));
	DEBUG_DEC;
	return;
    }
    if (state->num_errors) {
	local_error = 0;
	if (state->num_errors > best_num_errors) return;
	if (state->num_errors == best_num_errors
	    && state->match_level < best_error_match) return;
    } else local_error = 1; /* if we have an error, it was local */
    SET_OB(parse_user);

    for (which = 0; which < 3; which++) {
	if (ob)
	    for (try = 0, ret = 0; !ret && try < 8; try++) {
		if (try == 4) {
			    SET_OB(parse_vn->handler);
		}
		args = make_function(func, prefixes[which], state, try % 4);
		args += push_real_names(try);
		DEBUG_P(("Trying %s ...", func));
		ret = process_answer(state,
				     apply(func, ob, args, ORIGIN_DRIVER),
				     which);
		if (ob->flags & O_DESTRUCTED) {
		    DEBUG_DEC;
		    return;
		}
		if (ret == -2) {
		    DEBUG_DEC;
		    return;
		}
		if (ret) break;
	    }
	if (try == 8) {
	    if (state->num_errors == best_num_errors) {
		DEBUG_P(("Nothing matched and we have a better match"));
		DEBUG_DEC;
		return;
	    }
	    if (state->num_errors++ == 0) {
		if (current_possible_error) FREE_MSTR(current_possible_error);
		current_possible_error = make_error_message(which);
	    }
	}
	while (mtch != state->num_matches && !((-matches[mtch].token) & OBJ_A_TOKEN)) {
	    mtch++;
	    if (mtch == state->num_matches) break;
	}
	if (mtch == state->num_matches) break;
	if (matches[mtch].token != ERROR_TOKEN) {
	    SET_OB(loaded_objects[matches[mtch].val.number]);
	} else {
	    DEBUG_P(("Skipping next iteration; match is an error"));
	    ob = 0;
	}
	mtch++;
    }
    if (state->num_errors) {
	if (state->num_errors == best_num_errors &&
	    state->match_level <= best_error_match) {
	    DEBUG_P(("Have better match; aborting ..."));
	    DEBUG_DEC;
	    return;
	}
	best_num_errors = state->num_errors;
	best_error_match = state->match_level;
	if (current_error) FREE_MSTR(current_error);
	if (local_error) {
	    current_error = current_possible_error;
	    current_possible_error = 0;
	} else {
	    for (mtch = 0; matches[mtch].token != ERROR_TOKEN; mtch++)
		;
	    current_error = string_copy(matches[mtch].val.string, 
					"we_are_finished");		
	}
	DEBUG_P(("current error set to %s", current_error));
    } else {
	best_match = state->match_level;
	if (best_result) free_parse_result(best_result);
	best_result = ALLOCATE(parse_result_t, TAG_PARSER, "we_are_finished");
	clear_result(best_result);
	SET_OB(parse_vn->handler);
	best_result->ob = ob;
	add_ref(ob, "best_result");
	for (try = 0; try < 4; try++) {
	    args = make_function(func, "do_", state, try);
	    args += push_real_names(try);
	    best_result->res[try].func = string_copy(func, "best_result");
	    best_result->res[try].num = args;
	    if (args) {
		p = (char *)(best_result->res[try].args = CALLOCATE(args,
				       svalue_t, TAG_PARSER, "best_result"));
		memcpy(p, (char *)(sp - args + 1), args * sizeof(svalue_t));
		sp -= args;
	    }
	}
	DEBUG_P(("Saving successful match: %s", best_result->res[0].func));
    }
    DEBUG_DEC;
}

static void do_the_call PROT((void)) {
    int i, n;
    object_t *ob = best_result->ob;
    
    for (i = 0; i < 4; i++) {
	if (ob->flags & O_DESTRUCTED) return;
	n = best_result->res[i].num;
	if (n) {
	    memcpy((char *)(sp + 1), best_result->res[i].args, n*sizeof(svalue_t));
	    /*
	     * Make sure we haven't dumped any dested obs onto the stack;
	     * this also updates sp.
	     */
	    while (n--) {
		if ((++sp)->type == T_OBJECT && (sp->u.ob->flags & O_DESTRUCTED)) {
		    free_object(sp->u.ob, "do_the_call");
		    *sp = const0;
		}
	    }
	    FREE(best_result->res[i].args);
	}
	best_result->res[i].args = 0;
	DEBUG_P(("Calling %s ...", best_result->res[i].func));
	if (apply(best_result->res[i].func, ob,
		  best_result->res[i].num, ORIGIN_DRIVER)) return;
    }
    error("Parse accepted, but no do_* function found in object %s!\n",
	  ob->name);
}

static void parse_rule P1(parse_state_t *, state) {
    char buf[1024];
    int tok;
    parse_state_t local_state;
    match_t *mp;
    int start;

    DEBUG_INC;
    DEBUG_P(("parse_rule"));
    while (1) {
	tok = parse_vn->token[state->tok_index++];
	if (state->word_index == num_words && tok) {
	    DEBUG_P(("Ran out of words to parse."));
	    DEBUG_DEC;
	    return;
	}
	switch (tok) {
	case 0:
	    if (state->word_index == num_words)
		we_are_finished(state);
	    DEBUG_P(("exiting parse_rule ..."));
	    DEBUG_DEC;
	    return;
	case OBJ_TOKEN:
	case LIV_TOKEN:
	case OBJ_A_TOKEN:
	case LIV_A_TOKEN:
	case OBS_TOKEN:
	case ADD_MOD(OBS_TOKEN, VIS_ONLY_MODIFIER):
	case LVS_TOKEN:
	case ADD_MOD(LVS_TOKEN, VIS_ONLY_MODIFIER):
	    local_state = *state;
	    parse_obj(tok, &local_state);
	    if (!best_match && state->num_errors < best_num_errors) {
		start = state->word_index++;
		while (state->word_index <= num_words) {
		    local_state = *state;
		    mp = add_match(&local_state, ERROR_TOKEN,
				   start, state->word_index - 1);
		    error_there_is_no(buf, start, state->word_index - 1);
		    mp->val.string = buf;
		    parse_rule(&local_state);
		    state->word_index++;
		}
	    }
	    DEBUG_P(("Done trying to match OBJ"));
	    DEBUG_DEC;
	    return;
	case STR_TOKEN:
	    if (!parse_vn->token[state->tok_index]) {
		/* At end; match must be the whole thing */
		start = state->word_index;
		state->word_index = num_words;
		add_match(state, STR_TOKEN, start, num_words - 1);
		DEBUG_P(("Taking rest of sentence as STR"));
		parse_rule(state);
	    } else {
		start = state->word_index++;
		while (state->word_index <= num_words) {
		    local_state = *state;
		    add_match(&local_state, STR_TOKEN,
			      start, state->word_index - 1);
		    DEBUG_P(("Trying potential STR match"));
		    parse_rule(&local_state);
		    state->word_index++;
		}
	    }
	    DEBUG_P(("Done trying to match STR"));
	    DEBUG_DEC;
	    return;
	case WRD_TOKEN:
	    add_match(state, WRD_TOKEN, state->word_index, state->word_index);
	    state->word_index++;
	    DEBUG_P(("Trying WRD match"));
	    parse_rule(state);
	    return;
	default:
	    if (literals[tok - 1] == words[state->word_index].string) {
		state->word_index++;
		DEBUG_P(("Matched literal: %s", literals[tok - 1]));
		state->match_level += 2;
	    } else {
		if (state->tok_index == 1) {
		    DEBUG_DEC;
		    return;
		}
		
		DEBUG_P(("last match to error ..."));
		switch (parse_vn->token[state->tok_index - 2]) {
		case STR_TOKEN:
		    DEBUG_P(("Nope.  STR rule last."));
		    DEBUG_DEC;
		    return;
		case OBJ_TOKEN:
		case LIV_TOKEN:
		case OBJ_A_TOKEN:
		case LIV_A_TOKEN:
		case OBS_TOKEN:
		case ADD_MOD(OBS_TOKEN, VIS_ONLY_MODIFIER):
		case LVS_TOKEN:
		case ADD_MOD(LVS_TOKEN, VIS_ONLY_MODIFIER):
		    {
			match_t *last;
			
			while (literals[tok - 1] != words[state->word_index++].string) {
			    if (state->word_index == num_words) {
				DEBUG_P(("Literal not found in forward search"));
				DEBUG_DEC;
				return;
			    }
			}
			last = &matches[state->num_matches];
			switch (last->token) {
			case ADD_MOD(LVS_TOKEN, VIS_ONLY_MODIFIER):
			case LIV_A_TOKEN:
			    state->match_level -= 4;
			    break;
			case LIV_TOKEN:
			case OBJ_A_TOKEN:
			case ADD_MOD(OBS_TOKEN, VIS_ONLY_MODIFIER):
			case LVS_TOKEN:
			    state->match_level -= 3;
			    break;
			case OBJ_TOKEN:
			case OBS_TOKEN:
			    state->match_level -= 2;
			    break;
			case STR_TOKEN:
			case WRD_TOKEN:
			    state->match_level--;
			}
			DEBUG_P(("Changing last match."));
			last->token = ERROR_TOKEN;
			last->last = state->word_index-1;
			if (state->num_errors++ == 0) {
			    error_there_is_no(buf, last->first, 
					      state->word_index - 1);
			    last->val.string = buf;
			}
		    }
		    break;
		default:
		    DEBUG_P(("default case"));
		    DEBUG_DEC;
		    return;
		}
	    }
	}
    }
    DEBUG_DEC;
}

static int check_literal P2(int, lit, int, start) {
    DEBUG_PP(("check_literal: %s", literals[lit - 1]));

    while (start < num_words) {
	if (literals[lit - 1] == words[start++].string) {
	    DEBUG_PP(("yes"));
	    return start;
	}
    }
    DEBUG_PP(("no"));
    return 0;
}

static void parse_rules PROT((void)) {
    int pos;
    parse_state_t local_state;

    parse_vn = parse_verb_entry->node;
    while (parse_vn) {
	DEBUG_PP(("Rule: %s", rule_string(parse_vn)));
	if (!parse_restricted || parse_vn->handler == parse_restricted) {
	    pos = 0;
	    if ((!parse_vn->lit[0] ||
		 (pos = check_literal(parse_vn->lit[0], 1)))
		&& (!parse_vn->lit[1] ||
		    check_literal(parse_vn->lit[1], pos))) {
		DEBUG_P(("Trying rule: %s", rule_string(parse_vn)));

		local_state.tok_index = 0;
		local_state.word_index = 1;
		local_state.match_level = 1;
		local_state.num_matches = 0;
		local_state.num_errors = 0;
		parse_rule(&local_state);
	    }
	}
	parse_vn = parse_vn->next;
    }
}

static void reset_error PROT((void)) {
    if (current_error)
	FREE_MSTR(current_error);
    current_error = 0;
    best_match = 0;
    best_error_match = 0;
    best_num_errors = 5732; /* Yes.  Exactly 5,732 errors.  Don't ask. */
}

static void parse_recurse P3(char **, iwords, char **, ostart, char **, oend) {
    char buf[1024];
    char *p, *q;
    char **iwp = iwords;
    int first = 1;
    int l, idx;

    if (*iwords[0]) {
	*buf = 0;
	p = buf;
	do {
	    l = iwp[1] - iwp[0] - 1;
	    strcpy(p, *iwp++);
	    p += l;
	    if ((q = findstring(buf))) {
		words[num_words].type = 0;
		words[num_words].start = ostart[0];
		words[num_words].end = oend[iwp - iwords - 1];
		words[num_words++].string = q;
		idx = iwp - iwords;
		parse_recurse(iwp, ostart + idx, oend + idx);
		num_words--;
	    } else if (first) {
		l = p - buf;
		words[num_words].type = WORD_ALLOCATED;
		words[num_words].string = new_string(l, "parse_recurse");
		words[num_words].start = ostart[0];
		words[num_words].end = oend[iwp - iwords - 1];
		memcpy(words[num_words].string, buf, l);
		words[num_words++].string[l] = 0;
		idx = iwp - iwords;
		parse_recurse(iwp, ostart + idx, oend + idx);
		num_words--;
		FREE_MSTR(words[num_words].string);
	    }
	    first = 0;
	    *p++ = ' ';
	} while (*iwp[0]);
    } else {
#ifdef DEBUG
	if (debug_parse_depth) {
	    char dbuf[1024];
	    int i;
	    char *p;
	    p = strput(dbuf, "Trying interpretation: ");
	    for (i = 0; i < num_words; i++) {
		p = strput(p, words[i].string);
		p = strput(p, ":");
	    }
	    DEBUG_P((dbuf));
	}
#endif
	parse_rules();
    }
}

static void parse_sentence P1(char *, input) {
    char *starts[256];
    char *orig_starts[256];
    char *orig_ends[256];
    char c, buf[1024], *p, *start, *inp;
    int n = 0;
    int i;
    int flag;
    
    free_words();
    p = start = buf;
    flag = 0;
    inp = input;
    while (*inp && (isspace(*inp) || isignore(*inp)))
	inp++;
    orig_starts[0] = inp;
    
    while ((c = *inp++)) {
	if (isignore(c)) continue;
	if (isupper(c))
	    c = tolower(c);
	if (iskeep(c)) {
	    if (!flag)
		flag = 1;
	    *p++ = c;
	} else {
	    /* whitespace or punctuation */
	    if (!isspace(c))
		while (*inp && !iskeep(*inp) && !isspace(*inp))
		    inp++;
	    else
		inp--;

	    if (flag) {
		flag = 0;
		*p++ = 0;
		orig_ends[n] = inp - 1; /* points to where c was */
		starts[n++] = start;
		start = p;
		while (*inp && isspace(*inp))
		    inp++;
		orig_starts[n] = inp;
	    } else {
		while (*inp && isspace(*inp))
		    inp++;
	    }
	}
    }
    if (flag) {
	*p++ = 0;
	orig_ends[n] = inp - 2;
	starts[n++] = start;
    } else {
	if (n)
	    orig_ends[n - 1] = inp - 2;
	else
	    orig_ends[0] = inp - 2;
    }
    starts[n] = p;
    *p = 0;

    reset_error();
    /* find an interpretation, first word must be shared (verb) */
    for (i = 1; i <= n; i++) {
	if ((parse_verb_entry = parse_verb(buf))) {
	    if (!objects_loaded && (parse_verb_entry->flags & VB_HAS_OBJ)) 
		load_objects();
	    num_words = 1;
	    words[0].start = orig_starts[0];
	    words[0].end = orig_ends[i-1];
	    parse_recurse(&starts[i], &orig_starts[i], &orig_ends[i]);
	}
	starts[i][-1] = ' ';
    }
}

void f_parse_sentence PROT((void)) {
    if (!current_object->pinfo)
	error("%s is not known by the parser.  Call parse_init() first.\n",
	      current_object->name);

    if (pi)
	error("Illegal to call parse_sentence() recursively.\n");
    
    /* may not be done in case of an error, or in case of tail recursion.
       if we are called tail recursively, we don't need this any more.
       */
    if (best_result) {
	free_parse_result(best_result);
	best_result = 0;
    }

    if (st_num_arg == 2 && (sp--)->u.number) {
#ifdef DEBUG
	debug_parse_depth = 1;
	if ((sp + 1)->u.number > 1)
	    debug_parse_verbose = 1;
	else
	    debug_parse_verbose = 0;
    } else {
	debug_parse_depth = 0;
#endif
    }

    (++sp)->type = T_ERROR_HANDLER;
    sp->u.error_handler = free_parse_globals;

    parse_user = current_object;
    pi = current_object->pinfo;
    parse_restricted = 0;
    parse_sentence((sp-1)->u.string);

    free_parse_globals();
    
    if (best_match) {
	do_the_call();
	sp--; /* pop the error handler */
	free_string_svalue(sp);
	put_number(1);
    } else {
	sp--; /* pop the error handler */
	free_string_svalue(sp);
	if (current_error) {
	    sp->subtype = STRING_MALLOC;
	    sp->u.string = current_error;
	    current_error = 0;
	} else put_number(0);
    }

    if (best_result) {
	free_parse_result(best_result);
	best_result = 0;
    }
}

void f_parse_my_rules PROT((void)) {
    int flag = (st_num_arg == 3 ? (sp--)->u.number : 0);
    
    if (!(sp-1)->u.ob->pinfo)
	error("%s is not known by the parser.  Call parse_init() first.\n",
	      (sp-1)->u.ob->name);
    if (!current_object->pinfo)
	error("%s is not known by the parser.  Call parse_init() first.\n",
	      current_object->name);

    if (pi)
	error("Illegal to call parse_sentence() recursively.\n");
    
    (++sp)->type = T_ERROR_HANDLER;
    sp->u.error_handler = free_parse_globals;

    parse_user = (sp-2)->u.ob;
    pi = parse_user->pinfo;
    parse_restricted = current_object;
    parse_sentence((sp-1)->u.string);
    
    if (best_match) {
	if (flag) {
	    do_the_call();
	    sp--; /* pop the error handler */
	    free_string_svalue(sp--);
	    free_svalue(sp, "parse_my_rules"); /* may have been destructed */
	    put_number(1);
	} else {
	    int n;
	    array_t *arr;
	    /* give them the info for the wildcard call */
	    n = best_result->res[3].num;
	    arr = allocate_empty_array(n);
	    if (n) {
		memcpy((char *)arr->item, best_result->res[3].args, n*sizeof(svalue_t));
		while (n--) {
		    if (arr->item[n].type == T_OBJECT && arr->item[n].u.ob->flags & O_DESTRUCTED) {
			free_object(arr->item[n].u.ob, "parse_my_rules");
			arr->item[n] = const0;
		    }
		}
		FREE(best_result->res[3].args);
	    }
	    best_result->res[3].args = 0;
	    sp--; /* pop the error handler */
	    free_string_svalue(sp--);
	    free_svalue(sp, "parse_my_rules"); /* may have been destructed */
	    put_array(arr);
	}
    } else {
	sp--; /* pop the error handler */
	free_string_svalue(sp--);
	free_svalue(sp, "parse_my_rules"); /* may have been destructed */
	if (current_error) {
	    sp->type = T_STRING;
	    sp->subtype = STRING_MALLOC;
	    sp->u.string = current_error;
	    current_error = 0;
	} else put_number(0);
    }
    free_parse_globals();
}

void f_parse_add_rule() {
    int tokens[10];
    int lit[2], i, j;
    svalue_t *ret;
    char *verb, *rule;
    object_t *handler;
    verb_t *verb_entry;
    verb_node_t *verb_node;
    int h;
    
    rule = (sp-1)->u.string;
    verb_entry = 0;
    verb = findstring((sp-2)->u.string);
    CHECK_TYPES(sp, T_OBJECT, 3, F_PARSE_ADD_RULE);
    handler = sp->u.ob;
    if (!(handler->pinfo))
	error("%s is not known by the parser.  Call parse_init() first.\n",
	      handler->name);

    /* Create the rule */
    make_rule(rule, tokens);

    /* Now find a verb entry to put it in */
    if (verb) {
	verb_entry = verbs[DO_HASH(verb, VERB_HASH_SIZE)];
	while (verb_entry) {
	    if (verb_entry->name == verb)
		break;
	    verb_entry = verb_entry->next;
	}
    }
    
    if (!verb_entry) {
	if (!verb)
	    verb = make_shared_string((sp-2)->u.string);
	else
	    ref_string(verb);
	
	h = DO_HASH(verb, VERB_HASH_SIZE);
	verb_entry = ALLOCATE(verb_t, TAG_PARSER, "parse_add_rule");
	verb_entry->name = verb;
	verb_entry->node = 0;
	verb_entry->next = verbs[h];
	verbs[h] = verb_entry;
    }

    /* Add a new node */
    for (i = 0, j = 0; tokens[i]; i++) {
	if (tokens[i] > 0 && j < 2)
	    lit[j++] = tokens[i];
    }

    while (j < 2)
	lit[j++] = 0;

    verb_node = (verb_node_t *)DXALLOC(sizeof(verb_node_t) + sizeof(int)*i,
				       TAG_PARSER, "parse_add_rule");

    verb_node->lit[0] = lit[0];
    verb_node->lit[1] = lit[1];
    for (j = 0; j <= i; j++) {
	if (tokens[j] <= OBJ_A_TOKEN)
	    verb_entry->flags |= VB_HAS_OBJ;
	verb_node->token[j] = tokens[j];
    }
    verb_node->handler = handler;
    handler->pinfo->flags |= PI_VERB_HANDLER;
    verb_node->next = verb_entry->node;
    verb_entry->node = verb_node;

    ret = apply("livings_are_remote", handler, 0, ORIGIN_DRIVER);
    if (!IS_ZERO(ret))
        handler->pinfo->flags |=PI_REMOTE_LIVINGS;

    /* return */
    pop_stack();
    free_string_svalue(sp--);
    free_string_svalue(sp--);
}

void f_parse_dump PROT((void))
{
    int i;
    outbuffer_t ob;

    outbuf_zero(&ob);
    for (i = 0; i < VERB_HASH_SIZE; i++) {
	verb_t *v = verbs[i];
	while (v) {
	    verb_node_t *vn = v->node;
	    outbuf_addv(&ob, "Verb %s:\n", v->name);
	    while (vn) {
		outbuf_addv(&ob, "  (%s) %s\n", vn->handler->name, rule_string(vn));
		vn = vn->next;
	    }
	    v = v->next;
	}
    }
    outbuf_push(&ob);
}