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. @TiogaIOImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Written by Bill Paxton, January 1981 Paxton. October 18, 1982 11:28 am Russ Atkinson, July 25, 1983 2:37 pm Michael Plass, March 29, 1985 2:06:49 pm PST format Rick Beach, March 28, 1985 9:43:10 am PST Doug Wyatt, September 22, 1986 5:39:49 pm PDT MONITOR: PGF Allocation Put/Get Support PutFileImpl When add a new "system" property that should not go on files, add registration at end of TEditDocuments2Impl so will not copy/write the property. move to the next node GetFileImpl The character properties and character sets are not stored in the node until after the rope is stored, so that NodePropsImpl can do its consistency check. MAIN PROGRAM read 4 bytes of looks from control stream if reach here, then want to start a new text node Κ(D˜codešœ™Kšœ Οmœ7™BKšœ$™$Kšœ"™"K™$K™3K™)K™-—K˜šΟk ˜ Kšœžœ ˜Kšœžœžœ ˜Kšœ žœKžœ(˜…Kšœ žœ˜Kšœ žœ˜-Kšœ žœŽ˜—K˜KšΟn œžœž˜Kšžœ ˜Kšžœ ˜Kšœžœžœ ˜K˜Kšœžœ˜Kšœ#˜#K˜Kšœ žœ˜&Kšœ žœ˜*Kšœ žœ˜(head™Kšœžœžœ˜K˜š Ÿœžœžœžœžœ˜+Kšžœžœžœ˜Kšžœžœžœžœ˜-Kš žœžœžœžœžœ˜2Kš žœžœžœžœžœ˜2Kšžœžœ ˜Kšœ˜K˜—š Ÿœžœžœžœžœ˜)Kšžœžœžœ˜Kš žœ žœ žœ žœžœ˜5Kšžœžœžœ ˜Kšžœžœžœžœ ˜"Kšžœžœžœžœ ˜#Kšœ˜K˜——™šŸ œžœžœžœ˜&Kšœ˜K˜&KšœΟc˜+šžœžœž˜)Kšœ0žœ˜8—Kšœžœ˜šžœžœž˜+Kšœ#žœžœ˜0—Kšœ  ˜4Kšœ ˜%šžœžœž˜'Kšœžœžœ˜,—Kšœ ˜5K˜K˜K˜—šœ žœžœ˜K˜—š Ÿœžœžœžœžœ˜Rš œžœžœžœ žœ˜VK˜——šŸ œžœžœžœ˜HKš œžœžœžœ žœ˜TK˜—š Ÿ œžœžœžœ žœ˜HKš œžœžœžœ žœ˜RK˜—šŸœžœžœžœžœžœžœžœ˜CKšœ˜Kšœ˜—šœžœžœžœžœžœžœžœ˜BK˜—š Ÿœžœ žœžœžœžœ#˜gKšœžœ˜Kšœžœ˜Kš žœžœžœžœžœžœ  ˜WKšœ"žœ˜5šžœžœ$ž˜1Kšœžœžœ!˜;Kšžœžœ ˜'Kšžœ˜šžœž˜Kšœžœžœžœ˜2Kšœ žœ ˜1Kšžœ˜—Kšžœ˜—šžœžœ ˜9Kšž˜K˜#K˜K˜0Kšœ žœ˜/Kšžœ˜—Kšžœžœ  ˜5K˜—š Ÿ œžœžœžœžœ"˜gKšœžœ˜Kšœžœ˜Kš žœžœžœžœ  ˜?šœžœ˜šœžœ˜šžœ$˜,šœžœžœ$˜:Kšžœ&žœ˜2———Kšžœ˜—šžœžœž˜+Kšœ žœžœ ˜5Kšœžœ ˜5Kšžœ˜šžœž˜Kšœžœžœžœ˜1Kšœ žœ ˜1Kšžœ˜—Kšžœ˜—šžœžœ ˜7Kšž˜K˜K˜K˜%Kšœžœ˜.Kšžœ˜—Kšžœžœ  ˜5K˜—š Ÿ œžœ žœžœžœžœ!˜]Kšœžœ˜Kšœžœ˜Kš žœ žœžœžœžœ  ˜4Kš œžœžœ žœžœ˜Yšžœžœ ž˜-Kšœ žœžœ˜7Kšžœžœ ˜'Kšžœ˜šžœž˜Kšœžœžœžœ˜0Kšœ žœ ˜1Kšžœ˜—Kšžœ˜—šžœžœ ˜7Kšž˜K˜K˜K˜*Kšœžœ˜-Kšžœ˜—Kšžœžœ  ˜5K˜—K˜š Ÿ œžœžœžœžœžœ˜7K˜,K˜Kšœžœ˜-šžœžœ˜K˜Kšœ.žœ˜3K˜—šžœžœžœ˜7K˜"K˜(Kšœžœ˜$K˜—šžœžœ˜Kšœ˜Kšœžœ˜K˜—K˜Kšœžœ ˜šžœžœ˜Kšœžœ ˜šžœžœ˜Kšœžœ ˜šžœžœ˜Kšœžœ ˜Kšœ˜—Kšœ˜—Kšœ˜—K˜K˜—šŸ œžœžœžœžœžœžœžœ˜CK˜,K˜Kšœ"˜"Kšœžœ˜K˜Kš žœžœžœžœžœ ˜5Kšœ žœ˜K˜Kš žœžœžœžœžœ ˜6Kšœžœ˜K˜(K˜"Kš žœžœžœžœžœ ˜5Kšœ žœ˜K˜Kšžœžœ ˜K˜K˜—Kšœ žœ˜'šœ žœ˜.K˜—š Ÿ œžœžœžœžœ˜CKšžœžœ žœ žœ˜8K˜K˜—šŸ œžœžœžœžœžœžœ˜TKšžœžœ žœžœ˜;K˜K˜—Kšœ žœ˜+Kšœ žœ˜š œ žœžœžœ žœžœ˜/K˜—š Ÿœžœžœžœžœžœ˜>Kš žœ žœ žœžœžœ˜GK˜K˜—šŸœžœžœžœžœžœžœžœ˜OKš žœ žœ žœžœžœ˜JK˜K˜——™ šŸ œžœžœ žœžœžœžœ˜QšŸœžœžœžœ˜8š œžœžœ žœžœžœ˜Ašžœžœž˜KšœZžœ˜aKšœ‘™‘Kšžœžœ˜—K˜—Kšžœ(žœžœ˜=K˜—šŸœžœžœžœ˜-KšžœNžœ*˜‚K˜—šŸ œžœžœ žœžœžœžœ˜RKšžœžœžœžœžœžœžœ žœžœžœžœžœ ˜–K˜—Kš žœžœžœžœžœžœ˜$K˜"Kš žœžœžœžœ ˜4Kš žœ žœžœžœ ˜:Kš žœžœžœžœžœžœ )˜OKšžœžœžœžœ(˜XKš žœžœžœžœ %˜?K˜K˜—š Ÿœžœžœžœžœ˜OKšŸœžœžœžœ˜>KšŸœžœžœžœ0˜RšŸœžœžœ˜$Kšœ˜Kšžœ˜Kšœ˜—š Ÿ œžœžœ žœžœ˜0Kšœ˜Kšžœžœ˜/K˜—šŸ œžœžœžœ ˜Ešžœžœž˜*Kšžœžœ ˜Kšžœ˜—K˜—šŸœžœ˜/šžœžœž˜*Kšžœžœ˜#Kšžœ˜—K˜—šŸ œžœ˜.Kšœžœ˜,Kšœžœ˜(Kšœžœ˜(Kšœžœ˜(Kšœžœ˜(K˜—Kšœžœ˜+K˜4K˜8Kšœ˜šž˜Kšœ%˜%Kšœ žœ žœ˜šœ ˜Kšœ žœ˜)Kšœžœ%˜-Kšœ?˜?šžœžœ˜ Kšœžœ žœ!žœ(˜oKšœ˜—šžœ˜Kšœžœ žœžœ˜WKš œžœ žœžœžœžœ˜LK˜—K˜—šœ ˜š œ žœžœ žœžœžœžœ˜IKšœžœ! ˜Dšžœžœžœ˜Kšœžœ!˜)K˜1šžœžœ ˜"Kšœ&˜&Kšœžœ ˜!K˜—šžœ ˜#Kšœ!˜!Kšœ&˜&K˜—K˜K˜—K˜—Kš žœ žœžœ&žœžœ˜JK˜—šœ ˜Kšœžœ˜Kšœžœ ˜K˜!šžœ žœ ˜*K˜K˜$K˜—šžœžœžœ ˜)Kšœžœ˜K˜3K˜!K˜K˜*šžœž˜Kšœžœ˜!Kšœžœ#˜+K˜.K˜4Kšžœžœ2˜<šžœ ˜"šžœž˜ K˜BK˜BK˜BKšžœ@˜G—K˜—K˜K˜Kšžœ˜—Kšžœ žœžœ˜K˜—šžœžœ #˜:K˜$K˜K˜—šžœ  ˜'K˜!K˜K˜—K˜—K˜Kšœ™Kšžœžœ žœ ˜!šžœžœ ˜Kšžœ žœžœ˜Kšžœžœžœžœ˜GKšžœD˜HKšžœ˜—šžœ ˜K˜&K˜#K˜&K˜K˜—Kšžœ˜—K˜K˜—K˜šŸœžœžœ žœžœ!žœžœžœžœ˜xKšœžœžœ˜K˜!Kšžœžœžœ;˜Nšžœ˜Kšœ žœžœ˜%Kšœžœžœžœ˜"Kšžœ žœžœ˜5Kš žœ žœžœžœžœ˜0K˜WKšœ žœ˜(Kšžœ žœ˜ šžœ˜KšŸœžœžœžœ˜4KšŸœžœžœ&˜QKšŸœžœžœžœ.˜NKš Ÿœžœžœžœžœ˜AKšœ žœžœ˜,Kšœ žœžœ˜,Kšœ žœL˜[Kšœ žœ^˜mK˜&Kšœ!˜!Kšœ˜Kšœ˜Kšœ!˜!Kšœ˜Kšœ˜Kšœ"˜"Kšœ ˜ Kšœ˜Kšœ˜Kšžœžœ#žœžœ˜4K˜—K˜—K˜K˜—šŸœžœžœ!žœžœžœžœ žœ˜qKšœžœ˜ K˜#Kšžœžœ(˜6šžœ˜Kš œžœžœžœžœ˜Kšœ=˜=Kšœ žœ˜ K˜—K˜K˜—š Ÿ œžœ žœžœžœ˜;Kšœžœ.˜7Kšžœžœ%˜.K˜K˜—šŸœžœžœ žœžœžœžœžœžœ˜ƒKšœ žœ1 ˜YKšœžœ#˜+K˜AK˜K˜—šŸœžœžœžœžœžœžœžœžœ˜‡šœ žœ˜Kšœžœžœžœ"˜8Kšžœ žœžœ˜+K˜=K˜—Kšœ7˜7K˜K˜—šœ-˜-K˜—K˜šŸ œžœžœžœžœžœžœ˜Sš Ÿœžœžœžœžœ˜3Kšœžœ˜ Kšœžœ˜Kšœžœ˜šžœ žœ,ž˜BKšœ žœ˜—Kš žœžœžœžœžœžœ˜KKšžœžœ˜K˜—Kšœ˜Kšœžœ˜Kšœ žœ˜Kšœžœžœ˜šž˜K˜)Kšžœžœžœžœ˜šžœžœ ž˜Kšžœžœ !˜:—K˜Kš žœ žœžœžœ ˜IKš žœžœžœžœž˜JKšžœ /˜EKšžœ˜Kšžœ˜—š œžœžœ žœ žœžœ˜EKšžœžœ˜ K˜—K˜K˜—šŸœžœžœ žœ˜žœ˜Wšžœ ž˜Kšœžœ ˜#Kšœ ˜/Kšžœžœ  ˜+—Kšœ! ˜:Kšœžœ˜K˜—šœ  /˜9K˜Kšœ žœ˜ Kšœžœ˜ Kšžœžœžœžœ˜%Kšœ ˜.šžœ ž˜Kš œžœžœ žœžœ˜#K˜=Kšœžœ˜K˜š žœžœžœ žœ ˜7K˜šŸ œžœžœ˜"Kš žœžœžœ žœžœžœ˜;K˜&K˜—šžœž˜šžœ˜!šœ ˜ Kšžœ ˜(Kšœžœ˜+Kšœ˜—Kšœ˜—K˜K˜K˜˜Kšœ)™)K˜K˜-K˜-Kšœ"žœ ˜5K˜—Kšžœžœ˜—K˜,Kšžœ˜—Kšœ žœ žœžœ ž˜-K˜3Kšžœ˜—Kšœ "˜4K˜Kšœžœ˜K˜—˜ K˜4K˜ Kšœžœ˜K˜—˜šœ"˜"Kšžœ˜$Kšœžœ˜+Kšœ˜—K˜ Kšœžœ˜K˜—šœC˜CKšžœ˜K˜—˜Kš žœžœžœžœ ˜1Kšœ$ !˜EKšœžœ˜K˜—Kšžœžœ˜—Kšœ1™1Kš žœ žœžœ žœžœžœ˜>Kšœžœ˜'K˜Kšžœžœž˜)Kšžœ-žœ˜7K˜Kšžœ˜—šžœžœžœ ˜7Kš žœžœžœžœ  ˜8Kšžœ ˜(K˜—Kšœ žœ˜Kšœ+žœ žœžœ˜LK˜K˜K˜—K˜š Ÿ œžœžœ žœ žœžœ˜UKšœžœ˜(K˜1Kšœ žœ˜Kšœ žœžœ˜&Kšœ žœžœžœ˜+Kšœ žœ˜Kšœžœ˜K˜ Kšœ žœžœ˜K˜š Ÿœžœžœžœ ˜=Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœ žœ˜K˜ šžœžœ0˜9Kšœžœžœž˜ Kšœžœ  ˜K˜!K˜Kšœžœžœ ˜>šžœ /˜