/*--------------------------------------------------------------------------- * Pattern Parser v 3.1 (non-compat mode) * (C) Copyright 1991 JnA (jna@cd.chalmers.se) * *--------------------------------------------------------------------------- * TODO: Improve this efun, make it more general. * EFUN parse_command * * int parse_command (string cmd, object env, string fmt, mixed &var, ...) * int parse_command (string cmd, object* arr, string fmt, mixed &var, ...) * * parse_command() is basically a spiffed up sscanf operating on word basis * and targeted at recognizing object descriptions from command strings. * * The efun takes the command string and the object(s) / * and tries to match it against the format string . Successfully * matched elements are assigned to the variables .... * The result from the efun is 1 if the command could be fully matched, * and 0 otherwise. * * If the objects are given as a single object , the efun matches * against the given object and all objects contained therein. Otherwise, * if the objects are given as an array of objects, the efun * matches only against the given objects. * * The format string consists of words, syntactic markers, and * %-directives for the values to parse and return in the variables. * A typical example is " 'get' / 'take' %i " or * " 'spray' / 'paint' [paint] %i ". The elements in detail are: * * 'word': obligatory text * [word]: optional text * / : Alternative marker * %o : Single item, object * %s : Any text * %w : Any word * %p : One of a list of prepositions. * If the variable associated with %p is used to pass * a list of words to the efun, the matching will take * only against this list. * %l : Living objects * %i : Any objects * %d : Number >= 0, or when given textual: 0-99. * * A in this context is any sequence of characters not containing * a space. 'living objects' are searched by calls to the (simul)efuns * find_player() and find_living(): both functions have to accept a name * as argument and return the object for this name, or 0 if there * is none. * * The results assigned to the variables by the %-directives are: * * %o : returns an object * %s : returns a string of words * %w : returns a string of one word * %p : if passed empty: a string * if passed as array of words: var[0] is the matched word * %i : returns an array with the following content: * [0]: int: the count/number recognized in the object spec * > 0: a count (e.g. 'three', '4') * < 0: an ordinal (e.g. 'second', 'third') * = 0: 'all' or a generic plural such as 'apples' * [1..]: object: all(!) objects matching the item description. * In the form this may be the whole * recursive inventory of the object. * It is up to the caller to interpret the recognized numeral * and to apply it on the list of matched objects. * %l : as %i, except that only living objects are returned. * * %i and %l match descriptions like 'three red roses','all nasty bugs' * or 'second blue sword'. * * Note: Patterns of type: "%s %w %i" might not work as one would expect. * %w will always succeed so the arg corresponding to %s will always be empty. * * * To make the efun useful it must have a certain support from the mudlib: * it calls a set of functions in objects to get the information it needs * to parse a string. * * 1. string *parse_command_id_list() * Normal singular names of the object. * * 2. string *parse_command_plural_id_list() - optional * Plural forms of the names returned by 1. * If this function doesn't exist, the parser tries to pluralize * the names returned by 1. * * 3. string *parse_command_adjectiv_id_list() - optional * All adjectives associated with this object. * * All names and adjectives may consist of several words separated * by spaces. * * These functions should exist in all objects and are therefore best * put into a mandatory inherit file (e.g. /std/object.c). * * In addition the master object may offer the same functions to provide * reasonable defaults (like 'thing' as generic singular name): * * string *parse_command_id_list() * - Would normally return: ({ "one", "thing" }) * * string *parse_command_plural_id_list() * - Would normally return: ({ "ones", "things", "them" }) * * string *parse_command_adjectiv_id_list() * - Would normally return ({ "iffish" }) * * Two additional functions in the master object provide the default * list of prepositions (needed for %p) and the single 'all' word: * * string *parse_command_prepos_list() * - Would normally return: ({ "in", "on", "under", "behind", "beside" }) * * string parse_command_all_word() * - Would normally return: "all" *--------------------------------------------------------------------------- * TODO: A proper localisation would at least put all the following into the * TODO:: master object as well. * * If you want to use a different language than English, you need to write * a small file 'parse_local.c' and include it into parse.c at the * marked position. * * The 'parse_local.c' has to contain your localized pluralmaker and * the textual number words and should look like this: * * ---------- SNIP ---------- * #define PARSE_FOREIGN * * char *parse_to_plural(str) * char *str; * { * * * Your own plural converter for your language * * * } * * * The numberwords below should be replaced for the new language * * * static char *ord1[] = {"", "first", "second", "third", "fourth", "fifth", * "sixth", "seventh", "eighth", "nineth", "tenth", * "eleventh", "twelfth", "thirteenth", "fourteenth", * "fifteenth", "sixteenth", "seventeenth", * "eighteenth","nineteenth"}; * * static char *ord10[] = {"", "", "twenty","thirty","forty","fifty","sixty", * "seventy", "eighty","ninety"}; * * static char *sord10[] = {"", "", "twentieth", "thirtieth", "fortieth", * "fiftieth", "sixtieth","seventieth", "eightieth", * "ninetieth"}; * * static char *num1[] = {"", "one","two","three","four","five","six", * "seven","eight","nine","ten", * "eleven","twelve","thirteen","fourteen","fifteen", * "sixteen", "seventeen","eighteen","nineteen"}; * * static char *num10[] = {"", "", "twenty","thirty","forty","fifty","sixty", * "seventy", "eighty","ninety"}; * ---------- SNIP ---------- *--------------------------------------------------------------------------- */ #include "driver.h" #if defined(USE_PARSE_COMMAND) #include "typedefs.h" #include #include #include #include "parse.h" #include "actions.h" #include "array.h" #include "gcollect.h" #include "interpret.h" #include "lex.h" #include "main.h" #include "mstrings.h" #include "object.h" #include "simulate.h" #include "stdstrings.h" #include "svalue.h" #include "wiz_list.h" #include "xalloc.h" /* For a localisation of parse_command(), * #include "parse_local.c" * here. */ /*-------------------------------------------------------------------------*/ /* Some useful string macros */ #define PREFIXED(x,y) (strncmp(x, y, strlen(x)) == 0) /*-------------------------------------------------------------------------*/ /* To make parse_command() reentrant, the module maintains a list * of previous contexts using this structure: */ typedef struct parse_context_s parse_context_t; struct parse_context_s { parse_context_t *previous; vector_t *id, *plid, *adjid; vector_t *id_d, *plid_d, *adjid_d, *prepos; string_t *allword; /* This context: the lists of ids and such. */ vector_t *wvec, *patvec, *obvec; /* Next context(!): word, pattern and object vector */ }; /*-------------------------------------------------------------------------*/ /* Arrays holding the constituent words of textual numbers from 0 to 99. * The numbers are constructed by concatenation. */ #ifndef PARSE_FOREIGN static char *ord1[] = {"", "first", "second", "third", "fourth", "fifth" , "sixth", "seventh", "eighth", "nineth", "tenth" , "eleventh", "twelfth", "thirteenth", "fourteenth" , "fifteenth", "sixteenth", "seventeenth" , "eighteenth","nineteenth"}; /* The ordinals from 1 to 19, also used to build the ordinals 20..99. */ static char *ord10[] = { "", "", "twenty","thirty","forty","fifty","sixty" , "seventy", "eighty","ninety"}; /* The first word for the ordinals 20..99. */ static char *sord10[] = { "", "", "twentieth", "thirtieth", "fortieth" , "fiftieth", "sixtieth","seventieth", "eightieth" , "ninetieth"}; /* The ordinals 20, 30, ..., 90. */ static char *num1[] = { "", "one","two","three","four","five","six" , "seven","eight","nine","ten" , "eleven","twelve","thirteen","fourteen","fifteen" , "sixteen", "seventeen","eighteen","nineteen"}; /* The numbers 1 to 19, also used to build the numbers 20..99. */ static char *num10[] = { "", "", "twenty","thirty","forty","fifty","sixty" , "seventy", "eighty","ninety"}; /* The first word for the numbers 20..99. */ #endif /*-------------------------------------------------------------------------*/ static svalue_t find_living_closures[2] = { { T_INVALID }, { T_INVALID } }; /* The closures to the functions 'find_living' and 'find_player', * which are generated at runtime to be able to find simul-efuns with * these names. * TODO: This too should go into a master function. */ static parse_context_t *gPrevious_context = NULL; /* The list of previous contexts. */ /* The following variables 'cache' the various lists read from the * matched objects and the master object. The original values are * save when parse_command() is called, making the efun re-entrant. */ static vector_t *gId_list = NULL; static vector_t *gPluid_list = NULL; static vector_t *gAdjid_list = NULL; /* Arrays of the lists from the objects matched against. * For example gId_list[2] returns the singular name list for * the third object. * The arrays are filled on demand only. svalue-0s denote entries * yet to fill, svalue-1s are entries where the object doesn't provide * the particular information. */ static vector_t *gId_list_d = NULL; static vector_t *gPluid_list_d = NULL; static vector_t *gAdjid_list_d = NULL; static vector_t *gPrepos_list = NULL; static string_t *gAllword = NULL; /* The lists and the 'all' word from the master object. */ /*-------------------------------------------------------------------------*/ static object_t * find_living_object (string_t *name, Bool player) /* Find the living ( is false) or player ( is true) * with the name . * Return the found object, or NULL if not found. * * The functions calls the (simul)efuns 'find_living' resp. * 'find_player' for this purpose. */ { svalue_t *svp; /* Get or create the closure for the function to call */ svp = &find_living_closures[player ? 1 : 0]; if (svp->type == T_INVALID) { /* We have to create the closure */ symbol_efun(player ? STR_PC_FIND_PLAYER : STR_PC_FIND_LIVING, svp); } /* Call the closure */ inter_sp++; put_ref_string(inter_sp, name); call_lambda(svp, 1); pop_stack(); /* In theory this could lose the last ref to the result object. * In praxis, those objects have more refs. */ return inter_sp[1].type != T_OBJECT ? NULL : inter_sp[1].u.ob; } /* find_living_object() */ /*-------------------------------------------------------------------------*/ #ifdef GC_SUPPORT void clear_parse_refs (void) /* GC support: Clear the references of all memory held by the parser. */ { clear_ref_in_vector( find_living_closures , sizeof find_living_closures / sizeof(svalue_t) ); } /* clear_parse_refs() */ /*-------------------------------------------------------------------------*/ void count_parse_refs (void) /* GC support: Count the references of all memory held by the parser. */ { count_ref_in_vector( find_living_closures , sizeof find_living_closures / sizeof(svalue_t) ); } /* count_parse_refs() */ #endif /* GC_SUPPORT */ #ifndef PARSE_FOREIGN /*-------------------------------------------------------------------------*/ static string_t * parse_one_plural (string_t *str) /* Change the singular noun to a plural and return it. * The result is either a new string with one reference, or itself * with an added reference. */ { static char pbuf[100]; /* Result buffer */ char ch, ch2; /* Last two characters in */ size_t sl; /* Last index in */ sl = mstrsize(str); if (sl < 3 || sl > sizeof(pbuf) - 10) return str; sl--; /* Copy except for the last char into pbuf */ ch = get_txt(str)[sl]; ch2 = get_txt(str)[sl-1]; strcpy(pbuf, get_txt(str)); pbuf[sl] = '\0'; /* Try to make plural based on the last two chars */ switch (ch) { case 's': case 'x': case 'h': return new_mstring(strcat(pbuf, "ses")); case 'y': return new_mstring(strcat(pbuf, "ies")); case 'e': if (ch2 == 'f') { pbuf[sl-1] = 0; return new_mstring(strcat(pbuf, "ves")); } } /* Some known special cases */ if (mstreq(str, STR_PC_CORPSE)) return ref_mstring(STR_PC_CORPSES); if (mstreq(str, STR_PC_TOOTH)) return ref_mstring(STR_PC_TEETH); if (mstreq(str, STR_PC_FOOT)) return ref_mstring(STR_PC_FEET); if (mstreq(str, STR_PC_MAN)) return ref_mstring(STR_PC_MEN); if (mstreq(str, STR_PC_WOMAN)) return ref_mstring(STR_PC_WOMEN); if (mstreq(str, STR_PC_CHILD)) return ref_mstring(STR_PC_CHILDREN); if (mstreq(str, STR_PC_SHEEP)) return ref_mstring(STR_PC_SHEEP); /* Default: just append 's' */ pbuf[sl] = ch; return new_mstring(strcat(pbuf, "s")); } /* parse_one_plural() */ /*-------------------------------------------------------------------------*/ static string_t * parse_to_plural (string_t *str) /* Change the singular name to a plural name. The result is a new * string with one reference. * * The algorithm groups the into runs delimited by 'of' (e.g. "the box * of the king" and pluralizes the last word before each 'of' and the last * word in the string (giving "the boxes of the kings"). * TODO: TubMud has a good plural maker. */ { vector_t *words; svalue_t stmp; string_t *sp; size_t il; Bool changed; /* If it's a single word, it's easy */ if (!(strchr(get_txt(str), ' '))) return parse_one_plural(str); /* Multiple words, possible grouped into runs delimited by 'of': * pluralize the last word in the string, and the last word * before each 'of'. */ words = explode_string(str, STR_SPACE); for (changed = MY_FALSE, il = 1; (p_int)il < VEC_SIZE(words); il++) { if ((mstreq(words->item[il].u.str, STR_PC_OF)) || (p_int)il+1 == VEC_SIZE(words)) { /* Got one to pluralize */ sp = parse_one_plural(words->item[il-1].u.str); if (sp != words->item[il-1].u.str) { put_string(&stmp, sp); transfer_svalue(&words->item[il-1], &stmp); changed = MY_TRUE; } else free_mstring(sp); /* Reuse the old reference */ } } /* If nothing changed, just return a copy of the original */ if (!changed) { free_array(words); return ref_mstring(str); } /* We changed it: return the new name */ sp = implode_string(words, STR_SPACE); free_array(words); return sp; } /* parse_to_plural() */ #endif /* PARSE_FOREIGN */ /*-------------------------------------------------------------------------*/ static void load_lpc_info (size_t ix, object_t *ob) /* Load the relevant information (singular names, plural names and adjectives) * for object into position of the cache lists, unless already * loaded. * * If the object does not provide plural names, they are synthesized from * the singular names. */ { Bool make_plural = MY_FALSE; /* TRUE: synthesize plurals */ svalue_t * ret; if (!ob || ob->flags & O_DESTRUCTED) return; /* Get the plural names, if any. */ if (gPluid_list && VEC_SIZE(gPluid_list) > (p_int)ix && gPluid_list->item[ix].type == T_NUMBER && gPluid_list->item[ix].u.number == 0 ) { ret = apply(STR_PC_P_ID_LIST, ob, 0); if (ret && ret->type == T_POINTER) assign_svalue_no_free(&gPluid_list->item[ix], ret); else { make_plural = MY_TRUE; gPluid_list->item[ix].u.number = 1; } } /* Get the singular names and, if desired, synthesize the * plural names. */ if (gId_list && VEC_SIZE(gId_list) > (p_int)ix && gId_list->item[ix].type == T_NUMBER && gId_list->item[ix].u.number == 0 && !(ob->flags & O_DESTRUCTED) ) { ret = apply(STR_PC_ID_LIST, ob, 0); if (ret && ret->type == T_POINTER) { assign_svalue_no_free(&gId_list->item[ix], ret); if (make_plural) { /* Pluralize the singular names */ vector_t *tmp, *sing; svalue_t sval; string_t *str; size_t il; tmp = allocate_array((size_t)VEC_SIZE(ret->u.vec)); if (!tmp) errorf("(parse_command) Out of memory: array[%lu] for " "plural names.\n" , (unsigned long)VEC_SIZE(ret->u.vec)); sing = ret->u.vec; for (il = 0; (p_int)il < VEC_SIZE(tmp); il++) { if (sing->item[il].type == T_STRING) { str = parse_to_plural(sing->item[il].u.str); put_string(&sval, str); transfer_svalue_no_free(&tmp->item[il],&sval); } } put_array(&sval, tmp); transfer_svalue_no_free(&gPluid_list->item[ix], &sval); } } else { gId_list->item[ix].u.number = 1; } } /* Get the adjectives, if any. */ if (gAdjid_list && VEC_SIZE(gAdjid_list) > (p_int)ix && gAdjid_list->item[ix].type == T_NUMBER && gAdjid_list->item[ix].u.number == 0 && !(ob->flags & O_DESTRUCTED) ) { ret = apply(STR_PC_ADJ_LIST, ob, 0); if (ret && ret->type == T_POINTER) assign_svalue_no_free(&gAdjid_list->item[ix], ret); else gAdjid_list->item[ix].u.number = 1; } } /* load_lpc_info() */ /*-------------------------------------------------------------------------*/ static void parse_error_handler (svalue_t *arg UNUSED) /* The current parse_command() processing was interrupted by an error. * Clean up the current context and restore the previous context. */ { #ifdef __MWERKS__ # pragma unused(arg) #endif parse_context_t *old; old = gPrevious_context; /* Delete and free the id arrays. */ if (gId_list) free_array(gId_list); if (gPluid_list) free_array(gPluid_list); if (gAdjid_list) free_array(gAdjid_list); if (gId_list_d) free_array(gId_list_d); if (gPluid_list_d) free_array(gPluid_list_d); if (gAdjid_list_d) free_array(gAdjid_list_d); if (gPrepos_list) free_array(gPrepos_list); if (gAllword) free_mstring(gAllword); /* Restore the previous lists */ gId_list_d = old->id_d; gPluid_list_d = old->plid_d; gAdjid_list_d = old->adjid_d; gPrepos_list = old->prepos; gId_list = old->id; gPluid_list = old->plid; gAdjid_list = old->adjid; gAllword = old->allword; /* Free the local arrays */ free_array(old->wvec); free_array(old->patvec); free_array(old->obvec); gPrevious_context = old->previous; xfree(old); } /* parse_error_handler() */ /*-------------------------------------------------------------------------*/ static INLINE void stack_put (svalue_t *pval, svalue_t *sp, size_t pos, int max) /* Store the value into the lvalue []. * If is NULL, [] not a lvalue or >= , * nothing happens - which is a good thing as this function stores * the parsed results into the variables passed to the efun, and we * never know what the wizards are going to pass there. */ { if (pval && pos < (size_t)max && sp[pos].type == T_LVALUE) transfer_svalue(sp[pos].u.lvalue, pval); } /* stack_put() */ /*-------------------------------------------------------------------------*/ static svalue_t * slice_words (vector_t *wvec, size_t from, size_t to) /* Return an imploded string of words from [..] as tabled * string svalue. * Return NULL if there is nothing to slice. */ { vector_t *slice; string_t *tx; static svalue_t stmp; if (from > to) return NULL; slice = slice_array(wvec, from, to); if (VEC_SIZE(slice)) tx = implode_string(slice, STR_SPACE); else tx = NULL; free_array(slice); if (tx) { put_string(&stmp, make_tabled(tx)); return &stmp; } else return NULL; } /* slice_words() */ /*-------------------------------------------------------------------------*/ static int find_string (string_t *str, vector_t *wvec, size_t *cix_in) /* Test if the (multi-word) string exists in the array of words * at or after position . * If found, return the starting position in and set to * the position of the last word of the found string. * If not round, return -1, will be set to the end of . */ { int fpos; string_t *p1; char *p2; vector_t *split; /* Step through wvec and look for a match */ for (; (p_int)*cix_in < VEC_SIZE(wvec); (*cix_in)++) { p1 = wvec->item[*cix_in].u.str; /* Quick test: first character has to match */ if (get_txt(p1)[0] != get_txt(str)[0]) continue; if (mstreq(p1, str)) /* str was one word and we found it */ return (int)*cix_in; if (!(p2 = strchr(get_txt(str), ' '))) continue; /* If str is a multiword string and we need to make some special checks */ if ((p_int)*cix_in + 1 == VEC_SIZE(wvec)) continue; split = explode_string(str, STR_SPACE); /* Now: wvec->size - *cix_in = 2: One extra word * = 3: Two extra words */ if (!split || VEC_SIZE(split) > (VEC_SIZE(wvec) - (p_int)*cix_in)) { if (split) free_array(split); continue; } /* Test if the following words match the string */ fpos = (int)*cix_in; for (; *cix_in < (size_t)(VEC_SIZE(split) + fpos); (*cix_in)++) { if (!mstreq(split->item[*cix_in-fpos].u.str, wvec->item[*cix_in].u.str)) break; } /* If all of split matched, we found it */ if ((p_int)(*cix_in - fpos) == VEC_SIZE(split)) return fpos; /* Not found: continue search */ *cix_in = fpos; } /* Not found */ return -1; } /* find_string() */ /*-------------------------------------------------------------------------*/ static int member_string (string_t *str, vector_t *svec) /* Test if string is member of the array . * Return the position if found, and -1 otherwise. */ { size_t il; if (!svec) return -1; for (il = 0; (p_int)il < VEC_SIZE(svec); il++) { if (svec->item[il].type != T_STRING) continue; if (mstreq(svec->item[il].u.str, str)) return (int)il; } return -1; } /* member_string() */ /*-------------------------------------------------------------------------*/ static Bool check_adjectiv (size_t obix, vector_t *wvec, size_t from, size_t to) /* Check if the command words [..] match the adjectives * for object . * Return TRUE if yes. */ { size_t il; size_t sum; /* Total length of command words tested */ size_t back; Bool fail; /* TRUE if not found */ string_t *adstr; char *adstrp; vector_t *ids; /* Adj list of the object */ /* Get the objects adj-list if existing */ if (gAdjid_list->item[obix].type == T_POINTER) ids = gAdjid_list->item[obix].u.vec; else ids = NULL; /* Scan the given command words, sum up their length and * test if all of them match the adjectives given. */ for (sum = 0, fail = MY_FALSE, il = from; il <= to; il++) { sum += mstrsize(wvec->item[il].u.str) + 1; if ((member_string(wvec->item[il].u.str, ids) < 0) && (member_string(wvec->item[il].u.str, gAdjid_list_d) < 0)) { fail = MY_TRUE; } } /* Simple case: all adjs were single words and matched. */ if (!fail) return MY_TRUE; if (from == to) return MY_FALSE; /* It could be that some of the adjectives provided by the object are * multi-words; in that case the above loop would signal a mismatch. * * To find these, concatenate the command words with spaces and * test them against the single adjective strings. * TODO: This test could be implemented faster. */ adstr = NULL; adstrp = xalloc(sum); /* Workspace */ /* Test the adjectives one after the other */ for (il = from; il < to;) { /* For every adjective, perform a greedy match first, ie * try to match the longer concatenated strings before * the shorter ones. */ for (back = to; back > il; back--) { /* Catenate the adjective from the command words */ adstrp[0] = '\0'; for (sum = il; sum <= back; sum++) { if (sum > il) strcat(adstrp, " "); strcat(adstrp, get_txt(wvec->item[sum].u.str)); } adstr = new_mstring(adstrp); if ((member_string(adstr, ids) >= 0) || (member_string(adstr, gAdjid_list_d) >= 0)) { /* Found: continue search after this matched adjective */ il = back + 1; break; } /* Not found: abort */ mstring_free(adstr); xfree(adstrp); return MY_FALSE; } } /* Found: clean up and return */ mstring_free(adstr); xfree(adstrp); return MY_TRUE; } /* check_adjectiv() */ /*-------------------------------------------------------------------------*/ static svalue_t * number_parse( vector_t *obvec UNUSED /* in: array of objects to match against */ , vector_t *wvec /* in: array of words to match */ , size_t *cix_in /* in-out: position in wvec */ , Bool *fail /* out: TRUE on mismatch */ ) /* Interpret the words in wvec[cix_in] as numeric descriptor, parse it * and return an int-svalue with the result: * > 0: a number ('one', 'two', 'three', or the number given) * < 0: an ordinal ('first', 'second') * = 0: any ('zero', 0, the gAllword) * On failure, return NULL. * * On return, is set to the success state of the match, and * has been set past the parsed number. */ { #ifdef __MWERKS__ # pragma unused(obvec) #endif static svalue_t stmp; /* Result buffer */ size_t cix; int ten, ones, num; cix = *cix_in; *fail = MY_FALSE; ones = 0; /* First try to parse the number in digit representation */ if (sscanf(get_txt(wvec->item[cix].u.str), "%d", &num)) { if (num >= 0) { (*cix_in)++; put_number(&stmp, num); return &stmp; } *fail = MY_TRUE; return NULL; /* Only nonnegative numbers allowed */ } /* Is it the 'all' word? */ if (gAllword && mstreq(wvec->item[cix].u.str, gAllword)) { (*cix_in)++; put_number(&stmp, 0); return &stmp; } /* Test the number against every known textual number. */ for (ten = 0; ten < 10; ten++) { char *second; /* Test if the first part of the word matches */ if (!PREFIXED(num10[ten], get_txt(wvec->item[cix].u.str))) continue; /* Yup, now match the rest */ second = get_txt(wvec->item[cix].u.str) + strlen(num10[ten]); for (ones = 0; ones < 10; ones++) { char *tmp; tmp = (ten>1) ? num1[ones] : num1[ten*10+ones]; if (!strcmp(second, tmp)) { (*cix_in)++; put_number(&stmp, ten*10+ones); return &stmp; } } /* for (ones) */ } /* for (ten) */ /* Test the number against every known textual ordinal. */ for (ten = 0; ten < 10; ten++) { char *second; /* Multiples of 10 have their own words */ if (!strcmp(sord10[ten], get_txt(wvec->item[cix].u.str))) { (*cix_in)++; put_number(&stmp, -(ten*10+ones)); return &stmp; } /* Test if the first part of the word matches */ if (!PREFIXED(ord10[ten], get_txt(wvec->item[cix].u.str))) continue; /* Yup, now match the rest */ second = get_txt(wvec->item[cix].u.str) + strlen(ord10[ten]); for(ones = 1; ones < 10; ones++) { char *tmp; tmp = (ten > 1) ? ord1[ones] : ord1[ten*10+ones]; if (!strcmp(second, tmp)) { (*cix_in)++; put_number(&stmp, -(ten*10+ones)); return &stmp; } } } /* Nothing matches */ *fail = MY_TRUE; return NULL; } /* number_parse() */ /*-------------------------------------------------------------------------*/ static Bool match_object (size_t obix, vector_t *wvec, size_t *cix_in, Bool *plur) /* Test if a given object matches the description [..]. * If is TRUE, only the plural description is considered, otherwise * both plural and singular. * * Return TRUE if the object matches; will be set to true if the * plural description matched, and will point to the word after * the matched description. * Return FALSE if it didn't match. */ { vector_t *ids; /* Id-list to test against */ int cplur; /* Which id-list to test (0..3) */ size_t il, old_cix; int pos; string_t *str; /* Loop over the four lists of ids */ for (cplur = *plur ? 2 : 0; cplur < 4; cplur++) { switch (cplur) { case 0: /* Global singular ids */ if (!gId_list_d) continue; ids = gId_list_d; break; case 1: /* Object singular ids */ if (!gId_list || VEC_SIZE(gId_list) <= (p_int)obix || gId_list->item[obix].type != T_POINTER) continue; ids = gId_list->item[obix].u.vec; break; case 2: /* Global plural ids */ if (!gPluid_list_d) continue; ids = gPluid_list_d; break; case 3: /* Object plural ids */ if (!gPluid_list || VEC_SIZE(gPluid_list) <= (p_int)obix || gPluid_list->item[obix].type != T_POINTER) continue; ids = gPluid_list->item[obix].u.vec; break; default: fatal("match_object() called with invalid arguments\n"); } if (!ids) fatal("match_object(): internal error\n"); /* Loop over the ids and find a match */ for (il = 0; (p_int)il < VEC_SIZE(ids); il++) { if (ids->item[il].type == T_STRING) { str = ids->item[il].u.str; /* A given id of the object */ old_cix = *cix_in; if ((pos = find_string(str, wvec, cix_in)) >= 0) { /* Id matched, now check a possible adjective */ if ((size_t)pos == old_cix || check_adjectiv(obix, wvec, old_cix, pos-1)) { if (cplur > 1) *plur = MY_TRUE; return MY_TRUE; } } *cix_in = old_cix; } } /* for(il) */ } /* for (cplur) */ /* Doesn't match */ return MY_FALSE; } /* match_object() */ /*-------------------------------------------------------------------------*/ static svalue_t * item_parse (vector_t *obvec, vector_t *wvec, size_t *cix_in, Bool *fail) /* Try to match as many objects in as possible onto the description * given in commandvector [..]. * Result is a vector with the found objects, and the first element is * a number returning a found numeral: 0 for 'all' or a generic plural, * > 0: for a numeral 'one', 'two' etc, < 0 for an ordinal 'first', 'second', etc. * is updated and is set to FALSE. * * On failure, return NULL, update and set to TRUE. */ { static svalue_t stmp; /* Result buffer */ vector_t *tmp; vector_t *ret; svalue_t *pval; size_t cix, tix; size_t max_cix; /* Highest cix used in matching */ Bool plur_flag; /* Plural numeral */ Bool match_all; /* 'all' numeral */ size_t obix; /* Intermediate result vector */ tmp = allocate_array(VEC_SIZE(obvec) + 1); /* Try to parse a numeral */ if ( NULL != (pval = number_parse(obvec, wvec, cix_in, fail)) ) assign_svalue_no_free(&tmp->item[0], pval); if (pval && pval->u.number > 1) { plur_flag = MY_TRUE; match_all = MY_FALSE; } else if (pval && pval->u.number == 0) { plur_flag = MY_TRUE; match_all = MY_TRUE; } else { plur_flag = MY_FALSE; match_all = MY_TRUE; } /* Scan the object vector and try to match each one of it */ for (max_cix = *cix_in, tix = 1, obix = 0; (p_int)obix < VEC_SIZE(obvec); obix++) { *fail = MY_FALSE; cix = *cix_in; if (obvec->item[obix].type != T_OBJECT) continue; /* Command was something like "get all": accept all objects */ if ((p_int)cix == VEC_SIZE(wvec) && match_all) { assign_svalue_no_free(&tmp->item[tix++], &obvec->item[obix]); continue; } /* Get the id-info for this object */ load_lpc_info(obix, obvec->item[obix].u.ob); if (obvec->item[obix].u.ob->flags & O_DESTRUCTED) /* Oops */ continue; if (match_object(obix, wvec, &cix, &plur_flag)) { assign_svalue_no_free(&tmp->item[tix++],&obvec->item[obix]); max_cix = (max_cix < cix) ? cix : max_cix; } } if (tix < 2) { /* No object matched: failure */ *fail = MY_TRUE; free_array(tmp); if (pval) (*cix_in)--; return NULL; } else { /* We got matches: now compute the results */ if ((p_int)(*cix_in) < VEC_SIZE(wvec)) *cix_in = max_cix + 1; ret = slice_array(tmp, 0, tix-1); if (!pval) { put_number(ret->item, plur_flag ? 0 : 1); } free_array(tmp); } /* Return the result */ put_array(&stmp, ret); return &stmp; } /* item_parse() */ /*-------------------------------------------------------------------------*/ static svalue_t * living_parse (vector_t *obvec, vector_t *wvec, size_t *cix_in, Bool *fail) /* Try to match as many living objects in as possible onto the * description given in commandvector [..]. * Result is a vector with the found objects, and the first element is * a number returning a found numeral: 0 for 'all' or a generic plural, * > 0: for a numeral 'one', 'two' etc, < 0 for an ordinal 'first', 'second', * etc. * is updated and is set to FALSE. * * On failure, return NULL, update and set to TRUE. */ { static svalue_t stmp; /* Result buffer */ vector_t *live; svalue_t *pval; object_t *ob; size_t obix, tix; *fail = MY_FALSE; /* Fill live with all living objects from */ tix = 0; live = allocate_array(VEC_SIZE(obvec)); for (obix = 0; (p_int)obix < VEC_SIZE(obvec); obix++) { if (obvec->item[obix].type != T_OBJECT) continue; if (obvec->item[obix].u.ob->flags & O_ENABLE_COMMANDS) assign_svalue_no_free(&live->item[tix++], &obvec->item[obix]); } /* If we have living objects, simply call item_parse() on * that array. If that succeeds, we have our result. */ if (tix) { pval = item_parse(live, wvec, cix_in, fail); if (pval) { free_array(live); return pval; } } free_array(live); /* We can't find a matching living object in obvec, but * maybe the command names a player or living by name. */ ob = find_living_object(wvec->item[*cix_in].u.str, MY_TRUE); if (!ob) ob = find_living_object(wvec->item[*cix_in].u.str, MY_FALSE); if (ob) { put_ref_object(&stmp, ob, "living_parse"); (*cix_in)++; return &stmp; } /* Not found */ *fail = MY_TRUE; return NULL; } /* living_parse() */ /*-------------------------------------------------------------------------*/ static svalue_t * single_parse (vector_t *obvec, vector_t *wvec, size_t *cix_in, Bool *fail) /* Find the first object in matching the description in [..] * and return it as an object svalue. is updated and is set * to false. * * If not found, return NULL, update and set to true. */ { size_t cix, obix; Bool plur_flag; svalue_t *osvp; /* Loop over the list of objects */ osvp = obvec->item; for (obix = 0; (p_int)obix < VEC_SIZE(obvec); obix++, osvp++) { if (osvp->type != T_OBJECT) continue; *fail = MY_FALSE; cix = *cix_in; load_lpc_info(obix,osvp->u.ob); if (osvp->u.ob->flags & O_DESTRUCTED) /* Oops */ continue; plur_flag = MY_FALSE; if (match_object(obix, wvec, &cix, &plur_flag)) { *cix_in = cix+1; (void)ref_object(osvp->u.ob, "single_parse"); return osvp; } } /* Not found */ *fail = MY_TRUE; return NULL; } /* single_parse() */ /*-------------------------------------------------------------------------*/ static svalue_t * prepos_parse (vector_t *wvec, size_t *cix_in, Bool *fail, svalue_t *prepos) /* Match the commandwords [..] against a list of prepositions. * On return, has been updated and gives the success. * * If is NULL or not an array of strings, the match takes place * against the prepositions given by the master object. The result will be * a static string-svalue with the matched preposition. * * If is an array of strings, it the list of prepositions matched * against. On success, the result is the array and first element * of the array will be the matched preposition (which has been swapped against * the original content of that element). */ { static svalue_t stmp; vector_t *pvec, *tvec; string_t *tmp; size_t pix, tix; /* Determine list to match against */ if (!prepos || prepos->type != T_POINTER) pvec = gPrepos_list; else pvec = prepos->u.vec; for (pix = 0; (p_int)pix < VEC_SIZE(pvec); pix++) { if (pvec->item[pix].type != T_STRING) continue; tmp = pvec->item[pix].u.str; if (!strchr(get_txt(tmp),' ')) { /* A single word match */ if (mstreq(tmp, wvec->item[*cix_in].u.str)) { (*cix_in)++; break; } } else { /* Multiword match */ tvec = explode_string(tmp, STR_SPACE); for (tix = 0; (p_int)tix < VEC_SIZE(tvec); tix++) { if ((p_int)(*cix_in+tix) >= VEC_SIZE(wvec) || (!mstreq(wvec->item[*cix_in+tix].u.str, tvec->item[tix].u.str)) ) break; } tix = ((p_int)tix == VEC_SIZE(tvec)) ? 1 : 0; if (tix) (*cix_in) += VEC_SIZE(tvec); free_array(tvec); if (tix) break; } } if ((p_int)pix == VEC_SIZE(pvec)) { *fail = MY_TRUE; } else if (pvec != gPrepos_list) { /* We received a prepos list: now move the found preposition * to the front. */ stmp = pvec->item[0]; pvec->item[0] = pvec->item[pix]; pvec->item[pix] = stmp; ref_array(pvec); /* The caller will free the prepos at some point. */ *fail = MY_FALSE; } else { /* We found a preposition in the master's list. */ assign_svalue_no_free(&stmp, &pvec->item[pix]); return &stmp; } return prepos; } /* prepos_parse() */ /*-------------------------------------------------------------------------*/ static svalue_t * one_parse ( vector_t *obvec /* in: array of objects to match against */ , string_t *pat /* in: word */ , vector_t *wvec /* in: array of command words */ , size_t *cix_in /* in-out: position in wvec */ , Bool *fail /* out: TRUE if mismatch */ , svalue_t *prep_param /* current lvalue stack position for %p */ ) /* Match a single pattern, consuming the words from the wvec. * On return, gives the status of the match, and and * have been updated. Direct result is the next matched value in * a static buffer, or NULL. * * The function does not handle alternatives or '%s', and is called * from sub_parse(). */ { static svalue_t stmp; /* The result buffer */ char ch; /* Command character */ svalue_t *pval; char *str1, *str2; /* Nothing left to parse? */ if ((p_int)*cix_in == VEC_SIZE(wvec)) { *fail = MY_TRUE; return NULL; } /* Get the command character */ ch = get_txt(pat)[0]; if (ch == '%') { ch = get_txt(pat)[1]; } pval = NULL; /* Interpret the possible patterns */ switch (ch) { case 'i': case 'I': /* Match an item */ pval = item_parse(obvec, wvec, cix_in, fail); break; case 'l': case 'L': /* Match a living item */ pval = living_parse(obvec, wvec, cix_in, fail); break; case 's': case 'S': *fail = MY_FALSE; /* This is a double %s in pattern, skip it */ break; case 'w': case 'W': /* Match the next word */ put_string(&stmp, make_tabled_from(wvec->item[*cix_in].u.str)); pval = &stmp; (*cix_in)++; *fail = MY_FALSE; break; case 'o': case 'O': /* Match an object */ pval = single_parse(obvec, wvec, cix_in, fail); break; case 'p': case 'P': /* Match a preposition */ pval = prepos_parse(wvec, cix_in, fail, prep_param); break; case 'd': case 'D': /* Match a number */ pval = number_parse(obvec, wvec, cix_in, fail); break; case '\'': /* Match a required word */ str1 = &get_txt(pat)[1]; str2 = get_txt(wvec->item[*cix_in].u.str); if (strncmp(str1, str2, strlen(str1)-1) == 0 && strlen(str1) == strlen(str2)+1) { *fail = MY_FALSE; (*cix_in)++; } else *fail = MY_TRUE; break; case '[': /* Match an optional word */ str1 = &get_txt(pat)[1]; str2 = get_txt(wvec->item[*cix_in].u.str); if (strncmp(str1, str2, strlen(str1)-1) == 0 && strlen(str1) == strlen(str2)+1) { (*cix_in)++; } *fail = MY_FALSE; break; default: *fail = MY_FALSE; /* Skip invalid patterns */ } return pval; } /* one_parse() */ /*-------------------------------------------------------------------------*/ static svalue_t * sub_parse ( vector_t *obvec /* in: array of objects to match against */ , vector_t *patvec /* in: array of pattern elements */ , size_t *pix_in /* in-out: position in patvec */ , vector_t *wvec /* in: array of command words */ , size_t *cix_in /* in-out: position in wvec */ , Bool *fail /* out: TRUE if mismatch */ , svalue_t *sp /* current lvalue stack position for %p */ ) /* Parse a vector of words against a pattern from the given position. * On return, gives the status of the match, and and * have been updated. Direct result is the next matched value, or NULL. * * The function handles all pattern elements except '%s' and is called * by e_parse_command(). */ { size_t cix, pix; /* Local positions */ Bool subfail; svalue_t *pval; /* There must be something left to match */ if ((p_int)*cix_in == VEC_SIZE(wvec)) { *fail = MY_TRUE; return NULL; } cix = *cix_in; pix = *pix_in; subfail = MY_FALSE; /* Try to parse a single pattern element */ pval = one_parse( obvec, patvec->item[pix].u.str , wvec, &cix, &subfail, sp); /* If no match (so far), try the next alternative. * There must be at least one '/' following in the pattern array. */ while (subfail) { pix++; cix = *cix_in; while ((p_int)pix < VEC_SIZE(patvec) && mstreq(patvec->item[pix].u.str, STR_SLASH)) { subfail = MY_FALSE; pix++; } if (!subfail && (p_int)pix < VEC_SIZE(patvec)) pval = one_parse( obvec, patvec->item[pix].u.str, wvec, &cix , &subfail, sp); else { /* No '/': failure */ *fail = MY_TRUE; *pix_in = pix-1; return NULL; } } /* We have a match: skip remaining alternatives */ if ((p_int)pix+1 < VEC_SIZE(patvec) && mstreq(patvec->item[pix+1].u.str, STR_SLASH)) { while ((p_int)pix+1 < VEC_SIZE(patvec) && mstreq(patvec->item[pix+1].u.str, STR_SLASH)) { pix += 2; } pix++; /* Skip last alternate after last '/' */ if ((p_int)pix >= VEC_SIZE(patvec)) pix = VEC_SIZE(patvec)-1; } /* That's it: return the result */ *cix_in = cix; *pix_in = pix; *fail = MY_FALSE; return pval; } /* sub_parse() */ /*-------------------------------------------------------------------------*/ Bool e_parse_command ( string_t *cmd /* Command to parse */ , svalue_t *ob_or_array /* Object or array of objects */ , string_t *pattern /* Special parsing pattern */ , svalue_t *stack_args /* Pointer to lvalue args on stack */ , int num_arg /* Number of lvalues on stack */ ) /* EFUN parse_command() * * This function implements the parse_command() efun, called from interpret.c. * Result is TRUE on success, and FALSE otherwise. */ { static svalue_t error_handler_addr = { T_ERROR_HANDLER }; vector_t *obvec = NULL; /* Objects to match against */ vector_t *patvec; /* Elements in pattern */ vector_t *wvec; /* Words in command */ parse_context_t *old; Bool fail; /* TRUE if the match failed */ size_t pix; /* Index in patvec */ size_t cix; /* Index in wvec */ size_t six; /* Index to the lvalues on the stack */ svalue_t *pval; /* Result from a subparse */ /* Pattern and commands can not be empty */ if (!mstrsize(cmd) || !mstrsize(pattern)) return MY_FALSE; /* Prepare some variables */ xallocate(old, sizeof *old, "parse context"); wvec = explode_string(cmd, STR_SPACE); if (!wvec) wvec = allocate_array(0); patvec = explode_string(pattern, STR_SPACE); if (!patvec) patvec = allocate_array(0); if (ob_or_array->type == T_POINTER) { /* There might be more references to this array, which could cause * real nightmares if load_lpc_info() changes the array. */ check_for_destr(ob_or_array->u.vec); obvec = slice_array(ob_or_array->u.vec, 0, VEC_SIZE(ob_or_array->u.vec) - 1); } else if (ob_or_array->type == T_OBJECT) { obvec = deep_inventory(ob_or_array->u.ob, /* take_top: */ MY_TRUE, /* depth: */ 0); } else { free_array(wvec); free_array(patvec); xfree(old); errorf("Bad second argument to parse_command()\n"); } /* Save the previous context and set up the error handler */ old->id = gId_list; old->plid = gPluid_list; old->adjid = gAdjid_list; old->id_d = gId_list_d; old->plid_d = gPluid_list_d; old->adjid_d = gAdjid_list_d; old->prepos = gPrepos_list; old->allword = gAllword; old->wvec = wvec; old->patvec = patvec; old->obvec = obvec; old->previous = gPrevious_context; gPrevious_context = old; push_error_handler(parse_error_handler, &error_handler_addr); /* Make space for the list arrays */ gId_list = allocate_array(VEC_SIZE(obvec)); gPluid_list = allocate_array(VEC_SIZE(obvec)); gAdjid_list = allocate_array(VEC_SIZE(obvec)); /* Get the default ids of 'general references' from master object */ pval = apply_master(STR_PC_ID_LIST, 0); if (pval && pval->type == T_POINTER) { gId_list_d = ref_array(pval->u.vec); } else gId_list_d = NULL; pval = apply_master(STR_PC_P_ID_LIST, 0); if (pval && pval->type == T_POINTER) { gPluid_list_d = ref_array(pval->u.vec); } else gPluid_list_d = NULL; pval = apply_master(STR_PC_ADJ_LIST, 0); if (pval && pval->type == T_POINTER) { gAdjid_list_d = ref_array(pval->u.vec); } else gAdjid_list_d = NULL; pval = apply_master(STR_PC_PREPOS, 0); if (pval && pval->type == T_POINTER) { gPrepos_list = ref_array(pval->u.vec); } else gPrepos_list = allocate_array(0); pval = apply_master(STR_PC_ALLWORD,0); if (pval && pval->type == T_STRING) gAllword = ref_mstring(pval->u.str); else gAllword = NULL; /* Loop through the pattern. Handle %s but not '/' */ for (six = 0, cix = 0, fail = MY_FALSE, pix = 0 ; (p_int)pix < VEC_SIZE(patvec); pix++) { pval = NULL; fail = MY_FALSE; if (mstreq(patvec->item[pix].u.str, STR_PERCENT_S)) { /* If at the end of the pattern, %s matches everything left * in the wvec. * Otherwise it matches everything up to the next pattern * element. */ if ((p_int)pix == VEC_SIZE(patvec)-1) { pval = slice_words(wvec, cix, VEC_SIZE(wvec)-1); cix = VEC_SIZE(wvec); } else { size_t fword, ocix, fpix; ocix = fword = cix; fpix = ++pix; /* Try parsing the next pattern element at increasingly * further distances from the current position in wvec. * The loop ends when a match is found, or wvec is exhausted. */ do { fail = MY_FALSE; pval = sub_parse(obvec, patvec, &pix, wvec, &cix, &fail , (six < (size_t)num_arg) ? stack_args[six].u.lvalue : 0); if (fail) { cix = ++ocix; pix = fpix; } } while (fail && (p_int)cix < VEC_SIZE(wvec)); /* If we failed to find a match, the whole pattern string * doesn't match. Otherwise store the wvec slice between * the current position and the match into the next * variable. */ if (!fail) { stack_put(pval, stack_args, six+1, num_arg); pval = slice_words(wvec, fword, ocix-1); stack_put(pval, stack_args, six++, num_arg); pval = NULL; } } } else if (!mstreq(patvec->item[pix].u.str, STR_SLASH)) { /* Everything else is handled by sub_parse() */ pval = sub_parse( obvec, patvec, &pix, wvec, &cix, &fail , (six < (size_t)num_arg) ? stack_args[six].u.lvalue : 0); } if (!fail && pval) stack_put(pval, stack_args, six++, num_arg); else if (fail) break; } /* for() */ /* Also fail when there are words left to parse and pattern exhausted. */ if ((p_int)cix < VEC_SIZE(wvec)) fail = MY_TRUE; pop_stack(); /* Clean up via the error handler */ return !fail; } /* e_parse_command() */ #endif /* USE_PARSE_COMMAND */ /***************************************************************************/