TiogaIOImpl.mesa
Copyright © 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
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;
MONITOR: PGF Allocation
pgf1, pgf2, pgf3: PGFNIL;
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;
};
Put/Get Support
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;
};
PutFileImpl
SimpleDoc: PROC [root: Node] RETURNS [simple: BOOLFALSE, rope: ROPENIL] = {
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,
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.
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: BOOLFALSE, rope: ROPENIL] = {
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: BOOLFALSE] = {
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];
};
};
move to the next node
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: BOOLFALSE] 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.STREAMNIL;
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: BOOLFALSE] 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: BOOLFALSE] 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: BOOLFALSE] 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: BOOLFALSE] = {
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: BOOLTRUE;
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: BOOLFALSE]
RETURNS [output: ROPE] = {
h: IO.STREAM = IO.ROS[];
WritePlain[h, root, restoreDashes];
RETURN [IO.RopeFromROS[h]]
};
GetFileImpl
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: REFNIL;
charSets: REFNIL;
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: BOOLFALSE;
value: REF;
value ← Tioga.ReadProp[propname, specs !
RuntimeError.UNCAUGHT => {disaster←TRUE}
];
IF disaster THEN ERROR FromFileError;
IF value # NIL THEN {
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.
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 }];
};
MAIN PROGRAM
pgf: PGSupport.PGF ← PGSupport.CreatePGF[];
text: REF TEXTNEW[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: NATMIN[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 => {
read 4 bytes of looks from control stream
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 reach here, then want to start a new text node
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: BOOLFALSE;
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.