/*** plc.h - PLC defn * * Copyright 1995, Microsoft Corporation * * Purpose: define the class for PLC (a PLx using * CP's and El.) * * Revision History: * * [] 01-Jun-1995 Dans Created * *************************************************************************/ #if !defined(_plc_h) #define _plc_h 1 typedef unsigned long ICP; typedef long DCP; typedef unsigned long CP; typedef unsigned long CCP; const CP cpNil = CP(-1); const ICP icpNil = ICP(-1); #define _IN(x) const x & #define _OUT(x) x & #define _IO(x) x & enum PLCF { plcfAdjustPieces = 0x1, // implies the pieces are really just like the cp's // and should be treated as such }; template inline void PrintElement ( FILE * pfile, El el ) { _ftprintf ( pfile, _TEXT("Element = %ld\n"), long(el) ); } template class PLC { public: PLC() { _icpAdjust = 0; _dcpAdjust = 0; _icpHint = 0; _fAdjustPieces = fFalse; } ~PLC() { } // initialize a brand new piece table BOOL FInit ( CP, // cp for end of table El&, // initial piece int plcf =0 // flags to initialize with ); // return the current maximum valid index ICP IcpMac() const { return _rgcp.size(); } // return the current maximum number of elements we can have w/o // allocating more memory ICP IcpMax() { return _rgcp.sizeMax(); } // split and adjust; this is for plcfAdjustPieces only BOOL FSplitAndAdjustAtCp ( CP, DCP, BOOL fDoit =fTrue ); // insert a new piece into me ICP IcpInsertAtCp ( CP, // insert here CCP, // insert this many _IN(El), // piece to insert BOOL =fTrue // do it or verify it can be done ); // insert another piece table into me ICP IcpInsertAtCp ( CP, // insert here _IO(PLC), // piece table to insert BOOL =fTrue // do it or verify it can be done ); // delete a range of the piece table BOOL FDeleteAtCp ( CP, // delete here CCP, // delete this many BOOL =fTrue // do it or verify it can be done ); // return the element at the given cp, offset by the appropriate // amount from the beginning of that piece. El ElAtCp ( CP cpAt, CCP & ccp ); // return the (adjusted) cp at the given index CP CpFromIcp ( ICP icp ) { assert ( icp < IcpMac() ); if ( icp < IcpMac() ) { return _rgcp[ icp ] + DcpAdjust ( icp ); } else { return cpNil; } } // return the cp that is the end of the piece table. CP CpLim() { return CpFromIcp ( IcpMac() - 1 ); } // check to see if to character positions are in the same piece. BOOL FInSamePiece ( CP cp1, CP cp2 ) { if ( cp1 > cp2 ) { CP cpT = cp1; cp1 = cp2; cp2 = cpT; } if ( cp1 == cp2 ) { return fTrue; } else { CCP ccp; El el = ElAtCp ( cp1, ccp ); if ( cp1 + ccp > cp2 ) { return fTrue; } } return fFalse; } void PrintAll ( FILE * ) const; protected: ICP _icpAdjust; // index to first cp pending adjust DCP _dcpAdjust; // amount of pending adjust ICP _icpHint; // last cp examined BOOL _fAdjustPieces; // whether adjustments are applied to // pieces in addition to cp's Array _rgcp; // array of cp's Array _rgEl; // array of El's CP CpAdjust ( ICP icp, CP cp ) const { /* ** takes _rgcp[icp] level cp to user cp level */ if ( icp < _icpAdjust ) { return cp; } else { return cp + _dcpAdjust; } } CP CpUnAdjust ( ICP icp, CP cp ) const { /* ** takes user cp to _rgcp[icp] level */ if ( icp < _icpAdjust ) { return cp; } else { return cp - _dcpAdjust; } } DCP DcpAdjust ( ICP icp ) const { return ( (icp < _icpAdjust) ? 0 : _dcpAdjust ); } BOOL FSplitAtCp ( CP cpAt, ICP icpKnown = icpNil, BOOL fDo = fTrue ); void AdjustIcpCp ( ICP icpAdjust, DCP dcpAdjust ); #if 0 void LazyAdjustIcpCp ( ICP icpAdjust, DCP dcpAdjust ); #endif void AdjustRgcp ( ICP icpFirst, ICP icpLim, DCP dcpAdjust ); ICP IcpFromCp ( CP cpSearch, DCP * pdcp ); #if 0 #if defined(DEBUG) void PrintIcp(ICP) const; void PrintCp(CP); void ConsistencyCheck() const; private: void Print(ICP,ICP) const; #endif #endif }; /*** PLC::FInit * * Purpose: * initialize a PLC * * Input: * cpEnd: count of characters in document * el: initial element to place in _rgEl[0] * plcf: flags for specific behaviors * * Output: * dynamic arrays are setup with 1 piece, adjustments = 0 * * Returns: * fTrue if successful, fFalse otherwise. * * Notes: * fFalse returned means that dynamic arrays could not be * allocated for some reason. * * plc always starts at 0. * *************************************************************************/ template BOOL PLC::FInit ( CP cpEnd, El & el, int plcf ) { BOOL fRetval = fFalse; CP cpStart = 0; /* ** smallest plc has two cp's and one el describing it */ assert ( cpEnd > 0 ); if ( _rgcp.append ( cpStart ) && _rgcp.append ( cpEnd ) && _rgEl.append ( el ) ) { _fAdjustPieces = plcf & plcfAdjustPieces; _icpAdjust = 2; _dcpAdjust = 0; _icpHint = 0; fRetval = fTrue; } return fRetval; } /*** PLC::AdjustRgcp * * Purpose: to apply an adjustment to all cp's from icpStart to icpLast * * Input: icpFirst icp to start with adjustments * icpLim icp limit * dcpAdjust adjustment to add to each cp in range * * Returns: none * * Note: this adjusts on range [icpFirst .. icpLim). * also, for huge arrays, must use the subscripting method to * ensure that we get the right address when adjusting values. * at least until c7. then we can change to using huge ptrs * w/o any problem. * *************************************************************************/ template void PLC::AdjustRgcp ( ICP icpFirst, ICP icpLim, DCP dcpAdjust ) { ICP icp; assert ( icpFirst < icpLim ); assert ( icpFirst < IcpMac() ); assert ( icpLim <= IcpMac() ); if ( dcpAdjust != 0 ) { for ( icp = icpFirst; icp < icpLim; icp++ ) { _rgcp[ icp ] += dcpAdjust; } if ( _fAdjustPieces ) { if ( icpLim == IcpMac() ) { icpLim--; } for ( icp = icpFirst; icp < icpLim; icp++ ) { _rgEl[ icp ] += dcpAdjust; } } } } /*** PLC::IcpFromCp * * Purpose: search for the cp containing a given cp and return its index * * Input: cpSearch cp to search for * pdcp ptr to dcp. receives the delta between * _rgcp[ icpFound ] and cpSearch. * * Returns: index of cp containing cpSearch if in range, icpNil if not * * Note: Will not work for cp < _rgcp[0]. * *************************************************************************/ template ICP PLC::IcpFromCp ( CP cpSearch, DCP * pdcp = 0 ) { ICP icpMac = IcpMac(); ICP icpTop = icpMac; ICP icpBot = 0; assert ( icpMac > 1 ); assert ( _icpHint <= icpMac ); if ( pdcp != 0 ) { *pdcp = 0; } /* ** Check our last position, maybe that one is it? */ if ( _icpHint < icpMac - 1 ) { DCP dcpAdjustHint = DcpAdjust ( _icpHint ); if ( (_rgcp[ _icpHint ] + dcpAdjustHint) <= cpSearch ) { if ((_rgcp[ _icpHint + 1 ] + DcpAdjust(_icpHint+1)) > cpSearch) { if ( pdcp != 0 ) { *pdcp = cpSearch - (_rgcp[ _icpHint ] + dcpAdjustHint); } return _icpHint; } else { // implies that cpSearch is in range [icpHint+1 - icpMac) icpBot = _icpHint + 1; } } else { // implies that cpSearch is in range [0 - icpHint) icpTop = _icpHint; } } assert ( cpSearch >= _rgcp[0] ); assert ( icpBot <= icpTop ); ICP icpMid; while ( icpBot + 1 < icpTop ) { icpMid = (icpTop + icpBot) >> 1; if ( (_rgcp[ icpMid ] + DcpAdjust ( icpMid )) <= cpSearch ) { icpBot = icpMid; } else { icpTop = icpMid; } } if ( pdcp != 0 ) { *pdcp = cpSearch - (_rgcp[ icpBot ] + DcpAdjust ( icpBot )); } _icpHint = icpBot; return icpBot; } /*** PLC::AdjustIcpCp * * Purpose: to perform a full adjustment (as opposed to lazy) * (from icpAdjustNew to IcpMac()-1) * * Input: icpAdjustNew index where adjustment starts * dcpAdjustNew amount of adjustment to apply * * Returns: none * * Notes: if there is already a pending adjustment * (_icpAdjust != IcpMac()), * it is folded in so the elements are only hit once with an * adjustment. * *************************************************************************/ template void PLC::AdjustIcpCp ( ICP icpAdjustNew, DCP dcpAdjustNew ) { ICP icpMac = IcpMac(); assert ( icpAdjustNew <= icpMac ); if ( _icpAdjust == icpMac ) { /* ** no pending adjustment, do the adjustment */ AdjustRgcp ( icpAdjustNew, icpMac, dcpAdjustNew ); } else { /* ** already a pending adjustment, coalesce into one or two disjoint ones */ if ( _icpAdjust == icpAdjustNew ) { AdjustRgcp ( _icpAdjust, icpMac, dcpAdjustNew + _dcpAdjust ); } else { /* ** Do two disjoint adjustments */ DCP dcpAdjTail = dcpAdjustNew + _dcpAdjust; if ( icpAdjustNew < _icpAdjust ) { AdjustRgcp ( icpAdjustNew, _icpAdjust, dcpAdjustNew ); AdjustRgcp ( _icpAdjust, icpMac, dcpAdjTail ); } else { AdjustRgcp ( _icpAdjust, icpAdjustNew, _dcpAdjust ); AdjustRgcp ( icpAdjustNew, icpMac, dcpAdjTail ); } } _dcpAdjust = 0; _icpAdjust = icpMac; } } /*** PLC::FSplitAtCp * * Purpose: split a piece into two, equivalent pieces * * Input: cpAt where to split a piece. * icpKnown if the icp is known, supply it. otherwise, it * will be searched for via cpAt. * fDoit, whether this is a TRY or a DO * * Output: piece table is updated if a split is legal to do and memory * is available. * * Returns: fTrue if successful split, fFalse otherwise. * * Notes: integrity of the piece table is assured and if it is on * piece boundary, no split will be done. * if fDoit is fFalse, we only get to the point of making sure * that there is enough space to accomplish the task but no * changes are made to the piece table other that extending * the space available. * *************************************************************************/ template BOOL PLC::FSplitAtCp ( CP cpAt, ICP icp, BOOL fDoit ) { DCP dcp; BOOL fRetval = fFalse; assert ( icp < IcpMac() || icp == icpNil ); if ( icp == icpNil ) { icp = IcpFromCp ( cpAt, &dcp ); } else { assert ( IcpFromCp ( cpAt ) == icp ); dcp = cpAt - CpAdjust ( icp, _rgcp[ icp ] ); } if ( dcp > 0 ) { if ( _rgcp.growMaxSize ( _rgcp.size() + 1 ) && _rgEl.growMaxSize ( _rgEl.size() + 1 ) ) { if ( !fDoit ) { return fTrue; } CP cpAtUnadj = CpUnAdjust ( icp, cpAt ); icp++; verify ( _rgcp.insertManyAt ( icp, 1 ) ); verify ( _rgEl.insertManyAt ( icp, 1 ) ); _rgcp[ icp ] = cpAtUnadj; _rgEl[ icp ] = _rgEl[ icp - 1 ]; // // if we are not adjusting the pieces, we need to adjust // the split here. // if ( !_fAdjustPieces ) { _rgEl[ icp ] += dcp; } if ( _icpAdjust >= icp ) { _icpAdjust++; } fRetval = fTrue; } else { fRetval = fFalse; } } else { // // already a piece boundary, let it fly! // fRetval = fTrue; } return fRetval; } /*** PLC::FSplitAndAdjustAtCp * * Purpose: cause a split to happen and adjust both cp and deltas. * * Input: cpAt where to split * dcp what the delta is * fDoit, whether this is a TRY or a DO * * Output: dynamic arrays are updated * Returns: fTrue if successful * * Notes: only available for _fAdjustPieces == TRUE * *************************************************************************/ template BOOL PLC::FSplitAndAdjustAtCp ( CP cpAt, DCP dcp, BOOL fDoit ) { if ( !_fAdjustPieces ) { return fFalse; } if ( !fDoit ) { return FSplitAtCp ( cpAt, icpNil, fFalse ); } // we need the dcpT of the current piece to know where to start // adjusting the pieces. DCP dcpT; ICP icp = IcpFromCp ( cpAt, &dcpT ); if ( FSplitAtCp ( cpAt, icp, fTrue ) ) { // if we actually split the piece up, (detectable via the dcpT) // we need to increment our index to point at the new split point if ( dcpT ) { icp++; } // when we have a negative dcp, we may need to // actually remove piece(s) // REVIEW: won't work with lazy adjustment like this. if ( dcp < 0 ) { ICP icpT = icp + 1; while ( icpT < _rgcp.size() && _rgcp [ icpT ] + dcp <= cpAt ) { _rgcp.deleteAt ( icpT ); if ( icpT < _rgEl.size() ) { _rgEl.deleteAt ( icpT ); } if ( _icpAdjust >= icpT ) { _icpAdjust--; } } } // in order to accomodate the adjustpiece dicsipline, we need to // adjust the current split piece, but not the CP associated with // it. We do that by forcing the adjustment here. // REVIEW: won't work with lazy adjustment like this. _rgEl[ icp ] += dcp; AdjustIcpCp ( icp + 1, dcp ); return fTrue; } return fFalse; } /*** PLC::IcpInsertAtCp * * Purpose: Insert a piece at a specific cp in the piece table. * * Input: cpAt where to insert the new characters * ccp how many characters are involved * el piece descriptor to add. * fDoit, whether this is a TRY or a DO * * Output: dynamic arrays are updated * Returns: icp of inserted piece, icpNil if unable to insert * * Notes: all ptrs to any pcd and cached icp's are potentially invalid * after this routine is called. * if fDoit is fFalse, we only get to the point of making sure * that there is enough space to accomplish the task but no * changes are made to the piece table other that extending * the space available. * *************************************************************************/ template ICP PLC::IcpInsertAtCp ( CP cpAt, CCP ccp, const El & el, BOOL fDoit ) { if ( _fAdjustPieces ) { return icpNil; } unsigned cInsert; DCP dcp; ICP icp = IcpFromCp ( cpAt, &dcp ); assert ( icp < IcpMac() ); if ( dcp == 0 ) { // delta between cpAt and _rgcp[icp]. /* ** direct hit implies that we don't need to split a piece, but ** we can optimize the pieces possibly if we are extending a piece */ if ( icp > 0 ) { El elExtend; /* ** Note that El's must have op+ and op== to work here. */ elExtend = _rgEl[ icp - 1 ] + (cpAt - CpAdjust ( icp - 1, _rgcp[ icp - 1 ])); if ( elExtend == el ) { if ( fDoit ) { /* ** if the previous el + count of cp's == incoming el, ** then it is just an extend of that el. ** ** Fast way out! just apply the adjustment and return */ AdjustIcpCp ( icp, ccp ); } return icp - 1; } else { cInsert = 1; } } else { /* ** implies that cpAt == _rgcp[0] */ cInsert = 1; } } else { /* ** implies that we need to split a piece as well as insert a piece ** advance icp because we leave the current piece alone. */ cInsert = 2; icp++; } if ( cInsert > 0 ) { /* ** Need to factor out the current adjustment when placing cpAt ** into the piece table, because cpAt is the "real" cp but the cp's ** in the piece table might have to be adjusted. ** Also, we use icp-1 because icp will be moved up! ** ** if cInsert == 1, we know that we were on a boundary and the ** correct adjustment to apply is from icp-1, unless ** we are inserting at 0 and that implies that no ** current adjustment applies (inserting before any ** characters in the document). ** if cInsert == 2, we use icp-1 because icp has already been ** incremented in preparation for the insert, but ** the prev icp is the correct adjustment value. ** */ CP cpAtUnadj = cpAt; if ( icp > 0 ) { cpAtUnadj = CpUnAdjust ( icp - 1, cpAt ); } if ( _rgcp.growMaxSize ( _rgcp.size() + cInsert ) && _rgcp.growMaxSize ( _rgEl.size() + cInsert ) ) { if ( !fDoit ) { return icp; } verify ( _rgcp.insertManyAt ( icp, cInsert ) ); verify ( _rgEl.insertManyAt ( icp, cInsert ) ); _rgcp[ icp ] = cpAtUnadj; _rgEl[ icp ] = el; if ( cInsert == 2 ) { ICP icpT = icp - 1; /* ** don't worry, adjustment starts at icp+1. */ _rgcp[ icp + 1 ] = cpAtUnadj; /* ** did a split as well...need to fix up the el to ** cover the split piece. */ _rgEl[ icp + 1 ] = _rgEl[ icpT ] + (cpAt - CpAdjust ( icpT, _rgcp[ icpT ] )); } } else { return icpNil; } } else { if ( !fDoit ) { return icp; } } if ( _icpAdjust >= icp ) { /* ** don't lose the current adjustment! */ _icpAdjust += cInsert; } AdjustIcpCp ( icp + 1, ccp ); return icp; } /*** PLC::FDeleteAtCp * * Purpose: delete characters from the piece table. * * Input: cpLo where to start the deletion * ccp count of cp's to delete (truncated to max cp) * fDoit, whether this is a TRY or a DO * * Output: piece table is updated * Returns: fTrue if successful, fFalse if not * * Exceptions: can fail if out of memory--delete can insert pieces! * * Notes: * if fDoit is fFalse, we only get to the point of making sure * that there is enough space to accomplish the task but no * changes are made to the piece table other that extending * the space available. * *************************************************************************/ template BOOL PLC::FDeleteAtCp ( CP cpLo, CCP ccp, BOOL fDoit ) { if ( _fAdjustPieces ) { return fFalse; } ICP icpMac = IcpMac(); ICP icpLast = icpMac - 1; ICP icpLo; ICP icpHi; DCP dcpLo; DCP dcpHi; BOOL fRetval = fTrue; long cicpDel; El elHi; assert ( cpLo < _rgcp[ icpLast ] + DcpAdjust ( icpLast ) ); assert ( icpLast == _rgEl.size() ); ccp = min ( ccp, _rgcp[ icpLast ] + DcpAdjust ( icpLast ) - cpLo ); icpLo = IcpFromCp ( cpLo, &dcpLo ); icpHi = IcpFromCp ( cpLo + ccp, &dcpHi ); cicpDel = icpHi - icpLo; /* ** check for the lower boundary--if on a boundary, no split is ** necessary for that end and need to insert one piece. */ if ( dcpLo != 0 ) { cicpDel--; } if ( icpHi < icpLast ) { elHi = _rgEl[ icpHi ]; } /* ** clear out any adjustments that could foul us up. basically do any ** adjustment for all indexes <= icpHi. */ if ( fDoit && _icpAdjust <= icpHi ) { AdjustRgcp ( _icpAdjust, icpHi + 1, _dcpAdjust ); _icpAdjust = icpHi + 1; } if ( cicpDel < 0 ) { /* ** implies that we need to insert one piece. */ assert ( cicpDel == -1 ); if ( _rgcp.growMaxSize ( _rgcp.size() + 1 ) && _rgEl.growMaxSize ( _rgEl.size() + 1 ) ) { if ( !fDoit ) { return fTrue; } verify ( _rgcp.insertManyAt ( icpLo + 1, 1 ) ); verify ( _rgEl.insertManyAt ( icpLo + 1, 1 ) ); } else { fRetval = fFalse; } } else if ( cicpDel > 0 ) { if ( !fDoit ) { return fTrue; } /* ** blow the hint... */ _icpHint = icpMac - cicpDel; /* ** and shrink the tables */ verify ( _rgcp.deleteManyAt ( icpLo + 1, cicpDel ) ); verify ( _rgEl.deleteManyAt ( icpLo + 1, cicpDel ) ); } if ( fRetval ) { if ( !fDoit ) { return fTrue; } /* ** Perform the fixups to the cp's and El's */ icpHi -= cicpDel; // point to cp to work with _rgcp[ icpHi ] = cpLo; if ( icpHi < _rgEl.size() ) { _rgEl[ icpHi ] = elHi + dcpHi; } _icpAdjust -= cicpDel; AdjustIcpCp ( icpHi + 1, - DCP(ccp) ); } return fRetval; } /*** PLC::ElAtCp * * Purpose: to return an El that corresponds to a particular * character position (cp). also returns remaining count of * characters left in the piece. * * Input: cp: character position to start with * ccp: reference to where to put the count of charaters * * Output: ccp is updated * * Returns: the element (El) at character position cp. * * Exceptions: returns a null ptr if cp out of range. * *************************************************************************/ template El PLC::ElAtCp ( CP cpAt, CCP & ccp ) { ICP icp = IcpMac() - 1; El el; ccp = 0; if ( cpAt < CpAdjust ( icp, _rgcp[ icp ] ) ) { DCP dcp; icp = IcpFromCp ( cpAt, &dcp ); el = _rgEl[ icp ]; if ( !_fAdjustPieces ) { el += dcp; } ccp = CpAdjust ( icp + 1, _rgcp[ icp + 1 ] ) - cpAt; } return el; } //----------------------------------------------------------------------------- // IcpInsertAtCp // // Purpose: // insert a given piece table into me at a particular location. // // Input: // cp, where to insert // plcEl, piece table that we are inserting // fDoIt, whether to commit the insert or not // // Returns: // icp of beginning of insert // // //----------------------------------------------------------------------------- template ICP PLC::IcpInsertAtCp ( CP cp, IO(PLC) plcEl, BOOL fDoIt ) { ICP icpRet = icpNil; ICP icpAdd = plcEl.IcpMac() + 2; CP cpSav = cp; if ( _rgcp.growMaxSize ( _rgcp.size() + icpAdd ) && _rgEl.growMaxSize ( _rgEl.size() + icpAdd ) ) { if ( fDoIt ) { CP cpNew = 0; CP cpLim = plcEl.CpLim() - 1; El el; CCP ccp; while ( cpNew < cpLim ) { el = plcEl.ElAtCp ( cpNew, ccp ); verify ( icpNil != IcpInsertAtCp ( cp, ccp, el ) ); cpNew += ccp; cp += ccp; } } icpRet = IcpFromCp ( cpSav ); } return icpRet; } template void PLC::PrintAll ( FILE * pfile ) const { _ftprintf ( pfile, _TEXT("\nAdjust index = %6lu, adjustment = %6ld, # of elements = %6lu.\n"), _icpAdjust, _dcpAdjust, _rgcp.size() ); ICP icpLast = _rgcp.size() - 1; for ( ICP icp = 0; icp <= icpLast; icp++ ) { _ftprintf ( pfile, _TEXT("_rgcp[ %6lu ] = %6lu (%6lu). "), icp, _rgcp[ icp ], _rgcp[ icp ] + DcpAdjust(icp) ); if ( icp < _rgEl.size() ) { PrintElement ( pfile, _rgEl[ icp ] ); } } _ftprintf ( pfile, _TEXT("\n") ); } #endif