mirror of
https://github.com/microsoft/Microsoft-3D-Movie-Maker.git
synced 2024-11-22 10:22:40 +01:00
1195 lines
30 KiB
C++
1195 lines
30 KiB
C++
/* Copyright (c) Microsoft Corporation.
|
|
Licensed under the MIT License. */
|
|
|
|
/***************************************************************************
|
|
Author: ******
|
|
Project: Kauai
|
|
Reviewed:
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Basic collection classes (continued from groups.cpp):
|
|
General List (GL), Allocated List (AL),
|
|
General Group (GG), Allocated Group (AG),
|
|
General String Table (GST), Allocated String Table (AST).
|
|
|
|
BASE ---> GRPB -+-> GLB -+-> GL
|
|
| +-> AL
|
|
|
|
|
+-> GGB -+-> GG
|
|
| +-> AG
|
|
|
|
|
+-> GSTB-+-> GST
|
|
+-> AST
|
|
|
|
***************************************************************************/
|
|
#include "util.h"
|
|
ASSERTNAME
|
|
|
|
|
|
RTCLASS(GSTB)
|
|
RTCLASS(GST)
|
|
RTCLASS(AST)
|
|
|
|
|
|
/***************************************************************************
|
|
Constructor for a base string table.
|
|
***************************************************************************/
|
|
GSTB::GSTB(long cbExtra, ulong grfgst)
|
|
{
|
|
AssertIn(cbExtra, 0, kcbMax);
|
|
Assert(cbExtra % size(long) == 0, "cbExtra not multiple of size(long)");
|
|
|
|
_cbEntry = cbExtra + size(long);
|
|
_cbstFree = (grfgst & fgstAllowFree) ? 0 : cvNil;
|
|
|
|
// use some reasonable values for _cbMinGrow* - code can always set
|
|
// set these to something else
|
|
_cbMinGrow1 = 120;
|
|
_cbMinGrow2 = 12 * _cbEntry;
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate the string table.
|
|
***************************************************************************/
|
|
bool GSTB::_FDup(PGSTB pgstbDst)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertPo(pgstbDst, fobjAssertFull);
|
|
Assert(_cbEntry == pgstbDst->_cbEntry,
|
|
"why do these have different sized entries?");
|
|
|
|
if (!GSTB_PAR::_FDup(pgstbDst, _bstMac, LwMul(_cbEntry, _ivMac)))
|
|
return fFalse;
|
|
|
|
pgstbDst->_cbEntry = _cbEntry;
|
|
pgstbDst->_bstMac = _bstMac;
|
|
pgstbDst->_cbstFree = _cbstFree;
|
|
AssertPo(pgstbDst, fobjAssertFull);
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
//string table on file
|
|
struct GSTF
|
|
{
|
|
short bo;
|
|
short osk;
|
|
long cbEntry;
|
|
long ibstMac;
|
|
long bstMac;
|
|
long cbstFree;
|
|
};
|
|
const BOM kbomGstf = 0x5FF00000L;
|
|
|
|
|
|
/***************************************************************************
|
|
Return the amount of space on file needed for the string table.
|
|
***************************************************************************/
|
|
long GSTB::CbOnFile(void)
|
|
{
|
|
AssertThis(0);
|
|
return size(GSTF) + LwMul(_ivMac, _cbEntry) + _bstMac;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Write the string table to disk. It is the client's responsibility to
|
|
ensure that the extra data has the given byte order (bo) and osk.
|
|
If the osk has a different character size than the current one, the
|
|
strings are actually saved in the corresponding osk with the same sized
|
|
characters.
|
|
***************************************************************************/
|
|
bool GSTB::FWrite(PBLCK pblck, short bo, short osk)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertPo(pblck, 0);
|
|
Assert(kboCur == bo || kboOther == bo, "bad bo");
|
|
AssertOsk(osk);
|
|
|
|
GSTF gstf;
|
|
bool fRet;
|
|
|
|
if (osk != koskCur)
|
|
{
|
|
switch (osk)
|
|
{
|
|
case koskSbMac:
|
|
case koskUniMac:
|
|
osk = koskMac;
|
|
break;
|
|
|
|
case koskSbWin:
|
|
case koskUniWin:
|
|
osk = koskWin;
|
|
break;
|
|
|
|
default:
|
|
Bug("unknown osk");
|
|
return fFalse;
|
|
}
|
|
}
|
|
|
|
gstf.bo = kboCur;
|
|
gstf.osk = osk;
|
|
gstf.cbEntry = _cbEntry;
|
|
gstf.ibstMac = _ivMac;
|
|
gstf.bstMac = _bstMac;
|
|
gstf.cbstFree = _cbstFree;
|
|
if (kboOther == bo)
|
|
{
|
|
SwapBytesBom(&gstf, kbomGstf);
|
|
Assert(gstf.osk == osk, "osk not invariant under byte swapping");
|
|
_SwapBytesRgbst();
|
|
}
|
|
if (koskCur != osk)
|
|
_TranslateGrst(osk, fFalse);
|
|
fRet = _FWrite(pblck, &gstf, size(gstf), _bstMac, LwMul(_cbEntry, _ivMac));
|
|
if (koskCur != osk)
|
|
_TranslateGrst(osk, fTrue);
|
|
if (kboOther == bo)
|
|
_SwapBytesRgbst();
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read string table data from a block.
|
|
***************************************************************************/
|
|
bool GSTB::_FRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
GSTF gstf;
|
|
long cbT;
|
|
short bo;
|
|
long cb;
|
|
bool fRet = fFalse;
|
|
|
|
if (!pblck->FUnpackData())
|
|
goto LFail;
|
|
|
|
cb = pblck->Cb();
|
|
if (cb < size(gstf))
|
|
goto LBug;
|
|
|
|
if (!pblck->FReadRgb(&gstf, size(gstf), 0))
|
|
goto LFail;
|
|
|
|
if (pbo != pvNil)
|
|
*pbo = gstf.bo;
|
|
if (posk != pvNil)
|
|
*posk = gstf.osk;
|
|
|
|
if ((bo = gstf.bo) == kboOther)
|
|
SwapBytesBom(&gstf, kbomGstf);
|
|
|
|
cb -= size(gstf);
|
|
//don't use LwMul, in case the file is corrupted, we don't want to assert,
|
|
//we should detect it below.
|
|
cbT = gstf.cbEntry * gstf.ibstMac;
|
|
if (gstf.bo != kboCur || !FIn(gstf.cbEntry, size(long), kcbMax) ||
|
|
(gstf.cbEntry % size(long)) != 0 || !FIn(gstf.ibstMac, 0, kcbMax) ||
|
|
!FIn(gstf.bstMac, gstf.ibstMac - LwMax(0, gstf.cbstFree), kcbMax) ||
|
|
cb != cbT + gstf.bstMac ||
|
|
(gstf.cbstFree == cvNil) != (_cbstFree == cvNil) ||
|
|
gstf.cbstFree != cvNil && !FIn(gstf.cbstFree, 0, LwMax(1, gstf.ibstMac)))
|
|
{
|
|
LBug:
|
|
Warn("file corrupt or not a GSTB");
|
|
goto LFail;
|
|
}
|
|
|
|
_cbEntry = gstf.cbEntry;
|
|
_ivMac = gstf.ibstMac;
|
|
_bstMac = gstf.bstMac;
|
|
_cbstFree = gstf.cbstFree;
|
|
fRet = _FReadData(pblck, cb - cbT, cbT, size(gstf));
|
|
if (fRet)
|
|
{
|
|
if (bo == kboOther)
|
|
_SwapBytesRgbst();
|
|
if (koskCur != gstf.osk)
|
|
fRet = _FTranslateGrst(gstf.osk);
|
|
}
|
|
|
|
LFail:
|
|
TrashVarIf(!fRet, pbo);
|
|
TrashVarIf(!fRet, posk);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Ensures that there is room to add at least cstnAdd new strings with
|
|
a total of cchAdd characters. If there is more than enough room and
|
|
fgrpShrink is passed, the GSTB may shrink.
|
|
***************************************************************************/
|
|
bool GSTB::FEnsureSpace(long cstnAdd, long cchAdd, ulong grfgrp)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cstnAdd, 0, kcbMax);
|
|
AssertIn(cchAdd, 0, kcbMax);
|
|
long cbstAdd;
|
|
long cbAdd = cchAdd * size(achar);
|
|
|
|
if (cvNil == _cbstFree)
|
|
cbstAdd = cstnAdd;
|
|
else
|
|
cbstAdd = LwMax(0, cstnAdd - _cbstFree);
|
|
if (cbstAdd > kcbMax / _cbEntry - _ivMac ||
|
|
cstnAdd > kcbMax - _bstMac ||
|
|
cbAdd > kcbMax - _bstMac - cstnAdd)
|
|
{
|
|
Bug("why is this string table growing so large?");
|
|
return fFalse;
|
|
}
|
|
|
|
return _FEnsureSizes(_bstMac + cbAdd + cstnAdd,
|
|
LwMul(_ivMac + cbstAdd, _cbEntry), grfgrp);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Set the minimum that a GSTB should grow by.
|
|
***************************************************************************/
|
|
void GSTB::SetMinGrow(long cstnAdd, long cchAdd)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cstnAdd, 0, kcbMax);
|
|
AssertIn(cchAdd, 0, kcbMax);
|
|
|
|
_cbMinGrow1 = CbRoundToLong(cchAdd * size(achar) + cstnAdd);
|
|
_cbMinGrow2 = LwMul(cstnAdd, _cbEntry);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Append an stn to string table.
|
|
***************************************************************************/
|
|
bool GSTB::FAddStn(PSTN pstn, void *pvExtra, long *pistn)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pstn, 0);
|
|
|
|
return FAddRgch(pstn->Prgch(), pstn->Cch(), pvExtra, pistn);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Replace the ith string.
|
|
***************************************************************************/
|
|
bool GSTB::FPutRgch(long istn, achar *prgch, long cch)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(istn, 0, _ivMac);
|
|
Assert(!FFree(istn), "string entry is free!");
|
|
AssertIn(cch, 0, kcchMaxGst + 1);
|
|
AssertPvCb(prgch, cch * size(achar));
|
|
|
|
long cchOld;
|
|
long bstOld;
|
|
achar *qst;
|
|
|
|
qst = _Qst(istn);
|
|
if ((cchOld = CchSt(qst)) == cch)
|
|
{
|
|
CopyPb(prgch, PrgchSt(qst), cch * size(achar));
|
|
goto LDone;
|
|
}
|
|
if (cchOld < cch && !_FEnsureSizes(_bstMac + (cch - cchOld) * size(achar),
|
|
LwMul(_ivMac, _cbEntry), fgrpNil))
|
|
{
|
|
return fFalse;
|
|
}
|
|
|
|
// remove the old one
|
|
bstOld = _Bst(istn);
|
|
_RemoveSt(bstOld);
|
|
|
|
// append the new one
|
|
*_Qbst(istn) = _bstMac;
|
|
_AppendRgch(prgch, cch);
|
|
|
|
LDone:
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Replace the ith string with stn.
|
|
***************************************************************************/
|
|
bool GSTB::FPutStn(long istn, PSTN pstn)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pstn, 0);
|
|
|
|
return FPutRgch(istn, pstn->Prgch(), pstn->Cch());
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get up to cchMax characters for the istn'th string.
|
|
***************************************************************************/
|
|
void GSTB::GetRgch(long istn, achar *prgch, long cchMax, long *pcch)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(istn, 0, _ivMac);
|
|
Assert(!FFree(istn), "string entry is free!");
|
|
AssertIn(cchMax, 0, kcbMax);
|
|
AssertPvCb(prgch, cchMax * size(achar));
|
|
AssertVarMem(pcch);
|
|
PST qst = _Qst(istn);
|
|
|
|
*pcch = LwMin(cchMax, CchSt(qst));
|
|
CopyPb(PrgchSt(qst), prgch, *pcch * size(achar));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Get the ith string.
|
|
***************************************************************************/
|
|
void GSTB::GetStn(long istn, PSTN pstn)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(istn, 0, _ivMac);
|
|
Assert(!FFree(istn), "string entry is free!");
|
|
AssertPo(pstn, 0);
|
|
|
|
pstn->SetSt(_Qst(istn));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Find the given stn in the string table.
|
|
***************************************************************************/
|
|
bool GSTB::FFindStn(PSTN pstn, long *pistn, ulong grfgst)
|
|
{
|
|
AssertThis(0);
|
|
AssertPo(pstn, 0);
|
|
|
|
return FFindRgch(pstn->Prgch(), pstn->Cch(), pistn, grfgst);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Search for the string in the string table. This version does a linear
|
|
search. GST overrides this to do a binary search if fgstSorted is
|
|
passed in grfgst.
|
|
***************************************************************************/
|
|
bool GSTB::FFindRgch(achar *prgch, long cch, long *pistn, ulong grfgst)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cch, 0, kcchMaxGst + 1);
|
|
AssertPvCb(prgch, cch * size(achar));
|
|
AssertVarMem(pistn);
|
|
long istn, bst;
|
|
PST qst;
|
|
|
|
for (istn = 0; istn < _ivMac; istn++)
|
|
{
|
|
bst = _Bst(istn);
|
|
if (bvNil == bst)
|
|
{
|
|
AssertIn(_cbstFree, 0, _ivMac);
|
|
Assert(istn < _ivMac - 1, "last element free");
|
|
continue;
|
|
}
|
|
AssertIn(bst, 0, _bstMac);
|
|
qst = (achar *)_Qb1(bst);
|
|
if (CchSt(qst) == cch && FEqualRgb(PrgchSt(qst), prgch, cch * size(achar)))
|
|
{
|
|
*pistn = istn;
|
|
return fTrue;
|
|
}
|
|
}
|
|
*pistn = istn;
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Find the string with the given extra data in the string table.
|
|
***************************************************************************/
|
|
bool GSTB::FFindExtra(void *prgbFind, PSTN pstn, long *pistn)
|
|
{
|
|
AssertThis(0);
|
|
AssertPvCb(prgbFind, CbExtra());
|
|
AssertNilOrPo(pstn, 0);
|
|
AssertNilOrVarMem(pistn);
|
|
|
|
long istn;
|
|
byte *qbExtra;
|
|
long cbExtra = CbExtra();
|
|
long ivMac = IvMac();
|
|
|
|
if (cbExtra == 0)
|
|
{
|
|
Bug("no extra data");
|
|
TrashVar(pistn);
|
|
return fFalse;
|
|
}
|
|
|
|
for (istn = 0; istn < ivMac; istn++)
|
|
{
|
|
qbExtra = (byte *)_Qbst(istn) + size(long);
|
|
if (FEqualRgb(prgbFind, qbExtra, cbExtra))
|
|
{
|
|
if (pstn != pvNil)
|
|
GetStn(istn, pstn);
|
|
if (pistn != pvNil)
|
|
*pistn = istn;
|
|
return fTrue;
|
|
}
|
|
}
|
|
|
|
if (pvNil != pstn)
|
|
pstn->SetNil();
|
|
TrashVar(pistn);
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Fetch the extra data for element istn.
|
|
***************************************************************************/
|
|
void GSTB::GetExtra(long istn, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(istn, 0, _ivMac);
|
|
Assert(!FFree(istn), "string entry is free!");
|
|
Assert(_cbEntry > size(long), "no extra data");
|
|
AssertPvCb(pv, _cbEntry - size(long));
|
|
|
|
byte *qb;
|
|
|
|
qb = (byte *)_Qbst(istn) + size(long);
|
|
CopyPb(qb, pv, _cbEntry - size(long));
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Put the extra data for element istn.
|
|
***************************************************************************/
|
|
void GSTB::PutExtra(long istn, void *pv)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(istn, 0, _ivMac);
|
|
Assert(!FFree(istn), "string entry is free!");
|
|
Assert(_cbEntry > size(long), "no extra data");
|
|
AssertPvCb(pv, _cbEntry - size(long));
|
|
|
|
byte *qb;
|
|
|
|
qb = (byte *)_Qbst(istn) + size(long);
|
|
CopyPb(pv, qb, _cbEntry - size(long));
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Return a volatile pointer to the string given the ibst (not the bst).
|
|
***************************************************************************/
|
|
achar *GSTB::_Qst(long ibst)
|
|
{
|
|
AssertIn(ibst, 0, _ivMac);
|
|
long bst = _Bst(ibst);
|
|
AssertIn(bst, 0, _bstMac);
|
|
return (achar *)_Qb1(bst);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Private api to append the string. It's assumed that the first block
|
|
is already big enough to accomodate the string.
|
|
***************************************************************************/
|
|
void GSTB::_AppendRgch(achar *prgch, long cch)
|
|
{
|
|
AssertIn(cch, 0, kcchMaxGst + 1);
|
|
AssertPvCb(prgch, cch * size(achar));
|
|
|
|
achar *qch;
|
|
|
|
Assert(_Cb1() >= _bstMac + (cch + 1) * size(achar),
|
|
"first block not big enough");
|
|
qch = (achar *)_Qb1(_bstMac);
|
|
*qch++ = (achar)cch;
|
|
CopyPb(prgch, qch, cch * size(achar));
|
|
_bstMac += (cch + 1) * size(achar);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Private api to remove the string.
|
|
***************************************************************************/
|
|
void GSTB::_RemoveSt(long bst)
|
|
{
|
|
AssertIn(bst, 0, _bstMac);
|
|
|
|
long cb;
|
|
byte *qb;
|
|
|
|
qb = _Qb1(bst);
|
|
cb = CchTotSt((PST)qb) * size(achar);
|
|
AssertIn(bst + cb, 0, _bstMac + 1);
|
|
if (bst + cb < _bstMac)
|
|
{
|
|
long cv;
|
|
|
|
BltPb(qb + cb, qb, _bstMac - bst - cb);
|
|
for (qb = _Qb2(0), cv = _ivMac; cv-- > 0; qb += _cbEntry)
|
|
{
|
|
if (*(long *)qb > bst)
|
|
{
|
|
Assert(*(long *)qb >= bst + cb, "overlapping strings in GSTB");
|
|
*(long *)qb -= cb;
|
|
}
|
|
}
|
|
}
|
|
_bstMac -= cb;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Swap the bytes in the BST values. Note that each bst is followed
|
|
by the extra data, so we can't just use SwapBytesRglw.
|
|
***************************************************************************/
|
|
void GSTB::_SwapBytesRgbst(void)
|
|
{
|
|
if (size(long) == _cbEntry)
|
|
SwapBytesRglw(_Qb2(0), _ivMac);
|
|
else
|
|
{
|
|
long cbst;
|
|
byte *qb;
|
|
|
|
for (cbst = _ivMac, qb = _Qb2(0); cbst-- > 0; qb += _cbEntry)
|
|
SwapBytesRglw(qb, 1);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Translate the strings to/from the platform osk. This only works if
|
|
CbCharOsk(osk) == CbCharOsk(koskCur) (it asserts otherwise).
|
|
***************************************************************************/
|
|
void GSTB::_TranslateGrst(short osk, bool fToCur)
|
|
{
|
|
AssertOsk(osk);
|
|
long bst;
|
|
byte *qb;
|
|
|
|
if (CbCharOsk(osk) != CbCharOsk(koskCur))
|
|
{
|
|
Bug("can't translate to this osk");
|
|
return;
|
|
}
|
|
|
|
qb = _Qb1(0);
|
|
for (bst = 0; bst < _bstMac; )
|
|
{
|
|
TranslateSt((achar *)(qb + bst), osk, fToCur);
|
|
bst += CchTotSt((PST)(qb + bst)) * size(achar);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Translate the strings to the current osk.
|
|
***************************************************************************/
|
|
bool GSTB::_FTranslateGrst(short osk)
|
|
{
|
|
AssertOsk(osk);
|
|
void *pvSrc;
|
|
long cbSrc;
|
|
long cbChar;
|
|
long ibst, bstOld, cch;
|
|
long *qbst;
|
|
achar rgch[kcchMaxSt];
|
|
bool fRet = fFalse;
|
|
|
|
if ((cbChar = CbCharOsk(osk)) == CbCharOsk(koskCur))
|
|
{
|
|
_TranslateGrst(osk, fTrue);
|
|
return fTrue;
|
|
}
|
|
|
|
if (cbChar != size(schar) && cbChar != size(wchar))
|
|
{
|
|
Bug("unknown osk");
|
|
return fFalse;
|
|
}
|
|
|
|
if (_bstMac == 0)
|
|
return fTrue;
|
|
|
|
if (!FAllocPv(&pvSrc, cbSrc = _bstMac, fmemNil, mprNormal))
|
|
return fFalse;
|
|
|
|
CopyPb(_Qb1(0), pvSrc, cbSrc);
|
|
_bstMac = 0;
|
|
|
|
for (ibst = 0 ; ibst < _ivMac; ibst++)
|
|
{
|
|
qbst = _Qbst(ibst);
|
|
bstOld = *qbst;
|
|
*qbst = _bstMac;
|
|
|
|
if (bstOld + cbChar > cbSrc)
|
|
goto LFail;
|
|
|
|
if (cbChar == size(schar))
|
|
{
|
|
schar chs = *(schar *)PvAddBv(pvSrc, bstOld);
|
|
cch = (long)(byte)chs;
|
|
}
|
|
else
|
|
{
|
|
wchar chw;
|
|
CopyPb(PvAddBv(pvSrc, bstOld), &chw, size(wchar));
|
|
if (osk == MacWin(koskUniWin, koskUniMac))
|
|
SwapBytesRgsw(&chw, 1);
|
|
cch = (long)(ushort)chw;
|
|
}
|
|
|
|
if (!FIn(cch, 0, kcchMaxSt + 1) || bstOld + (cch + 1) * cbChar > cbSrc)
|
|
goto LFail;
|
|
|
|
cch = CchTranslateRgb(PvAddBv(pvSrc, bstOld + cbChar), cch * cbChar,
|
|
osk, rgch, kcchMaxSt);
|
|
|
|
if (!_FEnsureSizes(_bstMac + (cch + 1) * size(achar),
|
|
LwMul(_ivMac, _cbEntry), fgrpNil))
|
|
{
|
|
goto LFail;
|
|
}
|
|
_AppendRgch(rgch, cch);
|
|
}
|
|
|
|
fRet = fTrue;
|
|
AssertDo(_FEnsureSizes(_bstMac, LwMul(_ivMac, _cbEntry), fgrpShrink), 0);
|
|
|
|
LFail:
|
|
FreePpv(&pvSrc);
|
|
return fRet;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Returns true iff ibst is out of range or the corresponding bst is
|
|
bvNil.
|
|
***************************************************************************/
|
|
bool GSTB::FFree(long istn)
|
|
{
|
|
AssertIn(istn, 0, kcbMax);
|
|
long bst;
|
|
|
|
if (!FIn(istn, 0, _ivMac))
|
|
return fTrue;
|
|
bst = _Bst(istn);
|
|
Assert(bvNil == bst || FIn(bst, 0, _bstMac), "bad bst");
|
|
return bvNil == bst;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Validate a string table.
|
|
***************************************************************************/
|
|
void GSTB::AssertValid(ulong grfobj)
|
|
{
|
|
long ibst;
|
|
long cchTot, cbstFree;
|
|
long bst;
|
|
|
|
GSTB_PAR::AssertValid(grfobj);
|
|
AssertIn(_cbEntry, size(long), kcbMax);
|
|
Assert(_cbEntry % size(long) == 0, "_cbEntry bad");
|
|
AssertIn(_ivMac, 0, kcbMax);
|
|
Assert(_Cb1() >= _bstMac, "grst area too small");
|
|
Assert(_Cb2() >= LwMul(_ivMac, _cbEntry), "rgbst area too small");
|
|
Assert(_cbstFree == cvNil || FIn(_cbstFree, 0, LwMax(1, _ivMac)),
|
|
"_cbstFree is wrong");
|
|
|
|
if (grfobj & fobjAssertFull)
|
|
{
|
|
// it would be nice to assert that no strings overlap each other,
|
|
// but that's hard to do in linear time, so just assert that the
|
|
// grst is the correct size.
|
|
for (cbstFree = cchTot = ibst = 0; ibst < _ivMac; ibst++)
|
|
{
|
|
bst = _Bst(ibst);
|
|
if (bvNil == bst)
|
|
{
|
|
Assert(ibst < _ivMac - 1, "last element free");
|
|
cbstFree++;
|
|
continue;
|
|
}
|
|
AssertIn(bst, 0, _bstMac);
|
|
AssertSt((achar *)_Qb1(bst));
|
|
cchTot += CchTotSt((PST)_Qb1(bst));
|
|
}
|
|
Assert(cchTot * size(achar) == _bstMac, "grst wrong size");
|
|
Assert(cbstFree == _cbstFree || _cbstFree == cvNil && cbstFree == 0,
|
|
"bad _cbstFree");
|
|
}
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Allocate a new string table and ensure that it has space for cstnInit
|
|
strings, totalling cchInit characters.
|
|
***************************************************************************/
|
|
PGST GST::PgstNew(long cbExtra, long cstnInit, long cchInit)
|
|
{
|
|
AssertIn(cbExtra, 0, kcbMax);
|
|
Assert(cbExtra % size(long) == 0, "cbExtra not multiple of size(long)");
|
|
AssertIn(cstnInit, 0, kcbMax);
|
|
AssertIn(cchInit, 0, kcbMax);
|
|
PGST pgst;
|
|
|
|
if ((pgst = NewObj GST(cbExtra)) == pvNil)
|
|
return pvNil;
|
|
if ((cstnInit > 0 || cchInit > 0) &&
|
|
!pgst->FEnsureSpace(cstnInit, cchInit, fgrpNil))
|
|
{
|
|
ReleasePpo(&pgst);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pgst, 0);
|
|
return pgst;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a string table from a block and return it.
|
|
***************************************************************************/
|
|
PGST GST::PgstRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
PGST pgst;
|
|
|
|
if ((pgst = NewObj GST(0)) == pvNil)
|
|
goto LFail;
|
|
if (!pgst->_FRead(pblck, pbo, posk))
|
|
{
|
|
ReleasePpo(&pgst);
|
|
LFail:
|
|
TrashVar(pbo);
|
|
TrashVar(posk);
|
|
return pvNil;
|
|
}
|
|
AssertPo(pgst, 0);
|
|
return pgst;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read a string table from file and return it.
|
|
***************************************************************************/
|
|
PGST GST::PgstRead(PFIL pfil, FP fp, long cb, short *pbo, short *posk)
|
|
{
|
|
BLCK blck(pfil, fp, cb);
|
|
return PgstRead(&blck, pbo, posk);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate this GST.
|
|
***************************************************************************/
|
|
PGST GST::PgstDup(void)
|
|
{
|
|
AssertThis(0);
|
|
PGST pgst;
|
|
|
|
if (pvNil == (pgst = PgstNew(_cbEntry - size(long))))
|
|
return pvNil;
|
|
|
|
if (!_FDup(pgst))
|
|
ReleasePpo(&pgst);
|
|
|
|
AssertNilOrPo(pgst, 0);
|
|
return pgst;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Append a string to the string table.
|
|
***************************************************************************/
|
|
bool GST::FAddRgch(achar *prgch, long cch, void *pvExtra, long *pistn)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cch, 0, kcchMaxGst + 1);
|
|
AssertPvCb(prgch, cch * size(achar));
|
|
AssertNilOrPvCb(pvExtra, _cbEntry - size(long));
|
|
AssertNilOrVarMem(pistn);
|
|
|
|
if (pistn != pvNil)
|
|
*pistn = _ivMac;
|
|
if (!FInsertRgch(_ivMac, prgch, cch, pvExtra))
|
|
{
|
|
TrashVar(pistn);
|
|
return fFalse;
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Find the given string and put its location in *pistn. If it's not
|
|
there, fill *pistn with where it would be. If fgstSorted or fgstUserSorted
|
|
is passed in, this does a binary search for the string; otherwise it
|
|
does a linear search.
|
|
***************************************************************************/
|
|
bool GST::FFindRgch(achar *prgch, long cch, long *pistn, ulong grfgst)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(cch, 0, kcchMaxGst);
|
|
AssertPvCb(prgch, cch * size(achar));
|
|
AssertVarMem(pistn);
|
|
|
|
if (!(grfgst & (fgstSorted | fgstUserSorted)))
|
|
return GSTB::FFindRgch(prgch, cch, pistn, grfgst);
|
|
|
|
//the table should be sorted, so do a binary search
|
|
long ivMin, ivLim, iv;
|
|
ulong fcmp;
|
|
achar *qst;
|
|
|
|
for (ivMin = 0, ivLim = _ivMac; ivMin < ivLim; )
|
|
{
|
|
iv = (ivMin + ivLim) / 2;
|
|
qst = _Qst(iv);
|
|
if (grfgst & fgstUserSorted)
|
|
fcmp = FcmpCompareUserRgch(prgch, cch, PrgchSt(qst), CchSt(qst));
|
|
else
|
|
fcmp = FcmpCompareRgch(prgch, cch, PrgchSt(qst), CchSt(qst));
|
|
|
|
if (fcmp == fcmpLt)
|
|
ivLim = iv;
|
|
else if (fcmp == fcmpGt)
|
|
ivMin = iv + 1;
|
|
else
|
|
{
|
|
Assert(fcmp == fcmpEq, "bad fcmp");
|
|
*pistn = iv;
|
|
return fTrue;
|
|
}
|
|
}
|
|
// this is where it would be
|
|
*pistn = ivMin;
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Insert a new entry into the string text.
|
|
***************************************************************************/
|
|
bool GST::FInsertRgch(long istn, achar *prgch, long cch, void *pvExtra)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(istn, 0, _ivMac + 1);
|
|
AssertIn(cch, 0, kcchMaxGst + 1);
|
|
AssertPvCb(prgch, cch * size(achar));
|
|
AssertNilOrPvCb(pvExtra, _cbEntry - size(long));
|
|
|
|
byte *qb;
|
|
|
|
if (!_FEnsureSizes(_bstMac + (cch + 1) * size(achar),
|
|
LwMul(_ivMac + 1, _cbEntry), fgrpNil))
|
|
{
|
|
return fFalse;
|
|
}
|
|
|
|
// make room for the entry
|
|
qb = (byte *)_Qbst(istn);
|
|
if (istn < _ivMac)
|
|
BltPb(qb, qb + _cbEntry, LwMul(_ivMac - istn, _cbEntry));
|
|
*(long *)qb = _bstMac;
|
|
if (size(long) < _cbEntry)
|
|
{
|
|
qb += size(long);
|
|
if (pvExtra != pvNil)
|
|
CopyPb(pvExtra, qb, _cbEntry - size(long));
|
|
else
|
|
TrashPvCb(qb, _cbEntry - size(long));
|
|
}
|
|
else
|
|
Assert(pvNil == pvExtra, "cbExtra is zero");
|
|
|
|
_ivMac++;
|
|
// put the string in
|
|
_AppendRgch(prgch, cch);
|
|
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Insert an stn into the string table
|
|
***************************************************************************/
|
|
bool GST::FInsertStn(long istn, PSTN pstn, void *pvExtra)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(istn, 0, _ivMac + 1);
|
|
AssertPo(pstn, 0);
|
|
AssertNilOrPvCb(pvExtra, _cbEntry - size(long));
|
|
|
|
return FInsertRgch(istn, pstn->Prgch(), pstn->Cch(), pvExtra);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete the string at location istn.
|
|
***************************************************************************/
|
|
void GST::Delete(long istn)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(istn, 0, _ivMac);
|
|
|
|
byte *qb;
|
|
long bst;
|
|
|
|
qb = (byte *)_Qbst(istn);
|
|
bst = *(long *)qb;
|
|
if (istn < --_ivMac)
|
|
BltPb(qb + _cbEntry, qb, LwMul(_ivMac - istn, _cbEntry));
|
|
TrashPvCb(_Qbst(_ivMac), _cbEntry);
|
|
_RemoveSt(bst);
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Move the entry at ivSrc to be immediately before the element that is
|
|
currently at ivTarget. If ivTarget > ivSrc, the entry actually ends
|
|
up at (ivTarget - 1) and the entry at ivTarget doesn't move. If
|
|
ivTarget < ivSrc, the entry ends up at ivTarget and the entry at
|
|
ivTarget moves to (ivTarget + 1). Everything in between is shifted
|
|
appropriately. ivTarget is allowed to be equal to IvMac().
|
|
***************************************************************************/
|
|
void GST::Move(long ivSrc, long ivTarget)
|
|
{
|
|
AssertThis(0);
|
|
AssertIn(ivSrc, 0, _ivMac);
|
|
AssertIn(ivTarget, 0, _ivMac + 1);
|
|
|
|
MoveElement(_Qbst(0), _cbEntry, ivSrc, ivTarget);
|
|
AssertThis(0);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Validate a string table.
|
|
***************************************************************************/
|
|
void GST::AssertValid(ulong grfobj)
|
|
{
|
|
GST_PAR::AssertValid(grfobj);
|
|
AssertVar(_cbstFree == cvNil, "bad _cbstFree in GST", &_cbstFree);
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
/***************************************************************************
|
|
Allocate a new allocated string table and ensure that it has space for
|
|
cstnInit strings, totalling cchInit characters.
|
|
***************************************************************************/
|
|
PAST AST::PastNew(long cbExtra, long cstnInit, long cchInit)
|
|
{
|
|
AssertIn(cbExtra, 0, kcbMax);
|
|
Assert(cbExtra % size(long) == 0, "cbExtra not multiple of size(long)");
|
|
AssertIn(cstnInit, 0, kcbMax);
|
|
AssertIn(cchInit, 0, kcbMax);
|
|
PAST past;
|
|
|
|
if ((past = NewObj AST(cbExtra)) == pvNil)
|
|
return pvNil;
|
|
if ((cstnInit > 0 || cchInit > 0) &&
|
|
!past->FEnsureSpace(cstnInit, cchInit, fgrpNil))
|
|
{
|
|
ReleasePpo(&past);
|
|
return pvNil;
|
|
}
|
|
AssertPo(past, 0);
|
|
return past;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read an allocated string table from a block and return it.
|
|
***************************************************************************/
|
|
PAST AST::PastRead(PBLCK pblck, short *pbo, short *posk)
|
|
{
|
|
AssertPo(pblck, 0);
|
|
AssertNilOrVarMem(pbo);
|
|
AssertNilOrVarMem(posk);
|
|
|
|
PAST past;
|
|
|
|
if ((past = NewObj AST(0)) == pvNil)
|
|
goto LFail;
|
|
if (!past->_FRead(pblck, pbo, posk))
|
|
{
|
|
ReleasePpo(&past);
|
|
LFail:
|
|
TrashVar(pbo);
|
|
TrashVar(posk);
|
|
return pvNil;
|
|
}
|
|
AssertPo(past, 0);
|
|
return past;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Read an allocated string table from file and return it.
|
|
***************************************************************************/
|
|
PAST AST::PastRead(PFIL pfil, FP fp, long cb, short *pbo, short *posk)
|
|
{
|
|
BLCK blck;
|
|
return PastRead(&blck, pbo, posk);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Duplicate this AST.
|
|
***************************************************************************/
|
|
PAST AST::PastDup(void)
|
|
{
|
|
AssertThis(0);
|
|
PAST past;
|
|
|
|
if (pvNil == (past = PastNew(_cbEntry - size(long))))
|
|
return pvNil;
|
|
|
|
if (!_FDup(past))
|
|
ReleasePpo(&past);
|
|
|
|
AssertNilOrPo(past, 0);
|
|
return past;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Append a string to the allocated string table.
|
|
***************************************************************************/
|
|
bool AST::FAddRgch(achar *prgch, long cch, void *pvExtra, long *pistn)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(cch, 0, kcchMaxGst + 1);
|
|
AssertPvCb(prgch, cch * size(achar));
|
|
AssertNilOrPvCb(pvExtra, _cbEntry - size(long));
|
|
AssertNilOrVarMem(pistn);
|
|
|
|
long ibst;
|
|
byte *qb;
|
|
|
|
if (_cbstFree > 0)
|
|
{
|
|
/* find the first free bst */
|
|
qb = _Qb2(0);
|
|
for (ibst = 0; ibst < _ivMac; ibst++, qb += _cbEntry)
|
|
{
|
|
if (*(long *)qb == bvNil)
|
|
break;
|
|
}
|
|
Assert(ibst < _ivMac - 1, "_cbstFree is wrong");
|
|
_cbstFree--;
|
|
}
|
|
else
|
|
ibst = _ivMac++;
|
|
|
|
if (!_FEnsureSizes(_bstMac + (cch + 1) * size(achar),
|
|
LwMul(_ivMac, _cbEntry), fgrpNil))
|
|
{
|
|
if (ibst == _ivMac - 1)
|
|
_ivMac--;
|
|
else
|
|
_cbstFree++;
|
|
TrashVar(pistn);
|
|
return fFalse;
|
|
}
|
|
|
|
// fill in the bst and extra data
|
|
qb = (byte *)_Qbst(ibst);
|
|
*(long *)qb = _bstMac;
|
|
if (size(long) < _cbEntry)
|
|
{
|
|
qb += size(long);
|
|
if (pvExtra != pvNil)
|
|
CopyPb(pvExtra, qb, _cbEntry - size(long));
|
|
else
|
|
TrashPvCb(qb, _cbEntry - size(long));
|
|
}
|
|
else
|
|
Assert(pvNil == pvExtra, "cbExtra is zero");
|
|
|
|
// put the string in
|
|
_AppendRgch(prgch, cch);
|
|
|
|
if (pvNil != pistn)
|
|
*pistn = ibst;
|
|
AssertThis(fobjAssertFull);
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Delete the string at location istn.
|
|
***************************************************************************/
|
|
void AST::Delete(long istn)
|
|
{
|
|
AssertThis(fobjAssertFull);
|
|
AssertIn(istn, 0, _ivMac);
|
|
Assert(!FFree(istn), "entry already free!");
|
|
|
|
byte *qb;
|
|
long bst;
|
|
|
|
qb = (byte *)_Qbst(istn);
|
|
bst = *(long *)qb;
|
|
|
|
if (istn == _ivMac - 1)
|
|
{
|
|
//move _ivMac back past any free entries on the end
|
|
while (--_ivMac > 0 && *(long *)(qb -= _cbEntry) == bvNil)
|
|
_cbstFree--;
|
|
TrashPvCb(_Qbst(_ivMac), LwMul(istn - _ivMac + 1, _cbEntry));
|
|
}
|
|
else
|
|
{
|
|
*(long *)qb = bvNil;
|
|
TrashPvCb(qb + size(long), _cbEntry - size(long));
|
|
_cbstFree++;
|
|
}
|
|
_RemoveSt(bst);
|
|
AssertThis(fobjAssertFull);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/***************************************************************************
|
|
Validate a string table.
|
|
***************************************************************************/
|
|
void AST::AssertValid(ulong grfobj)
|
|
{
|
|
AST_PAR::AssertValid(grfobj);
|
|
AssertIn(_cbstFree, 0, LwMax(1, _ivMac));
|
|
}
|
|
#endif //DEBUG
|
|
|
|
|
|
|