YggStabilizeImpl.mesa
Copyright Ó 1988 by Xerox Corporation. All rights reserved.
Bob Hagmann October 27, 1988 3:33:13 pm PDT
Take a volatile form of a document, and write it onto some files.
DIRECTORY
Basics USING [UnsafeBlock],
PBasics USING [charsPerWord, RawBytes],
Camelot USING [tidT],
IO USING [Close, PutChar, PutRope, STREAM, UnsafePutBlock],
Process USING [MsecToTicks, Pause],
Rope USING [Equal, Length, ROPE],
YggDID USING [DID, EqualDIDs],
YggDIDPrivate USING [DIDRep],
YggDIDMap USING [AddComponentFile, GetComponentFiles, OpenDocumentFromDID, SwapLinkInfo],
YggFile USING [Create, FileFromComponentFiles, FileHandle, PagesForBytes, StreamFromOpenFileAndTid],
YggFixedNames USING [Inlinks, Outlinks],
YggIndexMaint USING [NewValueForAttribute, NewValueForAttributeCommitStatus, NewValueForMetaAttribute],
YggInternal USING [Document, FileHandle],
YggRep USING [AccurateGMT, AccurateGMTRep, AccurateGMTRepByteSize, Attribute, AttributePreamble, AttributeValue, BitsRep, date, did, DocType, float, int, lastReservedDocType, LatchVDoc, linkMod, metaAttributeMod, rope, shortRope, SizeOfBits, TypedPrimitiveElement, unknown, uninterpretedBytes, UnlatchVDoc, VDoc],
YggEnvironment USING [nullDID, nullTransID, TransID],
YggTransaction USING [GetPossibleDocumentUpdates, IsTopLevel],
YggVolatileObjectCache USING [InvalidateDID, PromoteToParent, ReserveDIDInCache, UnreserveDIDInCache];
YggStabilizeImpl: CEDAR PROGRAM
IMPORTS IO, Process, Rope, YggDID, YggDIDMap, YggFile, YggFixedNames, YggIndexMaint, YggRep, YggTransaction, YggVolatileObjectCache
EXPORTS YggDID, YggRep
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
DID: PUBLIC TYPE ~ REF DIDRep;
DIDRep: PUBLIC TYPE ~ YggDIDPrivate.DIDRep;
AllNulls: REF PACKED ARRAY [0..PBasics.charsPerWord) OF CHARNEW[ PACKED ARRAY [0..PBasics.charsPerWord) OF CHAR ← ALL[0C]];
Exported procedures for transactions
PreCommit: PUBLIC PROC[tid: Camelot.tidT] ~ {
A transaction is trying to commit. Apply intentions.
Take the volatile form of documents, and write them out
vDocs: LIST OF YggRep.VDoc;
IF YggTransaction.IsTopLevel[tid] THEN { -- top level xact trying to commit
tryingToLatch: BOOLTRUE;
vDocs ← YggTransaction.GetPossibleDocumentUpdates[tid]; -- get documents (really VDoc's) that were write locked during the xact
WHILE tryingToLatch DO
latchFailed: LIST OF YggRep.VDoc ← NIL;
FOR vD: LIST OF YggRep.VDoc ← vDocs, vD.rest UNTIL vD = NIL DO
IF ~YggRep.LatchVDoc[vD.first, FALSE] THEN {
latchFailed ← vD;
EXIT;
};
ENDLOOP;
IF latchFailed # NIL THEN {
FOR vD: LIST OF YggRep.VDoc ← vDocs, vD.rest UNTIL vD = NIL DO
IF vD = latchFailed THEN EXIT;
IF ~YggRep.UnlatchVDoc[vD.first] THEN ERROR;
ENDLOOP;
Process.Pause[Process.MsecToTicks[13]];
LOOP;
};
tryingToLatch ← FALSE;
FOR vD: LIST OF YggRep.VDoc ← vDocs, vD.rest UNTIL vD = NIL DO
doc: YggInternal.Document ← NIL;
meta: YggInternal.FileHandle ← NIL;
metaAttributesInAttributesFileChanged: BOOLFALSE;
contentsInAttributesFileChanged: BOOLFALSE;
setContentsAsAttribute: BOOLFALSE;
componentFiles: LIST OF YggInternal.FileHandle ← NIL;
attributesStream: IO.STREAMNIL;
doc ← YggDIDMap.OpenDocumentFromDID[vD.first.did, tid];
[vD.first.fromDID, vD.first.toDID, vD.first.linkType] ← YggDIDMap.SwapLinkInfo[doc, vD.first.fromDID, vD.first.toDID, vD.first.linkType];
componentFiles ← YggDIDMap.GetComponentFiles[doc];
meta ← YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: $meta];
IF meta = NIL AND (vD.first.outlinksChanged # NIL OR vD.first.inlinksChanged # NIL OR vD.first.metaAttributesChanged # NIL) THEN metaAttributesInAttributesFileChanged ← TRUE;
IF vD.first.contentsChanged OR vD.first.namesOfAttributesChanged # NIL OR metaAttributesInAttributesFileChanged THEN { -- if a change really occured
contents, attributes, links: YggInternal.FileHandle ← NIL;
componentFiles: LIST OF YggInternal.FileHandle;
componentFiles ← YggDIDMap.GetComponentFiles[doc];
IF vD.first.contentsChanged THEN {
contents ← YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: $contents];
IF contents = NIL THEN {
contentsInAttributesFileChanged ← TRUE;
setContentsAsAttribute ← TRUE;
IF vD.first.contents.docType = YggRep.uninterpretedBytes OR vD.first.contents.docType >= YggRep.lastReservedDocType THEN {
byteSize: INT;
IF (byteSize ← YggRep.SizeOfBits[vD.first.contents.bits]) > 200 THEN {
contents ← YggFile.Create[size: YggFile.PagesForBytes[byteSize], did: vD.first.did, fileUse: $contents, nearToDid: YggEnvironment.nullDID, tid: tid];
YggDIDMap.AddComponentFile[doc: doc, componentFile: contents, tid: tid];
contentsInAttributesFileChanged ← FALSE;
setContentsAsAttribute ← FALSE;
};
};
};
}
ELSE {
IF YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: $contents] = NIL THEN setContentsAsAttribute ← TRUE;
};
IF vD.first.namesOfAttributesChanged # NIL OR metaAttributesInAttributesFileChanged OR contentsInAttributesFileChanged OR contents # NIL THEN {
attributes ← YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: $attributes];
IF attributes = NIL THEN {
attributes ← YggFile.Create[size: 1, did: vD.first.did, fileUse: $attributes, nearToDid: YggEnvironment.nullDID, tid: tid];
YggDIDMap.AddComponentFile[doc: doc, componentFile: attributes, tid: tid];
};
};
attributesStream ← StabilizeToFiles[tid, vD.first.did, vD.first, metaAttributesInAttributesFileChanged, contents, attributes, links, setContentsAsAttribute];
};
IF vD.first.namesOfAttributesChanged # NIL THEN { -- if an attribute really occured
FOR loa: LIST OF YggRep.Attribute ← vD.first.attributes, loa.rest UNTIL loa = NIL DO
FOR aC: LIST OF ROPE ← vD.first.namesOfAttributesChanged, aC.rest UNTIL aC = NIL DO
IF Rope.Equal[aC.first, loa.first.attributeName] THEN {
vD.first.attributesChanged ← CONS[loa.first, vD.first.attributesChanged];
EXIT;
};
ENDLOOP;
ENDLOOP;
};
IF vD.first.metaAttributesChanged # NIL THEN { -- if a meta attribute change occured
componentFiles: LIST OF YggInternal.FileHandle;
componentFiles ← YggDIDMap.GetComponentFiles[doc];
meta ← YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: $meta];
StabilizeMetaAttributes[tid, vD.first.did, vD.first, meta, attributesStream]; -- apply changes and write it out
IF meta# NIL AND attributesStream # NIL THEN IO.Close[attributesStream];
YggIndexMaint.NewValueForMetaAttribute[vD.first.did, tid, vD.first.metaAttributesChanged];
}
ELSE IF attributesStream # NIL THEN IO.Close[attributesStream];
IF vD.first.namesOfAttributesChanged # NIL THEN { -- if a change really occured
vDoc: YggRep.VDoc;
newValues: LIST OF LIST OF YggRep.AttributeValue ← NIL;
oldValues: LIST OF LIST OF YggRep.AttributeValue ← NIL;
vDoc ← vD.first;
FOR aC: LIST OF ROPE ← vDoc.namesOfAttributesChanged, aC.rest UNTIL aC = NIL DO
fetch all the new values for the attribute
FOR loa: LIST OF YggRep.Attribute ← vDoc.attributes, loa.rest UNTIL loa = NIL DO
IF Rope.Equal[aC.first, loa.first.attributeName] THEN newValues ← CONS[loa.first.value, newValues];
ENDLOOP;
FOR loa: LIST OF YggRep.Attribute ← vDoc.attributesChanged, loa.rest UNTIL loa = NIL DO
IF Rope.Equal[aC.first, loa.first.attributeName] THEN oldValues ← CONS[loa.first.value, oldValues];
ENDLOOP;
YggIndexMaint.NewValueForAttribute[vDoc.did, tid, aC.first, oldValues, newValues];
ENDLOOP;
};
ENDLOOP;
FOR vD: LIST OF YggRep.VDoc ← vDocs, vD.rest UNTIL vD = NIL DO
IF ~YggRep.UnlatchVDoc[vD.first] THEN ERROR;
ENDLOOP;
ENDLOOP;
};
};
Commit: PUBLIC PROC[tid: Camelot.tidT] ~ {
Commit actually occured.
vDocs: LIST OF YggRep.VDoc;
vDocs ← YggTransaction.GetPossibleDocumentUpdates[tid];
FOR vD: LIST OF YggRep.VDoc ← vDocs, vD.rest UNTIL vD = NIL DO
IF YggTransaction.IsTopLevel[tid] THEN {
IF vD.first.namesOfAttributesChanged # NIL OR vD.first.metaAttributesChanged # NIL THEN { -- if a change really occured
YggIndexMaint.NewValueForAttributeCommitStatus[vD.first.did, tid, TRUE];
vD.first.namesOfAttributesChanged ← NIL;
vD.first.attributesChanged ← NIL;
vD.first.metaAttributesChanged ← NIL;
vD.first.outlinksChanged ← NIL;
vD.first.inlinksChanged ← NIL;
vD.first.contentsChanged ← FALSE;
vD.first.linkChanged ← FALSE;
};
};
YggVolatileObjectCache.PromoteToParent[vD.first.did, tid];
ENDLOOP;
};
Abort: PUBLIC PROC[tid: Camelot.tidT] ~ {
Transaction has aborted. This transaction may or may not have gone through PreCommit.
vDocs: LIST OF YggRep.VDoc;
vDocs ← YggTransaction.GetPossibleDocumentUpdates[tid];
IF YggTransaction.IsTopLevel[tid] THEN { -- must back out of applied intentions
FOR vD: LIST OF YggRep.VDoc ← vDocs, vD.rest UNTIL vD = NIL DO
doc: YggInternal.Document ← NIL;
doc ← YggDIDMap.OpenDocumentFromDID[vD.first.did, tid];
[] ← YggDIDMap.SwapLinkInfo[doc, vD.first.fromDID, vD.first.toDID, vD.first.linkType];
YggVolatileObjectCache.InvalidateDID[vD.first.did, YggEnvironment.nullTransID];
Smash object cache for the null transaction => this makes the system rebuild the cache from stable values the next time. That's semantically OK, but not fast. If there are lots of aborts (which is not the expected case), then this could be a performance loose.
IF vD.first.namesOfAttributesChanged # NIL OR vD.first.metaAttributesChanged # NIL THEN { -- if a change really occured
YggIndexMaint.NewValueForAttributeCommitStatus[vD.first.did, tid, FALSE];
};
vD.first.namesOfAttributesChanged ← NIL;
vD.first.attributesChanged ← NIL;
vD.first.metaAttributesChanged ← NIL;
vD.first.outlinksChanged ← NIL;
vD.first.inlinksChanged ← NIL;
vD.first.contentsChanged ← FALSE;
vD.first.linkChanged ← FALSE;
ENDLOOP;
}
ELSE { -- smash object cache for xact that aborted
FOR vD: LIST OF YggRep.VDoc ← vDocs, vD.rest UNTIL vD = NIL DO
YggVolatileObjectCache.InvalidateDID[vD.first.did, tid];
ENDLOOP;
};
};
Exported conversion procedures
StabilizeMetaAttributes: PUBLIC PROC [transID: YggEnvironment.TransID, did: DID, vDoc: YggRep.VDoc, meta: YggFile.FileHandle, attributesStream: IO.STREAM] ~ {
applyLinkMods: PROC [links: LIST OF YggRep.AttributeValue, linksChanged: LIST OF YggRep.linkMod] RETURNS [newLinks: LIST OF YggRep.AttributeValue] ~ {
newLinks ← links;
FOR lolm: LIST OF YggRep.linkMod ← linksChanged, lolm.rest UNTIL lolm = NIL DO
FOR loav: LIST OF YggRep.AttributeValue ← newLinks, loav.rest UNTIL loav = NIL DO
IF Rope.Equal[lolm.first.linkName, loav.first.fieldName] THEN {
IF lolm.first.add THEN { -- add the link in
loav.first.valueSet ← CONS[[YggRep.did, lolm.first.linkValue], loav.first.valueSet];
EXIT;
}
ELSE { -- remove the link
gotOne: BOOLFALSE;
prevTPE: LIST OF YggRep.TypedPrimitiveElement ← NIL;
FOR lotpe: LIST OF YggRep.TypedPrimitiveElement ← loav.first.valueSet, lotpe.rest UNTIL lotpe = NIL DO
linkDID: DID;
IF lotpe.first.docType # YggRep.did THEN ERROR;
linkDID ← NARROW[lotpe.first.bits];
IF YggDID.EqualDIDs[linkDID, lolm.first.linkValue] THEN {
gotOne ← TRUE;
IF prevTPE = NIL THEN loav.first.valueSet ← lotpe.rest
ELSE prevTPE.rest ← lotpe.rest;
LOOP;
};
prevTPE ← lotpe;
ENDLOOP;
IF ~gotOne THEN ERROR;
};
};
REPEAT FINISHED => IF lolm.first.add THEN {
newLinks ← CONS[[lolm.first.linkName, LIST[[YggRep.did, lolm.first.linkValue]]], newLinks];
};
ENDLOOP;
ENDLOOP;
};
IF attributesStream = NIL THEN attributesStream ← YggFile.StreamFromOpenFileAndTid[meta, transID];
FOR macl: LIST OF YggRep.metaAttributeMod ← vDoc.metaAttributesChanged, macl.rest UNTIL macl = NIL DO
FOR loa: LIST OF YggRep.Attribute ← vDoc.metaAttributes, loa.rest UNTIL loa = NIL DO
IF Rope.Equal[loa.first.attributeName, macl.first.attributeName] THEN {
prev: LIST OF YggRep.AttributeValue ← NIL;
FOR listoav: LIST OF YggRep.AttributeValue ← loa.first.value, listoav.rest UNTIL listoav = NIL DO
tpe: YggRep.TypedPrimitiveElement ← listoav.first.valueSet.first;
didOnList: DID;
IF tpe.docType # YggRep.did THEN ERROR;
didOnList ← NARROW[tpe.bits];
IF YggDID.EqualDIDs[didOnList, macl.first.didValue] THEN {
IF macl.first.add THEN EXIT; -- already on list
prev.rest ← listoav;
EXIT;
};
prev ← listoav;
REPEAT FINISHED => IF macl.first.add THEN {
addItem: LIST OF YggRep.AttributeValue;
addItem ← LIST[[NIL, LIST[[YggRep.did, macl.first.didValue]]]];
IF prev = NIL THEN vDoc.metaAttributes ← LIST[[macl.first.attributeName, FALSE, addItem]]
ELSE prev.rest ← addItem
};
ENDLOOP;
LOOP;
};
REPEAT FINISHED => IF macl.first.add THEN {
addItem: YggRep.Attribute;
addItem ← [macl.first.attributeName, FALSE, LIST[[NIL, LIST[[YggRep.did, macl.first.didValue]]]]];
vDoc.metaAttributes ← CONS[addItem, vDoc.metaAttributes];
};
ENDLOOP;
ENDLOOP;
FOR restOfAttributes: LIST OF YggRep.Attribute ← vDoc.metaAttributes, restOfAttributes.rest UNTIL restOfAttributes = NIL DO
writeAttribute[restOfAttributes.first, attributesStream];
ENDLOOP;
apply link mods
vDoc.outlinks ← applyLinkMods[vDoc.outlinks, vDoc.outlinksChanged];
vDoc.inlinks ← applyLinkMods[vDoc.inlinks, vDoc.inlinksChanged];
IF vDoc.outlinks # NIL THEN writeAttribute[[YggFixedNames.Outlinks, FALSE, vDoc.outlinks], attributesStream];
IF vDoc.inlinks # NIL THEN writeAttribute[[YggFixedNames.Inlinks, FALSE, vDoc.inlinks], attributesStream];
IO.Close[attributesStream];
};
StabilizeToFiles: PROC [transID: YggEnvironment.TransID, did: DID, vDoc: YggRep.VDoc, metaAttributesInAttributesFile: BOOL, contents, attributes, links: YggFile.FileHandle, setContentsAsAttribute: BOOL] RETURNS [attributesStream: IO.STREAMNIL]~ {
Given a document, write it out to the files.
YggVolatileObjectCache.ReserveDIDInCache[did, transID];
IF contents # NIL THEN {
SELECT vDoc.contents.docType FROM
YggRep.unknown => ERROR;
YggRep.int, YggRep.shortRope, YggRep.float, YggRep.date => {};
YggRep.rope => {
contentsStream: IO.STREAMNIL;
rRope: ROPE;
rRope ← NARROW[vDoc.contents.bits];
contentsStream ← YggFile.StreamFromOpenFileAndTid[contents, transID];
IO.PutRope[contentsStream, rRope];
setContentsAsAttribute ← FALSE;
IO.Close[contentsStream];
};
ENDCASE => {
contentsStream: IO.STREAMNIL;
rBits: REF YggRep.BitsRep;
rBits ← NARROW[vDoc.contents.bits];
contentsStream ← YggFile.StreamFromOpenFileAndTid[contents, transID];
TRUSTED {
ptBits: LONG POINTER TO YggRep.BitsRep;
unsafeBlock: Basics.UnsafeBlock;
ptBits ← LOOPHOLE[rBits];
unsafeBlock ← [LOOPHOLE[ptBits, LONG POINTER TO PBasics.RawBytes] + SIZE[YggRep.BitsRep[0]], 0, rBits.validBytes];
IO.UnsafePutBlock [self: contentsStream, block: unsafeBlock];
};
setContentsAsAttribute ← FALSE;
IO.Close[contentsStream];
};
};
IF attributes # NIL THEN {
attributesStream ← YggFile.StreamFromOpenFileAndTid[attributes, transID];
writeInt[attributesStream, vDoc.contents.docType];
FOR restOfAttributes: LIST OF YggRep.Attribute ← vDoc.attributes, restOfAttributes.rest UNTIL restOfAttributes = NIL DO
writeAttribute[restOfAttributes.first, attributesStream];
ENDLOOP;
IF setContentsAsAttribute THEN writeAttribute[["$contents", FALSE, LIST[[NIL, LIST[vDoc.contents]]]], attributesStream];
};
YggVolatileObjectCache.UnreserveDIDInCache[did, transID];
};
writeInt: PROC [attributesStream: IO.STREAM, int: INT] = {
xyzInt: REF INTNEW[INT ← int];
TRUSTED {IO.UnsafePutBlock[attributesStream, [LOOPHOLE[xyzInt], 0, BYTES[INT]]];};
};
writeAttribute: PROC [attribute: YggRep.Attribute, attributesStream: IO.STREAM] = {
Given an attribute, write it out to the stream. See YggRep.mesa for a description of what the format is.
writeBits: PROC [tpe: YggRep.TypedPrimitiveElement] = {
Write the contents of a TypedPrimitiveElement. The type has already been writen.
SELECT tpe.docType FROM
YggRep.unknown => ERROR;
YggRep.int => {
ri: REF INT32;
ri ← NARROW[tpe.bits];
writeInt[attributesStream, ri^];
};
YggRep.shortRope => {
rRope: ROPE;
rRope ← NARROW[tpe.bits];
writeString[rRope, 0];
};
YggRep.rope => {
len: INT;
rRope: ROPE;
rRope ← NARROW[tpe.bits];
len ← Rope.Length[rRope];
writeInt[attributesStream, len];
writeString[rRope, 0];
};
YggRep.float => {
rReal: REF REAL32;
rReal ← NARROW[tpe.bits];
TRUSTED {
ptReal: LONG POINTER TO REAL32;
ptReal ← LOOPHOLE[rReal];
IO.UnsafePutBlock[attributesStream, [LOOPHOLE[ptReal], 0, BYTES[REAL32]]];
};
};
YggRep.date => {
rAccurateGMT: YggRep.AccurateGMT;
rAccurateGMT ← NARROW[tpe.bits];
TRUSTED {
ptAGMT: LONG POINTER TO YggRep.AccurateGMTRep;
ptAGMT ← LOOPHOLE[rAccurateGMT];
IO.UnsafePutBlock[attributesStream, [LOOPHOLE[ptAGMT], 0, YggRep.AccurateGMTRepByteSize]];
};
};
YggRep.did => {
rDID: DID;
rDID ← NARROW[tpe.bits];
TRUSTED {
ptDID: LONG POINTER TO DIDRep;
ptDID ← LOOPHOLE[rDID];
IO.UnsafePutBlock[attributesStream, [LOOPHOLE[ptDID], 0, BYTES[DIDRep]]];
};
};
ENDCASE => {
nullsToWrite: INT;
rBits: REF YggRep.BitsRep;
rBits ← NARROW[tpe.bits];
writeInt[attributesStream, rBits.validBytes];
TRUSTED {
ptBits: LONG POINTER TO YggRep.BitsRep;
ptBits ← LOOPHOLE[rBits];
IO.UnsafePutBlock[attributesStream, [LOOPHOLE[ptBits, LONG POINTER TO PBasics.RawBytes] + SIZE[YggRep.BitsRep[0]], 0, rBits.validBytes]];
};
nullsToWrite ← PBasics.charsPerWord - 1 - ((rBits.validBytes + PBasics.charsPerWord - 1) MOD PBasics.charsPerWord);
IF nullsToWrite > 0 THEN TRUSTED {IO.UnsafePutBlock[attributesStream, [LOOPHOLE[AllNulls], 0 , nullsToWrite]];};
};
};
writeString: PROC [string: ROPE, extraChars: INT] = {
len: INT;
nullsToWrite: INT;
len ← Rope.Length[string];
IF len > 0 THEN IO.PutRope[attributesStream, string];
nullsToWrite ← PBasics.charsPerWord - 1 - ((len + extraChars+ PBasics.charsPerWord - 1)) MOD PBasics.charsPerWord;
nullsToWrite ← PBasics.charsPerWord - (len+extraChars) MOD PBasics.charsPerWord;
TRUSTED {IO.UnsafePutBlock[attributesStream, [LOOPHOLE[AllNulls], 0 , nullsToWrite]];};
};
nullFieldNames: BOOLTRUE;
singletonFieldValues: BOOLTRUE;
numberOfAttributeValues: INT ← 0;
primitiveType: YggRep.DocType ← YggRep.unknown;
preample: YggRep.AttributePreamble;
FOR rv: LIST OF YggRep.AttributeValue ← attribute.value, rv.rest UNTIL rv = NIL DO
numberOfAttributeValues ← numberOfAttributeValues + 1;
IF rv.first.fieldName # NIL THEN nullFieldNames ← FALSE;
IF rv.first.valueSet.rest # NIL THEN singletonFieldValues ← FALSE;
FOR avl: LIST OF YggRep.TypedPrimitiveElement ← rv.first.valueSet, avl.rest UNTIL avl = NIL DO
tpe: YggRep.TypedPrimitiveElement = avl.first;
IF tpe.docType < YggRep.date AND (primitiveType = YggRep.unknown OR primitiveType = tpe.docType) THEN primitiveType ← tpe.docType ELSE primitiveType ← 10000;
ENDLOOP;
ENDLOOP;
preample[0].ordered ← attribute.ordered;
preample[0].noFieldNames ← nullFieldNames;
preample[0].singletonAttribute ← (numberOfAttributeValues = 1);
preample[0].singletonField ← singletonFieldValues;
preample[0].typeCode ← SELECT primitiveType FROM
YggRep.int => integer,
YggRep.rope => ropeLarge,
YggRep.shortRope => ropeShort,
YggRep.float => float,
YggRep.date => date,
YggRep.did => did,
YggRep.uninterpretedBytes => uninterpretedBytes,
ENDCASE => separate;
IF numberOfAttributeValues <= 0 THEN RETURN;
TRUSTED {IO.PutChar[attributesStream, LOOPHOLE[preample[0]]];}; -- @preample points to preample[0]
writeString[attribute.attributeName, 1]; -- write attribute name
IF numberOfAttributeValues # 1 THEN writeInt[attributesStream, numberOfAttributeValues]; -- write number of attribute values;
FOR rv: LIST OF YggRep.AttributeValue ← attribute.value, rv.rest UNTIL rv = NIL DO
IF ~nullFieldNames THEN writeString[rv.first.fieldName, 0]; -- write field name
FOR avl: LIST OF YggRep.TypedPrimitiveElement ← rv.first.valueSet, avl.rest UNTIL avl = NIL DO
tpe: YggRep.TypedPrimitiveElement = avl.first;
IF preample[0].typeCode = separate THEN writeInt[attributesStream, tpe.docType];
writeBits[tpe];
ENDLOOP;
IF ~preample[0].singletonField THEN writeInt[attributesStream, YggRep.unknown];
ENDLOOP;
};
END.