1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/tads/tads2/object.h"
24 #include "glk/tads/tads2/error.h"
25 #include "glk/tads/tads2/memory_cache_heap.h"
26 #include "glk/tads/os_glk.h"
27 
28 namespace Glk {
29 namespace TADS {
30 namespace TADS2 {
31 
32 /*
33  *   Get a property WITHOUT INHERITANCE.  The offset of the property's
34  *   prpdef is returned.  An offset of zero means the property wasn't
35  *   found.
36  */
objgetp(mcmcxdef * mctx,objnum objn,prpnum prop,dattyp * typptr)37 uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
38 {
39     objdef *objptr;
40     prpdef *p;
41     int     cnt;
42     uint    retval;                       /* property offset, if we find it */
43     uint    ignprop; /* ignored property - use if real property isn't found */
44     uchar   pbuf[2];                  /* property number in portable format */
45     uchar  *indp = nullptr;
46     uchar  *indbase;
47     int     last;
48     int     first;
49     int     cur = 0;
50 
51     oswp2(pbuf, prop);            /* get property number in portable foramt */
52     objptr = (objdef *)mcmlck(mctx, objn);      /* get a lock on the object */
53     ignprop = 0;                   /* assume we won't find ignored property */
54     cnt = objnprop(objptr);             /* get number of properties defined */
55     retval = 0;                                          /* presume failure */
56 
57     if (objflg(objptr) & OBJFINDEX)
58     {
59         /* there's an index -> do a binary search through the index */
60         indbase = (uchar *)objpfre(objptr);                   /* find index */
61         first = 0;
62         last = cnt - 1;
63         for (;;)
64         {
65             if (first > last) break;           /* crossed over -> not found */
66             cur = first + (last - first)/2;         /* split the difference */
67             indp = indbase + cur*4;            /* get pointer to this entry */
68             if (indp[0] == pbuf[0] && indp[1] == pbuf[1])
69             {
70                 retval = osrp2(indp + 2);
71                 break;
72             }
73             else if (indp[0] < pbuf[0]
74                      || (indp[0] == pbuf[0] && indp[1] < pbuf[1]))
75                 first = (cur == first ? first + 1 : cur);
76             else
77                 last = (cur == last ? last - 1 : cur);
78         }
79 
80         /* ignore ignored and deleted properties if possible */
81         while (retval
82                && ((prpflg(objptr + retval) & PRPFIGN) != 0
83                    || ((prpflg(objptr + retval) & PRPFDEL) != 0
84                        && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0))
85                && cur < cnt && indp[0] == indp[4] && indp[1] == indp[5])
86         {
87             indp += 4;
88             retval = osrp2(indp + 2);
89         }
90         if (retval && osrp2(objptr + retval) != prop)
91             assert(FALSE);
92     }
93     else
94     {
95         /* there's no index -> do sequential search through properties */
96         for (p = objprp(objptr) ; cnt ; p = objpnxt(p), --cnt)
97         {
98             /* if this is the property, and it's not being ignored, use it */
99             if (*(uchar *)p == pbuf[0] && *(((uchar *)p) + 1) == pbuf[1])
100             {
101                 if (prpflg(p) & PRPFIGN)                 /* this is ignored */
102                     ignprop = objpofs(objptr, p);  /* ... make a note of it */
103                 else if ((prpflg(p) & PRPFDEL) != 0         /* it's deleted */
104                          && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0)
105                     /* simply skip it */ ;
106                 else
107                 {
108                     retval = objpofs(objptr, p);         /* this is the one */
109                     break;                                    /* we're done */
110                 }
111             }
112         }
113     }
114 
115     if (!retval) retval = ignprop;     /* use ignored value if nothing else */
116     if (retval && typptr) *typptr = prptype(objofsp(objptr, retval));
117 
118     mcmunlck(mctx, objn);                 /* done with object, so unlock it */
119     return(retval);
120 }
121 
122 /* get the offset of the end of a property in an object */
objgetp_end(mcmcxdef * ctx,objnum objn,prpnum prop)123 uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop)
124 {
125     objdef *objp;
126     prpdef *propptr;
127     uint    ofs;
128     uint    valsiz;
129 
130     /* get the start of the object */
131     ofs = objgetp(ctx, objn, prop, 0);
132     if (ofs == 0)
133         return 0;
134 
135     /* get the object */
136     objp = mcmlck(ctx, (mcmon)objn);
137 
138     /* get the property */
139     propptr = objofsp(objp, ofs);
140 
141     /* get the data size */
142     valsiz = prpsize(propptr);
143 
144     /* done with the object */
145     mcmunlck(ctx, (mcmon)objn);
146 
147     /*
148      *   return the ending offset - it's the starting offset plus the
149      *   property header size plus the size of the property data
150      */
151     return ofs + PRPHDRSIZ + valsiz;
152 }
153 
154 /* determine whether an object is a descendant of another object */
objisd(mcmcxdef * ctx,objdef * objptr,objnum parentnum)155 static int objisd(mcmcxdef *ctx, objdef *objptr, objnum parentnum)
156 {
157     uchar *sc;
158     int    cnt;
159 
160     for (sc = objsc(objptr), cnt = objnsc(objptr) ; cnt ;
161          sc += 2, --cnt)
162     {
163         int     cursc = osrp2(sc);
164         int     ret;
165         objdef *curptr;
166 
167         if (cursc == parentnum) return(TRUE);
168 
169         curptr = (objdef *)mcmlck(ctx, (mcmon)cursc);
170         ret = objisd(ctx, curptr, parentnum);
171         mcmunlck(ctx, (mcmon)cursc);
172         if (ret) return(TRUE);
173     }
174     return(FALSE);
175 }
176 
177 /*
178  *   Get a property of an object, either from the object or from a
179  *   superclass (inherited).  If the inh flag is TRUE, we do not look at
180  *   all in the object itself, but restrict our search to inherited
181  *   properties only.  We return the byte offset of the prpdef within the
182  *   object in which the prpdef is found; the superclass object itself is
183  *   NOT locked upon return, but we will NOT unlock the object passed in
184  *   (in other words, all object locking status is the same as it was on
185  *   entry).  If the offset is zero, the property was not found.
186  *
187  *   This is an internal helper routine - it's not meant to be called
188  *   except by objgetap().
189  */
objgetap0(mcmcxdef * ctx,noreg objnum obj,prpnum prop,objnum * orn,int inh,dattyp * ortyp)190 static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
191                       objnum *orn, int inh, dattyp *ortyp)
192 {
193     uchar  *sc;
194     ushort  sccnt;
195     ushort  psav = 0;
196     dattyp  typsav = DAT_NIL;
197     objnum  osavn = MCMONINV;
198     uchar  *o1;
199     objnum  o1n;
200     ushort  poff;
201     int     found;
202     uint    retval;
203     dattyp  typ;
204     uchar   sclist[100];                           /* up to 50 superclasses */
205     objdef *objptr;
206 
207     NOREG((&obj))
208 
209     /* see if the property is in the current object first */
210     if (!inh && (retval = objgetp(ctx, obj, prop, &typ)) != 0)
211     {
212         /*
213          *   tell the caller which object this came from, if the caller
214          *   wants to know
215          */
216         if (orn != 0)
217             *orn = obj;
218 
219         /* if the caller wants to know the type, return it */
220         if (ortyp != 0)
221             *ortyp = typ;
222 
223         /* return the property offset */
224         return retval;
225     }
226 
227     /* lock the object, cache its superclass list, and unlock it */
228     objptr = (objdef *)mcmlck(ctx, (mcmon)obj);
229     sccnt = objnsc(objptr);
230     memcpy(sclist, objsc(objptr), (size_t)(sccnt << 1));
231     sc = sclist;
232     mcmunlck(ctx, (mcmon)obj);
233 
234     /* try to inherit the property */
235     for (found = FALSE ; sccnt != 0 ; sc += 2, --sccnt)
236     {
237         /* recursively look up the property in this superclass */
238         poff = objgetap0(ctx, (objnum)osrp2(sc), prop, &o1n, FALSE, &typ);
239 
240         /* if we found the property, remember it */
241         if (poff != 0)
242         {
243             int isdesc = 0;
244 
245             /* if we have a previous object, determine lineage */
246             if (found)
247             {
248                 o1 = mcmlck(ctx, o1n);
249                 isdesc = objisd(ctx, o1, osavn);
250                 mcmunlck(ctx, o1n);
251             }
252 
253             /*
254              *   if we don't already have a property, or the new object
255              *   is a descendant of the previously found object (meaning
256              *   that the new object's property should override the
257              *   previously found object's property), use this new
258              *   property
259              */
260             if (!found || isdesc)
261             {
262                 psav = poff;
263                 osavn = o1n;
264                 typsav = typ;
265                 found = TRUE;
266             }
267         }
268     }
269 
270     /* set return pointer and return the offset of what we found */
271     if (orn != 0)
272         *orn = osavn;
273 
274     /* return the object type if the caller wanted it */
275     if (ortyp != 0)
276         *ortyp = typsav;
277 
278     /* return the offset of the property if we found one, or zero if not */
279     return (found ? psav : 0);
280 }
281 
282 /*
283  *   Get a property of an object, either from the object or from a
284  *   superclass (inherited).  If the inh flag is TRUE, we do not look at
285  *   all in the object itself, but restrict our search to inherited
286  *   properties only.  We return the byte offset of the prpdef within the
287  *   object in which the prpdef is found; the superclass object itself is
288  *   NOT locked upon return, but we will NOT unlock the object passed in
289  *   (in other words, all object locking status is the same as it was on
290  *   entry).  If the offset is zero, the property was not found.
291  */
objgetap(mcmcxdef * ctx,noreg objnum obj,prpnum prop,objnum * ornp,int inh)292 uint objgetap(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
293               objnum *ornp, int inh)
294 {
295     uint    retval;
296     dattyp  typ;
297     objnum  orn;
298 
299     /*
300      *   even if the caller doesn't care about the original object number,
301      *   we do, so provide our own location to store it if necessary
302      */
303     if (ornp == 0)
304         ornp = &orn;
305 
306     /* keep going until we've finished translating synonyms */
307     for (;;)
308     {
309         /* look up the property */
310         retval = objgetap0(ctx, obj, prop, ornp, inh, &typ);
311 
312         /*
313          *   If we found something (i.e., retval != 0), check to see if we
314          *   have a synonym; if so, synonym translation is required
315          */
316         if (retval != 0 && typ == DAT_SYN)
317         {
318             prpnum  prvprop;
319             objdef *objptr;
320             prpdef *p;
321 
322             /*
323              *   Translation is required - get new property and try again.
324              *   First, remember the original property, so we can make
325              *   sure we're not going to loop (at least, not in this one
326              *   synonym definition).
327              */
328             prvprop = prop;
329 
330             objptr = (objdef *)mcmlck(ctx, (mcmon)*ornp);
331             p = objofsp(objptr, retval);
332             prop = osrp2(prpvalp(p));
333             mcmunlck(ctx, (mcmon)*ornp);
334 
335             /* check for direct circularity */
336             if (prop == prvprop)
337                 errsig(ctx->mcmcxgl->mcmcxerr, ERR_CIRCSYN);
338 
339             /* go back for another try with the new property */
340             continue;
341         }
342 
343         /* we don't have to perform a translation; return the result */
344         return retval;
345     }
346 }
347 
348 
349 /*
350  *   Expand an object by a requested size, and return a pointer to the
351  *   object's location.  The object will be unlocked and relocked by this
352  *   call.  The new size is written to the *siz argument.
353  */
objexp(mcmcxdef * ctx,objnum obj,ushort * siz)354 objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz)
355 {
356     ushort  oldsiz;
357     uchar  *p;
358 
359     oldsiz = mcmobjsiz(ctx, (mcmon)obj);
360     p = mcmrealo(ctx, (mcmon)obj, (ushort)(oldsiz + *siz));
361     *siz = mcmobjsiz(ctx, (mcmon)obj) - oldsiz;
362     return((objdef *)p);
363 }
364 
365 /*
366  *   Delete a property in an object.  Note that we never actually remove
367  *   anything marked as an original property, but just mark it 'ignore'.
368  *   This way, it's easy to restore the entire original state of the
369  *   objects, simply by deleting everything not marked original and
370  *   clearing the 'ignore' flag on the remaining properties.  If
371  *   'mark_only' is true, we'll only mark the property as deleted without
372  *   actually reclaiming the space; this is necessary when deleting a
373  *   method when other methods may follow, since p-code is not entirely
374  *   self-relative and thus can't always be relocated within an object.
375  */
objdelp(mcmcxdef * mctx,objnum objn,prpnum prop,int mark_only)376 void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only)
377 {
378     objdef *objptr;
379     uint    pofs;
380     prpdef *p;
381     prpdef *nxt;
382     size_t  movsiz;
383 
384     pofs = objgetp(mctx, objn, prop, (dattyp *)0);  /* try to find property */
385     if (!pofs) return;                   /* not defined - nothing to delete */
386 
387     objptr = (objdef *)mcmlck(mctx, objn);            /* get lock on object */
388     p = objofsp(objptr, pofs);                 /* get actual prpdef pointer */
389     nxt = objpnxt(p);                    /* find next prpdef after this one */
390 
391     /* if this is original, just mark 'ignore' */
392     if (prpflg(p) & PRPFORG)
393     {
394         prpflg(p) |= PRPFIGN;                    /* mark this as overridden */
395     }
396     else if (mark_only)
397     {
398         prpflg(p) |= PRPFDEL;     /* mark as deleted without removing space */
399     }
400     else
401     {
402         /* move prpdef's after current one down over current one */
403         movsiz = (uchar *)objptr + objfree(objptr) - (uchar *)nxt;
404         memmove(p, nxt, movsiz);
405 
406         objsnp(objptr, objnprop(objptr)-1);
407         objsfree(objptr, objfree(objptr) - (((uchar *)nxt) - ((uchar *)p)));
408     }
409 
410     /* tell cache manager this object has been changed, and unlock it */
411     mcmtch(mctx, objn);
412     mcmunlck(mctx, objn);
413 }
414 
415 /*
416  *   Set a property of an object to a new value, overwriting the original
417  *   value (if any); the object must be unlocked coming in.  If an undo
418  *   context is provided, an undo record is written; if the undo context
419  *   pointer is null, no undo information is kept.
420  */
objsetp(mcmcxdef * ctx,objnum objn,prpnum prop,dattyp typ,const void * val,objucxdef * undoctx)421 void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
422              const void *val, objucxdef *undoctx)
423 {
424     objdef *objptr;
425     prpdef *p;
426     uint    pofs;
427     uint    siz;
428     ushort  newsiz;
429     int     indexed;
430     int     prop_was_set;
431 
432     /* get a lock on the object */
433     objptr = (objdef *)mcmlck(ctx, objn);
434     indexed = objflg(objptr) & OBJFINDEX;
435 
436     /* catch any errors so we can unlock the object */
437     ERRBEGIN(ctx->mcmcxgl->mcmcxerr)
438     {
439         /* get the previous value of the property, if any */
440         pofs = objgetp(ctx, objn, prop, (dattyp *)0);
441         p = objofsp(objptr, pofs);
442         prop_was_set = (p != 0);
443 
444         /* start the undo record if we are keeping undo information */
445         if (undoctx && objuok(undoctx))
446         {
447             uchar  *up;
448             uchar   cmd;
449 
450             if (p)
451             {
452                 if (prpflg(p) & PRPFORG)
453                 {
454                     cmd = OBJUOVR;                     /* override original */
455                     p = (prpdef *)0;       /* pretend it doesn't even exist */
456                 }
457                 else cmd = OBJUCHG;                      /* change property */
458             }
459             else cmd = OBJUADD;            /* prop didn't exist - adding it */
460 
461             /* write header, reserve space, and get a pointer to the space */
462             up = objures(undoctx, cmd,
463                          (ushort)(sizeof(mcmon) + sizeof(prpnum)
464                                   + (p ? PRPHDRSIZ + prpsize(p) : 0)));
465 
466             /* write the object and property numbers */
467             memcpy(up, &objn, (size_t)sizeof(objn));
468             up += sizeof(mcmon);
469             memcpy(up, &prop, (size_t)sizeof(prop));
470             up += sizeof(prop);
471 
472             /* if there's existing data, write it */
473             if (p)
474             {
475                 memcpy(up, p, (size_t)(PRPHDRSIZ + prpsize(p)));
476                 up += PRPHDRSIZ + prpsize(p);
477             }
478 
479             /* update the undo context's head offset for the new value */
480             undoctx->objucxhead = up - undoctx->objucxbuf;
481         }
482 
483         /* get the size of the data */
484         siz = datsiz(typ, val);
485 
486         /*
487          *   If the property is already set, and the new data fits, use the
488          *   existing slot.  However, do not use existing slot if it's
489          *   in the non-mutable portion of the object.
490          */
491         if (!p || (uint)prpsize(p) < siz || pofs < (uint)objrst(objptr))
492         {
493             uint   avail;
494 
495             /* delete any existing value */
496             if (prop_was_set)
497                 objdelp(ctx, objn, prop, FALSE);
498 
499             /* get the top of the property area */
500             p = objpfre(objptr);
501 
502             /* make sure there's room at the top */
503             avail = mcmobjsiz(ctx, (mcmon)objn) - objfree(objptr);
504             if (avail < siz + PRPHDRSIZ)
505             {
506                 newsiz = 64 + ((objfree(objptr) + siz + PRPHDRSIZ) -
507                                mcmobjsiz(ctx, (mcmon)objn));
508                 objptr = objexp(ctx, objn, &newsiz);
509                 p = objpfre(objptr);       /* reset pointer if object moved */
510                 /* NOTE! Index (if present) is now invalid! */
511             }
512 
513             prpsetsize(p, siz);                /* set the new property size */
514             prpsetprop(p, prop);                     /* ... and property id */
515             prpflg(p) = 0;                         /* no property flags yet */
516             objsnp(objptr, objnprop(objptr) + 1);          /* one more prop */
517             objsfree(objptr, objfree(objptr) + siz + PRPHDRSIZ);
518         }
519 
520         /* copy the new data to top of object's free space */
521         prptype(p) = typ;
522         if (siz != 0) memcpy(prpvalp(p), val, (size_t)siz);
523     }
524     ERRCLEAN(ctx->mcmcxgl->mcmcxerr)
525     {
526         mcmunlck(ctx, objn);                           /* unlock the object */
527     }
528     ERRENDCLN(ctx->mcmcxgl->mcmcxerr)
529 
530     /* dirty the object, and release lock on object before return */
531     mcmtch(ctx, objn);                        /* mark the object as changed */
532     mcmunlck(ctx, objn);                                       /* unlock it */
533 
534     /* if necessary, rebuild the property index */
535     if (indexed) objindx(ctx, objn);
536 }
537 
538 /* set an undo savepoint */
objusav(objucxdef * undoctx)539 void objusav(objucxdef *undoctx)
540 {
541     /* the only thing in this record is the OBJUSAV header */
542     objures(undoctx, OBJUSAV, (ushort)0);
543 }
544 
545 /* reserve space in an undo buffer, and write header */
objures(objucxdef * undoctx,uchar cmd,ushort siz)546 uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz)
547 {
548     ushort  prv;
549     uchar  *p;
550 
551     /* adjust size to include header information */
552     siz += 1 + sizeof(ushort);
553 
554     /* make sure there's enough room overall for the record */
555     if (siz > undoctx->objucxsiz) errsig(undoctx->objucxerr, ERR_UNDOVF);
556 
557     /* if there's no information, reset buffers */
558     if (undoctx->objucxhead == undoctx->objucxprv)
559     {
560         undoctx->objucxhead = undoctx->objucxprv = undoctx->objucxtail = 0;
561         undoctx->objucxtop = 0;
562         goto done;
563     }
564 
565     /* if tail is below head, we can use to top of entire buffer */
566     if (undoctx->objucxtail < undoctx->objucxhead)
567     {
568         /* if there's enough space left after head, we're done */
569         if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
570             goto done;
571 
572         /* insufficient space:  wrap head down to bottom of buffer */
573         undoctx->objucxtop = undoctx->objucxprv;            /* last was top */
574         undoctx->objucxhead = 0;
575     }
576 
577     /* head is below tail:  delete records until we have enough room */
578     while (undoctx->objucxtail - undoctx->objucxhead < siz)
579     {
580         objutadv(undoctx);
581 
582         /* if the tail wrapped, advancing won't do any more good */
583         if (undoctx->objucxtail <= undoctx->objucxhead)
584         {
585             /* if there's enough room at the top, we're done */
586             if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
587                 goto done;
588 
589             /* still not enough room; wrap the head this time */
590             undoctx->objucxtop = undoctx->objucxprv;        /* last was top */
591             undoctx->objucxhead = 0;
592         }
593     }
594 
595 done:
596     /* save back-link, and set objucxprv pointer to the new record */
597     prv = undoctx->objucxprv;
598     undoctx->objucxprv = undoctx->objucxhead;
599 
600     /* write the header:  command byte, back-link to previous record */
601     p = &undoctx->objucxbuf[undoctx->objucxhead];
602     *p++ = cmd;
603     memcpy(p, &prv, sizeof(prv));
604 
605     /* advance the head pointer past the header */
606     undoctx->objucxhead += 1 + sizeof(prv);
607 
608     /* set the high-water mark if we've exceeded the old one */
609     if (undoctx->objucxprv > undoctx->objucxtop)
610         undoctx->objucxtop = undoctx->objucxprv;
611 
612     /* return the reserved space */
613     return &undoctx->objucxbuf[undoctx->objucxhead];
614 }
615 
616 /* advance the undo tail pointer over the record it points to */
objutadv(objucxdef * undoctx)617 void objutadv(objucxdef *undoctx)
618 {
619     uchar  *p;
620     ushort  siz;
621     uchar   pr[PRPHDRSIZ];                   /* space for a property header */
622     uchar   cmd;
623 
624     /* if we're at the most recently written record, flush buffer */
625     if (undoctx->objucxtail == undoctx->objucxprv)
626     {
627         undoctx->objucxtail = 0;
628         undoctx->objucxprv = 0;
629         undoctx->objucxhead = 0;
630         undoctx->objucxtop = 0;
631     }
632 
633     /* if we've reached high water mark, wrap back to bottom */
634     if (undoctx->objucxtail == undoctx->objucxtop)
635     {
636         undoctx->objucxtail = 0;
637         return;
638     }
639 
640     /* determine size by inspecting current record */
641     p = undoctx->objucxbuf + undoctx->objucxtail;
642     siz = 1 + sizeof(ushort);                          /* basic header size */
643 
644     cmd = *p++;
645     p += sizeof(ushort);                       /* skip the previous pointer */
646 
647     switch(cmd)
648     {
649     case OBJUCHG:
650         /* change:  property header (added below) plus data value */
651         memcpy(pr, p + sizeof(mcmon) + sizeof(prpnum), (size_t)PRPHDRSIZ);
652         siz += PRPHDRSIZ + prpsize(pr);
653         /* FALLTHROUGH */
654 
655     case OBJUADD:
656     case OBJUOVR:
657         /* add/override:  property header only */
658         siz += sizeof(mcmon) + sizeof(prpnum);
659         break;
660 
661     case OBJUCLI:
662         siz += (*undoctx->objucxcsz)(undoctx->objucxccx, p);
663         break;
664 
665     case OBJUSAV:
666         break;
667     }
668 
669     undoctx->objucxtail += siz;
670 }
671 
672 /* undo one undo record, and remove it from the undo list */
obj1undo(mcmcxdef * mctx,objucxdef * undoctx)673 void obj1undo(mcmcxdef *mctx, objucxdef *undoctx)
674 {
675     uchar  *p;
676 	prpnum  prop = 0;
677 	objnum  objn = 0;
678     uchar   cmd;
679     uchar   pr[PRPHDRSIZ];                     /* space for property header */
680     ushort  prv;
681     ushort  pofs;
682     objdef *objptr;
683 	int     indexed = 0;
684 
685     /* if there's no more undo, signal an error */
686     if (undoctx->objucxprv == undoctx->objucxhead)
687         errsig(undoctx->objucxerr, ERR_NOUNDO);
688 
689     /* move back to previous record */
690     undoctx->objucxhead = undoctx->objucxprv;
691     p = &undoctx->objucxbuf[undoctx->objucxprv];
692 
693     /* get command, and set undocxprv to previous record */
694     cmd = *p++;
695     memcpy(&prv, p, sizeof(prv));
696     p += sizeof(prv);
697 
698     /* if we're at the tail, no more undo; otherwise, use back link */
699     if (undoctx->objucxprv == undoctx->objucxtail)
700         undoctx->objucxprv = undoctx->objucxhead;
701     else
702         undoctx->objucxprv = prv;
703 
704     if (cmd == OBJUSAV) return;       /* savepointer marker - nothing to do */
705 
706     /* get object/property information for property-changing undo */
707     if (cmd != OBJUCLI)
708     {
709         memcpy(&objn, p, (size_t)sizeof(objn));
710         p += sizeof(objn);
711         memcpy(&prop, p, (size_t)sizeof(prop));
712         p += sizeof(prop);
713         objptr = mcmlck(mctx, objn);
714         indexed = (objflg(objptr) & OBJFINDEX);
715         mcmunlck(mctx, objn);
716     }
717 
718     switch(cmd)
719     {
720     case OBJUADD:
721         objdelp(mctx, objn, prop, FALSE);
722         if (indexed) objindx(mctx, objn);
723         break;
724 
725     case OBJUOVR:
726         objdelp(mctx, objn, prop, FALSE);  /* delete the non-original value */
727         pofs = objgetp(mctx, objn, prop, (dattyp *)0);  /* get ignored prop */
728         objptr = (objdef *)mcmlck(mctx, objn);           /* lock the object */
729         prpflg(objofsp(objptr, pofs)) &= ~PRPFIGN;     /* no longer ignored */
730         mcmunlck(mctx, objn);                          /* unlock the object */
731         break;
732 
733     case OBJUCHG:
734         memcpy(pr, p, (size_t)PRPHDRSIZ);
735         p += PRPHDRSIZ;
736         objsetp(mctx, objn, prop, prptype(pr), (void *)p, (objucxdef *)0);
737         break;
738 
739     case OBJUCLI:
740         (*undoctx->objucxcun)(undoctx->objucxccx, p);
741         break;
742     }
743 }
744 
745 /*
746  *   Determine if it's ok to add undo records - returns TRUE if a
747  *   savepoint has been stored in the undo log, FALSE if not.
748  */
objuok(objucxdef * undoctx)749 int objuok(objucxdef *undoctx)
750 {
751     ushort prv;
752 
753     /* see if there's any more undo information */
754     if (undoctx->objucxprv == undoctx->objucxhead)
755         return(FALSE);
756 
757     /* look for most recent savepoint marker */
758     for (prv = undoctx->objucxprv ;; )
759     {
760         if (undoctx->objucxbuf[prv] == OBJUSAV)
761             return(TRUE);               /* found a savepoint - can add undo */
762 
763         /* if we've reached the tail, there are no more undo records */
764         if (prv == undoctx->objucxtail)
765             return(FALSE);                /* no savepoints - can't add undo */
766 
767         /* get previous record */
768         memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
769     }
770 }
771 
772 /*
773  *   Undo back to the most recent savepoint.  If there is no savepoint in
774  *   the undo list, NOTHING will be undone.  This prevents reaching an
775  *   inconsistent state in which some, but not all, of the operations
776  *   between two savepoints are undone: either all operations between two
777  *   savepoints will be undone, or none will.
778  */
objundo(mcmcxdef * mctx,objucxdef * undoctx)779 void objundo(mcmcxdef *mctx, objucxdef *undoctx)
780 {
781     ushort prv;
782     ushort sav;
783 
784     /* see if there's any more undo information */
785     if (undoctx->objucxprv == undoctx->objucxhead)
786         errsig(undoctx->objucxerr, ERR_NOUNDO);
787 
788     /* look for most recent savepoint marker */
789     for (prv = undoctx->objucxprv ;; )
790     {
791         if (undoctx->objucxbuf[prv] == OBJUSAV)
792         {
793             sav = prv;
794             break;
795         }
796 
797         /* if we've reached the tail, there are no more undo records */
798         if (prv == undoctx->objucxtail)
799             errsig(undoctx->objucxerr, ERR_ICUNDO);
800 
801         /* get previous record */
802         memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
803     }
804 
805     /* now undo everything until we get to the savepoint */
806     do { obj1undo(mctx, undoctx); } while (undoctx->objucxhead != sav);
807 }
808 
809 /* initialize undo context */
objuini(mcmcxdef * ctx,ushort siz,void (* undocb)(void *,uchar *),ushort (* sizecb)(void *,uchar *),void * callctx)810 objucxdef *objuini(mcmcxdef *ctx, ushort siz,
811                    void (*undocb)(void *, uchar *),
812                    ushort (*sizecb)(void *, uchar *),
813                    void *callctx)
814 {
815     objucxdef *ret;
816     long       totsiz;
817 
818     /* force size into valid range */
819     totsiz = (long)siz + sizeof(objucxdef) - 1;
820     if (totsiz > 0xff00)
821         siz = 0xff00 - sizeof(objucxdef) + 1;
822 
823     ret = (objucxdef *)mchalo(ctx->mcmcxgl->mcmcxerr,
824                               (sizeof(objucxdef) + siz - 1),
825                               "objuini");
826 
827     ret->objucxmem  = ctx;
828     ret->objucxerr  = ctx->mcmcxgl->mcmcxerr;
829     ret->objucxsiz  = siz;
830     ret->objucxhead = ret->objucxprv = ret->objucxtail = ret->objucxtop = 0;
831 
832     /* set client callback functions */
833     ret->objucxcun = undocb;               /* callback to apply client undo */
834     ret->objucxcsz = sizecb;         /* callback to get size of client undo */
835     ret->objucxccx = callctx;      /* context for client callback functions */
836 
837     return(ret);
838 }
839 
840 /* discard all undo records */
objulose(objucxdef * ctx)841 void objulose(objucxdef *ctx)
842 {
843     if (ctx)
844         ctx->objucxhead =
845         ctx->objucxprv  =
846         ctx->objucxtail =
847         ctx->objucxtop  = 0;
848 }
849 
850 /* uninitialize the undo context - release allocated memory */
objuterm(objucxdef * uctx)851 void objuterm(objucxdef *uctx)
852 {
853     /* free the undo memory block */
854     mchfre(uctx);
855 }
856 
857 /* revert object to original (post-compilation) values */
objrevert(void * ctx0,mcmon objn)858 void objrevert(void *ctx0, mcmon objn)
859 {
860     mcmcxdef *mctx = (mcmcxdef *)ctx0;
861     uchar    *p;
862     prpdef   *pr;
863     int       cnt;
864     int       indexed;
865 
866     p = mcmlck(mctx, objn);
867     pr = objprp(p);
868     indexed = objflg(p) & OBJFINDEX;
869 
870     /* restore original settings */
871     objsfree(p, objrst(p));
872     objsnp(p, objstat(p));
873 
874     /* go through original properties and remove 'ignore' flag if set */
875     for (cnt = objnprop(p) ; cnt ; pr = objpnxt(pr), --cnt)
876         prpflg(pr) &= ~PRPFIGN;
877 
878     /* touch object and unlock it */
879     mcmtch(mctx, objn);
880     mcmunlck(mctx, objn);
881 
882     /* if it's indexed, rebuild the index */
883     if (indexed) objindx(mctx, objn);
884 }
885 
886 /* set 'ignore' flag for original properties set in mutable part */
objsetign(mcmcxdef * mctx,objnum objn)887 void objsetign(mcmcxdef *mctx, objnum objn)
888 {
889     objdef *objptr;
890     prpdef *mut;
891     prpdef *p;
892     int     statcnt;
893     int     cnt;
894     int     indexed;
895     prpdef *p1;
896 
897     objptr = (objdef *)mcmlck(mctx, (mcmon)objn);
898     p1 = objprp(objptr);
899     indexed = objflg(objptr) & OBJFINDEX;
900 
901     /* go through mutables, and set ignore on duplicates in non-mutables */
902     for (mut = (prpdef *)(objptr + objrst(objptr)),
903          cnt = objnprop(objptr) - objstat(objptr) ; cnt ;
904          mut = objpnxt(mut), --cnt)
905     {
906         for (p = p1, statcnt = objstat(objptr) ; statcnt ;
907              p = objpnxt(p), --statcnt)
908         {
909             /* if this static prop matches a mutable prop, ignore it */
910             if (prpprop(p) == prpprop(mut))
911             {
912                 prpflg(p) |= PRPFIGN;
913                 break;
914             }
915         }
916     }
917 
918     mcmtch(mctx, (mcmon)objn);
919     mcmunlck(mctx, (mcmon)objn);
920     if (indexed) objindx(mctx, objn);
921 }
922 
923 /*
924  *   Build or rebuild a property index for an object.
925  */
objindx(mcmcxdef * mctx,objnum objn)926 void objindx(mcmcxdef *mctx, objnum objn)
927 {
928     uint    newsiz;
929     uint    avail;
930     objdef *objptr;
931     uint    cnt;
932     prpdef *p;
933 	uchar  *indp = nullptr;
934     uchar  *indbase;
935     uint    icnt;
936     uint    first;
937     uint    last;
938 	uint    cur = 0;
939 
940     objptr = (objdef *)mcmlck(mctx, objn);            /* get object pointer */
941     cnt = objnprop(objptr);                     /* get number of properties */
942     p = objprp(objptr);         /* get pointer to properties (or old index) */
943     newsiz = 2 + 4*cnt;                 /* figure size needed for the index */
944 
945     avail = mcmobjsiz(mctx, objn) - objfree(objptr);
946 
947     /* insert space for the index; expand the object if necessary */
948     if (avail < newsiz)
949     {
950         ushort  need;
951 
952         newsiz += 10*4;                   /* add some extra space for later */
953         need = newsiz - avail;            /* compute amount of space needed */
954         objptr = objexp(mctx, objn, &need);
955         p = objprp(objptr);
956     }
957 
958     /* now build the index */
959     indbase = objpfre(objptr);
960     for (icnt = 0 ; cnt ; p = objpnxt(p), --cnt, ++icnt)
961     {
962         uint ofs = (uchar *)p - (uchar *)objptr;
963 
964         if (icnt)
965         {
966             /* figure out where to insert this property */
967             first = 0;
968             last = icnt - 1;
969             for (;;)
970             {
971                 if (first > last) break;
972                 cur = first + (last - first)/2;
973                 indp = indbase + cur*4;
974                 if (indp[0] == p[0] && indp[1] == p[1])
975                     break;
976                 else if (indp[0] < p[0]
977                          || (indp[0] == p[0] && indp[1] < p[1]))
978                     first = (cur == first ? first + 1 : cur);
979                 else
980                     last = (cur == last ? last - 1 : cur);
981             }
982 
983             /* make sure we're positioned just before insertion point */
984             while (cur < icnt
985                    && (indp[0] <= p[0]
986                        || (indp[0] == p[0] && indp[1] <= p[1])))
987             {
988                 indp += 4;
989                 ++cur;
990             }
991 
992             /* move elements above if any */
993             if (cur < icnt)
994                 memmove(indp + 4, indp, (size_t)((icnt - cur) * 4));
995         }
996         else
997             indp = indbase;
998 
999         /* insert property into index */
1000         indp[0] = p[0];
1001         indp[1] = p[1];
1002         oswp2(indp+2, ofs);
1003     }
1004 
1005     /* set the index flag, and dirty and free the object */
1006     objsflg(objptr, objflg(objptr) | OBJFINDEX);
1007     mcmtch(mctx, (mcmon)objn);
1008     mcmunlck(mctx, (mcmon)objn);
1009 }
1010 
1011 /* allocate and initialize an object */
objnew(mcmcxdef * mctx,int sccnt,ushort propspace,objnum * objnptr,int classflg)1012 objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
1013                objnum *objnptr, int classflg)
1014 {
1015     objdef *o;
1016     mcmon   objn;
1017 
1018     /* allocate cache object */
1019     o = (objdef *)mcmalo(mctx, (ushort)(OBJDEFSIZ + sccnt * 2 + propspace),
1020                          &objn);
1021 
1022     /* set up object descriptor for the new object */
1023     objini(mctx, sccnt, (objnum)objn, classflg);
1024 
1025     *objnptr = (objnum)objn;
1026     return(o);
1027 }
1028 
1029 /* initialize an already allocated object */
objini(mcmcxdef * mctx,int sccnt,objnum objn,int classflg)1030 void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg)
1031 {
1032     objdef *o;
1033     uint    flags = 0;
1034 
1035     /* get a lock on the object */
1036     o = (objdef *)mcmlck(mctx, objn);
1037 
1038     memset(o, 0, (size_t)10);
1039     objsnsc(o, sccnt);
1040     objsfree(o, ((uchar *)objsc(o) + 2*sccnt) - (uchar *)o);
1041 
1042     /* set up flags */
1043     if (classflg) flags |= OBJFCLASS;
1044     objsflg(o, flags);
1045 
1046     /* tell cache manager that this object has been modified */
1047     mcmtch(mctx, objn);
1048     mcmunlck(mctx, objn);
1049 }
1050 
1051 /*
1052  *   Get the first superclass of an object.  If it doesn't have any
1053  *   superclasses, return invalid.
1054  */
objget1sc(mcmcxdef * ctx,objnum objn)1055 objnum objget1sc(mcmcxdef *ctx, objnum objn)
1056 {
1057     objdef *p;
1058     objnum  retval;
1059 
1060     /* lock the object */
1061     p = mcmlck(ctx, (mcmon)objn);
1062 
1063     /* get the first superclass if it has any */
1064     if (objnsc(p) == 0)
1065         retval = MCMONINV;
1066     else
1067         retval = osrp2(objsc(p));
1068 
1069     /* unlock the object and return the superclass value */
1070     mcmunlck(ctx, (mcmon)objn);
1071     return retval;
1072 }
1073 
1074 } // End of namespace TADS2
1075 } // End of namespace TADS
1076 } // End of namespace Glk
1077