/* * 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); }