<> <> <> <> <> <> <> <> DIRECTORY Atom USING [EmptyAtom], Basics USING [BITXOR, LowHalf], PGSupport USING [FormatHashIndex, formatHashSize, LooksHashIndex, looksHashSize, noLooks, PGF, PGFBody, PropHashIndex, propHashSize], PrincOps USING [zXOR], TextLooks USING [Looks, LooksBytes, noLooks], TiogaFile USING [FileId, fileIdSize, FormatIndex, IntBytes, LengthByte, LooksIndex, numFormats, numLooks, numProps, PropIndex, ThirdByte, trailerLengthSize]; TiogaIOImpl: CEDAR MONITOR IMPORTS Atom, Basics EXPORTS PGSupport = BEGIN OPEN PGSupport; Looks: TYPE ~ TextLooks.Looks; noLooks: Looks ~ TextLooks.noLooks; PropIndex: TYPE ~ TiogaFile.PropIndex; FormatIndex: TYPE ~ TiogaFile.FormatIndex; LooksIndex: TYPE ~ TiogaFile.LooksIndex; <> pgf1, pgf2, pgf3: PGF _ NIL; AllocPGF: ENTRY PROC RETURNS [pgf: PGF] = { ENABLE UNWIND => NULL; IF pgf3 # NIL THEN { pgf _ pgf3; pgf3 _ NIL } ELSE IF pgf2 # NIL THEN { pgf _ pgf2; pgf2 _ NIL } ELSE IF pgf1 # NIL THEN { pgf _ pgf1; pgf1 _ NIL } ELSE pgf _ NEW[PGFBody]; }; FreePGF: PUBLIC ENTRY PROC [pgf: PGF] = { ENABLE UNWIND => NULL; IF pgf3 = pgf OR pgf2 = pgf OR pgf1 = pgf THEN ERROR; IF pgf3 = NIL THEN pgf3 _ pgf ELSE IF pgf2 = NIL THEN pgf2 _ pgf ELSE IF pgf1 = NIL THEN pgf1 _ pgf; }; <> CreatePGF: PROC RETURNS [pgf: PGF] = { pgf _ AllocPGF[]; pgf.looksTable[0] _ TextLooks.noLooks; pgf.looksNext _ 1; -- reserve 0 for noLooks FOR i:LooksHashIndex IN LooksHashIndex DO pgf.looksHashKeys[i].looks _ TextLooks.noLooks; ENDLOOP; pgf.formatTable[0] _ NIL; FOR i:FormatHashIndex IN FormatHashIndex DO pgf.formatHashKeys[i].formatName _ NIL; ENDLOOP; pgf.formatNext _ 1; -- reserve 0 for null formatname pgf.propNext _ 1; --reserve 0 for NIL FOR i:PropHashIndex IN PropHashIndex DO pgf.propHashKeys[i].propname _ NIL; ENDLOOP; [] _ EnterProp[$Prefix, pgf]; -- preload system atoms [] _ EnterProp[$Postfix, pgf]; }; BadIndex: ERROR = CODE; RetrieveFormatName: PROC [index: FormatIndex, pgf: PGF] RETURNS [formatName: ATOM] = { IF index >= pgf.formatNext THEN ERROR BadIndex; RETURN [pgf.formatTable[index]] }; RetrieveLooks: PROC [index: LooksIndex, pgf: PGF] RETURNS [looks: Looks] = { IF index >= pgf.looksNext THEN ERROR BadIndex; RETURN [pgf.looksTable[index]] }; RetrieveProp: PROC [index: PropIndex, pgf: PGF] RETURNS [propname: ATOM] = { IF index >= pgf.propNext THEN ERROR BadIndex; RETURN [pgf.propTable[index]] }; Munch: PROC [key: ATOM] RETURNS [CARDINAL] = TRUSTED MACHINE CODE { PrincOps.zXOR; }; validateMunch: BOOL[TRUE..TRUE] ~ (SIZE[ATOM]-SIZE[CARDINAL] = 1); EnterFormatName: PROC [formatName: ATOM, pgf: PGF] RETURNS [ok: BOOL, index: TiogaFile.FormatIndex] = { next: NAT _ pgf.formatNext; initloc, loc: NAT; IF formatName = NIL OR formatName = Atom.EmptyAtom[] THEN RETURN [TRUE, 0]; -- reserved loc _ initloc _ Munch[formatName] MOD formatHashSize; DO SELECT pgf.formatHashKeys[loc].formatName FROM formatName => RETURN [TRUE, pgf.formatHashVals[loc].index]; NIL => EXIT; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM formatHashSize => IF (loc _ 0)=initloc THEN ERROR; initloc => ERROR; -- should never have full table ENDCASE; ENDLOOP; IF next < TiogaFile.numFormats THEN -- room left in table BEGIN pgf.formatTable[next] _ formatName; pgf.formatNext _ next+1; pgf.formatHashKeys[loc].formatName _ formatName; pgf.formatHashVals[loc].index _ LOOPHOLE[next]; END; RETURN [FALSE, 0] }; -- index irrelevant in this case EnterLooks: PROC [looks: TextLooks.Looks, pgf: PGF] RETURNS [ok: BOOL, index: TiogaFile.LooksIndex] = { next: NAT _ pgf.looksNext; initloc, loc: NAT; IF looks = TextLooks.noLooks THEN RETURN [TRUE, 0]; -- reserved loc _ initloc _ LOOPHOLE[ Basics.BITXOR[ LOOPHOLE[looks, TextLooks.LooksBytes].byte0, Basics.BITXOR[LOOPHOLE[looks, TextLooks.LooksBytes].byte1, LOOPHOLE[looks, TextLooks.LooksBytes].byte2]],NAT] MOD looksHashSize; DO SELECT pgf.looksHashKeys[loc].looks FROM looks => RETURN [TRUE, pgf.looksHashVals[loc].index]; TextLooks.noLooks => EXIT; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM looksHashSize => IF (loc _ 0)=initloc THEN ERROR; initloc => ERROR; -- should never have full table ENDCASE; ENDLOOP; IF next < TiogaFile.numLooks THEN -- room left in table BEGIN pgf.looksTable[next] _ looks; pgf.looksNext _ next+1; pgf.looksHashKeys[loc].looks _ looks; pgf.looksHashVals[loc].index _ LOOPHOLE[next]; END; RETURN [FALSE, 0] }; -- index irrelevant in this case EnterProp: PROC [propname: ATOM, pgf: PGF] RETURNS [ok: BOOL, index: TiogaFile.PropIndex] = { next: NAT _ pgf.propNext; initloc, loc: NAT; IF propname = NIL THEN RETURN [TRUE, 0]; -- reserved loc _ initloc _ (LOOPHOLE[Basics.LowHalf[LOOPHOLE[propname]],NAT] / 16) MOD propHashSize; DO SELECT pgf.propHashKeys[loc].propname FROM propname => RETURN [TRUE, pgf.propHashVals[loc].index]; NIL => EXIT; -- this is an unused entry ENDCASE; SELECT (loc _ loc+1) FROM propHashSize => IF (loc _ 0)=initloc THEN ERROR; initloc => ERROR; -- should never have full table ENDCASE; ENDLOOP; IF next < TiogaFile.numProps THEN -- room left in table BEGIN pgf.propTable[next] _ propname; pgf.propNext _ next+1; pgf.propHashKeys[loc].propname _ propname; pgf.propHashVals[loc].index _ LOOPHOLE[next]; END; RETURN [FALSE, 0] }; -- index irrelevant in this case PutLength: PUBLIC PROC [put: PROC [CHAR], len: INT] = { first, second, fourth: TiogaFile.LengthByte; third: TiogaFile.ThirdByte; intBytes: TiogaFile.IntBytes _ LOOPHOLE[len]; IF intBytes.fourth#0 THEN { fourth.data _ intBytes.fourth; first.others _ second.others _ third.others _ TRUE }; IF intBytes.thirdTop#0 OR intBytes.thirdBottom#0 THEN { third.dataTop _ intBytes.thirdTop; third.dataBottom _ intBytes.thirdBottom; first.others _ second.others _ TRUE; }; IF intBytes.second#0 THEN { second.data _ intBytes.second; first.others _ TRUE; }; first.data _ intBytes.first; put[LOOPHOLE[first]]; IF first.others THEN { put[LOOPHOLE[second]]; IF second.others THEN { put[LOOPHOLE[third]]; IF third.others THEN { put[LOOPHOLE[fourth]]; }; }; }; }; GetLength: PUBLIC PROC [get: PROC RETURNS [CHAR]] RETURNS [INT] ~ { first, second, fourth: TiogaFile.LengthByte; third: TiogaFile.ThirdByte; intBytes: TiogaFile.IntBytes _ []; first _ LOOPHOLE[get[]]; intBytes.first _ first.data; IF NOT first.others THEN RETURN [LOOPHOLE[intBytes]]; second _ LOOPHOLE[get[]]; intBytes.second _ second.data; IF NOT second.others THEN RETURN [LOOPHOLE[intBytes]]; third _ LOOPHOLE[get[]]; intBytes.thirdBottom _ third.dataBottom; intBytes.thirdTop _ third.dataTop; IF NOT third.others THEN RETURN [LOOPHOLE[intBytes]]; fourth _ LOOPHOLE[get[]]; intBytes.fourth _ fourth.data; RETURN [LOOPHOLE[intBytes]]; }; fileIdSize: INT ~ TiogaFile.fileIdSize; FileIdIndex: TYPE ~ [0..TiogaFile.fileIdSize); PutFileId: PUBLIC PROC [put: PROC [CHAR], id: TiogaFile.FileId] ~ { FOR i: FileIdIndex IN FileIdIndex DO put[id[i]] ENDLOOP; }; GetFileId: PUBLIC PROC [get: PROC RETURNS [CHAR]] RETURNS [id: TiogaFile.FileId] ~ { FOR i: FileIdIndex IN FileIdIndex DO id[i] _ get[] ENDLOOP; }; lenSize: INT ~ TiogaFile.trailerLengthSize; LenIndex: TYPE ~ [0..lenSize); LenBytes: TYPE ~ PACKED ARRAY LenIndex OF CHAR; PutTrailerLength: PUBLIC PROC [put: PROC [CHAR], len: INT] ~ { FOR i: LenIndex IN LenIndex DO put[LOOPHOLE[len, LenBytes][i]] ENDLOOP; }; GetTrailerLength: PUBLIC PROC [get: PROC RETURNS [CHAR]] RETURNS [len: INT] ~ { FOR i: LenIndex IN LenIndex DO LOOPHOLE[len, LenBytes][i] _ get[] ENDLOOP; }; <> SimpleDoc: PROC [root: Node] RETURNS [simple: BOOL _ FALSE, rope: ROPE _ NIL] = { HasInterestingProp: PROC [node: Node] RETURNS [BOOL] = { interestingProp: PROC [name: ATOM, value: REF] RETURNS [BOOL] = { RETURN [SELECT name FROM $Viewer, $LockedViewer, $FromTiogaFile, $DocumentLock, $FileCreateDate, $FileExtension => FALSE, <> ENDCASE => TRUE]; }; RETURN [Tioga.MapProps[node, interestingProp, FALSE, FALSE]]; }; NoLooks: PROC [node: Node] RETURNS [BOOL] = { RETURN[TextLooks.CountRuns[runs: node.runs, start: 0, len: Tioga.Size[node], merge: TRUE, firstLooks: TextLooks.noLooks].count=0]; }; SimpleNode: PROC [node: Node] RETURNS [simple: BOOL _ FALSE, rope: ROPE _ NIL] = { IF (node.formatName=NIL) AND (NOT node.comment) AND (NOT HasInterestingProp[node]) AND (node.runs=NIL OR NoLooks[node]) THEN RETURN [TRUE, node.rope]; }; IF root=NIL THEN RETURN [TRUE, NIL]; [simple, rope] _ SimpleNode[root]; IF NOT simple THEN RETURN; -- not a simple root node IF root.child=NIL THEN RETURN; -- simple root and no child IF rope#NIL THEN RETURN [FALSE, NIL]; -- root has child and text, so not simple IF root.child.last AND root.child.child=NIL THEN [simple, rope] _ SimpleNode[root.child] ELSE RETURN [FALSE, NIL]; -- more than one child, so not simple }; PutDoc: PROC [data, comment, control: IO.STREAM, root: Node, flatten: BOOL] = { WriteControlChar: PROC [c: CHAR] ~ { IO.PutChar[control, c] }; WriteLen: PROC [len: INT] ~ INLINE { PGSupport.PutLength[WriteControlChar, len] }; WriteControlRope: PROC [r: ROPE] ~ { WriteLen[Rope.Size[r]]; IO.PutRope[control, r]; }; WriteRope: PROC [r: ROPE, stream: IO.STREAM] = { WriteLen[Rope.Size[r]]; IO.PutRope[stream, r]; IO.PutChar[stream, 15C]; }; CountLookBits: PROC [lks: TextLooks.Looks] RETURNS [cnt: NAT _ 0] = { FOR c: TextLooks.Look IN TextLooks.Look DO IF lks[c] THEN cnt _ cnt+1; ENDLOOP; }; WriteLookChars: PROC [lks: TextLooks.Looks] = { FOR c: TextLooks.Look IN TextLooks.Look DO IF lks[c] THEN WriteControlChar[c]; ENDLOOP; }; WriteLookBits: PROC [lks: TextLooks.Looks] = { bytes: TextLooks.LooksBytes ~ LOOPHOLE[lks]; WriteControlChar[LOOPHOLE[bytes.byte0]]; WriteControlChar[LOOPHOLE[bytes.byte1]]; WriteControlChar[LOOPHOLE[bytes.byte2]]; WriteControlChar[LOOPHOLE[bytes.byte3]]; }; pgf: PGSupport.PGF ~ PGSupport.CreatePGF[]; runReader: RunReader.Ref ~ RunReader.GetRunReader[]; ropeReader: RopeReader.Ref ~ RopeReader.GetRopeReader[]; node: Node _ root; DO child: Node ~ Tioga.FirstChild[node]; terminal: BOOL ~ (child=NIL); { -- write format formatName: ATOM ~ Tioga.GetFormat[node]; ok: BOOL; formatindex: TiogaFile.FormatIndex; [ok, formatindex] _ PGSupport.EnterFormatName[formatName, pgf]; IF ok THEN { WriteControlChar[(IF terminal THEN TiogaFile.terminalTextNodeFirst ELSE TiogaFile.startNodeFirst)+formatindex]; } ELSE { WriteControlChar[IF terminal THEN TiogaFile.terminalTextNode ELSE TiogaFile.startNode]; WriteControlRope[IF formatName=NIL THEN NIL ELSE Atom.GetPName[formatName]]; }; }; { -- write node props writeProp: PROC [name: ATOM, value: REF] RETURNS [quit: BOOL _ FALSE] = { specs: ROPE ~ Tioga.WriteProp[name, value]; -- write specs as a rope IF specs#NIL THEN { ok: BOOL; propindex: TiogaFile.PropIndex; [ok, propindex] _ PGSupport.EnterProp[name, pgf]; IF ok THEN { -- can use short form WriteControlChar[TiogaFile.propShort]; WriteControlChar[VAL[propindex]]; } ELSE { -- must write full prop name WriteControlChar[TiogaFile.prop]; WriteControlRope[Atom.GetPName[name]]; }; WriteControlRope[specs]; }; }; IF node.props#NIL THEN [] _ Tioga.MapProps[node, writeProp, FALSE, FALSE]; }; { -- now write contents size: INT ~ Tioga.Size[node]; rope: ROPE ~ node.rope; runs: TextLooks.Runs ~ node.runs; IF flatten THEN { -- flatten rope and runs node.rope _ Rope.Balance[rope]; node.runs _ TextLooks.Flatten[runs]; }; IF runs#NIL THEN { -- write looks, if any loc, numRuns: INT _ 0; [numRuns, , ] _ TextLooks.CountRuns[runs, 0, size]; WriteControlChar[TiogaFile.runs]; WriteLen[numRuns]; RunReader.SetPosition[runReader, runs, 0]; THROUGH [0..numRuns) DO len: INT; looks: TextLooks.Looks; ok: BOOL; looksindex: TiogaFile.LooksIndex; [len, looks] _ RunReader.MergedGet[runReader]; [ok, looksindex] _ PGSupport.EnterLooks[looks, pgf]; IF ok THEN WriteControlChar[TiogaFile.looksFirst+looksindex] ELSE { -- must write out the looks SELECT CountLookBits[looks] FROM 1 => { WriteControlChar[TiogaFile.look1]; WriteLookChars[looks] }; 2 => { WriteControlChar[TiogaFile.look2]; WriteLookChars[looks] }; 3 => { WriteControlChar[TiogaFile.look3]; WriteLookChars[looks] }; ENDCASE => { WriteControlChar[TiogaFile.looks]; WriteLookBits[looks] }; }; WriteLen[len]; loc _ loc+len; ENDLOOP; IF loc#size THEN ERROR; }; IF node.comment THEN { -- put text in comment area of file WriteControlChar[TiogaFile.comment]; WriteRope[rope, comment]; } ELSE { -- put text in data area of file WriteControlChar[TiogaFile.rope]; WriteRope[rope, data]; }; }; <> IF NOT terminal THEN node _ child ELSE DO -- node has no children IF node=root THEN GOTO Finis; IF NOT Tioga.IsLastSibling[node] THEN { node _ Tioga.Next[node]; EXIT } ELSE { node _ Tioga.Parent[node]; WriteControlChar[TiogaFile.endNode] }; ENDLOOP; REPEAT Finis => { WriteControlChar[TiogaFile.endOfFile]; RunReader.FreeRunReader[runReader]; RopeReader.FreeRopeReader[ropeReader]; PGSupport.FreePGF[pgf] }; ENDLOOP; }; ToStream: PUBLIC PROC [stream: IO.STREAM, node: Node, flatten, textOnly: BOOL _ FALSE] RETURNS [dataLen, count: INT] = { simple: BOOL; rope: ROPE; [simple, rope] _ SimpleDoc[node]; IF simple THEN { IO.PutRope[stream, rope]; dataLen _ count _ Rope.Size[rope] } ELSE { dataStart: INT ~ IO.GetIndex[stream]; comment, control: IO.STREAM _ NIL; IF textOnly THEN comment _ control _ IO.noWhereStream ELSE { comment _ IO.ROS[]; control _ IO.ROS[] }; PutDoc[data: stream, comment: comment, control: control, root: node, flatten: flatten]; dataLen _ IO.GetIndex[stream]-dataStart; IF textOnly THEN count _ dataLen ELSE { PutChar: PROC [c: CHAR] ~ { IO.PutChar[stream, c] }; PutId: PROC [id: TiogaFile.FileId] ~ INLINE { PGSupport.PutFileId[PutChar, id] }; PutLen: PROC [len: INT] ~ INLINE { PGSupport.PutTrailerLength[PutChar, len] }; PutRope: PROC [rope: ROPE] ~ INLINE { IO.PutRope[stream, rope] }; commentRope: ROPE ~ IO.RopeFromROS[comment]; controlRope: ROPE ~ IO.RopeFromROS[control]; commentLen: INT ~ TiogaFile.fileIdSize+TiogaFile.trailerLengthSize+ Rope.Size[commentRope]; controlLen: INT ~ TiogaFile.fileIdSize+TiogaFile.trailerLengthSize+ Rope.Size[controlRope]+TiogaFile.endSize; count _ dataLen+commentLen+controlLen; PutId[TiogaFile.commentHeaderId]; PutLen[commentLen]; PutRope[commentRope]; PutId[TiogaFile.controlHeaderId]; PutLen[controlLen]; PutRope[controlRope]; PutId[TiogaFile.controlTrailerId]; PutLen[0]; PutLen[dataLen]; PutLen[count]; IF (IO.GetIndex[stream]-dataStart)#count THEN ERROR; }; }; }; ToRope: PUBLIC PROC [node: Node, flatten, textOnly: BOOL _ FALSE] RETURNS [dataLen, count: INT, output: ROPE] = { simple: BOOL; [simple, output] _ SimpleDoc[node]; IF simple THEN { dataLen _ count _ Rope.Size[output] } ELSE { stream: IO.STREAM ~ IO.ROS[]; [dataLen, count] _ ToStream[stream, node, flatten, textOnly]; output _ IO.RopeFromROS[stream]; }; }; CreateFile: PROC [fileName: ROPE] RETURNS [FS.OpenFile] ~ { keep: INT ~ UserProfile.Number["Tioga.defaultKeep", 2]; RETURN[FS.Create[name: fileName, keep: keep]]; }; ToFile: PUBLIC PROC [fileName: ROPE, node: Node, start: INT _ 0, flatten, textOnly: BOOL _ FALSE] RETURNS [dataLen, count: INT] = { createName: ROPE ~ fileName.Flatten[0, fileName.SkipTo[0, "!"]]; -- strip version, if any file: FS.OpenFile ~ CreateFile[createName]; [dataLen, count] _ ToFileC[file, node, start, flatten, textOnly]; }; ToFileC: PUBLIC PROC [file: FS.OpenFile, node: Node, start: INT _ 0, flatten, textOnly: BOOL _ FALSE] RETURNS [dataLen, count: INT] = { innerFileIt: PROC ~ { stream: IO.STREAM ~ FS.StreamFromOpenFile[file, $write]; IF start#0 THEN IO.SetIndex[stream, start]; [dataLen, count] _ ToStream[stream, node, flatten, textOnly]; }; CedarProcess.DoWithPriority[savePriority, innerFileIt]; }; savePriority: CedarProcess.Priority _ normal; WritePlain: PUBLIC PROC [h: IO.STREAM, root: Node, restoreDashes: BOOL _ FALSE] = { HasInitialDashes: PROC [r: 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-2 OR c # '- OR Rope.Fetch[r, loc+1] # '- THEN RETURN [FALSE]; RETURN [TRUE] }; node: Node _ root; level: INTEGER _ 0; levelDelta: INTEGER; first: BOOL _ TRUE; DO [node, levelDelta] _ Tioga.Forward[node]; IF node=NIL THEN EXIT; IF first THEN first _ FALSE ELSE IO.PutChar[h, '\n]; -- carriage returns between nodes level _ level+levelDelta; THROUGH [1..level) DO IO.PutChar[h, '\t]; ENDLOOP; -- output level-1 tabs IF restoreDashes AND node.comment AND NOT HasInitialDashes[node.rope] THEN IO.PutRope[h, "-- "]; -- restore the leading dashes for Mesa comments IO.PutRope[h, node.rope]; ENDLOOP; { ENABLE IO.Error => IF ec=NotImplementedForThisStream THEN CONTINUE; IO.SetLength[h, IO.GetIndex[h]] }; }; WriteFilePlain: PUBLIC PROC [fileName: ROPE, root: Node] = { h: IO.STREAM ~ FS.StreamFromOpenFile[CreateFile[fileName], $write]; WritePlain[h, root]; IO.Close[h]; }; WriteFileCPlain: PUBLIC PROC [file: FS.OpenFile, root: Node] = { h: IO.STREAM ~ FS.StreamFromOpenFile[file, $write]; WritePlain[h, root]; IO.Close[h]; }; WriteMesaFilePlain: PUBLIC PROC [fileName: ROPE, root: Node] = { h: IO.STREAM ~ FS.StreamFromOpenFile[CreateFile[fileName], $write]; WritePlain[h, root, TRUE]; IO.Close[h]; }; WriteRopePlain: PUBLIC PROC [root: Node, restoreDashes: BOOL _ FALSE] RETURNS [output: ROPE] = { h: IO.STREAM = IO.ROS[]; WritePlain[h, root, restoreDashes]; RETURN [IO.RopeFromROS[h]] }; <> Open: PUBLIC PROC [fileName: ROPE, start, len: INT] RETURNS [control, comment, text: RopeReader.Ref, tiogaFile: BOOL, fh: FS.OpenFile, createDate: BasicTime.GMT] = { fh _ FS.Open[fileName]; [control, comment, text, tiogaFile, createDate] _ OpenC[fh, start, len]; }; OpenC: PUBLIC PROC [file: FS.OpenFile, start, len: INT] RETURNS [control, comment, text: RopeReader.Ref, tiogaFile: BOOL, createDate: BasicTime.GMT] = { Substr: PROC [start, len: INT] RETURNS [Rope.ROPE] = { RETURN [RopeIO.FromFileC[openFile: file, start: start, len: len]]; }; fileLen: INT; [bytes: fileLen, created: createDate] _ FS.GetInfo[file]; [control, comment, text, tiogaFile] _ DoOpen[fileLen, start, len, Substr]; }; DoOpen: PROC [size, start, len: INT, Substr: PROC [start, len: INT] RETURNS [ROPE]] RETURNS [control, comment, text: RopeReader.Ref, tiogaFile: BOOL] = { text _ RopeReader.Create[]; comment _ RopeReader.Create[]; control _ RopeReader.Create[]; tiogaFile _ TRUE; { -- for EXIT trailerLen: INT ~ TiogaFile.endSize; propsLen, textLen, fileLen, commentLen, controlLen: INT; commentStart, controlStart, trailerStart: INT; start _ MAX[0, MIN[start, size]]; len _ MAX[0, MIN[len, size-start]]; IF NOT len>trailerLen THEN GOTO FakeIt; trailerStart _ start+len-trailerLen; RopeReader.SetPosition[control, Substr[trailerStart, trailerLen]]; IF NOT ReadFileId[control, TiogaFile.controlTrailerId] THEN GOTO FakeIt; IF NOT (propsLen _ ReadLen[control]) IN [0..len-trailerLen) THEN GOTO FakeIt; IF NOT (textLen _ ReadLen[control]) IN [0..len-trailerLen) THEN GOTO FakeIt; IF NOT (fileLen _ ReadLen[control])=len THEN GOTO FakeIt; commentStart _ start+textLen; RopeReader.SetPosition[comment, Substr[commentStart, trailerStart-commentStart]]; IF NOT ReadFileId[comment, TiogaFile.commentHeaderId] THEN GOTO FakeIt; commentLen _ ReadLen[comment]; controlStart _ commentStart+commentLen; RopeReader.SetPosition[control, Substr[controlStart, trailerStart-controlStart]]; IF NOT ReadFileId[control, TiogaFile.controlHeaderId] THEN GOTO FakeIt; controlLen _ ReadLen[control]; IF NOT (textLen+commentLen+controlLen)=fileLen THEN GOTO FakeIt; RopeReader.SetPosition[text, Substr[start, textLen]]; EXITS FakeIt => { RopeReader.SetPosition[text, Substr[start, len]]; RopeReader.SetPosition[comment, NIL]; RopeReader.SetPosition[control, PhonyControl[len]]; tiogaFile _ FALSE; }; }; }; ReadFileId: PROC [rdr: RopeReader.Ref, fileId: TiogaFile.FileId] RETURNS [BOOL] = { get: PROC RETURNS [CHAR] ~ { RETURN[RopeReader.Get[rdr]] }; actual: TiogaFile.FileId ~ PGSupport.GetFileId[get]; RETURN [actual=fileId]; }; ReadLen: PROC [rdr: RopeReader.Ref] RETURNS [INT] = { get: PROC RETURNS [CHAR] ~ { RETURN[RopeReader.Get[rdr]] }; RETURN[PGSupport.GetLength[get]]; }; PhonyControl: PROC [len: INT] RETURNS [ROPE] = { stream: IO.STREAM ~ IO.ROS[]; Put: PROC [c: CHAR] ~ { IO.PutChar[stream, c] }; Put[TiogaFile.startNode]; -- start root node Put[VAL[0]]; -- null format for root Put[TiogaFile.terminalTextNode]; Put[VAL[0]]; -- null format for node Put[TiogaFile.rope]; -- rope for node PGSupport.PutLength[Put, len]; -- length of rope for node Put[TiogaFile.endNode]; -- end of root Put[TiogaFile.endOfFile]; RETURN[IO.RopeFromROS[stream]]; }; FromFileError: PUBLIC ERROR = CODE; FromFile: PUBLIC PROC [fileName: ROPE, start: INT _ 0, len: INT _ Tioga.maxLen] RETURNS [root: Node] = { control, comment, stext: RopeReader.Ref; tiogaFile: BOOL; fh: FS.OpenFile; createDate: BasicTime.GMT; StartRead[]; { ENABLE UNWIND => EndRead[]; [control, comment, stext, tiogaFile, fh, createDate] _ Open[fileName, start, len]; root _ Finish[control, comment, stext, tiogaFile]; }; AddFileExtension[root, fh]; AddCreateDate[root, createDate]; EndRead[]; }; FromFileC: PUBLIC PROC [file: FS.OpenFile, start: INT _ 0, len: INT _ Tioga.maxLen] RETURNS [root: Node] = { control, comment, stext: RopeReader.Ref; tiogaFile: BOOL; createDate: BasicTime.GMT; StartRead[]; { ENABLE UNWIND => EndRead[]; [control, comment, stext, tiogaFile, createDate] _ OpenC[file, start, len]; root _ Finish[control, comment, stext, tiogaFile]; }; AddFileExtension[root, file]; AddCreateDate[root, createDate]; EndRead[]; }; AddFileExtension: PROC [root: Node, file: FS.OpenFile] = { ForceLower: PROC [r: ROPE] RETURNS [ROPE] = { ForceCharLower: PROC [old: CHAR] RETURNS [new: CHAR] = { RETURN [Ascii.Lower[old]]; }; RETURN [Rope.Translate[base: r, translator: ForceCharLower]]; }; name: ROPE; cp: FS.ComponentPositions; [fullFName: name, cp: cp] _ FS.ExpandName[name: FS.GetName[file].fullFName]; TiogaPrivate.PutProp[root, $FileExtension, IF cp.ext.length=0 THEN $null ELSE Atom.MakeAtom[ForceLower[name.Substr[cp.ext.start, cp.ext.length]]]]; }; AddCreateDate: PROC [root: Node, createDate: BasicTime.GMT] = { TiogaPrivate.PutProp[root, $FileCreateDate, NEW[BasicTime.GMT _ createDate]]; }; FromRope: PUBLIC PROC [rope: ROPE, start: INT _ 0, len: INT _ Tioga.maxLen] RETURNS [root: Node] = { control, comment, stext: RopeReader.Ref; tiogaFile: BOOL; size: INT ~ Rope.Size[rope]; Substr: PROC [start, len: INT] RETURNS [ROPE] = { RETURN [Rope.Substr[rope, start, len]] }; start _ MAX[0, MIN[start, size]]; len _ MAX[0, MIN[len, size-start]]; StartRead[]; { ENABLE UNWIND => EndRead[]; [control, comment, stext, tiogaFile] _ DoOpen[size, start, len, Substr]; root _ Finish[control, comment, stext, tiogaFile]; }; EndRead[]; }; FromStream: PUBLIC PROC [stream: IO.STREAM, len: INT _ Tioga.maxLen] RETURNS [root: Node] = { rope: ROPE ~ RopeIO.GetRope[stream, len]; RETURN FromRope[rope]; }; collectionInterval: LONG CARDINAL; bigCollectionInterval: LONG CARDINAL = 1000000; startCount: INTEGER _ 0; StartRead: ENTRY PROC = { ENABLE UNWIND => NULL; IF startCount = 0 THEN { collectionInterval _ SafeStorage.SetCollectionInterval[bigCollectionInterval]; }; startCount _ startCount+1; }; EndRead: ENTRY PROC = { -- restore initial collectionInterval when all Get's done ENABLE UNWIND => NULL; startCount _ startCount-1; IF startCount = 0 THEN { [] _ SafeStorage.SetCollectionInterval[collectionInterval]; }; }; Finish: PROC [control, cmmnt, stext: RopeReader.Ref, tiogaFile: BOOL] RETURNS [root: Node] = { op: Op; parent, node, prev: Node; terminalNode: BOOL; textLength, runsLength: INT _ 0; formatName: ATOM; propname: ATOM; charProps: REF _ NIL; charSets: REF _ NIL; InsertNode: PROC [node: Node] = { IF prev#NIL THEN prev.next _ node ELSE IF parent#NIL THEN parent.child _ node; prev _ node; }; ReadByte: PROC RETURNS [Byte] = INLINE { RETURN [LOOPHOLE[ReadChar[]]]; }; ReadChar: PROC RETURNS [CHAR] = { RETURN [RopeReader.Get[control]]; }; ReadProp: PROC = { specs: ROPE _ GetControlRope[]; disaster: BOOL _ FALSE; value: REF; value _ Tioga.ReadProp[propname, specs ! RuntimeError.UNCAUGHT => {disaster_TRUE} ]; IF disaster THEN ERROR FromFileError; IF value # NIL THEN { <> IF propname = $CharProps THEN charProps _ value ELSE IF propname = $CharSets THEN charSets _ value ELSE TiogaPrivate.PutProp[node, propname, value]; }; }; GetRope: PROC [len: INT, rdr: RopeReader.Ref] RETURNS [ROPE] = { rope: ROPE; pos: INT; [rope, pos] _ RopeReader.Position[rdr]; RopeReader.SetIndex[rdr, pos+len]; RETURN [Rope.Substr[rope, pos, len]]; }; GetControlRope: PROC RETURNS [ROPE] = { RETURN [GetRope[ReadLength[], control]]; }; GetText: PROC = { len: NAT; IF (len _ ReadByte[]) > text.maxLength THEN text _ NEW[TEXT[len]]; FOR i: NAT IN [0..len) DO text[i] _ RopeReader.Get[control]; ENDLOOP; text.length _ len; }; GetAtom: PROC RETURNS [ATOM] = { GetText[]; -- get the print name RETURN [IF text.length = 0 THEN NIL ELSE Atom.MakeAtomFromRefText[text]]; }; ReadLength: PROC RETURNS [INT] = { RETURN [PGSupport.GetLength[ReadChar]]; }; NextOp: PROC RETURNS [op: Op] = { op _ RopeReader.Get[control ! RopeReader.ReadOffEnd => { op _ endOfFile; CONTINUE }]; }; <
> pgf: PGSupport.PGF _ PGSupport.CreatePGF[]; text: REF TEXT _ NEW[TEXT[32]]; terminalNode _ FALSE; op _ NextOp[]; DO SELECT op FROM IN [terminalTextNodeFirst..terminalTextNodeLast] => { formatName _ PGSupport.RetrieveFormatName[ LOOPHOLE[op-terminalTextNodeFirst, FormatIndex], pgf ! PGSupport.BadIndex => ERROR FromFileError ]; terminalNode _ TRUE; }; IN [startNodeFirst..startNodeLast] => formatName _ PGSupport.RetrieveFormatName[ LOOPHOLE[op-startNodeFirst, FormatIndex], pgf ! PGSupport.BadIndex => ERROR FromFileError ]; endNode => { IF prev#NIL THEN { prev.last _ TRUE; prev.next _ parent;}; prev _ parent; IF (parent _ parent.next)=NIL THEN EXIT; op _ NextOp[]; LOOP; }; startNode => { formatName _ GetAtom[]; [] _ PGSupport.EnterFormatName[formatName, pgf]; }; terminalTextNode => { formatName _ GetAtom[]; [] _ PGSupport.EnterFormatName[formatName, pgf]; terminalNode _ TRUE; }; rope, comment => { reader: RopeReader.Ref; IF op=rope THEN reader _ stext ELSE { reader _ cmmnt; node.comment _ TRUE;}; IF node=NIL THEN ERROR FromFileError; IF (textLength_ReadLength[]) > 0 THEN -- get the rope node.rope _ GetRope[textLength, reader]; IF charProps#NIL THEN {TiogaPrivate.PutProp[node,$CharProps,charProps]; charProps_NIL}; IF charSets#NIL THEN {TiogaPrivate.PutProp[node, $CharSets, charSets]; charSets _ NIL}; SELECT runsLength FROM 0 => NULL; -- no runs for this rope textLength => runsLength _ 0; -- correct length ENDCASE => ERROR FromFileError; -- mismatch RopeReader.BumpIndex[reader, 1]; -- skip CR at end of rope op _ NextOp[]; LOOP; }; runs => { -- runs, if any, come before corresponding rope lookRuns: TextLooks.Runs; numRuns: INT; pos: INT _ 0; IF node=NIL THEN ERROR FromFileError; numRuns _ ReadLength[]; -- read number of runs WHILE numRuns > 0 DO num: NAT _ MIN[numRuns, LAST[NAT]]; baseRuns: TextLooks.BaseRuns _ TextLooksSupport.NewBase[num]; len: INT _ pos; numRuns _ numRuns-num; FOR i:NAT IN [0..num) DO -- read runs for this baseRuns looks: TextLooks.Looks; ReadLookChars: PROC [num: NAT] = { FOR i:NAT IN [0..num) DO looks[ReadChar[]] _ TRUE; ENDLOOP; [] _ PGSupport.EnterLooks[looks, pgf]; }; SELECT op _ NextOp[] FROM IN [looksFirst .. looksLast] => { looks _ PGSupport.RetrieveLooks[ LOOPHOLE[op-looksFirst, LooksIndex], pgf ! PGSupport.BadIndex => ERROR FromFileError ] }; look1 => ReadLookChars[1]; look2 => ReadLookChars[2]; look3 => ReadLookChars[3]; TiogaFile.looks => { <> lb: TextLooks.LooksBytes; lb.byte0 _ ReadByte[]; lb.byte1 _ ReadByte[]; lb.byte2 _ ReadByte[]; lb.byte3 _ ReadByte[]; [] _ PGSupport.EnterLooks[looks _ LOOPHOLE[lb], pgf]; }; ENDCASE => ERROR FromFileError; baseRuns[i] _ [pos_pos+ReadLength[], looks]; ENDLOOP; lookRuns _ IF lookRuns=NIL THEN baseRuns ELSE TextLooks.Concat[lookRuns, baseRuns, len, pos-len]; ENDLOOP; runsLength _ pos; -- for use in checking rope length node.runs _ lookRuns; op _ NextOp[]; LOOP; }; prop => { [] _ PGSupport.EnterProp[propname _ GetAtom[], pgf]; ReadProp; op _ NextOp[]; LOOP; }; propShort => { propname _ PGSupport.RetrieveProp[ LOOPHOLE[ReadByte[], PropIndex], pgf ! PGSupport.BadIndex => ERROR FromFileError ]; ReadProp; op _ NextOp[]; LOOP; }; otherNode, otherNodeShort, otherNodeSpecs, otherNodeSpecsShort => { ERROR FromFileError; }; endOfFile => { IF parent=NIL THEN EXIT; -- have reached the root [] _ RopeReader.Backwards[control]; -- backup so read endOfFile again op _ endNode; LOOP; }; ENDCASE => ERROR FromFileError; <> IF charProps # NIL OR charSets # NIL THEN ERROR FromFileError; InsertNode[node _ NEW[Tioga.NodeBody]]; node.formatName _ formatName; IF terminalNode THEN terminalNode _ FALSE ELSE { node.next _ parent; parent _ node; prev _ NIL }; op _ NextOp[]; ENDLOOP; IF (root _ prev)=NIL THEN { -- don't have a normal tree IF node=NIL THEN root _ NEW[Tioga.NodeBody] -- null file ELSE root _ node; -- single node in file }; root.last _ TRUE; TiogaPrivate.PutProp[root, $FromTiogaFile, IF tiogaFile THEN $Yes ELSE $No]; PGSupport.FreePGF[pgf]; }; ReadIndent: PUBLIC PROC [fileName: ROPE, tabIndent: NAT _ 4] RETURNS [root: Node] = { input: ROPE _ RopeIO.FromFile[fileName]; rdr: RopeReader.Ref _ RopeReader.GetRopeReader[]; maxOpen: NAT = 40; openNodes: ARRAY [0..maxOpen] OF Node; openIndents: ARRAY [0..maxOpen] OF INTEGER; level, lvl: NAT; indent: INTEGER; node: Node; noMoreInput: BOOL _ FALSE; ReadLine: PROC RETURNS [ROPE] = { -- read line and set indent tab: CHAR=Ascii.TAB; space: CHAR=Ascii.SP; cr: CHAR=Ascii.CR; start, end: INT _ 0; indent _ 0; DO SELECT RopeReader.Get[rdr ! RopeReader.ReadOffEnd => { noMoreInput _ TRUE; EXIT }] FROM cr => EXIT; -- blank line tab => indent _ indent+tabIndent; space => indent _ indent+1; 0C => { noMoreInput _ TRUE; EXIT }; -- stop at first NULL char ENDCASE => { -- just saw the first nonblank char on the line start _ RopeReader.GetIndex[rdr]-1; DO SELECT RopeReader.Get[rdr ! RopeReader.ReadOffEnd => { end _ RopeReader.GetIndex[rdr]; noMoreInput _ TRUE; EXIT }] FROM cr => { end _ RopeReader.GetIndex[rdr]-1; EXIT }; 0C => { end _ RopeReader.GetIndex[rdr]-1; noMoreInput _ TRUE; EXIT }; ENDCASE; ENDLOOP; EXIT; }; ENDLOOP; RETURN [Rope.Substr[input, start, end-start]]; }; PlaceNode: PROC = { dest: Node; SELECT lvl FROM < level => { -- insert as sibling of node at lvl+1 dest _ openNodes[level _ lvl+1]; dest.last _ FALSE; node.next _ dest.next; dest.next _ node; }; = maxOpen => { -- insert as sibling of node at max level dest _ openNodes[level]; dest.last _ FALSE; node.next _ dest.next; dest.next _ node; }; ENDCASE => { -- increase level dest _ openNodes[level]; node.next _ dest; dest.child _ node; level _ level+1; }; node.last _ TRUE; openIndents[level] _ indent; openNodes[level] _ node; }; RopeReader.SetPosition[rdr,input,0]; openNodes[0] _ root _ Tioga.NewNode[]; root.last _ TRUE; openIndents[0] _ -1; level _ 0; UNTIL noMoreInput DO node _ Tioga.NewNode[]; IF (node.rope _ ReadLine[])=NIL THEN indent _ MAX[0,openIndents[level]]; FOR lvl _ level, lvl-1 DO IF openIndents[lvl] < indent THEN { PlaceNode[]; EXIT }; ENDLOOP; ENDLOOP; RopeReader.FreeRopeReader[rdr]; }; END.