ldmud-3.2.9/doc/
ldmud-3.2.9/doc/efun/
ldmud-3.2.9/mud/
ldmud-3.2.9/mud/heaven7/
ldmud-3.2.9/mud/heaven7/lib/
ldmud-3.2.9/mud/lp-245/
ldmud-3.2.9/mud/lp-245/banish/
ldmud-3.2.9/mud/lp-245/doc/
ldmud-3.2.9/mud/lp-245/doc/examples/
ldmud-3.2.9/mud/lp-245/doc/sefun/
ldmud-3.2.9/mud/lp-245/log/
ldmud-3.2.9/mud/lp-245/obj/Go/
ldmud-3.2.9/mud/lp-245/players/lars/
ldmud-3.2.9/mud/lp-245/room/death/
ldmud-3.2.9/mud/lp-245/room/maze1/
ldmud-3.2.9/mud/lp-245/room/sub/
ldmud-3.2.9/mud/lp-245/secure/
ldmud-3.2.9/mud/morgengrauen/
ldmud-3.2.9/mud/morgengrauen/lib/
ldmud-3.2.9/mud/sticklib/
ldmud-3.2.9/mud/sticklib/src/
ldmud-3.2.9/mudlib/uni-crasher/
ldmud-3.2.9/pkg/
ldmud-3.2.9/pkg/debugger/
ldmud-3.2.9/pkg/diff/
ldmud-3.2.9/pkg/misc/
ldmud-3.2.9/src/autoconf/
ldmud-3.2.9/src/bugs/
ldmud-3.2.9/src/bugs/MudCompress/
ldmud-3.2.9/src/bugs/b-020916-files/
ldmud-3.2.9/src/bugs/doomdark/
ldmud-3.2.9/src/bugs/ferrycode/ferry/
ldmud-3.2.9/src/bugs/ferrycode/obj/
ldmud-3.2.9/src/bugs/psql/
ldmud-3.2.9/src/done/
ldmud-3.2.9/src/done/order_alist/
ldmud-3.2.9/src/done/order_alist/obj/
ldmud-3.2.9/src/done/order_alist/room/
ldmud-3.2.9/src/gcc/
ldmud-3.2.9/src/gcc/2.7.0/
ldmud-3.2.9/src/gcc/2.7.1/
ldmud-3.2.9/src/hosts/
ldmud-3.2.9/src/hosts/GnuWin32/
ldmud-3.2.9/src/hosts/amiga/NetIncl/
ldmud-3.2.9/src/hosts/amiga/NetIncl/netinet/
ldmud-3.2.9/src/hosts/amiga/NetIncl/sys/
ldmud-3.2.9/src/hosts/i386/
ldmud-3.2.9/src/hosts/msdos/byacc/
ldmud-3.2.9/src/hosts/msdos/doc/
ldmud-3.2.9/src/hosts/os2/
ldmud-3.2.9/src/hosts/win32/
ldmud-3.2.9/src/util/
ldmud-3.2.9/src/util/erq/
ldmud-3.2.9/src/util/indent/hosts/next/
ldmud-3.2.9/src/util/xerq/
ldmud-3.2.9/src/util/xerq/lpc/
ldmud-3.2.9/src/util/xerq/lpc/www/
/*---------------------------------------------------------------------------
 * 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 */

/***************************************************************************/