source: libcfa/src/collections/string_res.cfa @ 0860d9c

Last change on this file since 0860d9c was 0860d9c, checked in by Michael Brooks <mlbrooks@…>, 9 months ago

Fix read-to-variable-length-string cases when internal buffer fills.

Also fix read-to-cstring ability to give no-exception cases when an entire buffer fills.

The added test cases run, and fail, when run against prior libcfa.
Doing so illustrates a CFA-string-level bug.
Doing so illustrates a C-string-level changed semantics.

At the CFA-string level, the bug was, when reading strings of just the right length,
what should be two reads ("abc" then "def") gets mashed into one ("abcdef").
These cases are clearly bugs because a test program that just echoes chuncks of delimeted input would do so inaccurately.
They're just hard to drive because the relevant chunk lengths are implementation-dependent, and sometimes big.

At the C-string level, the semantic change concerns when to throw the cstring_length exception.
By this change, make the original semantics,
"An exception means the maximum number of characters was read," into
"An exception means that if the buffer were larger, then more characters would have been read."

The added test cases cover the respective stop conditions for manipulator state "%s", include, exclude, getline, and getline/delimiter.

  • Property mode set to 100644
File size: 42.0 KB
Line 
1//
2// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
3//
4// The contents of this file are covered under the licence agreement in the
5// file "LICENCE" distributed with Cforall.
6//
7// string_res -- variable-length, mutable run of text, with resource semantics
8//
9// Author           : Michael L. Brooks
10// Created On       : Fri Sep 03 11:00:00 2021
11// Last Modified By : Peter A. Buhr
12// Last Modified On : Mon Aug 14 18:06:01 2023
13// Update Count     : 12
14//
15
16#include "string_res.hfa"
17#include "string_sharectx.hfa"
18#include "stdlib.hfa"
19#include <ctype.h>
20
21// Workaround for observed performance penalty from calling CFA's alloc.
22// Workaround is:  EndVbyte = TEMP_ALLOC(char, CurrSize)
23// Should be:      EndVbyte = alloc(CurrSize)
24#define TEMP_ALLOC(T, n) (( T* ) malloc( n * sizeof( T ) ))
25
26#include <assert.h>
27
28//######################### VbyteHeap "header" #########################
29
30#ifdef VbyteDebug
31HandleNode *HeaderPtr;
32#endif // VbyteDebug
33
34struct VbyteHeap {
35
36    int NoOfCompactions;                                // number of compactions of the byte area
37    int NoOfExtensions;                                 // number of extensions in the size of the byte area
38    int NoOfReductions;                                 // number of reductions in the size of the byte area
39   
40    int InitSize;                                       // initial number of bytes in the byte-string area
41    int CurrSize;                                       // current number of bytes in the byte-string area
42    char *StartVbyte;                                   // pointer to the `st byte of the start of the byte-string area
43    char *EndVbyte;                                     // pointer to the next byte after the end of the currently used portion of byte-string area
44    void *ExtVbyte;                                     // pointer to the next byte after the end of the byte-string area
45
46    HandleNode Header;                                  // header node for handle list
47}; // VbyteHeap
48
49   
50static void compaction( VbyteHeap & );                          // compaction of the byte area
51static void garbage( VbyteHeap &, int );                                // garbage collect the byte area
52static void extend( VbyteHeap &, int );                 // extend the size of the byte area
53static void reduce( VbyteHeap &, int );                 // reduce the size of the byte area
54
55static void ?{}( VbyteHeap &, size_t = 1000 );
56static void ^?{}( VbyteHeap & );
57
58static int ByteCmp( char *, int, int, char *, int, int );       // compare 2 blocks of bytes
59static char *VbyteAlloc( VbyteHeap &, int );                    // allocate a block bytes in the heap
60static char *VbyteTryAdjustLast( VbyteHeap &, int );
61
62static void AddThisAfter( HandleNode &, HandleNode & );
63static void DeleteNode( HandleNode & );
64static void MoveThisAfter( HandleNode &, const HandleNode & );          // move current handle after parameter handle
65
66
67// Allocate the storage for the variable sized area and intialize the heap variables.
68
69static void ?{}( VbyteHeap & this, size_t Size ) with(this) {
70#ifdef VbyteDebug
71    serr | "enter:VbyteHeap::VbyteHeap, this:" | &this | " Size:" | Size;
72#endif // VbyteDebug
73    NoOfCompactions = NoOfExtensions = NoOfReductions = 0;
74    InitSize = CurrSize = Size;
75    StartVbyte = EndVbyte = TEMP_ALLOC(char, CurrSize);
76    ExtVbyte = (void *)( StartVbyte + CurrSize );
77    Header.flink = Header.blink = &Header;
78    Header.ulink = & this;
79#ifdef VbyteDebug
80    HeaderPtr = &Header;
81    serr | "exit:VbyteHeap::VbyteHeap, this:" | &this;
82#endif // VbyteDebug
83} // VbyteHeap
84
85
86// Release the dynamically allocated storage for the byte area.
87
88static void ^?{}( VbyteHeap & this ) with(this) {
89    free( StartVbyte );
90} // ~VbyteHeap
91
92
93//######################### HandleNode #########################
94
95
96// Create a handle node. The handle is not linked into the handle list.  This is the responsibilitiy of the handle
97// creator.
98
99static void ?{}( HandleNode & this ) with(this) {
100#ifdef VbyteDebug
101    serr | "enter:HandleNode::HandleNode, this:" | &this;
102#endif // VbyteDebug
103    s = 0;
104    lnth = 0;
105#ifdef VbyteDebug
106    serr | "exit:HandleNode::HandleNode, this:" | &this;
107#endif // VbyteDebug
108} // HandleNode
109
110// Create a handle node. The handle is linked into the handle list at the end. This means that this handle will NOT be
111// in order by string address, but this is not a problem because a string with length zero does nothing during garbage
112// collection.
113
114static void ?{}( HandleNode & this, VbyteHeap & vh ) with(this) {
115#ifdef VbyteDebug
116    serr | "enter:HandleNode::HandleNode, this:" | &this;
117#endif // VbyteDebug
118    s = 0;
119    lnth = 0;
120    ulink = &vh;
121    AddThisAfter( this, *vh.Header.blink );
122#ifdef VbyteDebug
123    serr | "exit:HandleNode::HandleNode, this:" | &this;
124#endif // VbyteDebug
125} // HandleNode
126
127
128// Delete a node from the handle list by unchaining it from the list. If the handle node was allocated dynamically, it
129// is the responsibility of the creator to destroy it.
130
131static void ^?{}( HandleNode & this ) with(this) {
132#ifdef VbyteDebug
133    serr | "enter:HandleNode::~HandleNode, this:" | & this;
134    {
135        serr | nlOff;
136        serr | " lnth:" | lnth | " s:" | (void *)s | ",\"";
137        for ( i; lnth ) {
138            serr | s[i];
139        } // for
140        serr | "\" flink:" | flink | " blink:" | blink | nl;
141        serr | nlOn;
142    }
143#endif // VbyteDebug
144    DeleteNode( this );
145} // ~HandleNode
146
147
148//######################### String Sharing Context #########################
149
150static string_sharectx * ambient_string_sharectx;               // fickle top of stack
151static string_sharectx default_string_sharectx = {NEW_SHARING}; // stable bottom of stack
152
153void ?{}( string_sharectx & this, StringSharectx_Mode mode ) with( this ) {
154    (older){ ambient_string_sharectx };
155    if ( mode == NEW_SHARING ) {
156        (activeHeap){ new( (size_t) 1000 ) };
157    } else {
158        verify( mode == NO_SHARING );
159        (activeHeap){ 0p };
160    }
161    ambient_string_sharectx = & this;
162}
163
164void ^?{}( string_sharectx & this ) with( this ) {
165    if ( activeHeap ) delete( activeHeap );
166
167    // unlink this from older-list starting from ambient_string_sharectx
168    // usually, this==ambient_string_sharectx and the loop runs zero times
169    string_sharectx *& c = ambient_string_sharectx;
170    while ( c != &this ) &c = &c->older;              // find this
171    c = this.older;                                   // unlink
172}
173
174//######################### String Resource #########################
175
176
177VbyteHeap * DEBUG_string_heap() {
178    assert( ambient_string_sharectx->activeHeap && "No sharing context is active" );
179    return ambient_string_sharectx->activeHeap;
180}
181
182size_t DEBUG_string_bytes_avail_until_gc( VbyteHeap * heap ) {
183    return ((char*)heap->ExtVbyte) - heap->EndVbyte;
184}
185
186size_t DEBUG_string_bytes_in_heap( VbyteHeap * heap ) {
187    return heap->CurrSize;
188}
189
190const char * DEBUG_string_heap_start( VbyteHeap * heap ) {
191    return heap->StartVbyte;
192}
193
194// Returns the size of the string in bytes
195size_t size(const string_res &s) with(s) {
196    return Handle.lnth;
197}
198
199// Output operator
200ofstream & ?|?(ofstream &out, const string_res &s) {
201        // CFA string is NOT null terminated, so print exactly lnth characters in a minimum width of 0.
202        out | wd( 0, s.Handle.lnth, s.Handle.s ) | nonl;
203    return out;
204}
205
206void ?|?(ofstream &out, const string_res &s) {
207        (ofstream &)(out | s); ends( out );
208}
209
210// Input operator
211ifstream & ?|?(ifstream &in, string_res &s) {
212
213    // Reading into a temp before assigning to s is near zero overhead in typical cases because of sharing.
214    // If s is a substring of something larger, simple assignment takes care of that case correctly.
215    // But directly reading a variable amount of text into the middle of a larger context is not practical.
216    string_res temp;
217
218    // Read in chunks.  Often, one chunk is enough.  Keep the string that accumulates chunks last in the heap,
219    // so available room is rest of heap.  When a chunk fills the heap, force growth then take the next chunk.
220    for (bool cont = true; cont; ) {
221        cont = false;
222
223        // Append dummy content to temp, forcing expansion when applicable (occurs always on subsequent loops)
224        // length 2 ensures room for at least one real char, plus scanf/pipe-cstr's null terminator
225        temp += "--";
226        assert( temp.Handle.ulink->EndVbyte == temp.Handle.s + temp.Handle.lnth );    // last in heap
227
228        // reset, to overwrite the appended "--"
229        temp.Handle.lnth -= 2;
230        temp.Handle.ulink->EndVbyte -= 2;
231
232        // rest of heap is available to read into
233        int lenReadable = (char*)temp.Handle.ulink->ExtVbyte - temp.Handle.ulink->EndVbyte;
234        assert (lenReadable >= 2);
235
236        // get bytes
237        try {
238            in | wdi( lenReadable, temp.Handle.ulink->EndVbyte );
239        } catch (cstring_length*) {
240            cont = true;
241        }
242        int lenWasRead = strlen(temp.Handle.ulink->EndVbyte);
243
244        // update metadata
245        temp.Handle.lnth += lenWasRead;
246        temp.Handle.ulink->EndVbyte += lenWasRead;
247    }
248
249    s = temp;
250    return in;
251}
252
253void ?|?( ifstream & in, string_res & this ) {
254    (ifstream &)(in | this); ends( in );
255}
256
257ifstream & ?|?( ifstream & is, _Istream_Rstr f ) {
258        // .---------------,
259        // | | | | |...|0|0| null terminator and guard if missing
260        // `---------------'
261        enum { gwd = 128 + 1, wd = gwd - 1 };                           // guard and unguard width
262        char cstr[gwd];                                                                         // read in chunks
263        bool cont = false;
264
265        _Istream_Cstr cf = { cstr, (_Istream_str_base)f };
266        if ( ! cf.flags.rwd ) cf.wd = wd;
267
268        cstr[wd] = '\0';                                                                        // guard null terminate string
269        try {
270                is | cf;
271        } catch( cstring_length * ) {
272                cont = true;
273        } finally {
274        if ( ! cf.flags.ignore ) *(f.s) = cstr;                 // ok to initialize string
275        } // try
276        for ( ; cont; )  {                                                                      // overflow read ?
277                cont = false;
278                try {
279                        is | cf;
280                } catch( cstring_length * ) {
281                        cont = true;                                                            // continue not allowed
282                } finally {
283                        if ( ! cf.flags.ignore ) *(f.s) += cstr;        // build string chunk at a time
284                } // try
285        } // for
286        return is;
287} // ?|?
288
289void ?|?( ifstream & in, _Istream_Rstr f ) {
290    (ifstream &)(in | f); ends( in );
291}
292
293
294// Empty constructor
295void ?{}(string_res &s) with(s) {
296    if( ambient_string_sharectx->activeHeap ) {
297        (Handle){ * ambient_string_sharectx->activeHeap };
298        (shareEditSet_owns_ulink){ false };
299        verify( Handle.s == 0p && Handle.lnth == 0 );
300    } else {
301        (Handle){ * new( (size_t) 10 ) };  // TODO: can I lazily avoid allocating for empty string
302        (shareEditSet_owns_ulink){ true };
303        Handle.s = Handle.ulink->StartVbyte;
304        verify( Handle.lnth == 0 );
305    }
306    s.shareEditSet_prev = &s;
307    s.shareEditSet_next = &s;
308}
309
310static void eagerCopyCtorHelper(string_res &s, const char* rhs, size_t rhslnth) with(s) {
311    if( ambient_string_sharectx->activeHeap ) {
312        (Handle){ * ambient_string_sharectx->activeHeap };
313        (shareEditSet_owns_ulink){ false };
314    } else {
315        (Handle){ * new( rhslnth ) };
316        (shareEditSet_owns_ulink){ true };
317    }
318    Handle.s = VbyteAlloc(*Handle.ulink, rhslnth);
319    Handle.lnth = rhslnth;
320    memmove( Handle.s, rhs, rhslnth );
321    s.shareEditSet_prev = &s;
322    s.shareEditSet_next = &s;
323}
324
325// Constructor from a raw buffer and size
326void ?{}(string_res &s, const char* rhs, size_t rhslnth) with(s) {
327    eagerCopyCtorHelper(s, rhs, rhslnth);
328}
329
330// private ctor (not in header): use specified heap (ignore ambient) and copy chars in
331void ?{}( string_res &s, VbyteHeap & heap, const char* rhs, size_t rhslnth ) with(s) {
332    (Handle){ heap };
333    Handle.s = VbyteAlloc(*Handle.ulink, rhslnth);
334    Handle.lnth = rhslnth;
335    (s.shareEditSet_owns_ulink){ false };
336    memmove( Handle.s, rhs, rhslnth );
337    s.shareEditSet_prev = &s;
338    s.shareEditSet_next = &s;
339}
340
341// General copy constructor
342void ?{}(string_res &s, const string_res & s2, StrResInitMode mode, size_t start, size_t end ) {
343
344    verify( start <= end && end <= s2.Handle.lnth );
345
346    if (s2.Handle.ulink != ambient_string_sharectx->activeHeap && mode == COPY_VALUE) {
347        // crossing heaps (including private): copy eagerly
348        eagerCopyCtorHelper(s, s2.Handle.s + start, end - start);
349        verify(s.shareEditSet_prev == &s);
350        verify(s.shareEditSet_next == &s);
351    } else {
352        (s.Handle){};
353        s.Handle.s = s2.Handle.s + start;
354        s.Handle.lnth = end - start;
355        s.Handle.ulink = s2.Handle.ulink;
356
357        AddThisAfter(s.Handle, s2.Handle );                     // insert this handle after rhs handle
358        // ^ bug?  skip others at early point in string
359
360        if (mode == COPY_VALUE) {
361            verify(s2.Handle.ulink == ambient_string_sharectx->activeHeap);
362            // requested logical copy in same heap: defer copy until write
363
364            (s.shareEditSet_owns_ulink){ false };
365
366            // make s alone in its shareEditSet
367            s.shareEditSet_prev = &s;
368            s.shareEditSet_next = &s;
369        } else {
370            verify( mode == SHARE_EDITS );
371            // sharing edits with source forces same heap as source (ignore context)
372
373            (s.shareEditSet_owns_ulink){ s2.shareEditSet_owns_ulink };
374
375            // s2 is logically const but not implementation const
376            string_res & s2mod = (string_res &) s2;
377
378            // insert s after s2 on shareEditSet
379            s.shareEditSet_next = s2mod.shareEditSet_next;
380            s.shareEditSet_prev = &s2mod;
381            s.shareEditSet_next->shareEditSet_prev = &s;
382            s.shareEditSet_prev->shareEditSet_next = &s;
383        }
384    }
385}
386
387static void assignEditSet(string_res & this, string_res * shareEditSetStartPeer, string_res * shareEditSetEndPeer,
388    char * resultSesStart,
389    size_t resultSesLnth,
390    HandleNode * resultPadPosition, size_t bsize ) {
391
392    char * beforeBegin = shareEditSetStartPeer->Handle.s;
393    size_t beforeLen = this.Handle.s - beforeBegin;
394
395    char * afterBegin = this.Handle.s + this.Handle.lnth;
396    size_t afterLen = shareEditSetEndPeer->Handle.s + shareEditSetEndPeer->Handle.lnth - afterBegin;
397
398    size_t oldLnth = this.Handle.lnth;
399
400    this.Handle.s = resultSesStart + beforeLen;
401    this.Handle.lnth = bsize;
402    if (resultPadPosition)
403        MoveThisAfter( this.Handle, *resultPadPosition );
404
405    // adjust all substring string and handle locations, and check if any substring strings are outside the new base string
406    char *limit = resultSesStart + resultSesLnth;
407    for ( string_res * p = this.shareEditSet_next; p != &this; p = p->shareEditSet_next ) {
408        verify (p->Handle.s >= beforeBegin);
409        if ( p->Handle.s >= afterBegin ) {
410            verify ( p->Handle.s <= afterBegin + afterLen );
411            verify ( p->Handle.s + p->Handle.lnth <= afterBegin + afterLen );
412            // p starts after the edit
413            // take start and end as end-anchored
414            size_t startOffsetFromEnd = afterBegin + afterLen - p->Handle.s;
415            p->Handle.s = limit - startOffsetFromEnd;
416            // p->Handle.lnth unaffected
417        } else if ( p->Handle.s <= beforeBegin + beforeLen ) {
418            // p starts before, or at the start of, the edit
419            if ( p->Handle.s + p->Handle.lnth <= beforeBegin + beforeLen ) {
420                // p ends before the edit
421                // take end as start-anchored too
422                // p->Handle.lnth unaffected
423            } else if ( p->Handle.s + p->Handle.lnth < afterBegin ) {
424                // p ends during the edit; p does not include the last character replaced
425                // clip end of p to end at start of edit
426                p->Handle.lnth = beforeLen - ( p->Handle.s - beforeBegin );
427            } else {
428                // p ends after the edit
429                verify ( p->Handle.s + p->Handle.lnth <= afterBegin + afterLen );
430                // take end as end-anchored
431                // stretch-shrink p according to the edit
432                p->Handle.lnth += this.Handle.lnth;
433                p->Handle.lnth -= oldLnth;
434            }
435            // take start as start-anchored
436            size_t startOffsetFromStart = p->Handle.s - beforeBegin;
437            p->Handle.s = resultSesStart + startOffsetFromStart;
438        } else {
439            verify ( p->Handle.s < afterBegin );
440            // p starts during the edit
441            verify( p->Handle.s + p->Handle.lnth >= beforeBegin + beforeLen );
442            if ( p->Handle.s + p->Handle.lnth < afterBegin ) {
443                // p ends during the edit; p does not include the last character replaced
444                // set p to empty string at start of edit
445                p->Handle.s = this.Handle.s;
446                p->Handle.lnth = 0;
447            } else {
448                // p includes the end of the edit
449                // clip start of p to start at end of edit
450                int charsToClip = afterBegin - p->Handle.s;
451                p->Handle.s = this.Handle.s + this.Handle.lnth;
452                p->Handle.lnth -= charsToClip;
453            }
454        }
455        if (resultPadPosition)
456            MoveThisAfter( p->Handle, *resultPadPosition );     // move substring handle to maintain sorted order by string position
457    }
458}
459
460// traverse the share-edit set (SES) to recover the range of a base string to which `this` belongs
461static void locateInShareEditSet( string_res &this, string_res *&shareEditSetStartPeer, string_res *&shareEditSetEndPeer ) {
462    shareEditSetStartPeer = & this;
463    shareEditSetEndPeer = & this;
464    for (string_res * editPeer = this.shareEditSet_next; editPeer != &this; editPeer = editPeer->shareEditSet_next) {
465        if ( editPeer->Handle.s < shareEditSetStartPeer->Handle.s ) {
466            shareEditSetStartPeer = editPeer;
467        }
468        if ( shareEditSetEndPeer->Handle.s + shareEditSetEndPeer->Handle.lnth < editPeer->Handle.s + editPeer->Handle.lnth) {
469            shareEditSetEndPeer = editPeer;
470        }
471    }
472}
473
474static string_res & assign_(string_res &this, const char* buffer, size_t bsize, const string_res & valSrc) {
475
476    string_res * shareEditSetStartPeer;
477    string_res * shareEditSetEndPeer;
478    locateInShareEditSet( this, shareEditSetStartPeer, shareEditSetEndPeer );
479
480    verify( shareEditSetEndPeer->Handle.s >= shareEditSetStartPeer->Handle.s );
481    size_t origEditSetLength = shareEditSetEndPeer->Handle.s + shareEditSetEndPeer->Handle.lnth - shareEditSetStartPeer->Handle.s;
482    verify( origEditSetLength >= this.Handle.lnth );
483
484    if ( this.shareEditSet_owns_ulink ) {                 // assigning to private context
485        // ok to overwrite old value within LHS
486        char * prefixStartOrig = shareEditSetStartPeer->Handle.s;
487        int prefixLen = this.Handle.s - prefixStartOrig;
488        char * suffixStartOrig = this.Handle.s + this.Handle.lnth;
489        int suffixLen = shareEditSetEndPeer->Handle.s + shareEditSetEndPeer->Handle.lnth - suffixStartOrig;
490
491        int delta = bsize - this.Handle.lnth;
492        if ( char * oldBytes = VbyteTryAdjustLast( *this.Handle.ulink, delta ) ) {
493            // growing: copy from old to new
494            char * dest = VbyteAlloc( *this.Handle.ulink, origEditSetLength + delta );
495            char *destCursor = dest;  memcpy(destCursor, prefixStartOrig, prefixLen);
496            destCursor += prefixLen;  memcpy(destCursor, buffer         , bsize    );
497            destCursor += bsize;      memcpy(destCursor, suffixStartOrig, suffixLen);
498            assignEditSet(this, shareEditSetStartPeer, shareEditSetEndPeer,
499                dest,
500                origEditSetLength + delta,
501                0p, bsize);
502            free( oldBytes );
503        } else {
504            // room is already allocated in-place: bubble suffix and overwite middle
505            memmove( suffixStartOrig + delta, suffixStartOrig, suffixLen );
506            memcpy( this.Handle.s, buffer, bsize );
507
508            assignEditSet(this, shareEditSetStartPeer, shareEditSetEndPeer,
509                shareEditSetStartPeer->Handle.s,
510                origEditSetLength + delta,
511                0p, bsize);
512        }
513
514    } else if (                                           // assigning to shared context
515        this.Handle.lnth == origEditSetLength &&          // overwriting entire run of SES
516        & valSrc &&                                       // sourcing from a managed string
517        valSrc.Handle.ulink == this.Handle.ulink  ) {     // sourcing from same heap
518
519        // SES's result will only use characters from the source string => reuse source
520        assignEditSet(this, shareEditSetStartPeer, shareEditSetEndPeer,
521            valSrc.Handle.s,
522            valSrc.Handle.lnth,
523            &((string_res&)valSrc).Handle, bsize);
524       
525    } else {
526        // overwriting a proper substring of some string: mash characters from old and new together (copy on write)
527        // OR we are importing characters: need to copy eagerly (can't refer to source)
528
529        // full string is from start of shareEditSetStartPeer thru end of shareEditSetEndPeer
530        // `this` occurs in the middle of it, to be replaced
531        // build up the new text in `pasting`
532
533        string_res pasting = {
534            * this.Handle.ulink,                               // maintain same heap, regardless of context
535            shareEditSetStartPeer->Handle.s,                   // start of SES
536            this.Handle.s - shareEditSetStartPeer->Handle.s }; // length of SES, before this
537        append( pasting,
538            buffer,                                            // start of replacement for this
539            bsize );                                           // length of replacement for this
540        append( pasting,
541            this.Handle.s + this.Handle.lnth,                  // start of SES after this
542            shareEditSetEndPeer->Handle.s + shareEditSetEndPeer->Handle.lnth -
543            (this.Handle.s + this.Handle.lnth) );              // length of SES, after this
544
545        // The above string building can trigger compaction.
546        // The reference points (that are arguments of the string building) may move during that building.
547        // From this point on, they are stable.
548
549        assignEditSet(this, shareEditSetStartPeer, shareEditSetEndPeer,
550            pasting.Handle.s,
551            pasting.Handle.lnth,
552            &pasting.Handle, bsize);
553    }
554
555    return this;
556}
557
558string_res & assign(string_res &this, const char* buffer, size_t bsize) {
559    return assign_(this, buffer, bsize, *0p);
560}
561
562string_res & ?=?(string_res &s, char other) {
563    return assign(s, &other, 1);
564}
565
566// Copy assignment operator
567string_res & ?=?(string_res & this, const string_res & rhs) with( this ) {
568    return assign_(this, rhs.Handle.s, rhs.Handle.lnth, rhs);
569}
570
571string_res & ?=?(string_res & this, string_res & rhs) with( this ) {
572    const string_res & rhs2 = rhs;
573    return this = rhs2;
574}
575
576
577// Destructor
578void ^?{}(string_res &s) with(s) {
579    // much delegated to implied ^VbyteSM
580
581    // sever s from its share-edit peers, if any (four no-ops when already solo)
582    s.shareEditSet_prev->shareEditSet_next = s.shareEditSet_next;
583    s.shareEditSet_next->shareEditSet_prev = s.shareEditSet_prev;
584    // s.shareEditSet_next = &s;
585    // s.shareEditSet_prev = &s;
586
587    if (shareEditSet_owns_ulink && s.shareEditSet_next == &s) { // last one out
588        delete( s.Handle.ulink );
589    }
590}
591
592
593// Returns the character at the given index
594// With unicode support, this may be different from just the byte at the given
595// offset from the start of the string.
596char ?[?](const string_res &s, size_t index) with(s) {
597    //TODO: Check if index is valid (no exceptions yet)
598    return Handle.s[index];
599}
600
601void assignAt(const string_res &s, size_t index, char val) {
602    string_res editZone = { s, SHARE_EDITS, index, index+1 };
603    assign(editZone, &val, 1);
604}
605
606
607///////////////////////////////////////////////////////////////////
608// Concatenation
609
610void append(string_res &str1, const char * buffer, size_t bsize) {
611    size_t clnth = str1.Handle.lnth + bsize;
612    if ( str1.Handle.s + str1.Handle.lnth == buffer ) { // already juxtapose ?
613        // no-op
614    } else {                                            // must copy some text
615        if ( str1.Handle.s + str1.Handle.lnth == VbyteAlloc(*str1.Handle.ulink, 0) ) { // str1 at end of string area ?
616            VbyteAlloc( *str1.Handle.ulink, bsize ); // create room for 2nd part at the end of string area
617        } else {                                        // copy the two parts
618            char * str1newBuf = VbyteAlloc( *str1.Handle.ulink, clnth );
619            char * str1oldBuf = str1.Handle.s;  // must read after VbyteAlloc call in case it gs's
620            str1.Handle.s = str1newBuf;
621            memcpy( str1.Handle.s, str1oldBuf,  str1.Handle.lnth );
622        } // if
623        memcpy( str1.Handle.s + str1.Handle.lnth, buffer, bsize );
624    } // if
625    str1.Handle.lnth = clnth;
626}
627
628void ?+=?(string_res &str1, const string_res &str2) {
629    append( str1, str2.Handle.s, str2.Handle.lnth );
630}
631
632void ?+=?(string_res &s, char other) {
633    append( s, &other, 1 );
634}
635
636
637
638
639
640//////////////////////////////////////////////////////////
641// Comparisons
642
643int cmp(const string_res &s1, const string_res &s2) {
644    // return 0;
645    int ans1 = memcmp(s1.Handle.s, s2.Handle.s, min(s1.Handle.lnth, s2.Handle.lnth));
646    if (ans1 != 0) return ans1;
647    return s1.Handle.lnth - s2.Handle.lnth;
648}
649
650bool ?==?(const string_res &s1, const string_res &s2) { return cmp(s1, s2) == 0; }
651bool ?!=?(const string_res &s1, const string_res &s2) { return cmp(s1, s2) != 0; }
652bool ?>? (const string_res &s1, const string_res &s2) { return cmp(s1, s2) >  0; }
653bool ?>=?(const string_res &s1, const string_res &s2) { return cmp(s1, s2) >= 0; }
654bool ?<=?(const string_res &s1, const string_res &s2) { return cmp(s1, s2) <= 0; }
655bool ?<? (const string_res &s1, const string_res &s2) { return cmp(s1, s2) <  0; }
656
657int cmp (const string_res &s1, const char* s2) {
658    string_res s2x = s2;
659    return cmp(s1, s2x);
660}
661
662bool ?==?(const string_res &s1, const char* s2) { return cmp(s1, s2) == 0; }
663bool ?!=?(const string_res &s1, const char* s2) { return cmp(s1, s2) != 0; }
664bool ?>? (const string_res &s1, const char* s2) { return cmp(s1, s2) >  0; }
665bool ?>=?(const string_res &s1, const char* s2) { return cmp(s1, s2) >= 0; }
666bool ?<=?(const string_res &s1, const char* s2) { return cmp(s1, s2) <= 0; }
667bool ?<? (const string_res &s1, const char* s2) { return cmp(s1, s2) <  0; }
668
669int cmp (const char* s1, const string_res & s2) {
670    string_res s1x = s1;
671    return cmp(s1x, s2);
672}
673
674bool ?==?(const char* s1, const string_res &s2) { return cmp(s1, s2) == 0; }
675bool ?!=?(const char* s1, const string_res &s2) { return cmp(s1, s2) != 0; }
676bool ?>? (const char* s1, const string_res &s2) { return cmp(s1, s2) >  0; }
677bool ?>=?(const char* s1, const string_res &s2) { return cmp(s1, s2) >= 0; }
678bool ?<=?(const char* s1, const string_res &s2) { return cmp(s1, s2) <= 0; }
679bool ?<? (const char* s1, const string_res &s2) { return cmp(s1, s2) <  0; }
680
681
682
683//////////////////////////////////////////////////////////
684// Search
685
686bool contains(const string_res &s, char ch) {
687    for ( i; size(s) ) {
688        if (s[i] == ch) return true;
689    }
690    return false;
691}
692
693int find(const string_res &s, char search) {
694    return findFrom(s, 0, search);
695}
696
697int findFrom(const string_res &s, size_t fromPos, char search) {
698    // FIXME: This paricular overload (find of single char) is optimized to use memchr.
699    // The general overload (find of string, memchr applying to its first character) and `contains` should be adjusted to match.
700    char * searchFrom = s.Handle.s + fromPos;
701    size_t searchLnth = s.Handle.lnth - fromPos;
702    int searchVal = search;
703    char * foundAt = (char *) memchr(searchFrom, searchVal, searchLnth);
704    if (foundAt == 0p) return s.Handle.lnth;
705    else return foundAt - s.Handle.s;
706}
707
708int find(const string_res &s, const string_res &search) {
709    return findFrom(s, 0, search);
710}
711
712int findFrom(const string_res &s, size_t fromPos, const string_res &search) {
713    return findFrom(s, fromPos, search.Handle.s, search.Handle.lnth);
714}
715
716int find(const string_res &s, const char* search) {
717    return findFrom(s, 0, search);
718}
719int findFrom(const string_res &s, size_t fromPos, const char* search) {
720    return findFrom(s, fromPos, search, strlen(search));
721}
722
723int find(const string_res &s, const char* search, size_t searchsize) {
724    return findFrom(s, 0, search, searchsize);
725}
726
727int findFrom(const string_res &s, size_t fromPos, const char* search, size_t searchsize) {
728
729    /* Remaining implementations essentially ported from Sunjay's work */
730
731
732    // FIXME: This is a naive algorithm. We probably want to switch to someting
733    // like Boyer-Moore in the future.
734    // https://en.wikipedia.org/wiki/String_searching_algorithm
735
736    // Always find the empty string
737    if (searchsize == 0) {
738        return 0;
739    }
740
741    for ( i; fromPos ~ s.Handle.lnth ) {
742        size_t remaining = s.Handle.lnth - i;
743        // Never going to find the search string if the remaining string is
744        // smaller than search
745        if (remaining < searchsize) {
746            break;
747        }
748
749        bool matched = true;
750        for ( j; searchsize ) {
751            if (search[j] != s.Handle.s[i + j]) {
752                matched = false;
753                break;
754            }
755        }
756        if (matched) {
757            return i;
758        }
759    }
760
761    return s.Handle.lnth;
762}
763
764bool includes(const string_res &s, const string_res &search) {
765    return includes(s, search.Handle.s, search.Handle.lnth);
766}
767
768bool includes(const string_res &s, const char* search) {
769    return includes(s, search, strlen(search));
770}
771
772bool includes(const string_res &s, const char* search, size_t searchsize) {
773    return find(s, search, searchsize) < s.Handle.lnth;
774}
775
776bool startsWith(const string_res &s, const string_res &prefix) {
777    return startsWith(s, prefix.Handle.s, prefix.Handle.lnth);
778}
779
780bool startsWith(const string_res &s, const char* prefix) {
781    return startsWith(s, prefix, strlen(prefix));
782}
783
784bool startsWith(const string_res &s, const char* prefix, size_t prefixsize) {
785    if (s.Handle.lnth < prefixsize) {
786        return false;
787    }
788    return memcmp(s.Handle.s, prefix, prefixsize) == 0;
789}
790
791bool endsWith(const string_res &s, const string_res &suffix) {
792    return endsWith(s, suffix.Handle.s, suffix.Handle.lnth);
793}
794
795bool endsWith(const string_res &s, const char* suffix) {
796    return endsWith(s, suffix, strlen(suffix));
797}
798
799bool endsWith(const string_res &s, const char* suffix, size_t suffixsize) {
800    if (s.Handle.lnth < suffixsize) {
801        return false;
802    }
803    // Amount to offset the bytes pointer so that we are comparing the end of s
804    // to suffix. s.bytes + offset should be the first byte to compare against suffix
805    size_t offset = s.Handle.lnth - suffixsize;
806    return memcmp(s.Handle.s + offset, suffix, suffixsize) == 0;
807}
808
809    /* Back to Mike's work */
810
811
812///////////////////////////////////////////////////////////////////////////
813// charclass, include, exclude
814
815void ?{}( charclass_res & this, const string_res & chars) {
816    (this){ chars.Handle.s, chars.Handle.lnth };
817}
818
819void ?{}( charclass_res & this, const char * chars ) {
820    (this){ chars, strlen(chars) };
821}
822
823void ?{}( charclass_res & this, const char * chars, size_t charssize ) {
824    (this.chars){ chars, charssize };
825    // now sort it ?
826}
827
828void ^?{}( charclass_res & this ) {
829    ^(this.chars){};
830}
831
832static bool test( const charclass_res & mask, char c ) {
833    // instead, use sorted char list?
834    return contains( mask.chars, c );
835}
836
837int exclude(const string_res &s, const charclass_res &mask) {
838    for ( i; size(s) ) {
839        if ( test(mask, s[i]) ) return i;
840    }
841    return size(s);
842}
843
844int include(const string_res &s, const charclass_res &mask) {
845    for ( i; size(s) ) {
846        if ( ! test(mask, s[i]) ) return i;
847    }
848    return size(s);
849}
850
851//######################### VbyteHeap "implementation" #########################
852
853
854// Add a new HandleNode node n after the current HandleNode node.
855
856static void AddThisAfter( HandleNode & this, HandleNode & n ) with(this) {
857#ifdef VbyteDebug
858    serr | "enter:AddThisAfter, this:" | &this | " n:" | &n;
859#endif // VbyteDebug
860    // Performance note: we are on the critical path here. MB has ensured that the verifies don't contribute to runtime (are compiled away, like they're supposed to be).
861    verify( n.ulink != 0p );
862    verify( this.ulink == n.ulink );
863    flink = n.flink;
864    blink = &n;
865    n.flink->blink = &this;
866    n.flink = &this;
867#ifdef VbyteDebug
868    {
869                serr | "HandleList:";
870                serr | nlOff;
871                for ( HandleNode *ni = HeaderPtr->flink; ni != HeaderPtr; ni = ni->flink ) {
872                        serr | "\tnode:" | ni | " lnth:" | ni->lnth | " s:" | (void *)ni->s | ",\"";
873                        for ( i; ni->lnth ) {
874                                serr | ni->s[i];
875                        } // for
876                        serr | "\" flink:" | ni->flink | " blink:" | ni->blink | nl;
877                } // for
878                serr | nlOn;
879    }
880    serr | "exit:AddThisAfter";
881#endif // VbyteDebug
882} // AddThisAfter
883
884
885// Delete the current HandleNode node.
886
887static void DeleteNode( HandleNode & this ) with(this) {
888#ifdef VbyteDebug
889    serr | "enter:DeleteNode, this:" | &this;
890#endif // VbyteDebug
891    flink->blink = blink;
892    blink->flink = flink;
893#ifdef VbyteDebug
894    serr | "exit:DeleteNode";
895#endif // VbyteDebug
896} //  DeleteNode
897
898
899
900// Allocates specified storage for a string from byte-string area. If not enough space remains to perform the
901// allocation, the garbage collection routine is called.
902
903static char * VbyteAlloc( VbyteHeap & this, int size ) with(this) {
904#ifdef VbyteDebug
905    serr | "enter:VbyteAlloc, size:" | size;
906#endif // VbyteDebug
907    uintptr_t NoBytes;
908    char *r;
909
910    NoBytes = ( uintptr_t )EndVbyte + size;
911    if ( NoBytes > ( uintptr_t )ExtVbyte ) {            // enough room for new byte-string ?
912                garbage( this, size );                                  // firer up the garbage collector
913                verify( (( uintptr_t )EndVbyte + size) <= ( uintptr_t )ExtVbyte  && "garbage run did not free up required space" );
914    } // if
915    r = EndVbyte;
916    EndVbyte += size;
917#ifdef VbyteDebug
918    serr | "exit:VbyteAlloc, r:" | (void *)r | " EndVbyte:" | (void *)EndVbyte | " ExtVbyte:" | ExtVbyte;
919#endif // VbyteDebug
920    return r;
921} // VbyteAlloc
922
923
924// Adjusts the last allocation in this heap by delta bytes, or resets this heap to be able to offer
925// new allocations of its original size + delta bytes. Positive delta means bigger;
926// negative means smaller.  A null return indicates that the original heap location has room for
927// the requested growth.  A non-null return indicates that copying to a new location is required
928// but has not been done; the returned value is the old heap storage location; `this` heap is
929// modified to reference the new location.  In the copy-requred case, the caller should use
930// VbyteAlloc to claim the new space, while doing optimal copying from old to new, then free old.
931
932static char * VbyteTryAdjustLast( VbyteHeap & this, int delta ) with(this) {
933
934    if ( ( uintptr_t )EndVbyte + delta <= ( uintptr_t )ExtVbyte ) {
935        // room available
936        EndVbyte += delta;
937        return 0p;
938    }
939
940    char *oldBytes = StartVbyte;
941
942    NoOfExtensions += 1;
943    CurrSize *= 2;
944    StartVbyte = EndVbyte = TEMP_ALLOC(char, CurrSize);
945    ExtVbyte = StartVbyte + CurrSize;
946
947    return oldBytes;
948}
949
950
951// Move an existing HandleNode node h somewhere after the current HandleNode node so that it is in ascending order by
952// the address in the byte string area.
953
954static void MoveThisAfter( HandleNode & this, const HandleNode  & h ) with(this) {
955#ifdef VbyteDebug
956    serr | "enter:MoveThisAfter, this:" | & this | " h:" | & h;
957#endif // VbyteDebug
958    verify( h.ulink != 0p );
959    verify( this.ulink == h.ulink );
960    if ( s < h.s ) {                                    // check argument values
961                // serr | "VbyteSM: Error - Cannot move byte string starting at:" | s | " after byte string starting at:"
962                //      | ( h->s ) | " and keep handles in ascending order";
963                // exit(-1 );
964                verify( 0 && "VbyteSM: Error - Cannot move byte strings as requested and keep handles in ascending order");
965    } // if
966
967    HandleNode *i;
968    for ( i = h.flink; i->s != 0 && s > ( i->s ); i = i->flink ); // find the position for this node after h
969    if ( & this != i->blink ) {
970                DeleteNode( this );
971                AddThisAfter( this, *i->blink );
972    } // if
973#ifdef VbyteDebug
974    {
975        serr | "HandleList:";
976        serr | nlOff;
977        for ( HandleNode *n = HeaderPtr->flink; n != HeaderPtr; n = n->flink ) {
978            serr | "\tnode:" | n | " lnth:" | n->lnth | " s:" | (void *)n->s | ",\"";
979            for ( i; n->lnth ) {
980                        serr | n->s[i];
981            } // for
982            serr | "\" flink:" | n->flink | " blink:" | n->blink | nl;
983        } // for
984        serr | nlOn;
985    }
986    serr | "exit:MoveThisAfter";
987#endif // VbyteDebug
988} // MoveThisAfter
989
990
991
992
993
994//######################### VbyteHeap #########################
995
996// Compare two byte strings in the byte-string area. The routine returns the following values:
997//
998// 1 => Src1-byte-string > Src2-byte-string
999// 0 => Src1-byte-string = Src2-byte-string
1000// -1 => Src1-byte-string < Src2-byte-string
1001
1002int ByteCmp( char *Src1, int Src1Start, int Src1Lnth, char *Src2, int Src2Start, int Src2Lnth )  {
1003#ifdef VbyteDebug
1004    serr | "enter:ByteCmp, Src1Start:" | Src1Start | " Src1Lnth:" | Src1Lnth | " Src2Start:" | Src2Start | " Src2Lnth:" | Src2Lnth;
1005#endif // VbyteDebug
1006    int cmp;
1007
1008    CharZip: for ( int i = 0; ; i += 1 ) {
1009        if ( i == Src2Lnth - 1 ) {
1010            for ( ; ; i += 1 ) {
1011                if ( i == Src1Lnth - 1 ) {
1012                    cmp = 0;
1013                    break CharZip;
1014                } // exit
1015                if ( Src1[Src1Start + i] != ' ') {
1016                        // SUSPECTED BUG:  this could be be why Peter got the bug report about == " "  (why is this case here at all?)
1017                    cmp = 1;
1018                    break CharZip;
1019                } // exit
1020            } // for
1021        } // exit
1022        if ( i == Src1Lnth - 1 ) {
1023            for ( ; ; i += 1 ) {
1024                if ( i == Src2Lnth - 1 ) {
1025                    cmp = 0;
1026                    break CharZip;
1027                } // exit
1028                if ( Src2[Src2Start + i] != ' ') {
1029                    cmp = -1;
1030                    break CharZip;
1031                } // exit
1032            } // for
1033        } // exit
1034      if ( Src2[Src2Start + i] != Src1[Src1Start+ i]) {
1035            cmp = Src1[Src1Start + i] > Src2[Src2Start + i] ? 1 : -1;
1036            break CharZip;
1037        } // exit
1038    } // for
1039#ifdef VbyteDebug
1040    serr | "exit:ByteCmp, cmp:" | cmp;
1041#endif // VbyteDebug
1042    return cmp;
1043} // ByteCmp
1044
1045
1046// The compaction moves all of the byte strings currently in use to the beginning of the byte-string area and modifies
1047// the handles to reflect the new positions of the byte strings. Compaction assumes that the handle list is in ascending
1048// order by pointers into the byte-string area.  The strings associated with substrings do not have to be moved because
1049// the containing string has been moved. Hence, they only require that their string pointers be adjusted.
1050
1051void compaction(VbyteHeap & this) with(this) {
1052    HandleNode *h;
1053    char *obase, *nbase, *limit;
1054   
1055    NoOfCompactions += 1;
1056    EndVbyte = StartVbyte;
1057    h = Header.flink;                                   // ignore header node
1058    for () {
1059                memmove( EndVbyte, h->s, h->lnth );
1060                obase = h->s;
1061                h->s = EndVbyte;
1062                nbase = h->s;
1063                EndVbyte += h->lnth;
1064                limit = obase + h->lnth;
1065                h = h->flink;
1066               
1067                // check if any substrings are allocated within a string
1068               
1069                for () {
1070                        if ( h == &Header ) break;                      // end of header list ?
1071                        if ( h->s >= limit ) break;                     // outside of current string ?
1072                        h->s = nbase + (( uintptr_t )h->s - ( uintptr_t )obase );
1073                        h = h->flink;
1074                } // for
1075                if ( h == &Header ) break;                      // end of header list ?
1076    } // for
1077} // compaction
1078
1079
1080static double heap_expansion_freespace_threshold = 0.1;  // default inherited from prior work: expand heap when less than 10% "free" (i.e. garbage)
1081                                                         // probably an unreasonable default, but need to assess early-round tests on changing it
1082
1083void TUNING_set_string_heap_liveness_threshold( double val ) {
1084    heap_expansion_freespace_threshold = 1.0 - val;
1085}
1086
1087
1088// Garbage determines the amount of free space left in the heap and then reduces, leave the same, or extends the size of
1089// the heap.  The heap is then compacted in the existing heap or into the newly allocated heap.
1090
1091void garbage(VbyteHeap & this, int minreq ) with(this) {
1092#ifdef VbyteDebug
1093    serr | "enter:garbage";
1094    {
1095                serr | "HandleList:";
1096                for ( HandleNode *n = Header.flink; n != &Header; n = n->flink ) {
1097                        serr | nlOff;
1098                        serr | "\tnode:" | n | " lnth:" | n->lnth | " s:" | (void *)n->s | ",\"";
1099                        for ( i; n->lnth ) {
1100                                serr | n->s[i];
1101                        } // for
1102                        serr | nlOn;
1103                        serr | "\" flink:" | n->flink | " blink:" | n->blink;
1104                } // for
1105    }
1106#endif // VbyteDebug
1107    int AmountUsed, AmountFree;
1108
1109    AmountUsed = 0;
1110    for ( HandleNode *i = Header.flink; i != &Header; i = i->flink ) { // calculate amount of byte area used
1111                AmountUsed += i->lnth;
1112    } // for
1113    AmountFree = ( uintptr_t )ExtVbyte - ( uintptr_t )StartVbyte - AmountUsed;
1114   
1115    if ( ( double ) AmountFree < ( CurrSize * heap_expansion_freespace_threshold ) || AmountFree < minreq ) {   // free space less than threshold or not enough to serve cur request
1116
1117                extend( this, max( CurrSize, minreq ) );                                // extend the heap
1118
1119                        //  Peter says, "This needs work before it should be used."
1120                        //  } else if ( AmountFree > CurrSize / 2 ) {           // free space greater than 3 times the initial allocation ?
1121                        //              reduce(( AmountFree / CurrSize - 3 ) * CurrSize ); // reduce the memory
1122
1123        // `extend` implies a `compaction` during the copy
1124
1125    } else {
1126        compaction(this);                                       // in-place
1127    }// if
1128#ifdef VbyteDebug
1129    {
1130                serr | "HandleList:";
1131                for ( HandleNode *n = Header.flink; n != &Header; n = n->flink ) {
1132                        serr | nlOff;
1133                        serr | "\tnode:" | n | " lnth:" | n->lnth | " s:" | (void *)n->s | ",\"";
1134                        for ( i; n->lnth ) {
1135                                serr | n->s[i];
1136                        } // for
1137                        serr | nlOn;
1138                        serr | "\" flink:" | n->flink | " blink:" | n->blink;
1139                } // for
1140    }
1141    serr | "exit:garbage";
1142#endif // VbyteDebug
1143} // garbage
1144
1145#undef VbyteDebug
1146
1147
1148
1149// Extend the size of the byte-string area by creating a new area and copying the old area into it. The old byte-string
1150// area is deleted.
1151
1152void extend( VbyteHeap & this, int size ) with (this) {
1153#ifdef VbyteDebug
1154    serr | "enter:extend, size:" | size;
1155#endif // VbyteDebug
1156    char *OldStartVbyte;
1157
1158    NoOfExtensions += 1;
1159    OldStartVbyte = StartVbyte;                         // save previous byte area
1160   
1161    CurrSize += size > InitSize ? size : InitSize;      // minimum extension, initial size
1162    StartVbyte = EndVbyte = TEMP_ALLOC(char, CurrSize);
1163    ExtVbyte = (void *)( StartVbyte + CurrSize );
1164    compaction(this);                                   // copy from old heap to new & adjust pointers to new heap
1165    free( OldStartVbyte );                              // release old heap
1166#ifdef VbyteDebug
1167    serr | "exit:extend, CurrSize:" | CurrSize;
1168#endif // VbyteDebug
1169} // extend
1170
1171//WIP
1172#if 0
1173
1174// Extend the size of the byte-string area by creating a new area and copying the old area into it. The old byte-string
1175// area is deleted.
1176
1177void VbyteHeap::reduce( int size ) {
1178#ifdef VbyteDebug
1179    serr | "enter:reduce, size:" | size;
1180#endif // VbyteDebug
1181    char *OldStartVbyte;
1182
1183    NoOfReductions += 1;
1184    OldStartVbyte = StartVbyte;                         // save previous byte area
1185   
1186    CurrSize -= size;
1187    StartVbyte = EndVbyte = new char[CurrSize];
1188    ExtVbyte = (void *)( StartVbyte + CurrSize );
1189    compaction();                                       // copy from old heap to new & adjust pointers to new heap
1190    delete  OldStartVbyte;                              // release old heap
1191#ifdef VbyteDebug
1192    serr | "exit:reduce, CurrSize:" | CurrSize;
1193#endif // VbyteDebug
1194} // reduce
1195
1196
1197#endif
Note: See TracBrowser for help on using the repository browser.