#ifdef RCSID
static char RCSid[] =
"$Header: d:/cvsroot/tads/TADS2/OBJ.C,v 1.2 1999/05/17 02:52:12 MJRoberts Exp $";
#endif

/* Copyright (c) 1991, 2002 Michael J. Roberts.  All Rights Reserved. */
/*
Name
  obj.c - object manipulation
Function
  Operations on TADS objects
Notes
  A TADS object is stored in a single cache object.  The object is laid
  out in memory as follows:
  
  {
     ushort   workspace            - workspace in which object is defined
     ushort   flags                - object flags
     ushort   sc_count             - number of superclasses
     ushort   prop_used            - number of entries actually used
     ushort   free_offset          - offset of next free byte in prop area
     objnum   sclist[sc_count]     - array of superclass object numbers
     prpdef   proplist[prop_max]   - array of properties of the object
  }
  
  The number of superclasses is defined when the object is compiled,
  and does not change at runtime.  Properties can be added and replaced
  at runtime; the object's cache object may need to be resized from
  time to time to accomdate added or expanded property values.
  
  Objects are stored in a portable format.  Each ushort (including
  the objnum) in the object header is stored as a 2-byte array,
  least significant byte first; the array is stored with no padding
  for alignment.
  
  Each property is defined by a prpdef structure (see prp.h).
  
  The meaning of propval varies by datatype.  For DAT_NUMBER,
  the value is the actual number in question.  For DAT_OBJECT, it
  is the object number of the object.  For DAT_NIL and DAT_TRUE,
  there is no value.  For DAT_SSTRING, DAT_CODE, DAT_LIST, and
  DAT_DSTRING, the value is stored immediately following the prpdef,
  and takes up the number of bytes given in the prpsize field.
Modified
  08/11/91 MJRoberts     - creation
*/

#include "os.h"
#include "obj.h"
#include "prp.h"
#include "dat.h"
#include "mch.h"
#include "mcm.h"
#include "err.h"

/*
 *   Get a property WITHOUT INHERITANCE.  The offset of the property's
 *   prpdef is returned.  An offset of zero means the property wasn't
 *   found.
 */
uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
{
    objdef *objptr;
    prpdef *p;
    int     cnt;
    uint    retval;                       /* property offset, if we find it */
    uint    ignprop; /* ignored property - use if real property isn't found */
    uchar   pbuf[2];                  /* property number in portable format */
    uchar  *indp;
    uchar  *indbase;
    int     last;
    int     first;
    int     cur;
    
    oswp2(pbuf, prop);            /* get property number in portable foramt */
    objptr = (objdef *)mcmlck(mctx, objn);      /* get a lock on the object */
    ignprop = 0;                   /* assume we won't find ignored property */
    cnt = objnprop(objptr);             /* get number of properties defined */
    retval = 0;                                          /* presume failure */
    
    if (objflg(objptr) & OBJFINDEX)
    {
        /* there's an index -> do a binary search through the index */
        indbase = (uchar *)objpfre(objptr);                   /* find index */
        first = 0;
        last = cnt - 1;
        for (;;)
        {
            if (first > last) break;           /* crossed over -> not found */
            cur = first + (last - first)/2;         /* split the difference */
            indp = indbase + cur*4;            /* get pointer to this entry */
            if (indp[0] == pbuf[0] && indp[1] == pbuf[1])
            {
                retval = osrp2(indp + 2);
                break;
            }
            else if (indp[0] < pbuf[0]
                     || (indp[0] == pbuf[0] && indp[1] < pbuf[1]))
                first = (cur == first ? first + 1 : cur);
            else
                last = (cur == last ? last - 1 : cur);
        }

        /* ignore ignored and deleted properties if possible */
        while (retval
               && ((prpflg(objptr + retval) & PRPFIGN) != 0
                   || ((prpflg(objptr + retval) & PRPFDEL) != 0
                       && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0))
               && cur < cnt && indp[0] == indp[4] && indp[1] == indp[5])
        {
            indp += 4;
            retval = osrp2(indp + 2);
        }
        if (retval && osrp2(objptr + retval) != prop)
            assert(FALSE);
    }
    else
    {
        /* there's no index -> do sequential search through properties */
        for (p = objprp(objptr) ; cnt ; p = objpnxt(p), --cnt)
        {
            /* if this is the property, and it's not being ignored, use it */
            if (*(uchar *)p == pbuf[0] && *(((uchar *)p) + 1) == pbuf[1])
            {
                if (prpflg(p) & PRPFIGN)                 /* this is ignored */
                    ignprop = objpofs(objptr, p);  /* ... make a note of it */
                else if ((prpflg(p) & PRPFDEL) != 0         /* it's deleted */
                         && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0)
                    /* simply skip it */ ;
                else
                {
                    retval = objpofs(objptr, p);         /* this is the one */
                    break;                                    /* we're done */
                }
            }
        }
    }

    if (!retval) retval = ignprop;     /* use ignored value if nothing else */
    if (retval && typptr) *typptr = prptype(objofsp(objptr, retval));
    
    mcmunlck(mctx, objn);                 /* done with object, so unlock it */
    return(retval);
}

/* get the offset of the end of a property in an object */
uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop)
{
    objdef *objp;
    prpdef *propptr;
    uint    ofs;
    uint    valsiz;
    
    /* get the start of the object */
    ofs = objgetp(ctx, objn, prop, 0);
    if (ofs == 0)
        return 0;

    /* get the object */
    objp = mcmlck(ctx, (mcmon)objn);

    /* get the property */
    propptr = objofsp(objp, ofs);

    /* get the data size */
    valsiz = prpsize(propptr);

    /* done with the object */
    mcmunlck(ctx, (mcmon)objn);

    /* 
     *   return the ending offset - it's the starting offset plus the
     *   property header size plus the size of the property data 
     */
    return ofs + PRPHDRSIZ + valsiz;
}

/* determine whether an object is a descendant of another object */
static int objisd(mcmcxdef *ctx, objdef *objptr, objnum parentnum)
{
    uchar *sc;
    int    cnt;
    
    for (sc = objsc(objptr), cnt = objnsc(objptr) ; cnt ;
         sc += 2, --cnt)
    {
        int     cursc = osrp2(sc);
        int     ret;
        objdef *curptr;
        
        if (cursc == parentnum) return(TRUE);
        
        curptr = (objdef *)mcmlck(ctx, (mcmon)cursc);
        ret = objisd(ctx, curptr, parentnum);
        mcmunlck(ctx, (mcmon)cursc);
        if (ret) return(TRUE);
    }
    return(FALSE);
}

/*
 *   Get a property of an object, either from the object or from a
 *   superclass (inherited).  If the inh flag is TRUE, we do not look at
 *   all in the object itself, but restrict our search to inherited
 *   properties only.  We return the byte offset of the prpdef within the
 *   object in which the prpdef is found; the superclass object itself is
 *   NOT locked upon return, but we will NOT unlock the object passed in
 *   (in other words, all object locking status is the same as it was on
 *   entry).  If the offset is zero, the property was not found.
 *   
 *   This is an internal helper routine - it's not meant to be called
 *   except by objgetap().  
 */
static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
                      objnum *orn, int inh, dattyp *ortyp)
{
    uchar  *sc;
    ushort  sccnt;
    ushort  psav;
    dattyp  typsav;
    objnum  osavn;
    uchar  *o1;
    objnum  o1n;
    ushort  poff;
    int     found;
    uint    retval;
    dattyp  typ;
    uchar   sclist[100];                           /* up to 50 superclasses */
    objdef *objptr;
    
    NOREG((&obj))
    
    /* see if the property is in the current object first */
    if (!inh && (retval = objgetp(ctx, obj, prop, &typ)) != 0)
    {
        /* 
         *   tell the caller which object this came from, if the caller
         *   wants to know 
         */
        if (orn != 0)
            *orn = obj;

        /* if the caller wants to know the type, return it */
        if (ortyp != 0)
            *ortyp = typ;

        /* return the property offset */
        return retval;
    }
    
    /* lock the object, cache its superclass list, and unlock it */
    objptr = (objdef *)mcmlck(ctx, (mcmon)obj);
    sccnt = objnsc(objptr);
    memcpy(sclist, objsc(objptr), (size_t)(sccnt << 1));
    sc = sclist;
    mcmunlck(ctx, (mcmon)obj);
    
    /* try to inherit the property */
    for (found = FALSE ; sccnt != 0 ; sc += 2, --sccnt)
    {
        /* recursively look up the property in this superclass */
        poff = objgetap0(ctx, (objnum)osrp2(sc), prop, &o1n, FALSE, &typ);

        /* if we found the property, remember it */
        if (poff != 0)
        {
            int isdesc;
            
            /* if we have a previous object, determine lineage */
            if (found)
            {
                o1 = mcmlck(ctx, o1n);
                isdesc = objisd(ctx, o1, osavn);
                mcmunlck(ctx, o1n);
            }
            
            /*
             *   if we don't already have a property, or the new object
             *   is a descendant of the previously found object (meaning
             *   that the new object's property should override the
             *   previously found object's property), use this new
             *   property 
             */
            if (!found || isdesc)
            {
                psav = poff;
                osavn = o1n;
                typsav = typ;
                found = TRUE;
            }
        }
    }

    /* set return pointer and return the offset of what we found */
    if (orn != 0)
        *orn = osavn;

    /* return the object type if the caller wanted it */
    if (ortyp != 0)
        *ortyp = typsav;

    /* return the offset of the property if we found one, or zero if not */
    return (found ? psav : 0);
}

/*
 *   Get a property of an object, either from the object or from a
 *   superclass (inherited).  If the inh flag is TRUE, we do not look at
 *   all in the object itself, but restrict our search to inherited
 *   properties only.  We return the byte offset of the prpdef within the
 *   object in which the prpdef is found; the superclass object itself is
 *   NOT locked upon return, but we will NOT unlock the object passed in
 *   (in other words, all object locking status is the same as it was on
 *   entry).  If the offset is zero, the property was not found.  
 */
uint objgetap(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
              objnum *ornp, int inh)
{
    uint    retval;
    dattyp  typ;
    objnum  orn;

    /* 
     *   even if the caller doesn't care about the original object number,
     *   we do, so provide our own location to store it if necessary 
     */
    if (ornp == 0)
        ornp = &orn;

    /* keep going until we've finished translating synonyms */
    for (;;)
    {
        /* look up the property */
        retval = objgetap0(ctx, obj, prop, ornp, inh, &typ);
        
        /* 
         *   If we found something (i.e., retval != 0), check to see if we
         *   have a synonym; if so, synonym translation is required 
         */
        if (retval != 0 && typ == DAT_SYN)
        {
            prpnum  prvprop;
            objdef *objptr;
            prpdef *p;

            /* 
             *   Translation is required - get new property and try again.
             *   First, remember the original property, so we can make
             *   sure we're not going to loop (at least, not in this one
             *   synonym definition).  
             */
            prvprop = prop;

            objptr = (objdef *)mcmlck(ctx, (mcmon)*ornp);
            p = objofsp(objptr, retval);
            prop = osrp2(prpvalp(p));
            mcmunlck(ctx, (mcmon)*ornp);

            /* check for direct circularity */
            if (prop == prvprop)
                errsig(ctx->mcmcxgl->mcmcxerr, ERR_CIRCSYN);

            /* go back for another try with the new property */
            continue;
        }

        /* we don't have to perform a translation; return the result */
        return retval;
    }
}


/*
 *   Expand an object by a requested size, and return a pointer to the
 *   object's location.  The object will be unlocked and relocked by this
 *   call.  The new size is written to the *siz argument.
 */
objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz)
{
    ushort  oldsiz;
    uchar  *p;
    
    oldsiz = mcmobjsiz(ctx, (mcmon)obj);
    p = mcmrealo(ctx, (mcmon)obj, (ushort)(oldsiz + *siz));
    *siz = mcmobjsiz(ctx, (mcmon)obj) - oldsiz;
    return((objdef *)p);
}

/*
 *   Delete a property in an object.  Note that we never actually remove
 *   anything marked as an original property, but just mark it 'ignore'.
 *   This way, it's easy to restore the entire original state of the
 *   objects, simply by deleting everything not marked original and
 *   clearing the 'ignore' flag on the remaining properties.  If
 *   'mark_only' is true, we'll only mark the property as deleted without
 *   actually reclaiming the space; this is necessary when deleting a
 *   method when other methods may follow, since p-code is not entirely
 *   self-relative and thus can't always be relocated within an object.  
 */
void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only)
{
    objdef *objptr;
    uint    pofs;
    prpdef *p;
    prpdef *nxt;
    size_t  movsiz;
    
    pofs = objgetp(mctx, objn, prop, (dattyp *)0);  /* try to find property */
    if (!pofs) return;                   /* not defined - nothing to delete */
    
    objptr = (objdef *)mcmlck(mctx, objn);            /* get lock on object */
    p = objofsp(objptr, pofs);                 /* get actual prpdef pointer */
    nxt = objpnxt(p);                    /* find next prpdef after this one */
    
    /* if this is original, just mark 'ignore' */
    if (prpflg(p) & PRPFORG)
    {
        prpflg(p) |= PRPFIGN;                    /* mark this as overridden */
    }
    else if (mark_only)
    {
        prpflg(p) |= PRPFDEL;     /* mark as deleted without removing space */
    }
    else
    {
        /* move prpdef's after current one down over current one */
        movsiz = (uchar *)objptr + objfree(objptr) - (uchar *)nxt;
        memmove(p, nxt, movsiz);

        objsnp(objptr, objnprop(objptr)-1);
        objsfree(objptr, objfree(objptr) - (((uchar *)nxt) - ((uchar *)p)));
    }
    
    /* tell cache manager this object has been changed, and unlock it */
    mcmtch(mctx, objn);
    mcmunlck(mctx, objn);
}

/*
 *   Set a property of an object to a new value, overwriting the original
 *   value (if any); the object must be unlocked coming in.  If an undo
 *   context is provided, an undo record is written; if the undo context
 *   pointer is null, no undo information is kept.  
 */
void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
             void *val, objucxdef *undoctx)
{
    objdef *objptr;
    prpdef *p;
    uint    pofs;
    uint    siz;
    ushort  newsiz;
    int     indexed;
    int     prop_was_set;
    
    /* get a lock on the object */
    objptr = (objdef *)mcmlck(ctx, objn);
    indexed = objflg(objptr) & OBJFINDEX;

    /* catch any errors so we can unlock the object */
    ERRBEGIN(ctx->mcmcxgl->mcmcxerr)
    {
        /* get the previous value of the property, if any */
        pofs = objgetp(ctx, objn, prop, (dattyp *)0);
        p = objofsp(objptr, pofs);
        prop_was_set = (p != 0);

        /* start the undo record if we are keeping undo information */
        if (undoctx && objuok(undoctx))
        {
            uchar  *up;
            uchar   cmd;

            if (p)
            {
                if (prpflg(p) & PRPFORG)
                {
                    cmd = OBJUOVR;                     /* override original */
                    p = (prpdef *)0;       /* pretend it doesn't even exist */
                }
                else cmd = OBJUCHG;                      /* change property */
            }
            else cmd = OBJUADD;            /* prop didn't exist - adding it */

            /* write header, reserve space, and get a pointer to the space */
            up = objures(undoctx, cmd,
                         (ushort)(sizeof(mcmon) + sizeof(prpnum)
                                  + (p ? PRPHDRSIZ + prpsize(p) : 0)));

            /* write the object and property numbers */
            memcpy(up, &objn, (size_t)sizeof(objn));
            up += sizeof(mcmon);
            memcpy(up, &prop, (size_t)sizeof(prop));
            up += sizeof(prop);

            /* if there's existing data, write it */
            if (p)
            {
                memcpy(up, p, (size_t)(PRPHDRSIZ + prpsize(p)));
                up += PRPHDRSIZ + prpsize(p);
            }

            /* update the undo context's head offset for the new value */
            undoctx->objucxhead = up - undoctx->objucxbuf;
        }

        /* get the size of the data */
        siz = datsiz(typ, val);

        /*
         *   If the property is already set, and the new data fits, use the
         *   existing slot.  However, do not use existing slot if it's
         *   in the non-mutable portion of the object.
         */
        if (!p || prpsize(p) < siz || pofs < objrst(objptr))
        {
            uint   avail;

            /* delete any existing value */
            if (prop_was_set)
                objdelp(ctx, objn, prop, FALSE);

            /* get the top of the property area */
            p = objpfre(objptr);        

            /* make sure there's room at the top */
            avail = mcmobjsiz(ctx, (mcmon)objn) - objfree(objptr);
            if (avail < siz + PRPHDRSIZ)
            {
                newsiz = 64 + ((objfree(objptr) + siz + PRPHDRSIZ) -
                               mcmobjsiz(ctx, (mcmon)objn));
                objptr = objexp(ctx, objn, &newsiz);
                p = objpfre(objptr);       /* reset pointer if object moved */
                /* NOTE! Index (if present) is now invalid! */
            }

            prpsetsize(p, siz);                /* set the new property size */
            prpsetprop(p, prop);                     /* ... and property id */
            prpflg(p) = 0;                         /* no property flags yet */
            objsnp(objptr, objnprop(objptr) + 1);          /* one more prop */
            objsfree(objptr, objfree(objptr) + siz + PRPHDRSIZ);
        }

        /* copy the new data to top of object's free space */
        prptype(p) = typ;
        if (siz != 0) memcpy(prpvalp(p), val, (size_t)siz);
    }
    ERRCLEAN(ctx->mcmcxgl->mcmcxerr)
    {
        mcmunlck(ctx, objn);                           /* unlock the object */
    }
    ERRENDCLN(ctx->mcmcxgl->mcmcxerr)
        
    /* dirty the object, and release lock on object before return */
    mcmtch(ctx, objn);                        /* mark the object as changed */
    mcmunlck(ctx, objn);                                       /* unlock it */

    /* if necessary, rebuild the property index */
    if (indexed) objindx(ctx, objn);
}

/* set an undo savepoint */
void objusav(objucxdef *undoctx)
{
    /* the only thing in this record is the OBJUSAV header */
    objures(undoctx, OBJUSAV, (ushort)0);
}

/* reserve space in an undo buffer, and write header */
uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz)
{
    ushort  prv;
    uchar  *p;
    
    /* adjust size to include header information */
    siz += 1 + sizeof(ushort);
    
    /* make sure there's enough room overall for the record */
    if (siz > undoctx->objucxsiz) errsig(undoctx->objucxerr, ERR_UNDOVF);
    
    /* if there's no information, reset buffers */
    if (undoctx->objucxhead == undoctx->objucxprv)
    {
        undoctx->objucxhead = undoctx->objucxprv = undoctx->objucxtail = 0;
        undoctx->objucxtop = 0;
        goto done;
    }
    
    /* if tail is below head, we can use to top of entire buffer */
    if (undoctx->objucxtail < undoctx->objucxhead)
    {
        /* if there's enough space left after head, we're done */
        if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
            goto done;
        
        /* insufficient space:  wrap head down to bottom of buffer */
        undoctx->objucxtop = undoctx->objucxprv;            /* last was top */
        undoctx->objucxhead = 0;
    }
    
    /* head is below tail:  delete records until we have enough room */
    while (undoctx->objucxtail - undoctx->objucxhead < siz)
    {
        objutadv(undoctx);
        
        /* if the tail wrapped, advancing won't do any more good */
        if (undoctx->objucxtail <= undoctx->objucxhead)
        {
            /* if there's enough room at the top, we're done */
            if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
                goto done;
            
            /* still not enough room; wrap the head this time */
            undoctx->objucxtop = undoctx->objucxprv;        /* last was top */
            undoctx->objucxhead = 0;
        }
    }
    
done:
    /* save back-link, and set objucxprv pointer to the new record */
    prv = undoctx->objucxprv;
    undoctx->objucxprv = undoctx->objucxhead;
    
    /* write the header:  command byte, back-link to previous record */
    p = &undoctx->objucxbuf[undoctx->objucxhead];
    *p++ = cmd;
    memcpy(p, &prv, sizeof(prv));
    
    /* advance the head pointer past the header */
    undoctx->objucxhead += 1 + sizeof(prv);
    
    /* set the high-water mark if we've exceeded the old one */
    if (undoctx->objucxprv > undoctx->objucxtop)
        undoctx->objucxtop = undoctx->objucxprv;

    /* return the reserved space */
    return &undoctx->objucxbuf[undoctx->objucxhead];
}

/* advance the undo tail pointer over the record it points to */
void objutadv(objucxdef *undoctx)
{
    uchar  *p;
    ushort  siz;
    uchar   pr[PRPHDRSIZ];                   /* space for a property header */
    uchar   cmd;
    
    /* if we're at the most recently written record, flush buffer */
    if (undoctx->objucxtail == undoctx->objucxprv)
    {
        undoctx->objucxtail = 0;
        undoctx->objucxprv = 0;
        undoctx->objucxhead = 0;
        undoctx->objucxtop = 0;
    }

    /* if we've reached high water mark, wrap back to bottom */
    if (undoctx->objucxtail == undoctx->objucxtop)
    {
        undoctx->objucxtail = 0;
        return;
    }
    
    /* determine size by inspecting current record */
    p = undoctx->objucxbuf + undoctx->objucxtail;
    siz = 1 + sizeof(ushort);                          /* basic header size */
    
    cmd = *p++;
    p += sizeof(ushort);                       /* skip the previous pointer */
    
    switch(cmd)
    {
    case OBJUCHG:
        /* change:  property header (added below) plus data value */
        memcpy(&pr, p + sizeof(mcmon) + sizeof(prpnum), (size_t)PRPHDRSIZ);
        siz += PRPHDRSIZ + prpsize(&pr);
        /* FALLTHROUGH */

    case OBJUADD:
    case OBJUOVR:
        /* add/override:  property header only */
        siz += sizeof(mcmon) + sizeof(prpnum);
        break;
        
    case OBJUCLI:
        siz += (*undoctx->objucxcsz)(undoctx->objucxccx, p);
        break;

    case OBJUSAV:
        break;
    }
    
    undoctx->objucxtail += siz;
}

/* undo one undo record, and remove it from the undo list */
void obj1undo(mcmcxdef *mctx, objucxdef *undoctx)
{
    uchar  *p;
    prpnum  prop;
    objnum  objn;
    uchar   cmd;
    uchar   pr[PRPHDRSIZ];                     /* space for property header */
    ushort  prv;
    ushort  pofs;
    objdef *objptr;
    int     indexed;

    /* if there's no more undo, signal an error */
    if (undoctx->objucxprv == undoctx->objucxhead)
        errsig(undoctx->objucxerr, ERR_NOUNDO);
    
    /* move back to previous record */
    undoctx->objucxhead = undoctx->objucxprv;
    p = &undoctx->objucxbuf[undoctx->objucxprv];
    
    /* get command, and set undocxprv to previous record */
    cmd = *p++;
    memcpy(&prv, p, sizeof(prv));
    p += sizeof(prv);
    
    /* if we're at the tail, no more undo; otherwise, use back link */
    if (undoctx->objucxprv == undoctx->objucxtail)
        undoctx->objucxprv = undoctx->objucxhead;
    else
        undoctx->objucxprv = prv;
    
    if (cmd == OBJUSAV) return;       /* savepointer marker - nothing to do */
    
    /* get object/property information for property-changing undo */
    if (cmd != OBJUCLI)
    {
        memcpy(&objn, p, (size_t)sizeof(objn));
        p += sizeof(objn);
        memcpy(&prop, p, (size_t)sizeof(prop));
        p += sizeof(prop);
        objptr = mcmlck(mctx, objn);
        indexed = (objflg(objptr) & OBJFINDEX);
        mcmunlck(mctx, objn);
    }
    
    switch(cmd)
    {
    case OBJUADD:
        objdelp(mctx, objn, prop, FALSE);
        if (indexed) objindx(mctx, objn);
        break;
        
    case OBJUOVR:
        objdelp(mctx, objn, prop, FALSE);  /* delete the non-original value */
        pofs = objgetp(mctx, objn, prop, (dattyp *)0);  /* get ignored prop */
        objptr = (objdef *)mcmlck(mctx, objn);           /* lock the object */
        prpflg(objofsp(objptr, pofs)) &= ~PRPFIGN;     /* no longer ignored */
        mcmunlck(mctx, objn);                          /* unlock the object */
        break;
        
    case OBJUCHG:
        memcpy(&pr, p, (size_t)PRPHDRSIZ);
        p += PRPHDRSIZ;
        objsetp(mctx, objn, prop, prptype(&pr), (void *)p, (objucxdef *)0);
        break;
        
    case OBJUCLI:
        (*undoctx->objucxcun)(undoctx->objucxccx, p);
        break;
    }
}

/*
 *   Determine if it's ok to add undo records - returns TRUE if a
 *   savepoint has been stored in the undo log, FALSE if not. 
 */
int objuok(objucxdef *undoctx)
{
    ushort prv;

    /* see if there's any more undo information */
    if (undoctx->objucxprv == undoctx->objucxhead)
        return(FALSE);

    /* look for most recent savepoint marker */
    for (prv = undoctx->objucxprv ;; )
    {
        if (undoctx->objucxbuf[prv] == OBJUSAV)
            return(TRUE);               /* found a savepoint - can add undo */

        /* if we've reached the tail, there are no more undo records */
        if (prv == undoctx->objucxtail)
            return(FALSE);                /* no savepoints - can't add undo */

        /* get previous record */
        memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
    }
}

/*
 *   Undo back to the most recent savepoint.  If there is no savepoint in
 *   the undo list, NOTHING will be undone.  This prevents reaching an
 *   inconsistent state in which some, but not all, of the operations
 *   between two savepoints are undone: either all operations between two
 *   savepoints will be undone, or none will. 
 */
void objundo(mcmcxdef *mctx, objucxdef *undoctx)
{
    ushort prv;
    ushort sav;

    /* see if there's any more undo information */
    if (undoctx->objucxprv == undoctx->objucxhead)
        errsig(undoctx->objucxerr, ERR_NOUNDO);

    /* look for most recent savepoint marker */
    for (prv = undoctx->objucxprv ;; )
    {
        if (undoctx->objucxbuf[prv] == OBJUSAV)
        {
            sav = prv;
            break;
        }
        
        /* if we've reached the tail, there are no more undo records */
        if (prv == undoctx->objucxtail)
            errsig(undoctx->objucxerr, ERR_ICUNDO);

        /* get previous record */
        memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
    }
    
    /* now undo everything until we get to the savepoint */
    do { obj1undo(mctx, undoctx); } while (undoctx->objucxhead != sav);
}

/* initialize undo context */
objucxdef *objuini(mcmcxdef *ctx, ushort siz,
                   void (*undocb)(void *, uchar *), 
                   ushort (*sizecb)(void *, uchar *),
                   void *callctx)
{
    objucxdef *ret;
    long       totsiz;

    /* force size into valid range */
    totsiz = (long)siz + sizeof(objucxdef) - 1;
    if (totsiz > 0xff00)
        siz = 0xff00 - sizeof(objucxdef) + 1;

    ret = (objucxdef *)mchalo(ctx->mcmcxgl->mcmcxerr,
                              (ushort)(sizeof(objucxdef) + siz - 1),
                              "objuini");
    
    ret->objucxmem  = ctx;
    ret->objucxerr  = ctx->mcmcxgl->mcmcxerr;
    ret->objucxsiz  = siz;
    ret->objucxhead = ret->objucxprv = ret->objucxtail = ret->objucxtop = 0;
    
    /* set client callback functions */
    ret->objucxcun = undocb;               /* callback to apply client undo */
    ret->objucxcsz = sizecb;         /* callback to get size of client undo */
    ret->objucxccx = callctx;      /* context for client callback functions */
    
    return(ret);
}

/* discard all undo records */
void objulose(objucxdef *ctx)
{
    if (ctx)
        ctx->objucxhead =
        ctx->objucxprv  =
        ctx->objucxtail =
        ctx->objucxtop  = 0;
}

/* revert object to original (post-compilation) values */
void objrevert(void *ctx0, mcmon objn)
{
    mcmcxdef *mctx = (mcmcxdef *)ctx0;
    uchar    *p;
    prpdef   *pr;
    int       cnt;
    int       indexed;
    
    p = mcmlck(mctx, objn);
    pr = objprp(p);
    indexed = objflg(p) & OBJFINDEX;
    
    /* restore original settings */
    objsfree(p, objrst(p));
    objsnp(p, objstat(p));

    /* go through original properties and remove 'ignore' flag if set */
    for (cnt = objnprop(p) ; cnt ; pr = objpnxt(pr), --cnt)
        prpflg(pr) &= ~PRPFIGN;
    
    /* touch object and unlock it */
    mcmtch(mctx, objn);
    mcmunlck(mctx, objn);
    
    /* if it's indexed, rebuild the index */
    if (indexed) objindx(mctx, objn);
}

/* set 'ignore' flag for original properties set in mutable part */
void objsetign(mcmcxdef *mctx, objnum objn)
{
    objdef *objptr;
    prpdef *mut;
    prpdef *p;
    int     statcnt;
    int     cnt;
    int     indexed;
    prpdef *p1;
    
    objptr = (objdef *)mcmlck(mctx, (mcmon)objn);
    p1 = objprp(objptr);
    indexed = objflg(objptr) & OBJFINDEX;

    /* go through mutables, and set ignore on duplicates in non-mutables */
    for (mut = (prpdef *)(objptr + objrst(objptr)),
         cnt = objnprop(objptr) - objstat(objptr) ; cnt ;
         mut = objpnxt(mut), --cnt)
    {
        for (p = p1, statcnt = objstat(objptr) ; statcnt ;
             p = objpnxt(p), --statcnt)
        {
            /* if this static prop matches a mutable prop, ignore it */
            if (prpprop(p) == prpprop(mut))
            {
                prpflg(p) |= PRPFIGN;
                break;
            }
        }
    }
    
    mcmtch(mctx, (mcmon)objn);
    mcmunlck(mctx, (mcmon)objn);
    if (indexed) objindx(mctx, objn);
}

/*
 *   Build or rebuild a property index for an object.  
 */
void objindx(mcmcxdef *mctx, objnum objn)
{
    uint    newsiz;
    uint    avail;
    objdef *objptr;
    uint    cnt;
    prpdef *p;
    uchar  *indp;
    uchar  *indbase;
    uint    icnt;
    uint    first;
    uint    last;
    uint    cur;
    
    objptr = (objdef *)mcmlck(mctx, objn);            /* get object pointer */
    cnt = objnprop(objptr);                     /* get number of properties */
    p = objprp(objptr);         /* get pointer to properties (or old index) */
    newsiz = 2 + 4*cnt;                 /* figure size needed for the index */
    
    avail = mcmobjsiz(mctx, objn) - objfree(objptr);
    
    /* insert space for the index; expand the object if necessary */
    if (avail < newsiz)
    {
        ushort  need;
        
        newsiz += 10*4;                   /* add some extra space for later */
        need = newsiz - avail;            /* compute amount of space needed */
        objptr = objexp(mctx, objn, &need);
        p = objprp(objptr);
    }
    
    /* now build the index */
    indbase = objpfre(objptr);
    for (icnt = 0 ; cnt ; p = objpnxt(p), --cnt, ++icnt)
    {
        uint ofs = (uchar *)p - (uchar *)objptr;
        
        if (icnt)
        {
            /* figure out where to insert this property */
            first = 0;
            last = icnt - 1;
            for (;;)
            {
                if (first > last) break;
                cur = first + (last - first)/2;
                indp = indbase + cur*4;
                if (indp[0] == p[0] && indp[1] == p[1])
                    break;
                else if (indp[0] < p[0]
                         || (indp[0] == p[0] && indp[1] < p[1]))
                    first = (cur == first ? first + 1 : cur);
                else
                    last = (cur == last ? last - 1 : cur);
            }
            
            /* make sure we're positioned just before insertion point */
            while (cur < icnt
                   && (indp[0] <= p[0]
                       || (indp[0] == p[0] && indp[1] <= p[1])))
            {
                indp += 4;
                ++cur;
            }
                
            /* move elements above if any */
            if (cur < icnt)
                memmove(indp + 4, indp, (size_t)((icnt - cur) * 4));
        }
        else
            indp = indbase;
        
        /* insert property into index */
        indp[0] = p[0];
        indp[1] = p[1];
        oswp2(indp+2, ofs);
    }
    
    /* set the index flag, and dirty and free the object */
    objsflg(objptr, objflg(objptr) | OBJFINDEX);
    mcmtch(mctx, (mcmon)objn);
    mcmunlck(mctx, (mcmon)objn);
}

/* allocate and initialize an object */
objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
               objnum *objnptr, int classflg)
{
    objdef *o;
    mcmon   objn;

    /* allocate cache object */
    o = (objdef *)mcmalo(mctx, (ushort)(OBJDEFSIZ + sccnt * 2 + propspace),
                         &objn);
    
    /* set up object descriptor for the new object */
    objini(mctx, sccnt, (objnum)objn, classflg);

    *objnptr = (objnum)objn;
    return(o);
}

/* initialize an already allocated object */
void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg)
{
    objdef *o;
    uint    flags = 0;

    /* get a lock on the object */
    o = (objdef *)mcmlck(mctx, objn);
    
    memset(o, 0, (size_t)10);
    objsnsc(o, sccnt);
    objsfree(o, ((uchar *)objsc(o) + 2*sccnt) - (uchar *)o);
    
    /* set up flags */
    if (classflg) flags |= OBJFCLASS;
    objsflg(o, flags);

    /* tell cache manager that this object has been modified */
    mcmtch(mctx, objn);
    mcmunlck(mctx, objn);
}

/*
 *   Get the first superclass of an object.  If it doesn't have any
 *   superclasses, return invalid.
 */
objnum objget1sc(mcmcxdef *ctx, objnum objn)
{
    objdef *p;
    objnum  retval;

    /* lock the object */
    p = mcmlck(ctx, (mcmon)objn);

    /* get the first superclass if it has any */
    if (objnsc(p) == 0)
        retval = MCMONINV;
    else
        retval = osrp2(objsc(p));

    /* unlock the object and return the superclass value */
    mcmunlck(ctx, (mcmon)objn);
    return retval;
}
