FSNameImpl.Mesa
Last Edited by: Schroeder, December 15, 1983 9:26 am
Last Edited by: Levin, August 9, 1983 11:28 am
DIRECTORY
Basics USING [DIVMOD],
FS USING [ComponentPositions, ComponentRopes, maxFNameLength, Position],
FSBackdoor USING [highestVersion, lowestVersion, ProduceError, Version],
FSFileOps USING [GetVolumeDesc, VolumeDesc],
FSName USING [ParsedFName, VersionInfo],
List USING [Assoc],
ProcessProps USING [GetPropList],
Rope USING [Cat, Concat, Fetch, Flatten, Index, Length, NewText, Replace, Substr, ROPE, Text];
FSNameImpl: CEDAR MONITOR
IMPORTS Basics, FSBackdoor, FSFileOps, List, ProcessProps, Rope
EXPORTS FS, FSBackdoor, FSName
= BEGIN
Global State
initialDefaultWDir: Rope.ROPE = "[]<>";
defaultWDir: Rope.ROPE ← initialDefaultWDir;
Exported to FS
SetDefaultWDir: PUBLIC PROC [dir: Rope.ROPE] =
BEGIN
Inner: ENTRY PROC =
{ defaultWDir ← dir };
places: Places;
IF Rope.Length[dir] = 0
THEN dir ← initialDefaultWDir
ELSE BEGIN
[dir, places] ← ExpandAndFindPlaces[NIL, dir, directory];
IF places.serverEnd # 1 THEN DirNotLocal[dir];
IF places.dirEnd = places.serverEnd THEN NoVolumePart[dir];
dir ← Rope.Flatten[dir];
END;
Inner[];
END;
GetDefaultWDir: PUBLIC ENTRY PROC RETURNS [Rope.ROPE] =
{ RETURN [defaultWDir] };
ExpandName: PUBLIC PROC[name: Rope.ROPE, wDir: Rope.ROPE] RETURNS [fullFName: Rope.ROPE, cp: FS.ComponentPositions, dirOmitted: BOOLEANFALSE] =
BEGIN
places: Places;
fNLen: INT;
[fullFName, places] ← ExpandAndFindPlaces[name, wDir, pattern];
fNLen ← Rope.Length[fullFName];
cp.server ← [1, places.serverEnd-1];
IF places.dirEnd >= places.serverEnd+2
THEN cp.dir ← [places.serverEnd+2, places.dirEnd-places.serverEnd-2]
ELSE { dirOmitted ← TRUE; cp.dir ← [places.serverEnd+1, 0] };
cp.subDirs ← IF places.subDirEnd > places.dirEnd
THEN [places.dirEnd+1, places.subDirEnd-places.dirEnd-1]
ELSE [places.dirEnd+1, 0];
cp.base ← [places.subDirEnd+1, places.rootEnd-places.subDirEnd-1];
cp.ext ← IF places.bang > places.rootEnd
THEN [places.rootEnd+1, places.bang-places.rootEnd-1]
ELSE [places.rootEnd, 0];
IF places.bang < fNLen
THEN BEGIN
[] ← ParseVersion[fullFName, places.bang+1, TRUE];
cp.ver ← [places.bang+1, fNLen-places.bang-1];
END
ELSE cp.ver ← [fNLen, 0];
END;
ConstructFName: PUBLIC PROC [cr: FS.ComponentRopes, omitDir: BOOLEAN] RETURNS [fName: Rope.ROPE] =
BEGIN
fName ← Rope.Cat[ "[", cr.server, "]" ];
IF NOT omitDir THEN fName ← Rope.Cat[ fName, "<", cr.dir, ">" ];
IF Rope.Length[cr.subDirs] > 0 THEN fName ← Rope.Cat[ fName, cr.subDirs, ">" ];
fName ← Rope.Cat[ fName, cr.base ];
IF Rope.Length[cr.ext] > 0 THEN fName ← Rope.Cat[ fName, ".", cr.ext ];
IF Rope.Length[cr.ver] > 0 THEN fName ← Rope.Cat[ fName, "!", cr.ver ];
END;
Exported to FSBackdoor
MakeFName: PUBLIC PROC [nameBody: Rope.ROPE, version: FSBackdoor.Version, prefix: Rope.ROPE] RETURNS [Rope.ROPE] =
BEGIN
IF Rope.Fetch[nameBody, 0] = '[
THEN RETURN [ Rope.Concat [ nameBody, VersionPartFromVersion[version] ] ]
ELSE BEGIN
IF prefix = NIL THEN prefix ← initialDefaultWDir;
RETURN [ Rope.Cat [ prefix, nameBody, VersionPartFromVersion[version] ] ];
END;
END;
Exported to FSName
ParseClientName: PUBLIC PROC [clientName, wDir: Rope.ROPE, defaultVersionHigh, pattern: BOOLEAN] RETURNS [pn: FSName.ParsedFName, vI: FSName.VersionInfo] =
BEGIN
vName: Rope.ROPENIL;
places: Places;
[pn.fullName, places] ← ExpandAndFindPlaces[clientName, wDir, IF pattern THEN pattern ELSE fName];
IF places.bang < Rope.Length[pn.fullName]
THEN [pn.version, vI] ← ParseVersion[pn.fullName, places.bang+1, pattern]
ELSE BEGIN
vI ← missing;
pn.version ← IF defaultVersionHigh THEN FSBackdoor.highestVersion ELSE FSBackdoor.lowestVersion;
END;
IF IsLocal[pn.fullName]
THEN BEGIN
vL: CARDINAL ← places.dirEnd - places.serverEnd - 2;
IF vL # 0 THEN vName ← Rope.Substr[pn.fullName, places.serverEnd + 2, vL];
pn.nameBody ← Rope.Flatten[ Rope.Substr[pn.fullName, places.dirEnd + 1, places.bang - places.dirEnd - 1] ];
END
ELSE pn.nameBody ← Rope.Flatten[ Rope.Substr[pn.fullName, 0, places.bang] ];
pn.volDesc ← FSFileOps.GetVolumeDesc[vName];
END;
ParseCacheName: PUBLIC PROC [volName, cacheName: Rope.ROPE, pattern: BOOLEAN] RETURNS [pn: FSName.ParsedFName, vI: FSName.VersionInfo] =
BEGIN
firstChar: CHARACTER;
length, bangIndex: CARDINAL;
nameUse: NameUse = IF pattern THEN pattern ELSE fName;
pn ← [NIL, NIL, FSBackdoor.highestVersion, FSFileOps.GetVolumeDesc[volName]];
vI ← missing;
IF Rope.Length[cacheName] = 0
THEN BEGIN
IF pattern
THEN { pn.nameBody ← "[*"; RETURN }
ELSE IllegalName[cacheName, nameUse];
END;
FOR i: INT IN [0 .. Rope.Length[cacheName]) DO -- determine syntax convention
SELECT Rope.Fetch[cacheName, i] FROM
'[, '], '<, '> => EXIT;
'/ => { cacheName ← ConvertSlashName[cacheName]; EXIT };
ENDCASE;
ENDLOOP;
firstChar ← Rope.Fetch[cacheName, 0];
IF firstChar # '[
THEN BEGIN
IF firstChar = '*
THEN cacheName ← Rope.Concat["[", cacheName]
ELSE IllegalName[cacheName, nameUse];
END;
length ← Rope.Length[cacheName];
bangIndex ← Rope.Index[cacheName, IF length > 6 THEN length - 6 ELSE 0, "!"];
IF bangIndex # length AND NOT (bangIndex = length-2 AND Rope.Fetch[cacheName, length-1] = '*)
THEN [pn.version, vI] ← ParseVersion[cacheName, bangIndex+1, pattern];
pn.nameBody ← Rope.Flatten[ Rope.Substr[cacheName, 0, bangIndex] ];
END;
ConvertNamebodyPattern: PUBLIC PROC [nbP: Rope.ROPE] RETURNS [Rope.Text] =
{ RETURN [ IF Rope.Length[nbP] = 0 THEN "*" ELSE ConvertSlashName[nbP] ] };
ParseName: PUBLIC PROC [volName, fName: Rope.ROPE] RETURNS [p: FSName.ParsedFName] =
BEGIN
length: CARDINAL = Rope.Length[fName];
bangIndex: CARDINAL = Rope.Index[fName, IF length > 6 THEN length - 6 ELSE 0, "!"];
IF bangIndex = length
THEN p.version ← FSBackdoor.highestVersion
ELSE [p.version, ] ← ParseVersion[fName, bangIndex+1, FALSE];
p.nameBody ← Rope.Flatten[ Rope.Substr[fName, 0, bangIndex] ];
p.fullName ← fName;
p.volDesc ← FSFileOps.GetVolumeDesc[volName];
END;
IsLocal: PUBLIC PROC [name: Rope.ROPE] RETURNS [BOOLEAN] =
{RETURN[ Rope.Length[name]=0 OR Rope.Fetch[name, 0]#'[ OR Rope.Fetch[name, 1]='] ]};
ServerAndFileRopes: PUBLIC PROC [gName: Rope.ROPE] RETURNS [server, file: Rope.ROPE] =
BEGIN
closingBracket: CARDINAL = Rope.Index[gName, 0, "]"];
server ← Rope.Substr[gName, 1, closingBracket-1];
file ← Rope.Substr[gName, closingBracket+1];
END;
BangStarFile: PUBLIC PROC [file: Rope.ROPE] RETURNS [Rope.ROPE] =
BEGIN
length: CARDINAL = Rope.Length[file];
bangIndex: CARDINAL = Rope.Index[file, IF length > 6 THEN length - 6 ELSE 0, "!"];
RETURN [ Rope.Replace [ file, bangIndex, length - bangIndex, "!*" ] ];
END;
BangVersionFile: PUBLIC PROC [file: Rope.ROPE, version: FSBackdoor.Version] RETURNS [Rope.ROPE] =
BEGIN
length: CARDINAL = Rope.Length[file];
bangIndex: CARDINAL = Rope.Index[file, IF length > 6 THEN length - 6 ELSE 0, "!"];
RETURN [ Rope.Replace [ file, bangIndex, length - bangIndex, VersionPartFromVersion[version] ] ];
END;
VersionFromRope: PUBLIC PROC [r: Rope.ROPE] RETURNS [v: FSBackdoor.Version] =
BEGIN
IF Rope.Length[r] = 0
THEN v ← FSBackdoor.highestVersion
ELSE [v, ] ← ParseVersion[r, 0, FALSE];
END;
VersionPartFromVersion: PUBLIC PROC [version: FSBackdoor.Version] RETURNS [r: Rope.Text] =
BEGIN
Decimate: PROC [num: CARDINAL] =
BEGIN
q, r: CARDINAL;
[q, r] ← Basics.DIVMOD[num, 10];
IF q # 0 THEN Decimate[q];
AppendChar[t, r+'0];
END;
t: REF TEXT;
SELECT version FROM
FSBackdoor.lowestVersion, FSBackdoor.highestVersion => r ← NIL;
ENDCASE => TRUSTED BEGIN
r ← Rope.NewText[6];
t ← LOOPHOLE[r];
t[0] ← '!;
t.length ← 1;
Decimate[version];
END;
END;
Internal procedures
NameUse: TYPE = {fName, pattern, directory};
Places: TYPE = RECORD[serverEnd, dirEnd, subDirEnd, bang, rootEnd, firstStar: CARDINAL ← 0];
ExpandAndFindPlaces: PROC [name, wDir: Rope.ROPE, nameUse: NameUse] RETURNS [fN: Rope.ROPE, p: Places] =
If an FS.Error is not generated then the full FName is returned as "fN" and the parts of this FName are located by the indexes in "p" according to the following rules (first means left-most, last means right-most, and after means to-the-right-of):
p.serverEnd = index of the "]";
p.dirEnd = IF no ">" THEN p.serverEnd ELSE index of first ">";
p.subDirEnd = IF no ">" THEN p.dirEnd ELSE index of last ">";
p.bang = IF no "!" THEN Rope.Length[fN] ELSE index of first "!";
p.rootEnd = IF no "." after p.subDirEnd THEN p.bang ELSE index of last "." after p.subDirEnd;
p.firstStar = index of the first "*".
Note that all parsing stops when the first "!" is encountered. It is the client's responsibility to verify that the characters following the "!" constitute a valid version part.
BEGIN
i: INT;
leftPoint: BOOLEANFALSE;
fNLen: CARDINAL ← Rope.Length[name];
fN ← name;
IF fNLen # 0
THEN SELECT Rope.Fetch[fN, 0] FROM
'[ => NULL;
'/ => { fN ← ConvertSlashName[fN]; fNLen ← Rope.Length[fN] };
ENDCASE =>
BEGIN -- need a working directory
IF Rope.Length[wDir] # 0
THEN wDir ← ConvertWDir[wDir] -- use working directory argument
ELSE BEGIN
wDir ← NARROW [ List.Assoc[key: $WorkingDirectory, aList: ProcessProps.GetPropList[]] ];
IF Rope.Length[wDir] # 0
THEN wDir ← ConvertWDir[wDir] -- use process working directory
ELSE wDir ← GetDefaultWDir[]; -- use default working directory
END;
FOR i IN [1 .. fNLen) DO -- convert the name part if necessary
SELECT Rope.Fetch[fN, i] FROM
'], '<, '>, '! => NULL;
'/ => fN ← ConvertSlashName[fN];
ENDCASE => LOOP;
EXIT;
ENDLOOP;
fN ← Rope.Cat[wDir, fN];
fNLen ← Rope.Length[fN];
END; -- of need a working directory
i ← 0;
DO
i ← i + 1;
IF i >= fNLen THEN EXIT;
SELECT Rope.Fetch[fN, i] FROM
IN ['a .. 'z], IN ['A .. 'Z], IN ['0 .. '9], '$, '-, '+ =>
NULL;
'] =>
IF p.serverEnd = 0
THEN p.subDirEnd ← p.dirEnd ← p.serverEnd ← i
ELSE IllegalName[fN, nameUse]; -- this is the second ']
'< =>
IF p.serverEnd = i - 1
THEN leftPoint ← TRUE
ELSE IllegalName[fN, nameUse]; -- '< does not immediately follow ']
'> =>
IF leftPoint AND ( i > p.subDirEnd + 1 )
THEN BEGIN
IF p.dirEnd = p.serverEnd
THEN p.dirEnd ← i; -- end of directory part
p.subDirEnd ← i; -- end of subdirectory part
END
ELSE IllegalName[fN, nameUse]; -- '> in the wrong place
'. =>
p.rootEnd ← i;
'! =>
{p.bang ← i; EXIT};
'# =>
IF p.serverEnd # 0
THEN BEGIN -- not in server part
IF p.serverEnd = 1 AND leftPoint AND i = 3
THEN DO -- LName with a max 20 digit hex number as a volume id
i ← i + 1;
IF i >= fNLen THEN EXIT;
IF i > 23 THEN IllegalName[fN, nameUse];
SELECT Rope.Fetch[fN, i] FROM
IN ['0 .. '9], IN ['A .. 'H] => NULL;
ENDCASE => IllegalName[fN, nameUse];
ENDLOOP
ELSE IllegalName[fN, nameUse]; -- sharpSign in the wrong place
END;
'* =>
IF nameUse = pattern
THEN { IF p.firstStar = 0 THEN p.firstStar ← i }
ELSE NoPatterns[fN]; -- star and we're not parsing a pattern
ENDCASE => IllegalCharacter[fN]; -- illegal character
ENDLOOP;
IF p.serverEnd = 0 THEN IllegalName[fN, nameUse]; -- server part didn't end
IF p.bang = 0 THEN p.bang ← fNLen;
IF p.bang > FS.maxFNameLength - 6
THEN TooLong[fN];
IF p.firstStar = 0
THEN BEGIN -- not a pattern
IF p.serverEnd = 1 AND nameUse # directory AND p.subDirEnd+1 >= p.bang
THEN IllegalName[fN, nameUse]; -- LName has no simple name part
END
ELSE BEGIN -- have a pattern
IF (p.serverEnd = 1 AND p.firstStar < p.dirEnd) OR (p.firstStar < p.serverEnd)
THEN IllegalName[fN, nameUse]; -- first star is in volume part of LName or in server part
END;
IF p.rootEnd <= p.subDirEnd THEN p.rootEnd ← p.bang;
END;
ParseVersion: PROC [name: Rope.ROPE, index: CARDINAL, pattern: BOOLEANFALSE] RETURNS [value: FSBackdoor.Version, vI: FSName.VersionInfo] =
BEGIN
c: CHAR;
lastIndex: CARDINAL = Rope.Length[name] - 1;
IF lastIndex IN [index .. index+4]
THEN SELECT c ← Rope.Fetch[name, index] FROM
IN ['0 .. '9] => BEGIN
num: LONG CARDINAL ← 0;
DO
num ← num*10 + (c - '0);
IF index = lastIndex
THEN BEGIN
IF num NOT IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion)
THEN EXIT;
value ← [num];
vI ← number;
RETURN;
END;
index ← index + 1;
c ← Rope.Fetch[name, index];
IF c NOT IN ['0 .. '9]
THEN EXIT;
ENDLOOP;
END;
'H, 'h =>
IF index = lastIndex
THEN { value ← FSBackdoor.highestVersion; vI ← bangH; RETURN };
'L, 'l =>
IF index = lastIndex
THEN { value ← FSBackdoor.lowestVersion; vI ← bangL; RETURN };
'* =>
IF index = lastIndex AND pattern
THEN { value ← FSBackdoor.highestVersion; vI ← bangStar; RETURN };
ENDCASE;
IllegalVersion[name];
END;
QuotedName: PROC [n: Rope.ROPE] RETURNS [Rope.ROPE] =
BEGIN
quoteRope: Rope.ROPE = "\"";
RETURN [ Rope.Cat[quoteRope, n, quoteRope] ];
END;
DirNotLocal: PROC [n: Rope.ROPE] =
{ FSBackdoor.ProduceError[badWorkingDir,
Rope.Concat[QuotedName[n], " is not a local directory."] ] };
NoVolumePart: PROC [n: Rope.ROPE] =
{ FSBackdoor.ProduceError[badWorkingDir,
Rope.Concat[QuotedName[n], " needs a volume part."] ] };
TooLong: PROC [n: Rope.ROPE] =
{ FSBackdoor.ProduceError[ illegalName,
Rope.Concat[QuotedName[n], " has more than 120 characters."] ] };
IllegalCharacter: PROC [n: Rope.ROPE] =
{ FSBackdoor.ProduceError[ illegalName,
Rope.Concat[QuotedName[n], " contains an illegal character."] ] };
IllegalVersion: PROC [n: Rope.ROPE] =
{ FSBackdoor.ProduceError[ illegalName,
Rope.Concat[QuotedName[n], " has an illegal version part."] ] };
IllegalName: PROC [n: Rope.ROPE, nameUse: NameUse] =
BEGIN
e: Rope.ROPE = SELECT nameUse FROM
fName => " is not a legal FName.",
pattern => " is not a legal pattern.",
directory => " is not a legal directory name",
ENDCASE => ERROR;
FSBackdoor.ProduceError [illegalName, Rope.Concat[QuotedName[n], e] ];
END;
NoPatterns: PROC [n: Rope.ROPE] =
{ FSBackdoor.ProduceError[ patternNotAllowed,
Rope.Concat[QuotedName[n], " contains a \"*\", but patterns are not allow for this operation."] ] };
AppendChar: PROC [text: REF TEXT, c: CHAR] = INLINE
BEGIN
text[text.length] ← c;
text.length ← text.length + 1;
END;
ConvertWDir: PROC [wDir: Rope.ROPE] RETURNS [w: Rope.ROPE] =
BEGIN
SELECT Rope.Fetch[wDir, 0] FROM
'[ =>
BEGIN
c: CHAR = Rope.Fetch[wDir, Rope.Length[wDir] - 1];
IF c # '> AND c # '] THEN wDir ← Rope.Cat[wDir, ">"];
w ← wDir;
END;
'/ =>
BEGIN
c: CHAR = Rope.Fetch[wDir, Rope.Length[wDir] - 1];
IF c # '/ THEN wDir ← Rope.Cat[wDir, "/"];
w ← ConvertSlashName[wDir];
END;
ENDCASE => IllegalName[wDir, directory];
END;
ConvertSlashName: PROC [r: Rope.ROPE] RETURNS [t: Rope.Text] = TRUSTED
BEGIN
rL: INT = Rope.Length[r];
text: REF TEXT;
slashCount: CARDINALIF Rope.Fetch[r, 0] = '/ THEN 0 ELSE 2;
t ← Rope.NewText[rL+1];
text ← LOOPHOLE[t];
text.length ← 0;
FOR i: INT IN [0 .. rL) DO
c: CHAR = Rope.Fetch[r, i];
SELECT c FROM
'/ => BEGIN
slashCount ← slashCount + 1;
SELECT slashCount FROM
1 => AppendChar[text, '[];
2 => BEGIN -- end of server part
AppendChar[text, ']];
FOR j: INT IN [i+1 .. rL) DO -- see if we also need a "<"
SELECT Rope.Fetch[r, j] FROM
'/, '*, '> => {AppendChar[text, '<]; EXIT};
'! => EXIT;
ENDCASE;
ENDLOOP;
END;
ENDCASE => AppendChar[text, '>];
END;
'* => BEGIN -- "*" can hide any number of slashes
IF slashCount < 2 THEN slashCount ← 2;
AppendChar[text, c];
END;
ENDCASE => AppendChar[text, c];
ENDLOOP;
END;
END.