GetFileImpl.mesa
Copyright Ó 1984, 1985, 1986, 1987, 1988 by Xerox Corporation. All rights reserved.
written by Paxton. January 1981
Paxton. December 1, 1982 9:44 am
Paul Rovner, August 10, 1983 4:28 pm
Russ Atkinson, June 18, 1984 8:29:12 pm PDT
Rick Beach, March 28, 1985 9:46:42 am PST
Michael Plass, October 19, 1987 9:47:57 am PDT
Doug Wyatt, February 17, 1988 5:31:29 pm PST
DIRECTORY
Ascii USING [Lower],
Atom USING [EmptyAtom, MakeAtom, MakeAtomFromRefText],
Basics USING [BITXOR, LowHalf],
BasicTime USING [GMT],
FileOps USING [comment, endNode, endOfFile, FormatIndex, IntBytes, LengthByte, look1, look2, look3, looks, looksFirst, LooksIndex, looksLast, numFormats, numLooks, numProps, Op, otherNode, otherNodeShort, otherNodeSpecs, otherNodeSpecsShort, prop, PropIndex, propShort, rope, runs, startNode, startNodeFirst, startNodeLast, terminalTextNode, terminalTextNodeFirst, terminalTextNodeLast, ThirdByte],
FileReader USING [FromRope, FromStream, Open, OpenC],
FS USING [ComponentPositions, ExpandName, GetName, OpenFile, Position],
IO USING [STREAM],
NodeProps USING [DoSpecs, PutProp],
PGSupport USING [FormatHashIndex, formatHashSize, LooksHashIndex, looksHashSize, noLooks, PGF, PGFBody, PropHashIndex, propHashSize],
PrincOps USING [zXOR],
PutGet USING [],
Rope USING [MaxLen, ROPE, Substr, Translate],
RopeEdit USING [MaxLen, Substr],
RopeReader USING [Backwards, BumpIndex, Get, GetRope, Position, ReadOffEnd, Ref, SetIndex],
Rosary USING [ROSARY, Size],
RuntimeError USING [UNCAUGHT],
SafeStorage USING [SetCollectionInterval],
TextEdit USING [],
TextLooks USING [CreateRun, Concat, Looks, LooksBytes, noLooks, Runs],
TextNode USING [MaxLen, Node, NodeRep];
GetFileImpl: CEDAR MONITOR
IMPORTS Ascii, Atom, Basics, FileReader, FS, NodeProps, Rope, RopeEdit, RopeReader, Rosary, RuntimeError, SafeStorage, TextLooks
EXPORTS PGSupport, PutGet
SHARES TextLooks
=
BEGIN
OPEN FileOps;
FormatHashIndex: TYPE ~ PGSupport.FormatHashIndex;
formatHashSize: NAT ~ PGSupport.formatHashSize;
LooksHashIndex: TYPE ~ PGSupport.LooksHashIndex;
looksHashSize: NAT ~ PGSupport.looksHashSize;
PGF: TYPE ~ PGSupport.PGF;
PGFBody: TYPE ~ PGSupport.PGFBody;
PropHashIndex: TYPE ~ PGSupport.PropHashIndex;
propHashSize: NAT ~ PGSupport.propHashSize;
Node: TYPE ~ TextNode.Node;
ROPE: TYPE = Rope.ROPE;
ROSARY: TYPE = Rosary.ROSARY;
FromFileError: PUBLIC ERROR = CODE;
FromFile:
PUBLIC
PROC [fileName:
ROPE, start:
INT ← 0, len:
INT ← TextNode.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] ←
FileReader.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 ← TextNode.MaxLen]
RETURNS [root: Node] = {
control, comment, stext: RopeReader.Ref;
tiogaFile: BOOL;
createDate: BasicTime.GMT;
StartRead[];
{
ENABLE
UNWIND => EndRead[];
[control, comment, stext, tiogaFile, createDate] ← FileReader.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];
NodeProps.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] = {
NodeProps.PutProp[root, $FileCreateDate,
NEW[BasicTime.GMT ← createDate]];
};
FromRope: PUBLIC PROC
[rope:
ROPE, start:
INT ← 0, len:
INT ← TextNode.MaxLen]
RETURNS [root: Node] = {
control, comment, stext: RopeReader.Ref;
tiogaFile: BOOL;
StartRead[];
{
ENABLE
UNWIND => EndRead[];
[control, comment, stext, tiogaFile] ← FileReader.FromRope[rope, start, len];
root ← Finish[control, comment, stext, tiogaFile];
};
EndRead[];
};
FromStream: PUBLIC PROC
[stream:
IO.
STREAM, len:
INT ← TextNode.MaxLen]
RETURNS [root: Node] = {
control, comment, stext: RopeReader.Ref;
tiogaFile: BOOL;
StartRead[];
{
ENABLE
UNWIND => EndRead[];
[control, comment, stext, tiogaFile] ← FileReader.FromStream[stream, len];
root ← Finish[control, comment, stext, tiogaFile];
};
EndRead[];
};
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;
IF (startCount ← startCount-1) = 0 THEN
[] ← SafeStorage.SetCollectionInterval[collectionInterval];
};
Finish:
PROC [control, cmmnt, stext: RopeReader.Ref, tiogaFile:
BOOL]
RETURNS [root: Node] = {
op: Op;
parent, node, prev: Node;
textNode: 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 ← NodeProps.DoSpecs[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 we can do a consistency check.
IF propname = $CharProps THEN charProps ← value
ELSE IF propname = $CharSets THEN charSets ← value
ELSE NodeProps.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 [RopeEdit.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] = {
first, second, fourth: LengthByte;
third: ThirdByte;
card: IntBytes;
first ← LOOPHOLE[ReadByte[]];
card.first ← first.data;
IF ~first.others THEN RETURN [LOOPHOLE[card]];
second ← LOOPHOLE[ReadByte[]];
card.second ← second.data;
IF ~second.others THEN RETURN [LOOPHOLE[card]];
third ← LOOPHOLE[ReadByte[]];
card.thirdBottom ← third.dataBottom;
card.thirdTop ← third.dataTop;
IF ~third.others THEN RETURN [LOOPHOLE[card]];
fourth ← LOOPHOLE[ReadByte[]];
card.fourth ← fourth.data;
RETURN [LOOPHOLE[card]];
};
NextOp:
PROC
RETURNS [op: Op] = {
op ← RopeReader.Get[control ! RopeReader.ReadOffEnd => { op ← endOfFile; CONTINUE }];
};
MAIN PROGRAM
pgf: PGF ← CreatePGF[];
text: REF TEXT ← NEW[TEXT[32]];
terminalNode ← FALSE;
op ← NextOp[];
DO
SELECT op
FROM
IN [terminalTextNodeFirst..terminalTextNodeLast] => {
formatName ← RetrieveFormatName[
LOOPHOLE[op-terminalTextNodeFirst, FormatIndex], pgf
! BadIndex => ERROR FromFileError
];
terminalNode ← TRUE;
};
IN [startNodeFirst..startNodeLast] =>
formatName ← RetrieveFormatName[
LOOPHOLE[op-startNodeFirst, FormatIndex], pgf
! 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[];
[] ← EnterFormatName[formatName, pgf];
};
terminalTextNode => {
formatName ← GetAtom[];
[] ← EnterFormatName[formatName, pgf];
terminalNode ← TRUE;
};
rope, comment => {
reader: RopeReader.Ref;
CheckRosary:
PROC [val:
REF, size:
INT]
RETURNS [
ROSARY] ~ {
WITH val
SELECT
FROM
rosary: ROSARY => IF Rosary.Size[rosary]=size THEN RETURN[rosary];
ENDCASE;
ERROR FromFileError;
};
IF op=rope THEN reader ← stext ELSE { reader ← cmmnt; textNode.comment ← TRUE;};
IF textNode=NIL OR textNode#node THEN ERROR FromFileError;
IF (textLength←ReadLength[])>0 THEN textNode.rope ← GetRope[textLength, reader];
IF charProps#NIL THEN textNode.charProps ← CheckRosary[charProps, textLength];
IF charSets#NIL THEN textNode.charSets ← CheckRosary[charSets, textLength];
charProps ← 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 textNode=NIL OR textNode#node THEN ERROR FromFileError;
numRuns ← ReadLength[]; -- read number of runs
FOR i:
INT
IN [0..numRuns)
DO
looks: TextLooks.Looks; -- looks for this run
length: INT; -- length of this run
run: TextLooks.Runs;
ReadLookChars:
PROC [num:
NAT] = {
FOR i:NAT IN [0..num) DO looks[ReadChar[]] ← TRUE; ENDLOOP;
[] ← EnterLooks[looks, pgf];
};
SELECT op ← NextOp[]
FROM
IN [looksFirst .. looksLast] => {
looks ← RetrieveLooks[
LOOPHOLE[op-looksFirst, LooksIndex], pgf
! BadIndex => ERROR FromFileError
]
};
look1 => ReadLookChars[1];
look2 => ReadLookChars[2];
look3 => ReadLookChars[3];
FileOps.looks => {
read 4 bytes of looks from control stream
lb: TextLooks.LooksBytes;
lb.byte0 ← ReadByte[]; lb.byte1 ← ReadByte[];
lb.byte2 ← ReadByte[]; lb.byte3 ← ReadByte[];
[] ← EnterLooks[looks ← LOOPHOLE[lb], pgf];
};
ENDCASE => ERROR FromFileError;
length ← ReadLength[];
run ← TextLooks.CreateRun[length, looks];
lookRuns ← IF lookRuns=NIL THEN run
ELSE TextLooks.Concat[lookRuns, run, pos, length];
pos ← pos+length;
ENDLOOP;
runsLength ← pos; -- for use in checking rope length
textNode.charLooks ← lookRuns;
op ← NextOp[]; LOOP;
};
prop => {
[] ← EnterProp[propname ← GetAtom[], pgf];
ReadProp;
op ← NextOp[]; LOOP;
};
propShort => {
propname ← RetrieveProp[
LOOPHOLE[ReadByte[], PropIndex], pgf
! 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 ← textNode ← NEW[TextNode.NodeRep]];
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[TextNode.NodeRep]
-- null file
ELSE root ← node; -- single node in file
root.last ← TRUE;
NodeProps.PutProp[root, $FromTiogaFile, IF tiogaFile THEN $Yes ELSE $No];
FreePGF[pgf];
};
PGSupport implementation
Looks: TYPE ~ TextLooks.Looks;
noLooks: Looks ~ TextLooks.noLooks;
PropIndex: TYPE ~ FileOps.PropIndex;
FormatIndex: TYPE ~ FileOps.FormatIndex;
LooksIndex: TYPE ~ FileOps.LooksIndex;
-- PGF is the file-level structure for put/get
CreatePGF:
PUBLIC
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];
};
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
};
BadIndex:
PUBLIC
ERROR =
CODE;
RetrieveFormatName:
PUBLIC
PROC [index: FormatIndex, pgf:
PGF]
RETURNS [formatName:
ATOM]
= {
IF index >= pgf.formatNext
THEN
ERROR BadIndex;
RETURN [pgf.formatTable[index]] };
RetrieveLooks:
PUBLIC
PROC [index: LooksIndex, pgf:
PGF]
RETURNS [looks: Looks]
= { IF index >= pgf.looksNext THEN ERROR BadIndex; RETURN [pgf.looksTable[index]] };
RetrieveProp:
PUBLIC
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};
true:
BOOL[
TRUE..
TRUE] ~ (
SIZE[
ATOM]-
SIZE[
CARDINAL] = 1);
EnterFormatName:
PUBLIC
PROC [formatName:
ATOM, pgf:
PGF]
RETURNS [ok:
BOOL, index: FileOps.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 < FileOps.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:
PUBLIC
PROC [looks: TextLooks.Looks, pgf:
PGF]
RETURNS [ok:
BOOL, index: FileOps.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 < FileOps.numLooks
THEN {
room left in table
pgf.looksTable[next] ← looks;
pgf.looksNext ← next+1;
pgf.looksHashKeys[loc].looks ← looks;
pgf.looksHashVals[loc].index ← LOOPHOLE[next];
};
RETURN [FALSE, 0] -- index irrelevant in this case
};
EnterProp:
PUBLIC
PROC [propname:
ATOM, pgf:
PGF]
RETURNS [ok:
BOOL, index: FileOps.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 < FileOps.numProps
THEN {
room left in table
pgf.propTable[next] ← propname;
pgf.propNext ← next+1;
pgf.propHashKeys[loc].propname ← propname;
pgf.propHashVals[loc].index ← LOOPHOLE[next];
};
RETURN [FALSE, 0] -- index irrelevant in this case
};
END.