Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _MDB_
# include "mdb.h"
#endif
#ifndef _MORK_
# include "mork.h"
#endif
#ifndef _MORKBLOB_
# include "morkBlob.h"
#endif
#ifndef _MORKNODE_
# include "morkNode.h"
#endif
#ifndef _MORKENV_
# include "morkEnv.h"
#endif
#ifndef _MORKARRAY_
# include "morkWriter.h"
#endif
// #ifndef _MORKFILE_
// #include "morkFile.h"
// #endif
#ifndef _MORKSTREAM_
# include "morkStream.h"
#endif
#ifndef _MORKSTORE_
# include "morkStore.h"
#endif
#ifndef _MORKATOMSPACE_
# include "morkAtomSpace.h"
#endif
#ifndef _MORKROWSPACE_
# include "morkRowSpace.h"
#endif
#ifndef _MORKROWMAP_
# include "morkRowMap.h"
#endif
#ifndef _MORKATOMMAP_
# include "morkAtomMap.h"
#endif
#ifndef _MORKROW_
# include "morkRow.h"
#endif
#ifndef _MORKTABLE_
# include "morkTable.h"
#endif
#ifndef _MORKCELL_
# include "morkCell.h"
#endif
#ifndef _MORKATOM_
# include "morkAtom.h"
#endif
#ifndef _MORKCH_
# include "morkCh.h"
#endif
// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
// ````` ````` ````` ````` `````
// { ===== begin morkNode interface =====
/*public virtual*/ void morkWriter::CloseMorkNode(
morkEnv* ev) // CloseTable() only if open
{
if (this->IsOpenNode()) {
this->MarkClosing();
this->CloseWriter(ev);
this->MarkShut();
}
}
/*public virtual*/
morkWriter::~morkWriter() // assert CloseTable() executed earlier
{
MORK_ASSERT(this->IsShutNode());
MORK_ASSERT(mWriter_Store == 0);
}
/*public non-poly*/
morkWriter::morkWriter(morkEnv* ev, const morkUsage& inUsage,
nsIMdbHeap* ioHeap, morkStore* ioStore,
nsIMdbFile* ioFile, nsIMdbHeap* ioSlotHeap)
: morkNode(ev, inUsage, ioHeap),
mWriter_Store(0),
mWriter_File(0),
mWriter_Bud(0),
mWriter_Stream(0),
mWriter_SlotHeap(0)
,
mWriter_CommitGroupIdentity(0) // see mStore_CommitGroupIdentity
,
mWriter_GroupBufFill(0)
,
mWriter_TotalCount(morkWriter_kCountNumberOfPhases),
mWriter_DoneCount(0)
,
mWriter_LineSize(0),
mWriter_MaxIndent(morkWriter_kMaxIndent),
mWriter_MaxLine(morkWriter_kMaxLine)
,
mWriter_TableForm(0),
mWriter_TableAtomScope('v'),
mWriter_TableRowScope(0),
mWriter_TableKind(0)
,
mWriter_RowForm(0),
mWriter_RowAtomScope(0),
mWriter_RowScope(0)
,
mWriter_DictForm(0),
mWriter_DictAtomScope('v')
,
mWriter_NeedDirtyAll(morkBool_kFalse),
mWriter_Incremental(morkBool_kTrue) // opposite of mWriter_NeedDirtyAll
,
mWriter_DidStartDict(morkBool_kFalse),
mWriter_DidEndDict(morkBool_kTrue)
,
mWriter_SuppressDirtyRowNewline(morkBool_kFalse),
mWriter_DidStartGroup(morkBool_kFalse),
mWriter_DidEndGroup(morkBool_kTrue),
mWriter_Phase(morkWriter_kPhaseNothingDone)
,
mWriter_BeVerbose(ev->mEnv_BeVerbose)
,
mWriter_TableRowArrayPos(0)
// empty constructors for map iterators:
,
mWriter_StoreAtomSpacesIter(),
mWriter_AtomSpaceAtomAidsIter()
,
mWriter_StoreRowSpacesIter(),
mWriter_RowSpaceTablesIter(),
mWriter_RowSpaceRowsIter() {
mWriter_GroupBuf[0] = 0;
mWriter_SafeNameBuf[0] = 0;
mWriter_SafeNameBuf[morkWriter_kMaxColumnNameSize * 2] = 0;
mWriter_ColNameBuf[0] = 0;
mWriter_ColNameBuf[morkWriter_kMaxColumnNameSize] = 0;
mdbYarn* y = &mWriter_ColYarn;
y->mYarn_Buf = mWriter_ColNameBuf; // where to put col bytes
y->mYarn_Fill = 0; // set later by writer
y->mYarn_Size = morkWriter_kMaxColumnNameSize; // our buf size
y->mYarn_More = 0; // set later by writer
y->mYarn_Form = 0; // set later by writer
y->mYarn_Grow = 0; // do not allow buffer growth
y = &mWriter_SafeYarn;
y->mYarn_Buf = mWriter_SafeNameBuf; // where to put col bytes
y->mYarn_Fill = 0; // set later by writer
y->mYarn_Size = morkWriter_kMaxColumnNameSize * 2; // our buf size
y->mYarn_More = 0; // set later by writer
y->mYarn_Form = 0; // set later by writer
y->mYarn_Grow = 0; // do not allow buffer growth
if (ev->Good()) {
if (ioSlotHeap && ioFile && ioStore) {
morkStore::SlotWeakStore(ioStore, ev, &mWriter_Store);
nsIMdbFile_SlotStrongFile(ioFile, ev, &mWriter_File);
nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mWriter_SlotHeap);
if (ev->Good()) {
mNode_Derived = morkDerived_kWriter;
}
} else
ev->NilPointerError();
}
}
void morkWriter::MakeWriterStream(morkEnv* ev) // give writer a suitable stream
{
mWriter_Incremental = !mWriter_NeedDirtyAll; // opposites
if (!mWriter_Stream && ev->Good()) {
if (mWriter_File) {
morkStream* stream = 0;
mork_bool frozen = morkBool_kFalse; // need to modify
nsIMdbHeap* heap = mWriter_SlotHeap;
if (mWriter_Incremental) {
stream =
new (*heap, ev) morkStream(ev, morkUsage::kHeap, heap, mWriter_File,
morkWriter_kStreamBufSize, frozen);
} else // compress commit
{
nsIMdbFile* bud = 0;
mWriter_File->AcquireBud(ev->AsMdbEnv(), heap, &bud);
if (bud) {
if (ev->Good()) {
mWriter_Bud = bud;
stream =
new (*heap, ev) morkStream(ev, morkUsage::kHeap, heap, bud,
morkWriter_kStreamBufSize, frozen);
} else
bud->Release();
}
}
if (stream) {
if (ev->Good())
mWriter_Stream = stream;
else
stream->CutStrongRef(ev->AsMdbEnv());
}
} else
this->NilWriterFileError(ev);
}
}
/*public non-poly*/ void morkWriter::CloseWriter(
morkEnv* ev) // called by CloseMorkNode();
{
if (this->IsNode()) {
morkStore::SlotWeakStore((morkStore*)0, ev, &mWriter_Store);
nsIMdbFile_SlotStrongFile((nsIMdbFile*)0, ev, &mWriter_File);
nsIMdbFile_SlotStrongFile((nsIMdbFile*)0, ev, &mWriter_Bud);
morkStream::SlotStrongStream((morkStream*)0, ev, &mWriter_Stream);
nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*)0, ev, &mWriter_SlotHeap);
this->MarkShut();
} else
this->NonNodeError(ev);
}
// } ===== end morkNode methods =====
// ````` ````` ````` ````` `````
/*static*/ void morkWriter::NonWriterTypeError(morkEnv* ev) {
ev->NewError("non morkWriter");
}
/*static*/ void morkWriter::NilWriterStoreError(morkEnv* ev) {
ev->NewError("nil mWriter_Store");
}
/*static*/ void morkWriter::NilWriterBudError(morkEnv* ev) {
ev->NewError("nil mWriter_Bud");
}
/*static*/ void morkWriter::NilWriterFileError(morkEnv* ev) {
ev->NewError("nil mWriter_File");
}
/*static*/ void morkWriter::NilWriterStreamError(morkEnv* ev) {
ev->NewError("nil mWriter_Stream");
}
/*static*/ void morkWriter::UnsupportedPhaseError(morkEnv* ev) {
ev->NewError("unsupported mWriter_Phase");
}
mork_bool morkWriter::WriteMore(
morkEnv* ev) // call until IsWritingDone() is true
{
if (this->IsOpenNode()) {
if (this->IsWriter()) {
if (!mWriter_Stream) this->MakeWriterStream(ev);
if (mWriter_Stream) {
if (ev->Bad()) {
ev->NewWarning("writing stops on error");
mWriter_Phase = morkWriter_kPhaseWritingDone;
}
switch (mWriter_Phase) {
case morkWriter_kPhaseNothingDone:
OnNothingDone(ev);
break;
case morkWriter_kPhaseDirtyAllDone:
OnDirtyAllDone(ev);
break;
case morkWriter_kPhasePutHeaderDone:
OnPutHeaderDone(ev);
break;
case morkWriter_kPhaseRenumberAllDone:
OnRenumberAllDone(ev);
break;
case morkWriter_kPhaseStoreAtomSpaces:
OnStoreAtomSpaces(ev);
break;
case morkWriter_kPhaseAtomSpaceAtomAids:
OnAtomSpaceAtomAids(ev);
break;
case morkWriter_kPhaseStoreRowSpacesTables:
OnStoreRowSpacesTables(ev);
break;
case morkWriter_kPhaseRowSpaceTables:
OnRowSpaceTables(ev);
break;
case morkWriter_kPhaseTableRowArray:
OnTableRowArray(ev);
break;
case morkWriter_kPhaseStoreRowSpacesRows:
OnStoreRowSpacesRows(ev);
break;
case morkWriter_kPhaseRowSpaceRows:
OnRowSpaceRows(ev);
break;
case morkWriter_kPhaseContentDone:
OnContentDone(ev);
break;
case morkWriter_kPhaseWritingDone:
OnWritingDone(ev);
break;
default:
this->UnsupportedPhaseError(ev);
}
} else
this->NilWriterStreamError(ev);
} else
this->NonWriterTypeError(ev);
} else
this->NonOpenNodeError(ev);
return ev->Good();
}
static const char morkWriter_kHexDigits[] = "0123456789ABCDEF";
mork_size morkWriter::WriteYarn(morkEnv* ev, const mdbYarn* inYarn)
// return number of atom bytes written on the current line (which
// implies that escaped line breaks will make the size value smaller
// than the entire yarn's size, since only part goes on a last line).
{
mork_size outSize = 0;
mork_size lineSize = mWriter_LineSize;
morkStream* stream = mWriter_Stream;
const mork_u1* b = (const mork_u1*)inYarn->mYarn_Buf;
if (b) {
int c;
mork_fill fill = inYarn->mYarn_Fill;
const mork_u1* end = b + fill;
while (b < end && ev->Good()) {
if (lineSize + outSize >= mWriter_MaxLine) // continue line?
{
stream->PutByteThenNewline(ev, '\\');
mWriter_LineSize = lineSize = outSize = 0;
}
c = *b++; // next byte to print
if (morkCh_IsValue(c)) {
stream->Putc(ev, c);
++outSize; // c
} else if (c == ')' || c == '$' || c == '\\') {
stream->Putc(ev, '\\');
stream->Putc(ev, c);
outSize += 2; // '\' c
} else {
outSize += 3; // '$' hex hex
stream->Putc(ev, '$');
stream->Putc(ev, morkWriter_kHexDigits[(c >> 4) & 0x0F]);
stream->Putc(ev, morkWriter_kHexDigits[c & 0x0F]);
}
}
}
mWriter_LineSize += outSize;
return outSize;
}
mork_size morkWriter::WriteAtom(morkEnv* ev, const morkAtom* inAtom)
// return number of atom bytes written on the current line (which
// implies that escaped line breaks will make the size value smaller
// than the entire atom's size, since only part goes on a last line).
{
mork_size outSize = 0;
mdbYarn yarn; // to ref content inside atom
if (morkAtom::AliasYarn(inAtom, &yarn)) {
if (mWriter_DidStartDict && yarn.mYarn_Form != mWriter_DictForm)
this->ChangeDictForm(ev, yarn.mYarn_Form);
outSize = this->WriteYarn(ev, &yarn);
// mWriter_LineSize += stream->Write(ev, inYarn->mYarn_Buf, outSize);
} else
inAtom->BadAtomKindError(ev);
return outSize;
}
void morkWriter::WriteAtomSpaceAsDict(morkEnv* ev, morkAtomSpace* ioSpace) {
morkStream* stream = mWriter_Stream;
nsIMdbEnv* mdbev = ev->AsMdbEnv();
mork_scope scope = ioSpace->SpaceScope();
if (scope < 0x80) {
if (mWriter_LineSize) stream->PutLineBreak(ev);
stream->PutString(ev, "< <(a=");
stream->Putc(ev, (int)scope);
++mWriter_LineSize;
stream->PutString(ev, ")> // (f=iso-8859-1)");
mWriter_LineSize = stream->PutIndent(ev, morkWriter_kDictAliasDepth);
} else
ioSpace->NonAsciiSpaceScopeName(ev);
if (ev->Good()) {
mdbYarn yarn; // to ref content inside atom
char buf[64]; // buffer for staging the dict alias hex ID
char* idBuf = buf + 1; // where the id always starts
buf[0] = '('; // we always start with open paren
morkBookAtom* atom = 0;
morkAtomAidMapIter* ai = &mWriter_AtomSpaceAtomAidsIter;
ai->InitAtomAidMapIter(ev, &ioSpace->mAtomSpace_AtomAids);
mork_change* c = 0;
for (c = ai->FirstAtom(ev, &atom); c && ev->Good();
c = ai->NextAtom(ev, &atom)) {
if (atom) {
if (atom->IsAtomDirty()) {
atom->SetAtomClean(); // neutralize change
morkAtom::AliasYarn(atom, &yarn);
mork_size size = ev->TokenAsHex(idBuf, atom->mBookAtom_Id);
if (yarn.mYarn_Form != mWriter_DictForm)
this->ChangeDictForm(ev, yarn.mYarn_Form);
mork_size pending =
yarn.mYarn_Fill + size + morkWriter_kYarnEscapeSlop + 4;
this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
mork_size bytesWritten;
nsresult rc =
stream->Write(mdbev, buf, size + 1, &bytesWritten); // + '('
if (NS_FAILED(rc)) {
NS_WARNING("Write failed");
}
mWriter_LineSize += bytesWritten;
pending -= (size + 1);
this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasValueDepth);
// Putc sets an error in morkEnv object if something goes wrong.
stream->Putc(ev, '='); // start alias
++mWriter_LineSize;
this->WriteYarn(ev, &yarn);
stream->Putc(ev, ')'); // end alias
++mWriter_LineSize;
++mWriter_DoneCount;
}
} else
ev->NilPointerError();
}
ai->CloseMapIter(ev);
}
if (ev->Good()) {
ioSpace->SetAtomSpaceClean();
// this->IndentAsNeeded(ev, 0);
// stream->PutByteThenNewline(ev, '>'); // end dict
stream->Putc(ev, '>'); // end dict
++mWriter_LineSize;
}
}
/*
(I'm putting the text of this message in file morkWriter.cpp.)
I'm making a change which should cause rows and tables to go away
when a Mork db is compress committed, when the rows and tables
are no longer needed. Because this is subtle, I'm describing it
here in case misbehavior is ever observed. Otherwise you'll have
almost no hope of fixing a related bug.
This is done entirely in morkWriter.cpp: morkWriter::DirtyAll(),
which currently marks all rows and tables dirty so they will be
written in a later phase of the commit. My change is to merely
selectively not mark certain rows and tables dirty, when they seem
to be superfluous.
A row is no longer needed when the mRow_GcUses slot hits zero, and
this is used by the following inline morkRow method:
mork_bool IsRowUsed() const { return mRow_GcUses != 0; }
Naturally disaster ensues if mRow_GcUses is ever smaller than right.
Similarly, we should drop tables when mTable_GcUses hits zero, but
only when a table contains no row members. We consider tables to
self reference (and prevent collection) when they contain content.
Again, disaster ensues if mTable_GcUses is ever smaller than right.
mork_count GetRowCount() const
{ return mTable_RowArray.mArray_Fill; }
mork_bool IsTableUsed() const
{ return (mTable_GcUses != 0 || this->GetRowCount() != 0); }
Now let's question why the design involves filtering what gets set
to dirty. Why not apply a filter in the later phase when we write
content? Because I'm afraid of missing some subtle interaction in
updating table and row relationships. It seems safer to write a row
or table when it starts out dirty, before morkWriter::DirtyAll() is
called. So this design calls for writing out rows and tables when
they are still clearly used, and additionally, <i>when we have just
been actively writing to them right before this commit</i>.
Presumably if they are truly useless, they will no longer be dirtied
in later sessions and will get collected during the next compress
commit. So we wait to collect them until they become all dead, and
not just mostly dead. (At which time you can feel free to go through
their pockets looking for loose change.)
*/
mork_bool morkWriter::DirtyAll(morkEnv* ev)
// DirtyAll() visits every store sub-object and marks
// them dirty, including every table, row, cell, and atom. The return
// equals ev->Good(), to show whether any error happened. This method is
// intended for use in the beginning of a "compress commit" which writes
// all store content, whether dirty or not. We dirty everything first so
// that later iterations over content can mark things clean as they are
// written, and organize the process of serialization so that objects are
// written only at need (because of being dirty). Note the method can
// stop early when any error happens, since this will abort any commit.
{
morkStore* store = mWriter_Store;
if (store) {
store->SetStoreDirty();
mork_change* c = 0;
if (ev->Good()) {
morkAtomSpaceMapIter* asi = &mWriter_StoreAtomSpacesIter;
asi->InitAtomSpaceMapIter(ev, &store->mStore_AtomSpaces);
mork_scope* key = 0; // ignore keys in map
morkAtomSpace* space = 0; // old val node in the map
for (c = asi->FirstAtomSpace(ev, key, &space); c && ev->Good();
c = asi->NextAtomSpace(ev, key, &space)) {
if (space) {
if (space->IsAtomSpace()) {
space->SetAtomSpaceDirty();
morkBookAtom* atom = 0;
morkAtomAidMapIter* ai = &mWriter_AtomSpaceAtomAidsIter;
ai->InitAtomAidMapIter(ev, &space->mAtomSpace_AtomAids);
for (c = ai->FirstAtom(ev, &atom); c && ev->Good();
c = ai->NextAtom(ev, &atom)) {
if (atom) {
atom->SetAtomDirty();
++mWriter_TotalCount;
} else
ev->NilPointerError();
}
ai->CloseMapIter(ev);
} else
space->NonAtomSpaceTypeError(ev);
} else
ev->NilPointerError();
}
}
if (ev->Good()) {
morkRowSpaceMapIter* rsi = &mWriter_StoreRowSpacesIter;
rsi->InitRowSpaceMapIter(ev, &store->mStore_RowSpaces);
mork_scope* key = 0; // ignore keys in map
morkRowSpace* space = 0; // old val node in the map
for (c = rsi->FirstRowSpace(ev, key, &space); c && ev->Good();
c = rsi->NextRowSpace(ev, key, &space)) {
if (space) {
if (space->IsRowSpace()) {
space->SetRowSpaceDirty();
if (ev->Good()) {
#ifdef MORK_ENABLE_PROBE_MAPS
morkRowProbeMapIter* ri = &mWriter_RowSpaceRowsIter;
#else /*MORK_ENABLE_PROBE_MAPS*/
morkRowMapIter* ri = &mWriter_RowSpaceRowsIter;
#endif /*MORK_ENABLE_PROBE_MAPS*/
ri->InitRowMapIter(ev, &space->mRowSpace_Rows);
morkRow* row = 0; // old key row in the map
for (c = ri->FirstRow(ev, &row); c && ev->Good();
c = ri->NextRow(ev, &row)) {
if (row && row->IsRow()) // need to dirty row?
{
if (row->IsRowUsed() || row->IsRowDirty()) {
row->DirtyAllRowContent(ev);
++mWriter_TotalCount;
}
} else
row->NonRowTypeWarning(ev);
}
ri->CloseMapIter(ev);
}
if (ev->Good()) {
morkTableMapIter* ti = &mWriter_RowSpaceTablesIter;
ti->InitTableMapIter(ev, &space->mRowSpace_Tables);
#ifdef MORK_BEAD_OVER_NODE_MAPS
morkTable* table = ti->FirstTable(ev);
for (; table && ev->Good(); table = ti->NextTable(ev))
#else /*MORK_BEAD_OVER_NODE_MAPS*/
mork_tid* tableKey = 0; // ignore keys in table map
morkTable* table = 0; // old key row in the map
for (c = ti->FirstTable(ev, tableKey, &table); c && ev->Good();
c = ti->NextTable(ev, tableKey, &table))
#endif /*MORK_BEAD_OVER_NODE_MAPS*/
{
if (table && table->IsTable()) // need to dirty table?
{
if (table->IsTableUsed() || table->IsTableDirty()) {
// table->DirtyAllTableContent(ev);
// only necessary to mark table itself dirty:
table->SetTableDirty();
table->SetTableRewrite();
++mWriter_TotalCount;
}
} else
table->NonTableTypeWarning(ev);
}
ti->CloseMapIter(ev);
}
} else
space->NonRowSpaceTypeError(ev);
} else
ev->NilPointerError();
}
}
} else
this->NilWriterStoreError(ev);
return ev->Good();
}
mork_bool morkWriter::OnNothingDone(morkEnv* ev) {
mWriter_Incremental = !mWriter_NeedDirtyAll; // opposites
if (!mWriter_Store->IsStoreDirty() && !mWriter_NeedDirtyAll) {
mWriter_Phase = morkWriter_kPhaseWritingDone;
return morkBool_kTrue;
}
// morkStream* stream = mWriter_Stream;
if (mWriter_NeedDirtyAll) this->DirtyAll(ev);
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseDirtyAllDone;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::StartGroup(morkEnv* ev) {
nsIMdbEnv* mdbev = ev->AsMdbEnv();
morkStream* stream = mWriter_Stream;
mWriter_DidStartGroup = morkBool_kTrue;
mWriter_DidEndGroup = morkBool_kFalse;
char buf[4 + morkWriter_kGroupBufSize + 2]; // "@$${" + groupid + "{@"
char* p = buf;
*p++ = '@';
*p++ = '$';
*p++ = '$';
*p++ = '{';
mork_token groupID = mWriter_CommitGroupIdentity;
mork_fill idFill = ev->TokenAsHex(p, groupID);
mWriter_GroupBufFill = 0;
// ev->TokenAsHex(mWriter_GroupBuf, groupID);
if (idFill < morkWriter_kGroupBufSize) {
// TokenAsHex appends a '\0', but it's not included in idFill count.
MORK_MEMCPY(mWriter_GroupBuf, p, idFill + 1);
mWriter_GroupBufFill = idFill;
} else {
*mWriter_GroupBuf = '\0';
}
p += idFill;
*p++ = '{';
*p++ = '@';
stream->PutLineBreak(ev);
morkStore* store = mWriter_Store;
if (store) // might need to capture commit group position?
{
mork_pos groupPos;
stream->Tell(mdbev, &groupPos);
if (!store->mStore_FirstCommitGroupPos)
store->mStore_FirstCommitGroupPos = groupPos;
else if (!store->mStore_SecondCommitGroupPos)
store->mStore_SecondCommitGroupPos = groupPos;
}
mork_size bytesWritten;
stream->Write(mdbev, buf, 4 + idFill + 2,
&bytesWritten); // '@$${' + idFill + '{@'
stream->PutLineBreak(ev);
mWriter_LineSize = 0;
return ev->Good();
}
mork_bool morkWriter::CommitGroup(morkEnv* ev) {
if (mWriter_DidStartGroup) {
nsIMdbEnv* mdbev = ev->AsMdbEnv();
mork_size bytesWritten;
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
stream->Putc(ev, '@');
stream->Putc(ev, '$');
stream->Putc(ev, '$');
stream->Putc(ev, '}');
mork_fill bufFill = mWriter_GroupBufFill;
if (bufFill) stream->Write(mdbev, mWriter_GroupBuf, bufFill, &bytesWritten);
stream->Putc(ev, '}');
stream->Putc(ev, '@');
stream->PutLineBreak(ev);
mWriter_LineSize = 0;
}
mWriter_DidStartGroup = morkBool_kFalse;
mWriter_DidEndGroup = morkBool_kTrue;
return ev->Good();
}
mork_bool morkWriter::AbortGroup(morkEnv* ev) {
if (mWriter_DidStartGroup) {
morkStream* stream = mWriter_Stream;
stream->PutLineBreak(ev);
stream->PutStringThenNewline(ev, "@$$}~~}@");
mWriter_LineSize = 0;
}
mWriter_DidStartGroup = morkBool_kFalse;
mWriter_DidEndGroup = morkBool_kTrue;
return ev->Good();
}
mork_bool morkWriter::OnDirtyAllDone(morkEnv* ev) {
if (ev->Good()) {
nsIMdbEnv* mdbev = ev->AsMdbEnv();
morkStream* stream = mWriter_Stream;
mork_pos resultPos;
if (mWriter_NeedDirtyAll) // compress commit
{
stream->Seek(mdbev, 0, &resultPos); // beginning of stream
stream->PutStringThenNewline(ev, morkWriter_kFileHeader);
mWriter_LineSize = 0;
} else // else mWriter_Incremental
{
mork_pos eos = stream->Length(ev); // length is end of stream
if (ev->Good()) {
stream->Seek(mdbev, eos, &resultPos); // goto end of stream
if (eos < 128) // maybe need file header?
{
stream->PutStringThenNewline(ev, morkWriter_kFileHeader);
mWriter_LineSize = 0;
}
this->StartGroup(ev); // begin incremental transaction
}
}
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhasePutHeaderDone;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnPutHeaderDone(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnPutHeaderDone()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
morkStore* store = mWriter_Store;
if (store)
store->RenumberAllCollectableContent(ev);
else
this->NilWriterStoreError(ev);
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseRenumberAllDone;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnRenumberAllDone(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnRenumberAllDone()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseStoreAtomSpaces;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnStoreAtomSpaces(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnStoreAtomSpaces()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
if (ev->Good()) {
morkStore* store = mWriter_Store;
if (store) {
morkAtomSpace* space = store->LazyGetGroundColumnSpace(ev);
if (space && space->IsAtomSpaceDirty()) {
// stream->PutStringThenNewline(ev, "// ground column space dict:");
if (mWriter_LineSize) {
stream->PutLineBreak(ev);
mWriter_LineSize = 0;
}
this->WriteAtomSpaceAsDict(ev, space);
space->SetAtomSpaceClean();
}
} else
this->NilWriterStoreError(ev);
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseStoreRowSpacesTables;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnAtomSpaceAtomAids(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnAtomSpaceAtomAids()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseStoreRowSpacesTables;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
void morkWriter::WriteAllStoreTables(morkEnv* ev) {
morkStore* store = mWriter_Store;
if (store && ev->Good()) {
morkRowSpaceMapIter* rsi = &mWriter_StoreRowSpacesIter;
rsi->InitRowSpaceMapIter(ev, &store->mStore_RowSpaces);
mork_scope* key = 0; // ignore keys in map
morkRowSpace* space = 0; // old val node in the map
mork_change* c = 0;
for (c = rsi->FirstRowSpace(ev, key, &space); c && ev->Good();
c = rsi->NextRowSpace(ev, key, &space)) {
if (space) {
if (space->IsRowSpace()) {
space->SetRowSpaceClean();
if (ev->Good()) {
morkTableMapIter* ti = &mWriter_RowSpaceTablesIter;
ti->InitTableMapIter(ev, &space->mRowSpace_Tables);
#ifdef MORK_BEAD_OVER_NODE_MAPS
morkTable* table = ti->FirstTable(ev);
for (; table && ev->Good(); table = ti->NextTable(ev))
#else /*MORK_BEAD_OVER_NODE_MAPS*/
mork_tid* key2 = 0; // ignore keys in table map
morkTable* table = 0; // old key row in the map
for (c = ti->FirstTable(ev, key2, &table); c && ev->Good();
c = ti->NextTable(ev, key2, &table))
#endif /*MORK_BEAD_OVER_NODE_MAPS*/
{
if (table && table->IsTable()) {
if (table->IsTableDirty()) {
mWriter_BeVerbose =
(ev->mEnv_BeVerbose || table->IsTableVerbose());
if (this->PutTableDict(ev, table)) this->PutTable(ev, table);
table->SetTableClean(ev);
mWriter_BeVerbose = ev->mEnv_BeVerbose;
}
} else
table->NonTableTypeWarning(ev);
}
ti->CloseMapIter(ev);
}
if (ev->Good()) {
mWriter_TableRowScope = 0; // ensure no table context now
#ifdef MORK_ENABLE_PROBE_MAPS
morkRowProbeMapIter* ri = &mWriter_RowSpaceRowsIter;
#else /*MORK_ENABLE_PROBE_MAPS*/
morkRowMapIter* ri = &mWriter_RowSpaceRowsIter;
#endif /*MORK_ENABLE_PROBE_MAPS*/
ri->InitRowMapIter(ev, &space->mRowSpace_Rows);
morkRow* row = 0; // old row in the map
for (c = ri->FirstRow(ev, &row); c && ev->Good();
c = ri->NextRow(ev, &row)) {
if (row && row->IsRow()) {
// later we should also check that table use count is nonzero:
if (row->IsRowDirty()) // && row->IsRowUsed() ??
{
mWriter_BeVerbose = ev->mEnv_BeVerbose;
if (this->PutRowDict(ev, row)) {
if (ev->Good() && mWriter_DidStartDict) {
this->EndDict(ev);
if (mWriter_LineSize < 32 && ev->Good())
mWriter_SuppressDirtyRowNewline = morkBool_kTrue;
}
if (ev->Good()) this->PutRow(ev, row);
}
mWriter_BeVerbose = ev->mEnv_BeVerbose;
}
} else
row->NonRowTypeWarning(ev);
}
ri->CloseMapIter(ev);
}
} else
space->NonRowSpaceTypeError(ev);
} else
ev->NilPointerError();
}
}
}
mork_bool morkWriter::OnStoreRowSpacesTables(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnStoreRowSpacesTables()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
// later we'll break this up, but today we'll write all in one shot:
this->WriteAllStoreTables(ev);
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnRowSpaceTables(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnRowSpaceTables()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnTableRowArray(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnTableRowArray()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnStoreRowSpacesRows(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnStoreRowSpacesRows()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseContentDone;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnRowSpaceRows(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnRowSpaceRows()");
mWriter_LineSize = 0;
if (mWriter_NeedDirtyAll) // compress commit
{
}
if (ev->Good())
mWriter_Phase = morkWriter_kPhaseContentDone;
else
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
return ev->Good();
}
mork_bool morkWriter::OnContentDone(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
// if ( mWriter_NeedDirtyAll )
// stream->PutStringThenNewline(ev, "// OnContentDone()");
mWriter_LineSize = 0;
if (mWriter_Incremental) {
if (ev->Good())
this->CommitGroup(ev);
else
this->AbortGroup(ev);
} else if (mWriter_Store && ev->Good()) {
// after rewriting everything, there are no transaction groups:
mWriter_Store->mStore_FirstCommitGroupPos = 0;
mWriter_Store->mStore_SecondCommitGroupPos = 0;
}
stream->Flush(ev->AsMdbEnv());
nsIMdbFile* bud = mWriter_Bud;
if (bud) {
bud->Flush(ev->AsMdbEnv());
bud->BecomeTrunk(ev->AsMdbEnv());
nsIMdbFile_SlotStrongFile((nsIMdbFile*)0, ev, &mWriter_Bud);
} else if (!mWriter_Incremental) // should have a bud?
this->NilWriterBudError(ev);
mWriter_Phase = morkWriter_kPhaseWritingDone; // stop always
mWriter_DoneCount = mWriter_TotalCount;
return ev->Good();
}
mork_bool morkWriter::OnWritingDone(morkEnv* ev) {
mWriter_DoneCount = mWriter_TotalCount;
ev->NewWarning("writing is done");
return ev->Good();
}
mork_bool morkWriter::PutTableChange(morkEnv* ev,
const morkTableChange* inChange) {
nsIMdbEnv* mdbev = ev->AsMdbEnv();
if (inChange->IsAddRowTableChange()) {
this->PutRow(ev, inChange->mTableChange_Row); // row alone means add
} else if (inChange->IsCutRowTableChange()) {
mWriter_Stream->Putc(ev, '-'); // prefix '-' indicates cut row
++mWriter_LineSize;
this->PutRow(ev, inChange->mTableChange_Row);
} else if (inChange->IsMoveRowTableChange()) {
this->PutRow(ev, inChange->mTableChange_Row);
char buf[64];
char* p = buf;
*p++ = '!'; // for moves, position is indicated by prefix '!'
mork_size posSize = ev->TokenAsHex(p, inChange->mTableChange_Pos);
p += posSize;
*p++ = ' ';
mork_size bytesWritten;
mWriter_Stream->Write(mdbev, buf, posSize + 2, &bytesWritten);
mWriter_LineSize += bytesWritten;
} else
inChange->UnknownChangeError(ev);
return ev->Good();
}
mork_bool morkWriter::PutTable(morkEnv* ev, morkTable* ioTable) {
if (ev->Good()) this->StartTable(ev, ioTable);
if (ev->Good()) {
if (ioTable->IsTableRewrite() || mWriter_NeedDirtyAll) {
morkArray* array = &ioTable->mTable_RowArray; // vector of rows
mork_fill fill = array->mArray_Fill; // count of rows
morkRow** rows = (morkRow**)array->mArray_Slots;
if (rows && fill) {
morkRow** end = rows + fill;
while (rows < end && ev->Good()) {
morkRow* r = *rows++; // next row to consider
this->PutRow(ev, r);
}
}
} else // incremental write only table changes
{
morkList* list = &ioTable->mTable_ChangeList;
morkNext* next = list->GetListHead();
while (next && ev->Good()) {
this->PutTableChange(ev, (morkTableChange*)next);
next = next->GetNextLink();
}
}
}
if (ev->Good()) this->EndTable(ev);
ioTable->SetTableClean(ev); // note this also cleans change list
mWriter_TableRowScope = 0;
++mWriter_DoneCount;
return ev->Good();
}
mork_bool morkWriter::PutTableDict(morkEnv* ev, morkTable* ioTable) {
morkRowSpace* space = ioTable->mTable_RowSpace;
mWriter_TableRowScope = space->SpaceScope();
mWriter_TableForm = 0; // (f=iso-8859-1)
mWriter_TableAtomScope = 'v'; // (a=v)
mWriter_TableKind = ioTable->mTable_Kind;
mWriter_RowForm = mWriter_TableForm;
mWriter_RowAtomScope = mWriter_TableAtomScope;
mWriter_RowScope = mWriter_TableRowScope;
mWriter_DictForm = mWriter_TableForm;
mWriter_DictAtomScope = mWriter_TableAtomScope;
// if ( ev->Good() )
// this->StartDict(ev); // delay as long as possible
if (ev->Good()) {
morkRow* r = ioTable->mTable_MetaRow;
if (r) {
if (r->IsRow())
this->PutRowDict(ev, r);
else
r->NonRowTypeError(ev);
}
morkArray* array = &ioTable->mTable_RowArray; // vector of rows
mork_fill fill = array->mArray_Fill; // count of rows
morkRow** rows = (morkRow**)array->mArray_Slots;
if (rows && fill) {
morkRow** end = rows + fill;
while (rows < end && ev->Good()) {
r = *rows++; // next row to consider
if (r && r->IsRow())
this->PutRowDict(ev, r);
else
r->NonRowTypeError(ev);
}
}
// we may have a change for a row which is no longer in the
// table, but contains a cell with something not in the dictionary.
// So, loop through the rows in the change log, writing out any
// dirty dictionary elements.
morkList* list = &ioTable->mTable_ChangeList;
morkNext* next = list->GetListHead();
while (next && ev->Good()) {
r = ((morkTableChange*)next)->mTableChange_Row;
if (r && r->IsRow()) this->PutRowDict(ev, r);
next = next->GetNextLink();
}
}
if (ev->Good()) this->EndDict(ev);
return ev->Good();
}
void morkWriter::WriteTokenToTokenMetaCell(morkEnv* ev, mork_token inCol,
mork_token inValue) {
morkStream* stream = mWriter_Stream;
mork_bool isKindCol = (morkStore_kKindColumn == inCol);
mork_u1 valSep = (mork_u1)((isKindCol) ? '^' : '=');
char buf[128]; // buffer for staging the two hex IDs
char* p = buf;
mork_size bytesWritten;
if (inCol < 0x80) {
stream->Putc(ev, '(');
stream->Putc(ev, (char)inCol);
stream->Putc(ev, valSep);
} else {
*p++ = '('; // we always start with open paren
*p++ = '^'; // indicates col is hex ID
mork_size colSize = ev->TokenAsHex(p, inCol);
p += colSize;
*p++ = (char)valSep;
stream->Write(ev->AsMdbEnv(), buf, colSize + 3, &bytesWritten);
mWriter_LineSize += bytesWritten;
}
if (isKindCol) {
p = buf;
mork_size valSize = ev->TokenAsHex(p, inValue);
p += valSize;
*p++ = ':';
*p++ = 'c';
*p++ = ')';
stream->Write(ev->AsMdbEnv(), buf, valSize + 3, &bytesWritten);
mWriter_LineSize += bytesWritten;
} else {
this->IndentAsNeeded(ev, morkWriter_kTableMetaCellValueDepth);
mdbYarn* yarn = &mWriter_ColYarn;
// mork_u1* yarnBuf = (mork_u1*) yarn->mYarn_Buf;
mWriter_Store->TokenToString(ev, inValue, yarn);
this->WriteYarn(ev, yarn);
stream->Putc(ev, ')');
++mWriter_LineSize;
}
// mork_fill fill = yarn->mYarn_Fill;
// yarnBuf[ fill ] = ')'; // append terminator
// mWriter_LineSize += stream->Write(ev, yarnBuf, fill + 1); // +1 for ')'
}
void morkWriter::WriteStringToTokenDictCell(morkEnv* ev, const char* inCol,
mork_token inValue)
// Note inCol should begin with '(' and end with '=', with col in between.
{
morkStream* stream = mWriter_Stream;
mWriter_LineSize += stream->PutString(ev, inCol);
this->IndentAsNeeded(ev, morkWriter_kDictMetaCellValueDepth);
mdbYarn* yarn = &mWriter_ColYarn;
// mork_u1* yarnBuf = (mork_u1*) yarn->mYarn_Buf;
mWriter_Store->TokenToString(ev, inValue, yarn);
this->WriteYarn(ev, yarn);
stream->Putc(ev, ')');
++mWriter_LineSize;
// mork_fill fill = yarn->mYarn_Fill;
// yarnBuf[ fill ] = ')'; // append terminator
// mWriter_LineSize += stream->Write(ev, yarnBuf, fill + 1); // +1 for ')'
}
void morkWriter::ChangeDictAtomScope(morkEnv* ev, mork_scope inScope) {
if (inScope != mWriter_DictAtomScope) {
ev->NewWarning("unexpected atom scope change");
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
mWriter_LineSize = 0;
char buf[128]; // buffer for staging the two hex IDs
char* p = buf;
*p++ = '<'; // we always start with open paren
*p++ = '('; // we always start with open paren
*p++ = (char)morkStore_kAtomScopeColumn;
mork_size scopeSize = 1; // default to one byte
if (inScope >= 0x80) {
*p++ = '^'; // indicates col is hex ID
scopeSize = ev->TokenAsHex(p, inScope);
p += scopeSize;
} else {
*p++ = '='; // indicates col is imm byte
*p++ = (char)(mork_u1)inScope;
}
*p++ = ')';
*p++ = '>';
*p = 0;
mork_size pending = scopeSize + 6;
this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
mork_size bytesWritten;
stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
mWriter_LineSize += bytesWritten;
mWriter_DictAtomScope = inScope;
}
}
void morkWriter::ChangeRowForm(morkEnv* ev, mork_cscode inNewForm) {
if (inNewForm != mWriter_RowForm) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
mWriter_LineSize = 0;
char buf[128]; // buffer for staging the two hex IDs
char* p = buf;
*p++ = '['; // we always start with open bracket
*p++ = '('; // we always start with open paren
*p++ = (char)morkStore_kFormColumn;
mork_size formSize = 1; // default to one byte
if (!morkCh_IsValue(inNewForm)) {
*p++ = '^'; // indicates col is hex ID
formSize = ev->TokenAsHex(p, inNewForm);
p += formSize;
} else {
*p++ = '='; // indicates col is imm byte
*p++ = (char)(mork_u1)inNewForm;
}
*p++ = ')';
*p++ = ']';
*p = 0;
mork_size pending = formSize + 6;
this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
mork_size bytesWritten;
stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
mWriter_LineSize += bytesWritten;
mWriter_RowForm = inNewForm;
}
}
void morkWriter::ChangeDictForm(morkEnv* ev, mork_cscode inNewForm) {
if (inNewForm != mWriter_DictForm) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
mWriter_LineSize = 0;
char buf[128]; // buffer for staging the two hex IDs
char* p = buf;
*p++ = '<'; // we always start with open angle
*p++ = '('; // we always start with open paren
*p++ = (char)morkStore_kFormColumn;
mork_size formSize = 1; // default to one byte
if (!morkCh_IsValue(inNewForm)) {
*p++ = '^'; // indicates col is hex ID
formSize = ev->TokenAsHex(p, inNewForm);
p += formSize;
} else {
*p++ = '='; // indicates col is imm byte
*p++ = (char)(mork_u1)inNewForm;
}
*p++ = ')';
*p++ = '>';
*p = 0;
mork_size pending = formSize + 6;
this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
mork_size bytesWritten;
stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
mWriter_LineSize += bytesWritten;
mWriter_DictForm = inNewForm;
}
}
void morkWriter::StartDict(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_DidStartDict) {
stream->Putc(ev, '>'); // end dict
++mWriter_LineSize;
}
mWriter_DidStartDict = morkBool_kTrue;
mWriter_DidEndDict = morkBool_kFalse;
if (mWriter_LineSize) stream->PutLineBreak(ev);
mWriter_LineSize = 0;
if (mWriter_TableRowScope) // blank line before table's dict?
stream->PutLineBreak(ev);
if (mWriter_DictForm || mWriter_DictAtomScope != 'v') {
stream->Putc(ev, '<');
stream->Putc(ev, ' ');
stream->Putc(ev, '<');
mWriter_LineSize = 3;
if (mWriter_DictForm)
this->WriteStringToTokenDictCell(ev, "(f=", mWriter_DictForm);
if (mWriter_DictAtomScope != 'v')
this->WriteStringToTokenDictCell(ev, "(a=", mWriter_DictAtomScope);
stream->Putc(ev, '>');
++mWriter_LineSize;
mWriter_LineSize = stream->PutIndent(ev, morkWriter_kDictAliasDepth);
} else {
stream->Putc(ev, '<');
// stream->Putc(ev, ' ');
++mWriter_LineSize;
}
}
void morkWriter::EndDict(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
if (mWriter_DidStartDict) {
stream->Putc(ev, '>'); // end dict
++mWriter_LineSize;
}
mWriter_DidStartDict = morkBool_kFalse;
mWriter_DidEndDict = morkBool_kTrue;
}
void morkWriter::StartTable(morkEnv* ev, morkTable* ioTable) {
mdbOid toid; // to receive table oid
ioTable->GetTableOid(ev, &toid);
if (ev->Good()) {
morkStream* stream = mWriter_Stream;
if (mWriter_LineSize) stream->PutLineBreak(ev);
mWriter_LineSize = 0;
// stream->PutLineBreak(ev);
char buf[64 + 16]; // buffer for staging hex
char* p = buf;
*p++ = '{'; // punct 1
mork_size punctSize =
(mWriter_BeVerbose) ? 10 : 3; // counting "{ {/*r=*/ "
if (ioTable->IsTableRewrite() && mWriter_Incremental) {
*p++ = '-';
++punctSize; // counting '-' // punct ++
++mWriter_LineSize;
}
mork_size oidSize = ev->OidAsHex(p, toid);
p += oidSize;
*p++ = ' '; // punct 2
*p++ = '{'; // punct 3
if (mWriter_BeVerbose) {
*p++ = '/'; // punct=4
*p++ = '*'; // punct=5
*p++ = 'r'; // punct=6
*p++ = '='; // punct=7
mork_token tableUses = (mork_token)ioTable->mTable_GcUses;
mork_size usesSize = ev->TokenAsHex(p, tableUses);
punctSize += usesSize;
p += usesSize;
*p++ = '*'; // punct=8
*p++ = '/'; // punct=9
*p++ = ' '; // punct=10
}
mork_size bytesWritten;
stream->Write(ev->AsMdbEnv(), buf, oidSize + punctSize, &bytesWritten);
mWriter_LineSize += bytesWritten;
mork_kind tk = mWriter_TableKind;
if (tk) {
this->IndentAsNeeded(ev, morkWriter_kTableMetaCellDepth);
this->WriteTokenToTokenMetaCell(ev, morkStore_kKindColumn, tk);
}
stream->Putc(ev, '('); // start 's' col cell
stream->Putc(ev, 's'); // column
stream->Putc(ev, '='); // column
mWriter_LineSize += 3;
int prio = (int)ioTable->mTable_Priority;
if (prio > 9) // need to force down to max decimal digit?
prio = 9;
prio += '0'; // add base digit zero
stream->Putc(ev, prio); // priority: (s=0
++mWriter_LineSize;
if (ioTable->IsTableUnique()) {
stream->Putc(ev, 'u'); // (s=0u
++mWriter_LineSize;
}
if (ioTable->IsTableVerbose()) {
stream->Putc(ev, 'v'); // (s=0uv
++mWriter_LineSize;
}
// stream->Putc(ev, ':'); // (s=0uv:
// stream->Putc(ev, 'c'); // (s=0uv:c
stream->Putc(ev, ')'); // end 's' col cell (s=0uv:c)
mWriter_LineSize += 1; // maybe 3 if we add ':' and 'c'
morkRow* r = ioTable->mTable_MetaRow;
if (r) {
if (r->IsRow()) {
mWriter_SuppressDirtyRowNewline = morkBool_kTrue;
this->PutRow(ev, r);
} else
r->NonRowTypeError(ev);
}
stream->Putc(ev, '}'); // end meta
++mWriter_LineSize;
if (mWriter_LineSize < mWriter_MaxIndent) {
stream->Putc(ev, ' '); // nice white space
++mWriter_LineSize;
}
}
}
void morkWriter::EndTable(morkEnv* ev) {
morkStream* stream = mWriter_Stream;
stream->Putc(ev, '}'); // end table
++mWriter_LineSize;
mWriter_TableAtomScope = 'v'; // (a=v)
}
mork_bool morkWriter::PutRowDict(morkEnv* ev, morkRow* ioRow) {
mWriter_RowForm = mWriter_TableForm;
morkCell* cells = ioRow->mRow_Cells;
if (cells) {
morkStream* stream = mWriter_Stream;
mdbYarn yarn; // to ref content inside atom
char buf[64]; // buffer for staging the dict alias hex ID
char* idBuf = buf + 1; // where the id always starts
buf[0] = '('; // we always start with open paren
morkCell* end = cells + ioRow->mRow_Length;
--cells; // prepare for preincrement:
while (++cells < end && ev->Good()) {
morkAtom* atom = cells->GetAtom();
if (atom && atom->IsAtomDirty()) {
if (atom->IsBook()) // is it possible to write atom ID?
{
if (!this->DidStartDict()) {
this->StartDict(ev);
if (ev->Bad()) break;
}
atom->SetAtomClean(); // neutralize change
this->IndentAsNeeded(ev, morkWriter_kDictAliasDepth);
morkBookAtom* ba = (morkBookAtom*)atom;
mork_size size = ev->TokenAsHex(idBuf, ba->mBookAtom_Id);
mork_size bytesWritten;
stream->Write(ev->AsMdbEnv(), buf, size + 1, &bytesWritten); // '('
mWriter_LineSize += bytesWritten;
if (morkAtom::AliasYarn(atom, &yarn)) {
mork_scope atomScope = atom->GetBookAtomSpaceScope(ev);
if (atomScope && atomScope != mWriter_DictAtomScope)
this->ChangeDictAtomScope(ev, atomScope);
if (mWriter_DidStartDict && yarn.mYarn_Form != mWriter_DictForm)
this->ChangeDictForm(ev, yarn.mYarn_Form);
mork_size pending =
yarn.mYarn_Fill + morkWriter_kYarnEscapeSlop + 1;
this->IndentOverMaxLine(ev, pending,
morkWriter_kDictAliasValueDepth);
stream->Putc(ev, '='); // start value
++mWriter_LineSize;
this->WriteYarn(ev, &yarn);
stream->Putc(ev, ')'); // end value
++mWriter_LineSize;
} else
atom->BadAtomKindError(ev);
++mWriter_DoneCount;
}
}
}
}
return ev->Good();
}
mork_bool morkWriter::IsYarnAllValue(const mdbYarn* inYarn) {
mork_fill fill = inYarn->mYarn_Fill;
const mork_u1* buf = (const mork_u1*)inYarn->mYarn_Buf;
const mork_u1* end = buf + fill;
--buf; // prepare for preincrement
while (++buf < end) {
mork_ch c = *buf;
if (!morkCh_IsValue(c)) return morkBool_kFalse;
}
return morkBool_kTrue;
}
mork_bool morkWriter::PutVerboseCell(morkEnv* ev, morkCell* ioCell,
mork_bool inWithVal) {
morkStream* stream = mWriter_Stream;
morkStore* store = mWriter_Store;
mdbYarn* colYarn = &mWriter_ColYarn;
morkAtom* atom = (inWithVal) ? ioCell->GetAtom() : (morkAtom*)0;
mork_column col = ioCell->GetColumn();
store->TokenToString(ev, col, colYarn);
mdbYarn yarn; // to ref content inside atom
morkAtom::AliasYarn(atom, &yarn); // works even when atom==nil
if (yarn.mYarn_Form != mWriter_RowForm)
this->ChangeRowForm(ev, yarn.mYarn_Form);
mork_size pending =
yarn.mYarn_Fill + colYarn->mYarn_Fill + morkWriter_kYarnEscapeSlop + 3;
this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
stream->Putc(ev, '('); // start cell
++mWriter_LineSize;
this->WriteYarn(ev, colYarn); // column
pending = yarn.mYarn_Fill + morkWriter_kYarnEscapeSlop;
this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellValueDepth);
stream->Putc(ev, '=');
++mWriter_LineSize;
this->WriteYarn(ev, &yarn); // value
stream->Putc(ev, ')'); // end cell
++mWriter_LineSize;
return ev->Good();
}
mork_bool morkWriter::PutVerboseRowCells(morkEnv* ev, morkRow* ioRow) {
morkCell* cells = ioRow->mRow_Cells;
if (cells) {
morkCell* end = cells + ioRow->mRow_Length;
--cells; // prepare for preincrement:
while (++cells < end && ev->Good()) {
// note we prefer to avoid writing cells here with no value:
if (cells->GetAtom()) // does cell have any value?
this->PutVerboseCell(ev, cells, /*inWithVal*/ morkBool_kTrue);
}
}
return ev->Good();
}
mork_bool morkWriter::PutCell(morkEnv* ev, morkCell* ioCell,
mork_bool inWithVal) {
morkStream* stream = mWriter_Stream;
char buf[128]; // buffer for staging hex ids
char* idBuf = buf + 2; // where the id always starts
buf[0] = '('; // we always start with open paren
buf[1] = '^'; // column is always a hex ID
mork_size colSize = 0; // the size of col hex ID
mork_size bytesWritten;
morkAtom* atom = (inWithVal) ? ioCell->GetAtom() : (morkAtom*)0;
mork_column col = ioCell->GetColumn();
char* p = idBuf;
colSize = ev->TokenAsHex(p, col);
p += colSize;
mdbYarn yarn; // to ref content inside atom
morkAtom::AliasYarn(atom, &yarn); // works even when atom==nil
if (yarn.mYarn_Form != mWriter_RowForm)
this->ChangeRowForm(ev, yarn.mYarn_Form);
if (atom && atom->IsBook()) // is it possible to write atom ID?
{
this->IndentAsNeeded(ev, morkWriter_kRowCellDepth);
*p++ = '^';
morkBookAtom* ba = (morkBookAtom*)atom;
mork_size valSize = ev->TokenAsHex(p, ba->mBookAtom_Id);
mork_fill yarnFill = yarn.mYarn_Fill;
mork_bool putImmYarn = (yarnFill <= valSize);
if (putImmYarn) putImmYarn = this->IsYarnAllValue(&yarn);
if (putImmYarn) // value no bigger than id?
{
p[-1] = '='; // go back and clobber '^' with '=' instead
if (yarnFill) {
MORK_MEMCPY(p, yarn.mYarn_Buf, yarnFill);
p += yarnFill;
}
*p++ = ')';
mork_size distance = (mork_size)(p - buf);
stream->Write(ev->AsMdbEnv(), buf, distance, &bytesWritten);
mWriter_LineSize += bytesWritten;
} else {
p += valSize;
*p = ')';
stream->Write(ev->AsMdbEnv(), buf, colSize + valSize + 4, &bytesWritten);
mWriter_LineSize += bytesWritten;
}
if (atom->IsAtomDirty()) {
atom->SetAtomClean();
++mWriter_DoneCount;
}
} else // must write an anonymous atom
{
mork_size pending =
yarn.mYarn_Fill + colSize + morkWriter_kYarnEscapeSlop + 2;
this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
mork_size bytesWritten;
stream->Write(ev->AsMdbEnv(), buf, colSize + 2, &bytesWritten);
mWriter_LineSize += bytesWritten;
pending -= (colSize + 2);
this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
stream->Putc(ev, '=');
++mWriter_LineSize;
this->WriteYarn(ev, &yarn);
stream->Putc(ev, ')'); // end cell
++mWriter_LineSize;
}
return ev->Good();
}
mork_bool morkWriter::PutRowCells(morkEnv* ev, morkRow* ioRow) {
morkCell* cells = ioRow->mRow_Cells;
if (cells) {
morkCell* end = cells + ioRow->mRow_Length;
--cells; // prepare for preincrement:
while (++cells < end && ev->Good()) {
// note we prefer to avoid writing cells here with no value:
if (cells->GetAtom()) // does cell have any value?
this->PutCell(ev, cells, /*inWithVal*/ morkBool_kTrue);
}
}
return ev->Good();
}
mork_bool morkWriter::PutRow(morkEnv* ev, morkRow* ioRow) {
if (ioRow && ioRow->IsRow()) {
mWriter_RowForm = mWriter_TableForm;
mork_size bytesWritten;
morkStream* stream = mWriter_Stream;
char buf[128 + 16]; // buffer for staging hex
char* p = buf;
mdbOid* roid = &ioRow->mRow_Oid;
mork_size ridSize = 0;
mork_scope tableScope = mWriter_TableRowScope;
if (ioRow->IsRowDirty()) {
if (mWriter_SuppressDirtyRowNewline || !mWriter_LineSize)
mWriter_SuppressDirtyRowNewline = morkBool_kFalse;
else {
if (tableScope) // in a table?
mWriter_LineSize = stream->PutIndent(ev, morkWriter_kRowDepth);
else
mWriter_LineSize = stream->PutIndent(ev, 0); // no indent
}
// mork_rid rid = roid->mOid_Id;
*p++ = '['; // start row punct=1
mork_size punctSize =
(mWriter_BeVerbose) ? 9 : 1; // counting "[ /*r=*/ "
mork_bool rowRewrite = ioRow->IsRowRewrite();
if (rowRewrite && mWriter_Incremental) {
*p++ = '-';
++punctSize; // counting '-'
++mWriter_LineSize;
}
if (tableScope && roid->mOid_Scope == tableScope)
ridSize = ev->TokenAsHex(p, roid->mOid_Id);
else
ridSize = ev->OidAsHex(p, *roid);
p += ridSize;
if (mWriter_BeVerbose) {
*p++ = ' '; // punct=2
*p++ = '/'; // punct=3
*p++ = '*'; // punct=4
*p++ = 'r'; // punct=5
*p++ = '='; // punct=6
mork_size usesSize = ev->TokenAsHex(p, (mork_token)ioRow->mRow_GcUses);
punctSize += usesSize;
p += usesSize;
*p++ = '*'; // punct=7
*p++ = '/'; // punct=8
*p++ = ' '; // punct=9
}
stream->Write(ev->AsMdbEnv(), buf, ridSize + punctSize, &bytesWritten);
mWriter_LineSize += bytesWritten;
// special case situation where row puts exactly one column:
if (!rowRewrite && mWriter_Incremental && ioRow->HasRowDelta()) {
mork_column col = ioRow->GetDeltaColumn();
morkCell dummy(col, morkChange_kNil, (morkAtom*)0);
morkCell* cell = 0;
mork_bool withVal = (ioRow->GetDeltaChange() != morkChange_kCut);
if (withVal) {
mork_pos cellPos = 0; // dummy pos
cell = ioRow->GetCell(ev, col, &cellPos);
}
if (!cell) cell = &dummy;
if (mWriter_BeVerbose)
this->PutVerboseCell(ev, cell, withVal);
else
this->PutCell(ev, cell, withVal);
} else // put entire row?
{
if (mWriter_BeVerbose)
this->PutVerboseRowCells(ev, ioRow); // write all, verbosely
else
this->PutRowCells(ev, ioRow); // write all, hex notation
}
stream->Putc(ev, ']'); // end row
++mWriter_LineSize;
} else {
this->IndentAsNeeded(ev, morkWriter_kRowDepth);
if (tableScope && roid->mOid_Scope == tableScope)
ridSize = ev->TokenAsHex(p, roid->mOid_Id);
else
ridSize = ev->OidAsHex(p, *roid);
stream->Write(ev->AsMdbEnv(), buf, ridSize, &bytesWritten);
mWriter_LineSize += bytesWritten;
stream->Putc(ev, ' ');
++mWriter_LineSize;
}
++mWriter_DoneCount;
ioRow->SetRowClean(); // try to do this at the very last
} else
ioRow->NonRowTypeWarning(ev);
return ev->Good();
}
// 456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789