<> <> <> <> <> <> <> <> <> DIRECTORY Ascii USING [CR, Lower, SP, TAB], Atom USING [MakeAtom, MakeAtomFromRefText], BasicTime USING [GMT], FS USING [ComponentPositions, ExpandName, GetInfo, GetName, Open, OpenFile, Position], IO USING [PutChar, RopeFromROS, ROS, STREAM], PGSupport USING [BadIndex, CreatePGF, EnterFormatName, EnterLooks, EnterProp, FreePGF, GetFileId, GetLength, GetTrailerLength, PGF, PutLength, RetrieveFormatName, RetrieveLooks, RetrieveProp], Rope USING [ROPE, Size, Substr, Translate], RopeIO USING [FromFile, FromFileC, GetRope], RopeReader USING [Backwards, BumpIndex, Create, FreeRopeReader, Get, GetIndex, GetRope, GetRopeReader, Position, ReadOffEnd, Ref, SetIndex, SetPosition], RuntimeError USING [UNCAUGHT], SafeStorage USING [SetCollectionInterval], TextLooks USING [BaseRuns, Concat, Looks, LooksBytes, Runs], TextLooksSupport USING [NewBase], Tioga USING [maxLen, NewNode, Node, NodeBody, ReadProp], TiogaFile USING [comment, commentHeaderId, controlHeaderId, controlTrailerId, endNode, endOfFile, endSize, FileId, FormatIndex, look1, look2, look3, looks, looksFirst, LooksIndex, looksLast, Op, otherNode, otherNodeShort, otherNodeSpecs, otherNodeSpecsShort, prop, PropIndex, propShort, rope, runs, startNode, startNodeFirst, startNodeLast, terminalTextNode, terminalTextNodeFirst, terminalTextNodeLast], TiogaPrivate USING [PutProp]; GetFileImpl: CEDAR MONITOR IMPORTS Ascii, Atom, FS, IO, PGSupport, Rope, RopeIO, RopeReader, RuntimeError, SafeStorage, TextLooks, TextLooksSupport, Tioga, TiogaPrivate EXPORTS Tioga SHARES TextLooks = BEGIN OPEN TiogaFile; ROPE: TYPE = Rope.ROPE; Byte: TYPE = [0..255]; Node: TYPE ~ Tioga.Node; 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 ReadId: 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.GetTrailerLength[get]]; }; 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 ReadId[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 ReadId[comment, TiogaFile.commentHeaderId] THEN GOTO FakeIt; commentLen _ ReadLen[comment]; controlStart _ commentStart+commentLen; RopeReader.SetPosition[control, Substr[controlStart, trailerStart-controlStart]]; IF NOT ReadId[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; }; }; }; 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.