/*--------------------------------------------------------------------------- * Pattern Parser v 3.1 (non-compat mode) * (C) Copyright 1991 JnA (jna@cd.chalmers.se) * *--------------------------------------------------------------------------- * TODO: Put this efun in the USE_DEPRECATED lot. * 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 <cmd> and the object(s) <env>/<arr> * and tries to match it against the format string <fmt>. Successfully * matched elements are assigned to the variables <var>.... * 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 <env>, the efun matches * against the given object and all objects contained therein. Otherwise, * if the objects are given as an array <arr> of objects, the efun * matches only against the given objects. * * The format string <fmt> 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 <word> 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 <env> form this may be the whole * recursive inventory of the <env> 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(SUPPLY_PARSE_COMMAND) #include "typedefs.h" #include <stdio.h> #include <ctype.h> #include <time.h> #define NO_REF_STRING #include "parse.h" #include "actions.h" #include "array.h" #include "closure.h" #include "gcollect.h" #include "instrs.h" #include "interpret.h" #include "main.h" #include "object.h" #include "simulate.h" #include "stdstrings.h" #include "stralloc.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 EQ(x,y) (strcmp(x,y)==0) #define EQN(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; char *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 char *gAllword = NULL; /* The lists and the 'all' word from the master object. */ /*-------------------------------------------------------------------------*/ static object_t * find_living_object (char *name, Bool player) /* Find the living (<player> is false) or player (<player> is true) * with the name <name>. * Return the found object, or NULL if not found. * * The functions calls the (simul)efuns 'find_living' resp. * 'find_player' for this purpose. */ { static char *function_names[2] = { "find_living", "find_player"}; svalue_t *sp, *svp; sp = inter_sp; sp++; /* 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 */ put_string(sp, make_shared_string(function_names[player ? 1 : 0])); if (!sp->u.string) error("(parse_command) Out of memory (%lu bytes) for string\n" , (unsigned long)strlen(function_names[player ? 1 : 0])); inter_sp = sp; symbol_efun(sp); *svp = *sp; inter_sp = sp - 1; } /* Call the closure */ put_string(sp, make_shared_string(name)); if ( !sp->u.string) error("(parse_command) Out of memory (%lu bytes) for result\n" , (unsigned long)strlen(name)); inter_sp = sp; call_lambda(svp, 1); pop_stack(); return sp->type != T_OBJECT ? NULL : sp->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 char * parse_one_plural (char *str) /* Change the singular noun <str> to a plural and return it. * The result is either <str> itself, or a pointer to a static buffer. */ { static char pbuf[100]; /* Result buffer */ char ch, ch2; /* Last two characters in <str> */ size_t sl; /* Last index in <str> */ sl = strlen(str); if (sl < 3 || sl > sizeof(pbuf) - 10) return str; sl--; /* Copy <str> except for the last char into pbuf */ ch = str[sl]; ch2 = str[sl-1]; strcpy(pbuf, str); pbuf[sl] = '\0'; /* Try to make plural based on the last two chars */ switch (ch) { case 's': case 'x': case 'h': return strcat(pbuf, "ses"); case 'y': return strcat(pbuf, "ies"); case 'e': if (ch2 == 'f') { pbuf[sl-1] = 0; return strcat(pbuf, "ves"); } } /* Some known special cases */ if (EQ(str,"corpse")) return "corpses"; if (EQ(str,"tooth")) return "teeth"; if (EQ(str,"foot")) return "feet"; if (EQ(str,"man")) return "men"; if (EQ(str,"woman")) return "women"; if (EQ(str,"child")) return "children"; if (EQ(str,"sheep")) return "sheep"; /* Default: just append 's' */ pbuf[sl] = ch; return strcat(pbuf, "s"); } /* parse_one_plural() */ /*-------------------------------------------------------------------------*/ static char * parse_to_plural (char *str) /* Change the singular name <str> to a plural name. The result is a newly * allocated string. * * The algorithm groups the <str> 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; char *sp; size_t il; Bool changed; /* If it's a single word, it's easy */ if (!(strchr(str,' '))) return string_copy(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 = old_explode_string(str, " "); for (changed = MY_FALSE, il = 1; il < VEC_SIZE(words); il++) { if ((EQ(words->item[il].u.string,"of")) || il+1 == VEC_SIZE(words)) { /* Got one to pluralize */ sp = parse_one_plural(words->item[il-1].u.string); if (sp != words->item[il-1].u.string) { put_malloced_string(&stmp, string_copy(sp)); transfer_svalue(&words->item[il-1], &stmp); changed = MY_TRUE; } } } /* If nothing changed, just return a copy of the original */ if (!changed) { free_array(words); return string_copy(str); } /* We changed it: return the new name */ sp = implode_string(words, " "); 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 <ob> into position <ix> 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) > 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) > 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; char *str; size_t il; tmp = allocate_array(VEC_SIZE(ret->u.vec)); if (!tmp) error("(parse_command) Out of memory: array[%lu] for " "plural names.\n", VEC_SIZE(ret->u.vec)); sing = ret->u.vec; for (il = 0; il < VEC_SIZE(tmp); il++) { if (sing->item[il].type == T_STRING) { str = parse_to_plural(sing->item[il].u.string); put_malloced_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) > 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) xfree(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 <pval> into the lvalue <sp>[<pos>]. * If <pval> is NULL, <sp>[<pos>] not a lvalue or <pos> >= <max>, * 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 < 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 <wvec>[<from>..<to>] as static shared * string svalue. * Return NULL if there is nothing to slice. */ { vector_t *slice; char *tx; static svalue_t stmp; if (from > to) return NULL; slice = slice_array(wvec, from, to); if (VEC_SIZE(slice)) tx = implode_string(slice," "); else tx = NULL; free_array(slice); if (tx) { put_string(&stmp, make_shared_string(tx)); xfree(tx); return &stmp; } else return NULL; } /* slice_words() */ /*-------------------------------------------------------------------------*/ static int find_string (char *str, vector_t *wvec, size_t *cix_in) /* Test if the (multi-word) string <str> exists in the array of words <wvec> * at or after position <cix_in>. * If found, return the starting position in <wvec> and set <cix_in> to * the position of the last word of the found string. * If not round, return -1, <cix_in> will be set to the end of <wvec>. */ { int fpos; char *p1, *p2; vector_t *split; /* Step through wvec and look for a match */ for (; *cix_in < VEC_SIZE(wvec); (*cix_in)++) { p1 = wvec->item[*cix_in].u.string; if (p1[0] != str[0]) /* Quick test: first character has to match */ continue; if (strcmp(p1, str) == 0) /* str was one word and we found it */ return (int)*cix_in; if (!(p2 = strchr(str,' '))) continue; /* If str is a multiword string and we need to make some special checks */ if (*cix_in + 1 == VEC_SIZE(wvec)) continue; split = old_explode_string(str," "); /* Now: wvec->size - *cix_in = 2: One extra word * = 3: Two extra words */ if (!split || VEC_SIZE(split) > (VEC_SIZE(wvec) - *cix_in)) { if (split) free_array(split); continue; } /* Test if the following words match the string */ fpos = (int)*cix_in; for (; *cix_in < VEC_SIZE(split) + (size_t)fpos; (*cix_in)++) { if (strcmp(split->item[*cix_in-fpos].u.string, wvec->item[*cix_in].u.string)) break; } /* If all of split matched, we found it */ if ((size_t)(*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 (char *str, vector_t *svec) /* Test if string <str> is member of the array <svec>. * Return the position if found, and -1 otherwise. */ { size_t il; if (!svec) return -1; for (il = 0; il < VEC_SIZE(svec); il++) { if (svec->item[il].type != T_STRING) continue; if (strcmp(svec->item[il].u.string, str) == 0) 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 <wvec>[<from>..<to>] match the adjectives * for object <obix>. * 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 */ char *adstr; 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 += strlen(wvec->item[il].u.string) + 1; if ((member_string(wvec->item[il].u.string, ids) < 0) && (member_string(wvec->item[il].u.string, 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 = 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 */ adstr[0] = '\0'; for (sum = il; sum <= back; sum++) { if (sum > il) strcat(adstr, " "); strcat(adstr, wvec->item[sum].u.string); } 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 */ xfree(adstr); return MY_FALSE; } } /* Found: clean up and return */ xfree(adstr); 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, <fail> is set to the success state of the match, and <cix_in> * 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(wvec->item[cix].u.string, "%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 && EQ(wvec->item[cix].u.string, 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 (!EQN(num10[ten], wvec->item[cix].u.string)) continue; /* Yup, now match the rest */ second = wvec->item[cix].u.string + strlen(num10[ten]); for (ones = 0; ones < 10; ones++) { char *tmp; tmp = (ten>1) ? num1[ones] : num1[ten*10+ones]; if (EQ(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 (EQ(sord10[ten], wvec->item[cix].u.string)) { (*cix_in)++; put_number(&stmp, -(ten*10+ones)); return &stmp; } /* Test if the first part of the word matches */ if (!EQN(ord10[ten], wvec->item[cix].u.string)) continue; /* Yup, now match the rest */ second = wvec->item[cix].u.string + strlen(ord10[ten]); for(ones = 1; ones < 10; ones++) { char *tmp; tmp = (ten > 1) ? ord1[ones] : ord1[ten*10+ones]; if (EQ(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 <obix> matches the description <wvec>[<cix_in>..]. * If <plur> is TRUE, only the plural description is considered, otherwise * both plural and singular. * * Return TRUE if the object matches; <plur> will be set to true if the * plural description matched, and <cix_in> 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; char *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) <= 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) <= 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; il < VEC_SIZE(ids); il++) { if (ids->item[il].type == T_STRING) { str = ids->item[il].u.string; /* 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 (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 <obvec> as possible onto the description * given in commandvector <wvec>[<cix_in>..]. * 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. * <cix_in> is updated and <fail> is set to FALSE. * * On failure, return NULL, update <cix_in> and set <fail> 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; 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 (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 (*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 <obvec> as possible onto the * description given in commandvector <wvec>[<cix_in>..]. * 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. * <cix_in> is updated and <fail> is set to FALSE. * * On failure, return NULL, update <cix_in> and set <fail> 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 <obvec> */ tix = 0; live = allocate_array(VEC_SIZE(obvec)); for (obix = 0; 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.string, MY_TRUE); if (!ob) ob = find_living_object(wvec->item[*cix_in].u.string, 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 <obvec> matching the description in <wvec>[<cix_in>..] * and return it as an object svalue. <cix_in> is updated and <fail> is set * to false. * * If not found, return NULL, update <cix_in> and set <fail> to true. */ { size_t cix, obix; Bool plur_flag; svalue_t *osvp; /* Loop over the list of objects */ osvp = obvec->item; for (obix = 0; 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 <wvec>[<cix_in>..] against a list of prepositions. * On return, <cix_in> has been updated and <fail> gives the success. * * If <prepos> 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 <prepos> is an array of strings, it the list of prepositions matched * against. On success, the result is the <prepos> 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; char *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; pix < VEC_SIZE(pvec); pix++) { if (pvec->item[pix].type != T_STRING) continue; tmp = pvec->item[pix].u.string; if (!strchr(tmp,' ')) { /* A single word match */ if (EQ(tmp, wvec->item[*cix_in].u.string)) { (*cix_in)++; break; } } else { /* Multiword match */ tvec = old_explode_string(tmp, " "); for (tix = 0; tix < VEC_SIZE(tvec); tix++) { if (*cix_in+tix >= VEC_SIZE(wvec) || (!EQ(wvec->item[*cix_in+tix].u.string, tvec->item[tix].u.string)) ) break; } tix = (tix == VEC_SIZE(tvec)) ? 1 : 0; if (tix) (*cix_in) += VEC_SIZE(tvec); free_array(tvec); if (tix) break; } } if (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; *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 */ , char *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, <fail> gives the status of the match, and <pix_in> and <cix_in> * 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 (*cix_in == VEC_SIZE(wvec)) { *fail = MY_TRUE; return NULL; } /* Get the command character */ ch = pat[0]; if (ch == '%') { ch = 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_shared_string(wvec->item[*cix_in].u.string)); 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 = &pat[1]; str2 = wvec->item[*cix_in].u.string; 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 = &pat[1]; str2 = wvec->item[*cix_in].u.string; 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, <fail> gives the status of the match, and <pix_in> and <cix_in> * 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 (*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.string , 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 (pix < VEC_SIZE(patvec) && EQ(patvec->item[pix].u.string, "/")) { subfail = MY_FALSE; pix++; } if (!subfail && pix < VEC_SIZE(patvec)) pval = one_parse( obvec, patvec->item[pix].u.string, wvec, &cix , &subfail, sp); else { /* No '/': failure */ *fail = MY_TRUE; *pix_in = pix-1; return NULL; } } /* We have a match: skip remaining alternatives */ if (pix+1 < VEC_SIZE(patvec) && EQ(patvec->item[pix+1].u.string,"/")) { while (pix+1 < VEC_SIZE(patvec) && EQ(patvec->item[pix+1].u.string,"/")) { pix += 2; } pix++; /* Skip last alternate after last '/' */ if (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 ( char *cmd /* Command to parse */ , svalue_t *ob_or_array /* Object or array of objects */ , char *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 <pattern> */ vector_t *wvec; /* Words in command <cmd> */ 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 (!strlen(cmd) || !strlen(pattern)) return MY_FALSE; /* Prepare some variables */ xallocate(old, sizeof *old, "parse context"); wvec = old_explode_string(cmd," "); if (!wvec) wvec = allocate_array(0); patvec = old_explode_string(pattern," "); 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); } else { free_array(wvec); free_array(patvec); xfree(old); error("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; error_handler_addr.u.error_handler = parse_error_handler; inter_sp++; inter_sp->type = T_LVALUE; inter_sp->u.lvalue = &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 = string_copy(pval->u.string); else gAllword = NULL; /* Loop through the pattern. Handle %s but not '/' */ for (six = 0, cix = 0, fail = MY_FALSE, pix = 0 ; pix < VEC_SIZE(patvec); pix++) { pval = NULL; fail = MY_FALSE; if (EQ(patvec->item[pix].u.string, "%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 (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 < num_arg) ? stack_args[six].u.lvalue : 0); if (fail) { cix = ++ocix; pix = fpix; } } while (fail && 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 (!EQ(patvec->item[pix].u.string,"/")) { /* Everything else is handled by sub_parse() */ pval = sub_parse( obvec, patvec, &pix, wvec, &cix, &fail , (six<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 (cix < VEC_SIZE(wvec)) fail = MY_TRUE; pop_stack(); /* Clean up via the error handler */ return !fail; } /* e_parse_command() */ #endif /* SUPPLY_PARSE_COMMAND */ /***************************************************************************/