<> <> <> <<>> DIRECTORY Atom USING [GetPName], Directory USING [GetProperty], File USING [Capability], FileIO USING [CapabilityFromStream, Open, StreamFromCapability], FileWriter USING [blockSize, BumpWriter, Close, OpenC, Ref, ToRope, ToStream, WriteChar, WriteRope], IO USING [Close, CreateOutputStreamToRope, Error, GetIndex, GetOutputStreamRope, Handle, PutChar, PutRope, SetLength], NameSymbolTable USING [RopeFromName], NodeProps USING [GetProp, PutProp, MapProps, GetSpecs], Process USING [GetPriority, Priority, priorityBackground, SetPriority], PropertyTypes USING [tCreateDate], PutGet, Rope USING [Fetch, Size, Substr], RopeEdit USING [BlankChar, ROPE], RopeIO USING [PutRope, ToFileC], RopeReader USING [FreeRopeReader, GetRopeReader, GetString, Ref, SetPosition], RunReader USING [FreeRunReader, GetRunReader, MergedGet, Ref, SetPosition], System USING [GreenwichMeanTime], T2FileOps USING [addLooksFirst, cmt, comment, default, fmt, format, heavyDuty, IntBytes, lastItem, lastLooksFirst, LengthByte, noClassData, noLooks, Op, prop, runs, startBI, startBranch, startBX, startClassData, startLI, startTI, ThirdByte], TiogaLooks USING [Look, Looks, noLooks, Runs], TiogaLooksOps USING [CountRuns], TiogaBasicClass USING [BasicClass], TiogaItemClass USING [ItemClass], TiogaNode USING [Name, NodeProps, nullName, Offset, Ref, RefBasicNode, RefBoxNode, RefBranchNode, RefItemNode, RefListNode, RefTextNode], TiogaNodeOps USING [BranchContents, FetchBasicClass, FetchItemClass, Forward, NarrowToTextNode, StepForwardNode]; PutFileImpl: CEDAR PROGRAM IMPORTS Directory, Atom, FileIO, FileWriter, IO, NameSymbolTable, NodeProps, Process, Rope, RopeEdit, RopeIO, RopeReader, RunReader, TiogaLooksOps, TiogaNodeOps EXPORTS PutGet SHARES RopeReader, FileWriter = BEGIN OPEN PutGet; ToRope: PUBLIC PROC [node: TiogaNode.RefBranchNode, flatten, textOnly: BOOLEAN _ FALSE] RETURNS [dataLen, count: TiogaNode.Offset, output: RopeEdit.ROPE] = { ctrl, data: FileWriter.Ref; simple: BOOLEAN; [output, simple] _ SimpleFile[node]; IF simple THEN { dataLen _ count _ Rope.Size[output]; RETURN }; [ctrl, data] _ FileWriter.ToRope[]; OutputStructure[ctrl, data, node, flatten]; [dataLen, count, output] _ FileWriter.Close[ctrl, data, textOnly] }; ToStream: PUBLIC PROC [stream: IO.Handle, node: TiogaNode.RefBranchNode, flatten, textOnly: BOOLEAN _ FALSE] RETURNS [dataLen, count: TiogaNode.Offset] = { ctrl, data: FileWriter.Ref; rope: RopeEdit.ROPE; simple: BOOLEAN; [rope, simple] _ SimpleFile[node]; IF simple THEN { RopeIO.PutRope[stream, rope]; dataLen _ count _ Rope.Size[rope]; RETURN }; [ctrl, data] _ FileWriter.ToStream[stream]; OutputStructure[ctrl, data, node, flatten]; [dataLen, count, ] _ FileWriter.Close[ctrl, data, textOnly] }; ToFile: PUBLIC PROC [fileName: RopeEdit.ROPE, node: TiogaNode.RefBranchNode, start: TiogaNode.Offset _ 0, flatten, textOnly: BOOLEAN _ FALSE] RETURNS [dataLen, count: TiogaNode.Offset] = { stream: IO.Handle _ FileIO.Open[fileName, overwrite]; capability: File.Capability _ FileIO.CapabilityFromStream[stream]; IO.Close[stream]; -- need to close first or get 0 length file! [dataLen, count] _ ToFileC[capability, node, start, flatten, textOnly] }; ToFileC: PUBLIC PROC [file: File.Capability, node: TiogaNode.RefBranchNode, start: TiogaNode.Offset _ 0, flatten, textOnly: BOOLEAN _ FALSE] RETURNS [dataLen, count: TiogaNode.Offset] = { ropeToFile: PROC [rope: RopeEdit.ROPE] = { RopeIO.ToFileC[file, rope, start] }; opener: PROC RETURNS [ctrl, data: FileWriter.Ref] = { [ctrl, data] _ FileWriter.OpenC[file, start] }; [dataLen, count] _ FileIt[ropeToFile, opener, node, flatten, textOnly] ; UpdateCreateDate[node, file] }; UpdateCreateDate: PROC [root: TiogaNode.RefBranchNode, file: File.Capability] = TRUSTED { createDate: System.GreenwichMeanTime; prop: REF System.GreenwichMeanTime; Directory.GetProperty[file: file, property: PropertyTypes.tCreateDate, propertyValue: DESCRIPTOR[@createDate, SIZE[System.GreenwichMeanTime]]]; IF (prop _ NARROW[NodeProps.GetProp[root, $FileCreateDate]]) # NIL THEN prop^ _ createDate ELSE NodeProps.PutProp[root, $FileCreateDate, NEW[System.GreenwichMeanTime _ createDate]] }; FileIt: PROC [ropeToFile: PROC [RopeEdit.ROPE], opener: PROC RETURNS [ctrl, data: FileWriter.Ref], node: TiogaNode.RefBranchNode, flatten, textOnly: BOOLEAN] RETURNS [dataLen, count: TiogaNode.Offset] = TRUSTED { ctrl, data: FileWriter.Ref; rope: RopeEdit.ROPE; simple: BOOLEAN; priority: Process.Priority = Process.GetPriority[]; Process.SetPriority[Process.priorityBackground]; [rope, simple] _ SimpleFile[node]; IF simple THEN { ropeToFile[rope]; dataLen _ count _ Rope.Size[rope]; RETURN }; [ctrl, data] _ opener[]; OutputStructure[ctrl, data, node, flatten]; [dataLen, count, ] _ FileWriter.Close[ctrl, data, textOnly]; Process.SetPriority[priority] }; SimpleFile: PROC [root: TiogaNode.RefBranchNode] RETURNS [rope: RopeEdit.ROPE, simple: BOOLEAN] = { SimpleNode: PROC [node: TiogaNode.RefBranchNode] RETURNS [RopeEdit.ROPE, BOOLEAN] = TRUSTED { HasInterestingProp: PROC RETURNS [BOOL] = TRUSTED { Check: PROC [name: ATOM, value: REF] RETURNS [BOOLEAN] = TRUSTED { RETURN [SELECT name FROM $Viewer, $FromTiogaFile, $DocumentLock, $FileCreateDate, $FileExtension => FALSE, <> ENDCASE => TRUE] }; RETURN [NodeProps.MapProps[node, Check, FALSE, FALSE]] }; IF node.format=TiogaNode.nullName AND ~node.comment AND ~HasInterestingProp[] THEN IF node.contents = NIL THEN RETURN [NIL, TRUE] ELSE WITH node.contents SELECT FROM t:TiogaNode.RefTextNode => IF t.runs=NIL THEN RETURN [t.rope, TRUE] ELSE WITH r:t.runs SELECT FROM base => IF r.length=1 AND r[0].looks=TiogaLooks.noLooks THEN RETURN [t.rope, TRUE]; ENDCASE; ENDCASE; RETURN [NIL, FALSE] }; IF root=NIL THEN RETURN [NIL, TRUE]; [rope, simple] _ SimpleNode[root]; IF ~simple OR root.contents#NIL THEN RETURN[NIL, FALSE]; -- root must not have contents <> IF root.child=NIL THEN RETURN; -- simple root and no child IF ~root.child.last OR root.child.child # NIL THEN RETURN [NIL, FALSE]; -- more than one child, so not simple [rope, simple] _ SimpleNode[root.child] }; OutputStructure: PROC [ctrl, data: FileWriter.Ref, root: TiogaNode.RefBranchNode, flatten: BOOLEAN] = { CountLookBits: PROC [lks: TiogaLooks.Looks] RETURNS [cnt: NAT] = INLINE { <> cnt _ 0; FOR c: CHARACTER IN TiogaLooks.Look DO IF lks[c] THEN cnt _ cnt+1; ENDLOOP }; NoOfRuns: PROCEDURE [runs:TiogaLooks.Runs, size:INT] RETURNS [n:INT] = INLINE { <> IF runs=NIL THEN RETURN [0]; [n, ,] _ TiogaLooksOps.CountRuns[runs, 0, size] }; Output32: PROC [len: TiogaNode.Offset, forwards:BOOLEAN] = { first, second, fourth: T2FileOps.LengthByte; third: T2FileOps.ThirdByte; lenBytes: T2FileOps.IntBytes _ LOOPHOLE[len]; IF lenBytes.fourth # 0 THEN { fourth.data _ lenBytes.fourth; first.others _ second.others _ third.others _ TRUE }; IF lenBytes.thirdTop # 0 OR lenBytes.thirdBottom # 0 THEN { third.dataTop _ lenBytes.thirdTop; third.dataBottom _ lenBytes.thirdBottom; first.others _ second.others _ TRUE }; IF lenBytes.second # 0 THEN { second.data _ lenBytes.second; first.others _ TRUE }; first.data _ lenBytes.first; <> IF forwards THEN { WriteChar[LOOPHOLE[first], ctrl]; IF first.others THEN { WriteChar[LOOPHOLE[second], ctrl]; IF second.others THEN { WriteChar[LOOPHOLE[third], ctrl]; IF third.others THEN { WriteChar[LOOPHOLE[fourth], ctrl] }}} } ELSE { IF first.others THEN { IF second.others THEN { IF third.others THEN WriteChar[LOOPHOLE[fourth], ctrl]; WriteChar[LOOPHOLE[third], ctrl]; }; WriteChar[LOOPHOLE[second], ctrl]; }; WriteChar[LOOPHOLE[first], ctrl]; }; }; OutputAtom: PROC [atom: ATOM] = INLINE { <> OutputSymbolTableIndex[Atom.GetPName[atom]] }; OutputB32: PROCEDURE [len: TiogaNode.Offset] = INLINE { <> Output32[len, FALSE]; }; OutputBranch: PROCEDURE [branch:TiogaNode.RefBranchNode] = { <> <> <> <> IsSimple: PROCEDURE [branch:TiogaNode.RefBranchNode] RETURNS [BOOLEAN] = { <> IF branch.comment THEN RETURN [FALSE]; IF branch.props#NIL THEN RETURN [FALSE]; IF branch.format#TiogaNode.nullName THEN RETURN [FALSE]; IF branch.child#NIL THEN RETURN [FALSE]; WITH branch.contents SELECT FROM t:TiogaNode.RefTextNode => { classInfo:TiogaItemClass.ItemClass _ TiogaNodeOps.FetchItemClass[t.class]; IF classInfo=NIL THEN ERROR; IF ~t.last THEN RETURN [FALSE]; IF t.comment THEN RETURN [FALSE]; IF t.props#NIL THEN RETURN [FALSE]; IF t.format#TiogaNode.nullName THEN RETURN [FALSE]; IF classInfo.flavor#$Text THEN RETURN [FALSE]; IF t.runs#NIL THEN RETURN[FALSE]; RETURN[TRUE]; }; ENDCASE => RETURN [FALSE]; }; IF branch.externalRepValid THEN { <> WriteRope: PROC [r: RopeEdit.ROPE, size: LONG INTEGER, writer: FileWriter.Ref, reader: RopeReader.Ref, start: LONG INTEGER_0] = { WHILE size>0 DO FileWriter.WriteRope[r, MIN[size, LAST[NAT]], writer, reader, start]; size _ size - LAST[NAT]; start _ start + LAST[NAT]; ENDLOOP}; brInfo:PutGet.SpanInfo _ NARROW[NARROW[NodeProps.GetProp[branch, $BranchInfo]]]; WriteRope[brInfo.externalRepRope, brInfo.charsLen, data, externRopeReader, brInfo.charsStart]; WriteRope[brInfo.externalRepRope, brInfo.ctrlLen, ctrl, externRopeReader, brInfo.ctrlStart]; } ELSE { IF branch.deleted THEN ERROR; IF IsSimple[branch] THEN { <> WITH branch.contents SELECT FROM t:TiogaNode.RefTextNode => IF Rope.Size[t.rope]#0 THEN OutputRope[t.rope, data]; ENDCASE => ERROR; -- shouldn't happen for simple nodes } ELSE { myCurrentBlock:OffsetBlockRef; myNextFreeCell:INTEGER; IF branch.comment THEN FileWriter.WriteChar[T2FileOps.comment, ctrl]; -- COMMENT IF branch.props # NIL THEN { node _ branch; -- make node available for property specs proc [] _ NodeProps.MapProps[branch, OutputProperty, FALSE, FALSE] }; -- PROPERTIES IF branch.format#TiogaNode.nullName THEN OutPutFormat[branch.format]; -- FORMAT IF branch.contents # NIL THEN OutputItemList[branch.contents]; -- CONTENTS FileWriter.WriteChar[T2FileOps.lastItem, ctrl]; -- LAST ITEM myCurrentBlock _ currentBlock; -- mark offset stream myNextFreeCell _ nextFreeCell; IF branch.child # NIL THEN -- CHILDREN FOR b:TiogaNode.RefBranchNode _ branch.child, NARROW[b.next] DO SaveStreamOffsets[]; OutputBranch[b]; IF b.last THEN EXIT; ENDLOOP; OutputHK[myCurrentBlock, myNextFreeCell]; -- CHILD H.K. <> <> }; }; }; OutputContents: PROCEDURE [c:TiogaNode.Ref] = { <> <> <> <> <> <> <> OutputItemList[ref:c, contentMode:TRUE]; }; OutputControlRope: PROC [r: RopeEdit.ROPE] = INLINE { <> OutputF32[Rope.Size[r]]; FileWriter.WriteRope[r, Rope.Size[r], ctrl, externRopeReader]; }; OutputCountRopeCR: PROC [r: RopeEdit.ROPE, writer: FileWriter.Ref] = INLINE { <> OutputF32[Rope.Size[r]]; OutputRope[r, writer]; }; OutputF32: PROCEDURE [len: TiogaNode.Offset] = INLINE { <> Output32[len, TRUE]; }; OutPutFormat: PROCEDURE [format:TiogaNode.Name] = { <> WriteChar[T2FileOps.format, ctrl]; OutputSymbolTableIndex[NameSymbolTable.RopeFromName[format]]; }; OutputYUKBranch: PROCEDURE [br:TiogaNode.RefBranchNode] = { <> <> <> <> <<>> startData:LONG CARDINAL; saveCtrl:FileWriter.Ref _ ctrl; -- remember the control stream dummyData:FileWriter.Ref; controlRope:RopeEdit.ROPE; controlRopeSize:LONG CARDINAL; <> startData _ data.blockCount*LONG[FileWriter.blockSize]+data.block.length; [ctrl, dummyData] _ FileWriter.ToRope[]; -- puts two NULLS at ctrl stream head - damn it OutputBranch[br]; -- BRANCH INFO controlRopeSize _ (ctrl.blockCount*LONG[FileWriter.blockSize]+ctrl.block.length); [ , , controlRope] _ FileWriter.Close[ctrl, dummyData, FALSE]; -- NB writes unreqd. junk ctrl _ saveCtrl; -- reset control stream WriteChar[T2FileOps.startBranch, ctrl]; -- LABELLED BRANCH TOKEN OutputF32[(data.blockCount*LONG[FileWriter.blockSize]+data.block.length) - startData]; OutputControlRope[Rope.Substr[controlRope, 2, controlRopeSize-2]]; }; OutputItemList: PROCEDURE [ref:TiogaNode.Ref, contentMode:BOOLEAN_FALSE] = { <> IF ref#NIL THEN DO WITH ref SELECT FROM t:TiogaNode.RefTextNode => OutputTI[t]; br:TiogaNode.RefBranchNode => OutputYUKBranch[br]; ENDCASE => OutputOther[ref, contentMode]; IF ref.last THEN RETURN ELSE ref _ NARROW[ref.next]; ENDLOOP }; OutputProperty: PROC [name: ATOM, value: REF] RETURNS [BOOLEAN] = { <> <> <> <> <> <> <<>> specs: RopeEdit.ROPE _ NodeProps.GetSpecs[name, value, node]; -- node set by caller IF specs=NIL THEN RETURN [FALSE]; WriteChar[T2FileOps.prop, ctrl]; OutputAtom[name]; OutputControlRope[specs]; RETURN [FALSE] }; OutputRope: PROC [r: RopeEdit.ROPE, writer: FileWriter.Ref] = INLINE { <> FileWriter.WriteRope[r, Rope.Size[r], writer, externRopeReader]; WriteChar[15C, writer] }; OutputRuns: PROCEDURE [runs:TiogaLooks.Runs, noOfRuns: TiogaNode.Offset] RETURNS [loc:TiogaNode.Offset _ 0] = { <> <> <> <> <> <> <> <<>> cnt: TiogaNode.Offset _ 0; RunReader.SetPosition[runReader, runs, 0]; <<** noOfRuns>> OutputF32[noOfRuns]; <<** {run}>> WHILE (cnt_cnt+1) <= noOfRuns DO -- Process each run descriptor looks: TiogaLooks.Looks; len: TiogaNode.Offset; noOfLookBitsToProcess:INT; lookIx:CARDINAL _ 0; [len, looks] _ RunReader.MergedGet[runReader]; noOfLookBitsToProcess _ CountLookBits[looks]; IF noOfLookBitsToProcess>0 THEN { -- ** lookSeq <<** {addlooks}>> WHILE noOfLookBitsToProcess>1 DO UNTIL looks[lookIx+FIRST[TiogaLooks.Look]] DO -- skip unset bits lookIx _ lookIx + 1; ENDLOOP; WriteChar[T2FileOps.addLooksFirst+lookIx, ctrl]; noOfLookBitsToProcess _ noOfLookBitsToProcess - 1; lookIx _ lookIx + 1; -- continue search with following bit ENDLOOP; <<** lastLooks>> UNTIL looks[lookIx+FIRST[TiogaLooks.Look]] DO -- skip unset bits lookIx _ lookIx + 1; ENDLOOP; WriteChar[T2FileOps.lastLooksFirst+lookIx, ctrl]; } ELSE <<** noLooks>> WriteChar[T2FileOps.noLooks, ctrl]; <<** runLength>> OutputF32[len]; loc _ loc+len; ENDLOOP; }; OutputSymbolTableIndex: PROCEDURE [rope:RopeEdit.ROPE] = INLINE { <> OutputControlRope[rope]; -- dummy writes rope rather tan ST index }; OutputTI: PROCEDURE [t:TiogaNode.RefTextNode] = { <> <> <> <> <> <> <> <> <> <> <> <> <> <> HeavyDuty: PROCEDURE [] RETURNS [BOOLEAN] = INLINE { <> RETURN [t.props#NIL] }; ropeSize: TiogaNode.Offset _ Rope.Size[t.rope]; noOfRuns: TiogaNode.Offset _ NoOfRuns[t.runs, ropeSize]; token:T2FileOps.Op _ T2FileOps.startTI; -- build item type token here doFormat, doHeavyDuty, doRuns:BOOLEAN _ FALSE; doClass:BOOLEAN _ TRUE; classInfo: TiogaItemClass.ItemClass _ TiogaNodeOps.FetchItemClass[t.class]; <> <<>> <<** SCANNER **>> <> IF HeavyDuty[] THEN { token _ token + T2FileOps.heavyDuty; doHeavyDuty _ TRUE; }; IF classInfo.flavor=$Text THEN { doClass_FALSE; token _ token + T2FileOps.default; }; IF t.format#TiogaNode.nullName THEN { token _ token + T2FileOps.fmt; doFormat _ TRUE; }; IF noOfRuns #0 THEN { doRuns _ TRUE; token _ token + T2FileOps.runs; }; IF t.comment THEN token _ token + T2FileOps.cmt; <<** WRITER **>> WriteChar[token, ctrl]; -- ITEM TOKEN IF doHeavyDuty THEN { -- PROPERTIES and CONTENTS node _ t; -- make node available for property specs proc [] _ NodeProps.MapProps[t, OutputProperty, FALSE, FALSE]; WriteChar[T2FileOps.noClassData, ctrl]; -- property terminator }; IF doClass THEN OutputAtom[classInfo.flavor]; -- CLASS ATOM IF doRuns THEN -- RUNS IF OutputRuns[t.runs, noOfRuns] # ropeSize THEN ERROR; -- sum of looks < rope length IF doFormat THEN -- FORMAT OutputSymbolTableIndex[NameSymbolTable.RopeFromName[t.format]]; OutputCountRopeCR[t.rope, IF t.comment THEN ctrl ELSE data]; -- TEXT }; OutputOther: PROCEDURE [t:TiogaNode.Ref, contentMode:BOOLEAN] = { doFormat, doHeavyDuty, doRuns:BOOLEAN _ FALSE; doClass, doContents:BOOLEAN _ TRUE; token:T2FileOps.Op; -- build item type token here flavor: ATOM; -- save item flavor here to avoid second type discrimination classData: RopeEdit.ROPE; contents:TiogaNode.Ref; <> <<>> <<** SCANNER **>> WITH t SELECT FROM bs:TiogaNode.RefBasicNode => { classInfo: TiogaBasicClass.BasicClass _ TiogaNodeOps.FetchBasicClass[bs.class]; IF ~contentMode THEN ERROR; -- basic node only allowed as contents token _ T2FileOps.startBI; IF bs.props#NIL THEN { token _ token + T2FileOps.heavyDuty; doHeavyDuty _ TRUE; }; IF (flavor _ classInfo.flavor) = $Basic THEN { doClass_FALSE; token _ token + T2FileOps.default; }; IF classInfo.get#NIL AND bs.data#NIL THEN classData _ NARROW[classInfo.get[bs, $Save]]; -- ELSE classData_NIL doContents _ FALSE; }; bx:TiogaNode.RefBoxNode => { classInfo: TiogaItemClass.ItemClass _ TiogaNodeOps.FetchItemClass[bx.class]; token _ T2FileOps.startBX; IF bx.contents#NIL OR bx.props#NIL THEN { token _ token + T2FileOps.heavyDuty; doHeavyDuty _ TRUE; }; IF (flavor _ classInfo.flavor) = $Box THEN { doClass_FALSE; token _ token + T2FileOps.default; }; IF classInfo.get#NIL AND bx.data#NIL THEN classData _ NARROW[classInfo.get[bx, $Save]]; -- ELSE classData_NIL contents _ bx.contents; }; li:TiogaNode.RefListNode => { classInfo: TiogaItemClass.ItemClass _ TiogaNodeOps.FetchItemClass[li.class]; token _ T2FileOps.startLI; IF li.contents#NIL OR li.props#NIL THEN { token _ token + T2FileOps.heavyDuty; doHeavyDuty _ TRUE; }; IF (flavor _ classInfo.flavor) = $List THEN { doClass_FALSE; token _ token + T2FileOps.default; }; IF classInfo.get#NIL AND li.data#NIL THEN classData _ NARROW[classInfo.get[li, $Save]]; -- ELSE classData_NIL contents _ li.contents; }; ENDCASE => ERROR; IF t.format#TiogaNode.nullName THEN { token _ token + T2FileOps.fmt; doFormat _ TRUE; }; IF t.comment THEN token _ token + T2FileOps.cmt; <<** WRITER **>> WriteChar[token, ctrl]; -- ITEM TOKEN IF doHeavyDuty THEN { -- PROPERTIES and CONTENTS [] _ NodeProps.MapProps[t, OutputProperty, FALSE, FALSE]; IF doContents THEN OutputContents[contents]; -- basics have no conts. }; <> IF classData=NIL THEN WriteChar[T2FileOps.noClassData, ctrl] -- TERMINATOR FOR CONTS ELSE { WriteChar[T2FileOps.startClassData, ctrl]; OutputControlRope[classData]; }; IF doClass THEN OutputAtom[flavor]; -- CLASS ATOM IF doFormat THEN -- FORMAT OutputSymbolTableIndex[NameSymbolTable.RopeFromName[t.format]]; }; WriteChar: PROC [c: CHARACTER, writer: FileWriter.Ref] = INLINE { FileWriter.WriteChar[c, writer] }; <<----------------------------------------------------->> << OFFSET STREAM MANAGEMENT This section contains procedures for creating, writing and reading the offset stream. An offset stream is a linked list of blocks containing offsets - pairs of values of the form (ctrl stream offset, data stream offset). These values are used to compute the child node lengths which are output at the end of a branch.>> NoOfPairs: CARDINAL = 63; OffsetPair: TYPE = RECORD [c, d:LONG CARDINAL]; -- 8 bytes OffsetBlockRef: TYPE = REF OffsetBlock; OffsetBlock: TYPE = RECORD [ next, prev: OffsetBlockRef _ NIL, -- 8 bytes pair: ARRAY [0..NoOfPairs) OF OffsetPair ]; offsetStreamHead: OffsetBlockRef _ NEW[OffsetBlock]; currentBlock: OffsetBlockRef _ offsetStreamHead; nextFreeCell: INTEGER _ 0; node: TiogaNode.Ref; SaveStreamOffsets: PROCEDURE [] = { <> currentBlock.pair[nextFreeCell].c _ ctrl.blockCount*LONG[FileWriter.blockSize]+ctrl.block.length; currentBlock.pair[nextFreeCell].d _ data.blockCount*LONG[FileWriter.blockSize]+data.block.length; nextFreeCell _ nextFreeCell + 1; IF nextFreeCell >= NoOfPairs THEN { IF currentBlock.next=NIL THEN { currentBlock.next _ NEW[OffsetBlock]; currentBlock.next.prev _ currentBlock; }; currentBlock _ currentBlock.next; nextFreeCell _ 0; }; }; OutputHK: PROCEDURE [b: OffsetBlockRef, n:INTEGER] = { <> nSibs: CARDINAL _ 0; this: OffsetPair; c: LONG CARDINAL _ ctrl.blockCount*LONG[FileWriter.blockSize]+ctrl.block.length; d: LONG CARDINAL _ data.blockCount*LONG[FileWriter.blockSize]+data.block.length; UNTIL currentBlock=b AND nextFreeCell=n DO IF nextFreeCell = 0 THEN { -- decrement pair pointer currentBlock _ currentBlock.prev; nextFreeCell _ NoOfPairs-1; IF currentBlock=NIL THEN ERROR; -- unexpected end of chain } ELSE nextFreeCell _ nextFreeCell - 1; this _ currentBlock.pair[nextFreeCell]; OutputB32[c-this.c]; OutputB32[d-this.d]; c _ this.c; d _ this.d; nSibs _ nSibs + 1; ENDLOOP; OutputB32[nSibs]; }; externRopeReader: RopeReader.Ref _ RopeReader.GetRopeReader[]; runReader: RunReader.Ref _ RunReader.GetRunReader[]; OutputBranch[root]; RunReader.FreeRunReader[runReader]; RopeReader.FreeRopeReader[externRopeReader]; }; <<>> WriteMesaFilePlain: PUBLIC PROC [fileName: RopeEdit.ROPE, root: TiogaNode.RefBranchNode]={ h: IO.Handle _ FileIO.Open[fileName: fileName, accessOptions: overwrite]; WritePlain[h, root, TRUE]; IO.Close[h] }; WriteFilePlain: PUBLIC PROC [fileName: RopeEdit.ROPE, root: TiogaNode.RefBranchNode] = { h: IO.Handle _ FileIO.Open[fileName: fileName, accessOptions: overwrite]; WritePlain[h, root]; IO.Close[h] }; WriteFileCPlain: PUBLIC PROC [file: File.Capability, root: TiogaNode.RefBranchNode] = { h: IO.Handle _ FileIO.StreamFromCapability[file]; WritePlain[h, root]; IO.Close[h] }; WriteRopePlain: PUBLIC PROC [root: TiogaNode.RefBranchNode] RETURNS [output: RopeEdit.ROPE] = { h: IO.Handle _ IO.CreateOutputStreamToRope[]; WritePlain[h, root]; RETURN [IO.GetOutputStreamRope[h]] }; WritePlain: PROC [h: IO.Handle, root: TiogaNode.RefBranchNode, restoreDashes: BOOL _ FALSE] = { HasInitialDashes: PROC [r: RopeEdit.ROPE] RETURNS [BOOL] = { loc: INT _ 0; size: INT = Rope.Size[r]; c: CHAR; WHILE loc < size AND RopeEdit.BlankChar[c _ Rope.Fetch[r, loc]] DO loc _ loc+1; ENDLOOP; IF loc > size OR c # '- OR Rope.Fetch[r, loc+1] # '- THEN RETURN [FALSE]; RETURN [TRUE] }; br: TiogaNode.RefBranchNode _ root; node: TiogaNode.Ref; level: INTEGER _ 0; levelDelta: INTEGER; first: BOOL _ TRUE; DO firstItem: BOOL _ TRUE; [br, levelDelta] _ TiogaNodeOps.Forward[br]; IF br=NIL THEN EXIT; IF first THEN first _ FALSE ELSE IO.PutChar[h, '\n]; -- carriage returns between nodes level _ level+levelDelta; IF (node _ TiogaNodeOps.BranchContents[br])=NIL THEN LOOP; THROUGH [1..level) DO IO.PutChar[h, '\t]; ENDLOOP; -- output level-1 tabs DO -- output the contents of the node text: TiogaNode.RefTextNode; IF (text _ TiogaNodeOps.NarrowToTextNode[node]) # NIL THEN { IF firstItem AND restoreDashes AND text.comment AND ~HasInitialDashes[text.rope] THEN IO.PutRope[h, "-- "]; -- restore the leading dashes for Mesa comments IO.PutRope[h, text.rope] }; firstItem _ FALSE; IF node.last AND node.next=br THEN EXIT; -- finished with the branch contents node _ TiogaNodeOps.StepForwardNode[node]; ENDLOOP; ENDLOOP; { ENABLE IO.Error => IF ec = NotImplementedForThisStream THEN GOTO Exit; IO.SetLength[h, IO.GetIndex[h]] } EXITS Exit => RETURN }; END.