<> <> <> <<>> <> <<>> 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 CHAR _ NEW[ PACKED ARRAY [0..PBasics.charsPerWord) OF CHAR _ ALL[0C]]; <> PreCommit: PUBLIC PROC[tid: Camelot.tidT] ~ { <> <> vDocs: LIST OF YggRep.VDoc; IF YggTransaction.IsTopLevel[tid] THEN { -- top level xact trying to commit tryingToLatch: BOOL _ TRUE; 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: BOOL _ FALSE; contentsInAttributesFileChanged: BOOL _ FALSE; setContentsAsAttribute: BOOL _ FALSE; componentFiles: LIST OF YggInternal.FileHandle _ NIL; attributesStream: IO.STREAM _ NIL; 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 <> 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] ~ { <> 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] ~ { <> 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]; < 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; }; }; <> 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: BOOL _ FALSE; 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; <> 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.STREAM _ NIL]~ { <> 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.STREAM _ NIL; rRope: ROPE; rRope _ NARROW[vDoc.contents.bits]; contentsStream _ YggFile.StreamFromOpenFileAndTid[contents, transID]; IO.PutRope[contentsStream, rRope]; setContentsAsAttribute _ FALSE; IO.Close[contentsStream]; }; ENDCASE => { contentsStream: IO.STREAM _ NIL; 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 INT _ NEW[INT _ int]; TRUSTED {IO.UnsafePutBlock[attributesStream, [LOOPHOLE[xyzInt], 0, BYTES[INT]]];}; }; writeAttribute: PROC [attribute: YggRep.Attribute, attributesStream: IO.STREAM] = { <> writeBits: PROC [tpe: YggRep.TypedPrimitiveElement] = { <> 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: BOOL _ TRUE; singletonFieldValues: BOOL _ TRUE; 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.