XNSFilingPathImpl.mesa
Copyright Ó 1990 by Xerox Corporation. All rights reserved.
Bill Jackson (bj), May 30, 1990 3:33 pm PDT
DIRECTORY
Ascii USING [NUL, SP, TAB],
Commander USING [CommandProc, Handle, Register],
Convert USING [CardFromRope, Error, RopeFromCard],
IO USING [BreakProc, CharClass, GetTokenRope, PutF, rope, STREAM],
MessageWindow USING [Append],
PFSBackdoor,
PFSNames,
ProcessProps USING [GetProp],
RefText USING [Append, Fetch, Length, ObtainScratch, ReleaseScratch],
Rope USING [Concat, Fetch, FromRefText, IsEmpty, Length, ROPE, Substr],
RuntimeError USING [ ],
XNSFilingNames USING [ ],
XNSFilingOps USING [Op],
XNSFilingPrivate USING [ ];
XNSFilingPathImpl: CEDAR MONITOR
IMPORTS Commander, Convert, IO, PFSBackdoor, PFSNames, ProcessProps, RefText, Rope
EXPORTS XNSFilingNames, XNSFilingPrivate
SHARES PFSNames ~ {
ROPE: TYPE ~ Rope.ROPE;
Magic
DebugOut: PUBLIC PROC [msg: ROPE] ~ {
IF ( debugging ) THEN {
cmd: Commander.Handle ~ NARROW[ProcessProps.GetProp[$CommanderHandle]];
out: IO.STREAM ~ IF ( cmd # NIL )
THEN cmd.out ELSE NARROW[ProcessProps.GetProp[$StdOut]];
IF ( out = NIL ) THEN { --MessageWindow.Append[msg, TRUE];-- RETURN };
out.PutF["%g\n", IO.rope[msg] ];
};
};
debugging: BOOLFALSE;
DebugCmd: Commander.CommandProc ~ {
atom: ATOM ~ NARROW[cmd.procData.clientData];
SELECT atom FROM
$true => { debugging ← TRUE };
$false => { debugging ← FALSE };
ENDCASE => { result ← $Failure };
};
XNSFilingNames
Error: ERROR ~ CODE;
Unparsed: TYPE ~ RECORD [ canonicalUnparsing: ROPENIL ];
UnparseName: PUBLIC PROC [name: PFSNames.PATH] RETURNS [filingName: ROPE] ~ {
serverComponent: BOOLTRUE;
cproc: PFSNames.ComponentProc ~ {
chunk: ROPE ~ UnparseComponent[comp];
filingName ← filingName.Concat[chunk];
IF ( serverComponent ) THEN {
IF ( comp.name.len # 0 ) THEN filingName ← filingName.Concat[":"];
serverComponent ← FALSE;
}
};
sproc: PFSNames.SeparatorProc ~ {
IF ( NOT serverComponent )
THEN { IF ( separatorPresent ) THEN filingName ← filingName.Concat["/"] }
ELSE { IF ( NOT separatorPresent ) THEN serverComponent ← FALSE };
};
unparsed: REF Unparsed;
name ← PFSNames.NonNIL[name];
unparsed ← WITH name.GetUnparsingHint[] SELECT FROM
unp: REF Unparsed => unp,
ENDCASE => NIL;
IF ( unparsed # NIL ) THEN RETURN [unparsed.canonicalUnparsing];
name.Map[cproc, sproc];
};
UnparseComponent: PUBLIC PROC [component: PFSNames.Component] RETURNS [r: ROPE] ~ {
chunk: ROPE ~ component.name.base.Substr[component.name.start, component.name.len];
vers: ROPE ~ UnparseVersion[component.version];
r ← Rope.Concat[chunk, vers];
};
UnparseVersion: PUBLIC PROC [version: PFSNames.Version] RETURNS [r: ROPE] ~ {
HardCase: PROC RETURNS [v: ROPE] ~ INLINE {
numb: ROPE ~ Convert.RopeFromCard[version.version];
v ← Rope.Concat["!", numb];
};
r ← SELECT version.versionKind FROM
none => NIL,
lowest => "!-",
highest => "!+",
all => "!*",
numeric => HardCase[],
ENDCASE => NIL;
};
ParseName: PUBLIC PROC [name: ROPE] RETURNS [parsedName: PFSNames.PATH] ~ {
Canonical parsing.
absolute, directory: BOOLFALSE;
comps: LIST OF PFSNames.Component ← NIL;
IF ( name.IsEmpty[] ) THEN RETURN [NIL];
IF ( FALSE ) THEN DebugOut[name];
absolute ← ( name.Fetch[0] = '/ );
IF ( absolute ) THEN comps ← CONS[ [], comps ];
directory ← ( name.Fetch[name.Length[]-1] = '/ );
{
ENABLE Error => {
msg: ROPE ~ name.Concat[": syntactically invalid file name"];
PFSBackdoor.ProduceError[invalidNameSyntax, msg];
};
pos: NATIF ( absolute ) THEN 1 ELSE 0;
limit: NAT ← name.Length[];
firstComponent: BOOLTRUE;
ScanComponent: PROC ~ {
nextComp: PFSNames.Component;
base: ROPE; start, len: INT;
[nextComp, pos] ← ParseComponent[name, pos];
base ← nextComp.name.base; start ← nextComp.name.start; len ← nextComp.name.len;
SELECT TRUE FROM
( NOT firstComponent ) => { NULL };
( absolute ) => { NULL };
( len <= 0 ) => { NULL };
( base.Fetch[start+len.PRED] = ': ) => {
absolute ← TRUE;
nextComp.name.len ← len.PRED;
};
ENDCASE => { NULL };
firstComponent ← FALSE;
comps ← CONS[nextComp, comps];
};
ScanSeparator: PROC ~ {
IF ( name.Fetch[pos] # '/ ) THEN ERROR Error[];
pos ← pos.SUCC;
};
DO
IF ( pos >= limit ) THEN EXIT;
ScanComponent[];
IF ( pos >= limit ) THEN EXIT;
ScanSeparator[];
ENDLOOP;
};
{
unparsed: REF Unparsed ~ NEW[Unparsed ← [name]];
parsedName ← PFSNames.ConstructName[components: comps, absolute: absolute, directory: directory, reverse: TRUE, unparsed: unparsed];
};
};
ParseComponent: PUBLIC PROC [name: ROPE, first: NAT ← 0]
RETURNS [component: PFSNames.Component ← [], next: NAT] ~ {
limit: NAT ← name.Length[];
next ← first;
DO
IF ( next >= limit ) THEN {
component.name ← [name, first, next-first];
EXIT;
};
SELECT ( name.Fetch[next] ) FROM
'! => {
component.name ← [name, first, next-first];
[component.version, next] ← ParseVersion[name, next];
EXIT;
};
'/ => {
component.name ← [name, first, next-first];
EXIT;
};
ENDCASE => next ← next.SUCC;
ENDLOOP;
};
ParseVersion: PUBLIC PROC [v: ROPE, first: NAT ← 0]
RETURNS [version: PFSNames.Version ← [none], next: NAT] ~ {
limit: NAT ← v.Length[];
next ← first;
IF ( next >= limit ) THEN RETURN;
IF ( v.Fetch[next]#'! ) THEN ERROR Error[];
next ← next.SUCC;
SELECT v.Fetch[next] FROM
IN ['0..'9] => {
vers: ROPE; card: CARD32;
start: NAT ← next;
next ← next.SUCC;
WHILE ( next < limit ) AND ( v.Fetch[next] IN ['0..'9] ) DO next ← next.SUCC ENDLOOP;
vers ← v.Substr[start, next-start];
card ← Convert.CardFromRope[vers];
version ← [numeric, card]; -- next ← next;
RETURN;
};
'H, 'h => RETURN[ [highest], next.SUCC ];
'L, 'l => RETURN[ [lowest], next.SUCC ];
'+ => RETURN[ [highest], next.SUCC ];
'- => RETURN[ [lowest], next.SUCC ];
'N, 'n => RETURN[ [next], next.SUCC ];
'* => RETURN[ [all], next.SUCC ];
ENDCASE => NULL;
ERROR Error[];
};
ParseNameFromStream: PUBLIC PROC [s: IO.STREAM] RETURNS [name: PFSNames.PATH] ~ {
A convenient way to read a whitespace-delimited name from a stream;
FileNameProc: IO.BreakProc ~ {
OPEN Ascii;
class: IO.CharClass ~ SELECT char FROM
NUL, SP, TAB => sepr,
ENDCASE => other;
RETURN [class];
};
ropeName: ROPEIO.GetTokenRope[s, FileNameProc].token;
name ← ParseName[ropeName];
};
Filename Utils
RopeForPath: PROC [path: PFSNames.PATH] RETURNS [rope: ROPENIL] ~ {
};
ParseFSName: PROC [file: ROPE]
RETURNS [dir: LIST OF ROPE, name: ROPE, vers: PFSNames.Version] ~ {
dirSeen: BOOLFALSE;
versR: ROPENIL;
stopAt: INT ← file.Length[];
FOR i: INT DECREASING IN [0..file.Length[]) DO
c: CHAR ~ file.Fetch[index: i];
SELECT c FROM
'! => { versR ← file.Substr[start: i]; stopAt ← i.PRED };
'> => {
IF ( dirSeen )
THEN dir ← CONS[file.Substr[start: i.SUCC, len: stopAt.PRED], dir]
ELSE name ← file.Substr[start: i.SUCC, len: stopAt - i];
dirSeen ← TRUE;
stopAt ← i-1;
};
'< => {
dir ← CONS[file.Substr[start: i.SUCC, len: stopAt-i], dir];
EXIT; -- this must be the last char
};
ENDCASE => NULL;
ENDLOOP;
vers ← [$none];
IF ( versR.Length[] > 1 ) THEN {
rope: ROPE ~ Rope.Substr[base: versR, start: 1];
card: CARD32 ← 0;
card ← Convert.CardFromRope[rope ! Convert.Error => { CONTINUE }];
IF ( card > 0 ) THEN vers ← [$numeric, card];
};
};
ConvertFSNameToXNS: PUBLIC PROC [file: ROPE, op: XNSFilingOps.Op]
RETURNS [name: ROPENIL] ~ {
text: REF TEXT ~ RefText.ObtainScratch[nChars: 200];
AddChar: PROC [c: CHAR] = { text[text.length] ← c; text.length ← text.length.SUCC };
versionR: ROPENIL;
text.length ← 0;
FOR i: INT IN [0..file.Length[]) DO
c: CHAR ~ file.Fetch[index: i];
SELECT c FROM
'< => { LOOP }; -- throw the < away.
'> => { AddChar['/] }; -- the XNS server uses / as a file separator
'* => { AddChar[c]; AddChar[c] }; -- kludge to handle *
'! => { versionR ← file.Substr[start: i]; EXIT }; -- the whole version including the !
ENDCASE => { AddChar[c] };
ENDLOOP;
name ← Rope.FromRefText[s: text];
RefText.ReleaseScratch[t: text];
IF ( versionR # NIL ) THEN {
SELECT versionR.Length[] FROM
0 => { versionR ← NIL }; -- no version specified
1 => {
the version rope is just a ! so do the 'default'
versionR ← SELECT op FROM
delete => "!-", -- lowest
enumerate => NIL,
enumerateNames => NIL,
rename => "!+", -- highest
retrieve => "!+", -- highest
store => "!+", -- is this right?
ENDCASE => ERROR; -- can't happen
};
2 => { -- is it H, L or *?
versionR ← SELECT versionR.Fetch[1] FROM
'h, 'H => "!+",
'l, 'L => "!-",
'* => NIL,
ENDCASE => versionR;
};
ENDCASE => NULL;
IF ( versionR # NIL ) THEN name ← name.Concat[versionR];
};
};
ConvertPathToFile: PROC [path: REF TEXT, isDir: BOOLFALSE]
RETURNS [file: ROPENIL] ~ {
slashSeen: BOOLFALSE;
state: {name, version} ← name;
name: REF TEXT ← RefText.ObtainScratch[nChars: 200];
ver: REF TEXT ← RefText.ObtainScratch[nChars: 200];
AddChar: PROC [t: REF TEXT, c: CHAR] ~ { t[t.length] ← c; t.length ← t.length.SUCC };
name.length ← 0;
ver.length ← 0;
AddChar[name, '<]; -- since the path doesn't have this
FOR i: INT IN [0..RefText.Length[path]) DO
c: CHAR ~ RefText.Fetch[path, i];
SELECT c FROM
'! => { ver.length ← 0; state ← version };
discard the version information since it is still a subdir
'/ => {
slashSeen ← TRUE;
ver.length ← 0; -- discard the version information since it is still a subdir
state ← name; -- go back to collection characters
AddChar[name, '>]; -- insert FS's notion of what a delineator is
};
'> => { -- an illegal character
RefText.ReleaseScratch[t: name];
RefText.ReleaseScratch[t: ver];
RETURN;
};
ENDCASE => {
SELECT state FROM
name => AddChar[name, c];
version => AddChar[ver, c];
ENDCASE => ERROR;
};
ENDLOOP;
IF ( state = version ) THEN { -- this is the version from the item last on the path
IF ( ( NOT slashSeen ) OR ( isDir ) ) THEN AddChar[name, '>];
AddChar[name, '!]; -- indicate the version
name ← RefText.Append[to: name, from: ver]; -- and glue it on
};
file ← Rope.FromRefText[s: name];
RefText.ReleaseScratch[t: name];
RefText.ReleaseScratch[t: ver];
};
Commander.Register["XNSFiling.DebugOn", DebugCmd, "start logging", $true];
Commander.Register["XNSFiling.DebugOff", DebugCmd, "stop logging", $false];
}.