diff options
Diffstat (limited to 'db/mork/src/morkWriter.cpp')
-rw-r--r-- | db/mork/src/morkWriter.cpp | 2206 |
1 files changed, 2206 insertions, 0 deletions
diff --git a/db/mork/src/morkWriter.cpp b/db/mork/src/morkWriter.cpp new file mode 100644 index 0000000000..98ca5457f0 --- /dev/null +++ b/db/mork/src/morkWriter.cpp @@ -0,0 +1,2206 @@ +/* -*- 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 + +//3456789_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; + stream->Write(mdbev, buf, size+1, &bytesWritten); // + '(' + mWriter_LineSize += bytesWritten; + + pending -= ( size + 1 ); + this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasValueDepth); + 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[ 64 ]; + 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 ) + { + MORK_MEMCPY(mWriter_GroupBuf, p, idFill + 1); + mWriter_GroupBufFill = idFill; + } + else + *mWriter_GroupBuf = 0; + + p += idFill; + *p++ = '{'; + *p++ = '@'; + *p = 0; + + 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, idFill + 6, &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(); +} + +//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789 + |