FSOnPFSImpl.mesa
Copyright Ó 1988, 1989, 1990, 1991 by Xerox Corporation. All rights reserved.
Andy Litman May 24, 1988 7:58:10 pm PDT
JKF October 31, 1988 9:23:34 am PST
Eduardo Pelegri-Llopart March 16, 1989 1:28:42 pm PST
Chauser, November 8, 1990 9:05 am PST
Doug Wyatt, September 25, 1990 4:51 pm PDT
Willie-s, August 20, 1991 5:52 pm PDT
Michael Plass, November 27, 1991 11:04 am PST
DIRECTORY
Commander, -- for debug expandname
BasicTime,
Convert,
FS,
FSBackdoor,
FSName,
IO,
PFS,
PFSNames,
RefText,
Rope;
FSOnPFSImpl: CEDAR PROGRAM
IMPORTS Commander, Convert, IO, PFSNames, PFS, RefText, Rope
EXPORTS FS, FSBackdoor, FSName
~ BEGIN
ROPE: PRIVATE TYPE = Rope.ROPE;
STREAM: PRIVATE TYPE = IO.STREAM;
Stuff from FS (to be replaced by PFS)
Error: PUBLIC ERROR [error: FS.ErrorDesc] ~ PFS.Error;
Wrap: PROC [inner: PROC, wDir: ROPE] ~ {
IF wDir = NIL THEN inner[] ELSE PFS.DoInWDir[PFS.PathFromRope[wDir], inner];
};
StreamOpen: PUBLIC PROC [fileName: ROPE,
accessOptions: FS.AccessOptions ¬ $read,
streamOptions: FS.StreamOptions ¬ FS.defaultStreamOptions,
keep: CARDINAL ¬ 1,
createByteCount: INT ¬ 2560,
streamBufferParms: FS.StreamBufferParms ¬ FS.defaultStreamBufferParms,
extendFileProc: FS.ExtendFileProc ¬ NIL,
wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT,
remoteCheck: BOOL ¬ TRUE,
wDir: ROPE ¬ NIL,
checkFileType: BOOL ¬ FALSE,
fileType: FS.FileType ¬ FS.tUnspecified
] RETURNS [st: STREAM] = {
StreamOpenInner: PROC ~ {
st ¬ PFS.StreamOpen[
fileName: PFS.PathFromRope[fileName],
accessOptions: (SELECT accessOptions FROM read => read, write => write, create => create, append => append, ENDCASE => ERROR),
wantedUniqueID: UIDFromGMT[wantedCreatedTime],
checkFileType: checkFileType,
fileType: [fileType],
createOptions: [keep: keep],
streamOptions: [includeFormatting: NOT streamOptions[tiogaRead], closeFSOpenFileOnClose: streamOptions[closeFSOpenFileOnClose]],
streamBufferParms: [bytesPerBuffer: streamBufferParms.vmPagesPerBuffer*512, nBuffers: streamBufferParms.nBuffers]
];
};
Wrap[StreamOpenInner, wDir];
};
OpenFileFromStream: PUBLIC PROC [self: STREAM] RETURNS [FS.OpenFile] = {
RETURN [WrapOpenFile[PFS.OpenFileFromStream[self]]]
};
StreamFromOpenFile: PUBLIC PROC [openFile: FS.OpenFile,
accessRights: FS.Lock ¬ $read,
initialPosition: FS.InitialPosition ¬ $start,
streamOptions: FS.StreamOptions ¬ FS.defaultStreamOptions,
streamBufferParms: FS.StreamBufferParms ¬ FS.defaultStreamBufferParms,
extendFileProc: FS.ExtendFileProc ¬ NIL
] RETURNS [stream: STREAM ¬ NIL] = {
stream ¬ PFS.StreamFromOpenFile[
openFile: BreakOpenFile[openFile],
accessOptions: SELECT TRUE FROM
(accessRights = read) => read,
((accessRights = write) AND (initialPosition = start)) => write,
((accessRights = write) AND (initialPosition = end)) => append,
ENDCASE => ERROR, -- Can't get here
streamOptions: [includeFormatting: NOT streamOptions[tiogaRead], closeFSOpenFileOnClose: streamOptions[closeFSOpenFileOnClose]],
streamBufferParms: [bytesPerBuffer: streamBufferParms.vmPagesPerBuffer*512, nBuffers: streamBufferParms.nBuffers]
];
};
Open: PUBLIC PROC [name: ROPE,
lock: FS.Lock ¬ $read,
wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT,
remoteCheck: BOOL ¬ TRUE,
wDir: ROPE ¬ NIL,
verifyNow: BOOL ¬ FALSE,
checkFileType: BOOL ¬ FALSE,
fileType: FS.FileType ¬ FS.tUnspecified
] RETURNS [file: FS.OpenFile ¬ FS.nullOpenFile] ~ {
OpenInner: PROC ~ {
file ¬ WrapOpenFile[PFS.Open[
name: PFS.PathFromRope[name],
access: SELECT lock FROM read => read, write => write, ENDCASE => ERROR,
wantedUniqueID: UIDFromGMT[wantedCreatedTime],
checkFileType: checkFileType,
fileType: [fileType]
]];
};
Wrap[OpenInner, wDir];
};
Create: PUBLIC PROC [name: ROPE,
setPages: BOOL ¬ FALSE, pages: INT ¬ 0,
setKeep: BOOL ¬ FALSE, keep: CARDINAL ¬ 1,
wDir: ROPE ¬ NIL,
fileType: FS.FileType ¬ FS.tUnspecified
] RETURNS [file: FS.OpenFile ¬ FS.nullOpenFile] ~ {
CreateInner: PROC ~ {
file ¬ WrapOpenFile[PFS.Open[
name: PFS.PathFromRope[name],
access: create,
fileType: [fileType],
createOptions: [keep: IF setKeep THEN keep ELSE 0, fileType: [fileType]]
]];
};
Wrap[CreateInner, wDir];
};
BreakOpenFile: PROC [file: FS.OpenFile] RETURNS [PFS.OpenFile] = {
WITH file SELECT FROM
pfs: REF PFS.OpenFile => RETURN [pfs­];
ENDCASE => RETURN [NIL];
};
WrapOpenFile: PROC [file: PFS.OpenFile] RETURNS [FS.OpenFile] = {
RETURN [IF file = NIL THEN FS.nullOpenFile ELSE [NEW[PFS.OpenFile ¬ file]]]
};
GetInfo: PUBLIC PROC [file: FS.OpenFile]
RETURNS [keep: CARDINAL ¬ 0, pages: INT ¬ 0, bytes: INT ¬ 0, created: BasicTime.GMT, lock: FS.Lock ¬ read, fileType: FS.FileType ¬ FS.tUnspecified] = {
known clients are only interested in the create time
uniqueID: PFS.UniqueID;
pfsFileType: PFS.FileType;
[uniqueID: uniqueID, bytes: bytes, fileType: pfsFileType] ¬ PFS.GetInfo[BreakOpenFile[file]];
pages ¬ (bytes + 511)/512;
created ¬ uniqueID.egmt.gmt;
fileType ¬ [pfsFileType];
};
nameFormat: PFS.NameFormat ¬ brackets;
RopeFromPath: PROC [path: PFS.PATH] RETURNS [ROPE] = INLINE {
RETURN [IF path = NIL THEN NIL ELSE PFS.RopeFromPath[path, nameFormat]]
};
GetName: PUBLIC PROC [file: FS.OpenFile] RETURNS [fullFName, attachedTo: ROPE] = {
takes advantage of the fact that all of the clients are only intersted in fullFName
fullPath, attachedPath: PFS.PATH;
[fullFName: fullPath, attachedTo: attachedPath] ¬ PFS.GetInfo[BreakOpenFile[file]];
fullFName ¬ RopeFromPath[fullPath];
attachedTo ¬ RopeFromPath[attachedPath];
};
SetByteCountAndCreatedTime: PUBLIC PROC [file: FS.OpenFile,
bytes: INT ¬ -1, created: BasicTime.GMT ¬ BasicTime.nullGMT] ~ {
PFS.SetByteCountAndUniqueID[BreakOpenFile[file], bytes, UIDFromGMT[created]];
};
FilePtr: TYPE = LONG POINTER TO INT;
Copy: PUBLIC PROC [from, to: ROPE,
setKeep: BOOL ¬ FALSE, keep: CARDINAL ¬ 1,
wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT,
remoteCheck: BOOL ¬ TRUE,
attach: BOOL ¬ FALSE,
wDir: ROPE ¬ NIL
] RETURNS [toFName: ROPE] = {
CopyInner: PROC ~ {
Confirm: PFS.NameConfirmProc ~ {
PROC [fullName: PATH, uniqueID: UniqueID] RETURNS [continue: BOOLFALSE];
toFName ¬ RopeFromPath[fullName];
RETURN [TRUE];
};
IF attach THEN toFName ¬ RopeFromPath[
PFS.Attach[
attachment: PFS.PathFromRope[to],
attachedFile: PFS.PathFromRope[from],
wantedUniqueID: UIDFromGMT[wantedCreatedTime],
remoteCheck: remoteCheck ! PFS.Error => { attach ¬ FALSE; CONTINUE }
]
];
IF NOT attach THEN {
PFS.Copy[
from: PFS.PathFromRope[from],
to: PFS.PathFromRope[to],
wantedUniqueID: UIDFromGMT[wantedCreatedTime],
confirmProc: Confirm
];
};
};
Wrap[CopyInner, wDir];
};
Rename: PUBLIC PROC [from, to: ROPE,
setKeep: BOOL ¬ FALSE, keep: CARDINAL ¬ 1,
wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT,
wDir: ROPE ¬ NIL
] = {
RenameInner: PROC ~ {
PFS.Rename[
from: PFS.PathFromRope[from],
to: PFS.PathFromRope[to],
wantedUniqueID: UIDFromGMT[wantedCreatedTime]
];
};
Wrap[RenameInner, wDir];
};
UIDFromGMT: PROC [gmt: BasicTime.GMT] RETURNS [PFS.UniqueID] ~ INLINE {
RETURN [[egmt: [gmt: gmt, usecs: 0]]]
};
Delete: PUBLIC PROC [name: ROPE,
wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT,
wDir: ROPE ¬ NIL
] = {
DeleteInner: PROC ~ {
PFS.Delete[name: PFS.PathFromRope[name], wantedUniqueID: UIDFromGMT[wantedCreatedTime]];
};
Wrap[DeleteInner, wDir];
};
FileInfo: PUBLIC PROC [
name: ROPE,
wantedCreatedTime: BasicTime.GMT ¬ BasicTime.nullGMT,
remoteCheck: BOOL ¬ TRUE,
wDir: ROPE ¬ NIL
] RETURNS [
fullFName, attachedTo: ROPE ¬ NIL,
keep: CARDINAL ¬ 1,
bytes: INT ¬ 0,
created: BasicTime.GMT ¬ BasicTime.nullGMT,
fileType: FS.FileType ¬ FS.tUnspecified
] = {
Takes advantage of the fact that the only client is C2CPrincOps which just
cares about the keep value, which is always 1 for UX
FileInfoInner: PROC ~ {
fullPath, attachedPath: PFS.PATH;
uniqueID: PFS.UniqueID;
pfsFileType: PFS.FileType;
[fullFName: fullPath, attachedTo: attachedPath, uniqueID: uniqueID, bytes: bytes, fileType: pfsFileType] ¬ PFS.FileInfo[name: PFS.PathFromRope[name], wantedUniqueID: UIDFromGMT[wantedCreatedTime]];
fullFName ¬ RopeFromPath[fullPath];
attachedTo ¬ RopeFromPath[attachedPath];
created ¬ uniqueID.egmt.gmt;
fileType ¬ [pfsFileType];
};
Wrap[FileInfoInner, wDir];
};
SetKeep: PUBLIC PROC [name: ROPE, keep: CARDINAL ¬ 1, wDir: ROPE ¬ NIL] = {
SetKeepInner: PROC ~ {
[] ¬ PFS.PathFromRope[name]; -- Syntax check
};
Wrap[SetKeepInner, wDir];
};
BytesForPages: PUBLIC PROC [pages: INT] RETURNS [bytes: INT] = {
RETURN [pages * 512] -- Ignore real page size
};
PagesForBytes: PUBLIC PROC [bytes: INT] RETURNS [pages: INT] = {
RETURN [(bytes+511)/512]
};
GetWDir: PUBLIC PROC [wDir: ROPE ¬ NIL] RETURNS [ans: ROPE] ~ {
GetWDirInner: PROC ~ {ans ¬ RopeFromPath[PFS.GetWDir[]]};
Wrap[GetWDirInner, wDir];
};
DIRPtr: TYPE = POINTER;
DirEntPtr: TYPE = POINTER;
EnumerateForNames: PUBLIC PROC [pattern: ROPE, proc: FS.NameProc, wDir: ROPE ¬ NIL] = {
EachPath: PFS.NameProc ~ {
PROC [name: PATH] RETURNS [continue: BOOLTRUE];
continue ¬ proc[RopeFromPath[name]];
};
EnumerateForNamesInner: PROC ~ {
PFS.EnumerateForNames[pattern: PFS.PathFromRope[pattern], proc: EachPath];
};
Wrap[EnumerateForNamesInner, wDir];
};
EnumerateForInfo: PUBLIC PROC [pattern: ROPE, proc: FS.InfoProc, wDir: ROPE ¬ NIL] = {
EachInfo: PFS.InfoProc ~ {
PROC [
fullFName, attachedTo: PATH,
uniqueID: UniqueID,
bytes: INT,
mutability: Mutability,
fileType: FileType
] RETURNS [continue: BOOLTRUE]
continue ¬ proc[fullFName~RopeFromPath[fullFName], attachedTo~RopeFromPath[attachedTo], created~uniqueID.egmt.gmt, bytes~bytes, keep~1, fileType~[fileType]];
};
EnumerateForInfoInner: PROC ~ {
PFS.EnumerateForInfo[pattern: PFS.PathFromRope[pattern], proc: EachInfo];
};
Wrap[EnumerateForInfoInner, wDir];
};
Close: PUBLIC PROC [file: FS.OpenFile] = {
PFS.Close[BreakOpenFile[file]]
};
nullCP: FS.ComponentPositions = [server: nullPos, dir: nullPos, subDirs: nullPos, base: nullPos, ext: nullPos, ver: nullPos];
nullPos: FS.Position = [start: 0, length: 0];
ExpandName: PUBLIC PROC[name: ROPE, wDir: ROPE ¬ NIL]
RETURNS [fullFName: ROPE ¬ NIL, cp: FS.ComponentPositions, dirOmitted: BOOL] = {
path: PFS.PATH;
scratch: REF TEXT ~ RefText.ObtainScratch[100];
text: REF TEXT ¬ scratch;
GetPath: PROC ~ {
path ¬ PFS.AbsoluteName[PFS.PathFromRope[name]];
};
lastVersion: PFSNames.Version ¬ [none];
Append: PROC [t: REF TEXT, p: PFS.PATH, i: INT] RETURNS [REF TEXT] ~ {
comp: PFSNames.Component ~ PFSNames.Fetch[p, i];
RETURN[ RefText.AppendRope[t, comp.name.base, comp.name.start, comp.name.len] ];
};
isADirectory: BOOL;
comps: NAT;
Wrap[GetPath, wDir];
comps ¬ PFSNames.ComponentCount[path];
isADirectory ¬ PFSNames.IsADirectory[path];
dirOmitted ¬ FALSE;
cp ¬ nullCP;
n.b. the view component is not represented in CFS-style names.
Form the server component
IF comps >= 2 THEN {
text ¬ RefText.AppendChar[text, '[ ];
cp.server.start ¬ text.length;
text ¬ Append[text, path, 1];
cp.server.length ¬ text.length-cp.server.start;
text ¬ RefText.AppendChar[text, '] ];
cp.dir.start ¬ text.length;
cp.subDirs.start ¬ text.length;
};
Form the directory component
SELECT TRUE FROM
comps>3 OR comps=3 AND isADirectory => {
text ¬ RefText.AppendChar[text, '< ];
cp.dir.start ¬ text.length;
text ¬ Append[text, path, 2];
cp.dir.length ¬ text.length-cp.dir.start;
text ¬ RefText.AppendChar[text, '> ];
cp.subDirs.start ¬ text.length;
};
comps=3 AND NOT isADirectory => {
cp.base.start ¬ text.length;
text ¬ Append[text, path, 2];
cp.base.length ¬ text.length-cp.base.start;
lastVersion ¬ path.ShortName[].version;
dirOmitted ¬ TRUE;
};
ENDCASE => dirOmitted ¬ TRUE;
Form the remaining the components ...
FOR i: NAT IN [3..comps) DO
IF i<comps-1 OR isADirectory THEN {
subdirectories and ...
text ¬ Append[text, path, i];
cp.subDirs.length ¬ text.length-cp.subDirs.start;
text ¬ RefText.AppendChar[text, '> ];
}
ELSE {
the base.
cp.base.start ¬ text.length;
text ¬ Append[text, path, i];
cp.base.length ¬ text.length-cp.base.start;
lastVersion ¬ path.ShortName[].version;
};
ENDLOOP;
Form the version part
IF lastVersion.versionKind # none THEN {
text ¬ RefText.AppendChar[text, '! ];
cp.ver.start ¬ text.length;
text ¬ SELECT lastVersion.versionKind FROM
lowest => RefText.AppendChar[text, 'L ],
highest => RefText.AppendChar[text, 'H ],
all => RefText.AppendChar[text, '* ],
numeric => Convert.AppendCard[text, lastVersion.version],
ENDCASE => text;
cp.ver.length ¬ text.length-cp.ver.start;
};
Parse the base into base and extension
FOR i: NAT DECREASING IN [cp.base.start..cp.base.start+cp.base.length) DO
IF text[i] = '. THEN {
cp.ext.start ¬ i+1;
cp.ext.length ¬ cp.base.start+cp.base.length-(i+1);
cp.base.length ¬ i-cp.base.start;
EXIT;
};
ENDLOOP;
Fix up the component positions so they are properly monotonic
IF cp.base = nullPos THEN { cp.base.start ¬ text.length };
IF cp.ext = nullPos THEN {cp.ext.start ¬ cp.base.start+cp.base.length;
IF cp.ext.start = CARDINAL[text.length-1] THEN cp.ext.start ¬ cp.ext.start+1};
IF cp.ver = nullPos THEN {cp.ver.start ¬ cp.ext.start+cp.ext.length;
IF cp.ver.start = CARDINAL[text.length-1] THEN cp.ver.start ¬ cp.ver.start+1};
fullFName ¬ Rope.FromRefText[text];
RefText.ReleaseScratch[scratch];
};
Stuff from FSBackdoor
EnumerateCacheForNames: PUBLIC PROC [proc: FSBackdoor.NameProc, volName, pattern: ROPE] = { -- This is pretty easy for Unix - it has no cache to enumerate.
};
group: PACKED ARRAY FSBackdoor.ErrorCode OF FS.ErrorGroup = [
-- 4-- bug, bug, bug, bug,
--14-- environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment, environment,
-- 2-- environment, environment,
-- 9-- client, client, client, client, client, client, client, client, client,
--12-- user, user, user, user, user, user, user, user, user, user, user, user
];
codeAtom: ARRAY FSBackdoor.ErrorCode OF ATOM = [
$ok, $inconsistent, $software, $badFP,
$wentOffline, $hardware, $volumeFull, $fragmented, $noMoreVersions, $serverInaccessible, $connectionRejected, $connectionTimedOut, $badCredentials, $accessDenied, $quotaExceeded, $invalidPropertyPage, $badBTree, $outOfPropertySpace,
$lockConflict, $fileBusy,
$noCache, $wrongLock, $globalWriteLock, $zeroKeep, $badByteCount, $unknownPage, $invalidOpenFile, $notImplemented, $fileTypeMismatch,
$nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed, $versionSpecified, $globalCreation, $badWorkingDir, $noKeeps, $cantUpdateTiogaFile
];
ProduceError: PUBLIC PROC [code: FSBackdoor.ErrorCode, explanation: ROPE] = {
ERROR Error [ [group[code], codeAtom[code], explanation] ];
};
From FSPseudoServers:
TranslateForRead: PUBLIC PROC [server: ROPE] RETURNS [LIST OF ROPE] = {
RETURN [NIL] -- who uses this stuff?
RETURN[UFSPseudoServers.TranslateForRead[server]]
};
Lookup: PUBLIC PROC [server: ROPE] RETURNS [FSPseudoServers.PseudoServerList] = TRUSTED {
RETURN [NIL] -- who uses this stuff?
RETURN[LOOPHOLE[UFSPseudoServers.Lookup[server]]]
};
From FSName
BangVersionFile: PUBLIC PROC [file: ROPE, version: FSBackdoor.Version] RETURNS [ROPE] = {
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] ] ];
};
VersionFromRope: PUBLIC PROC [r: ROPE] RETURNS [v: FSBackdoor.Version] = {
IF Rope.IsEmpty[r]
THEN v ¬ FSBackdoor.highestVersion
ELSE [v, ] ¬ ParseVersion[r, 0, FALSE];
};
VersionPartFromVersion: PROC [version: FSBackdoor.Version] RETURNS [r: Rope.Text] = {
t: REF TEXT;
SELECT version FROM
FSBackdoor.lowestVersion, FSBackdoor.highestVersion => r ¬ NIL;
ENDCASE => TRUSTED {
r ¬ Rope.NewText[6];
t ¬ LOOPHOLE[r];
t[0] ¬ '!;
t.length ¬ 1;
[] ¬ Convert.AppendInt[t, version];
};
};
ParseVersion: PROC [name: ROPE, index: CARDINAL, pattern: BOOL ¬ FALSE]
RETURNS [value: FSBackdoor.Version, vI: FSName.VersionInfo] = {
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] => {
num: LONG CARDINAL ¬ 0;
DO
num ¬ num*10 + (c - '0);
IF index = lastIndex THEN {
IF num NOT IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion)
THEN EXIT;
value ¬ [num];
vI ¬ number;
RETURN;
};
index ¬ index + 1;
c ¬ Rope.Fetch[name, index];
IF c NOT IN ['0 .. '9]
THEN EXIT;
ENDLOOP;
};
'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];
};
IllegalVersion: PROC [n: ROPE] = {
ProduceError[ illegalName, QuotedName[n, " has an illegal version part"] ];
};
QuotedName: PROC [n, rest: ROPE] RETURNS [ROPE] = {
RETURN [ Rope.Cat["\"", n, "\"", rest] ];
};
DebugExpandName: Commander.CommandProc ~ {
ENABLE Error => {msg ¬ error.explanation; result ¬ $Failure; CONTINUE};
fullFName: ROPE;
cp: FS.ComponentPositions;
WHILE Rope.Match[" *", cmd.commandLine] DO cmd.commandLine ¬ Rope.Substr[cmd.commandLine, 1] ENDLOOP;
cmd.commandLine ¬ Rope.Substr[cmd.commandLine, 0, cmd.commandLine.SkipTo[skip: "\l\r"]];
[fullFName, cp] ¬ ExpandName[cmd.commandLine];
IO.PutRope[cmd.out, fullFName];
IO.PutRope[cmd.out, "\n"];
IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.server.start]], [integer[cp.server.length]]];
IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.dir.start]], [integer[cp.dir.length]]];
IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.subDirs.start]], [integer[cp.subDirs.length]]];
IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.base.start]], [integer[cp.base.length]]];
IO.PutF[cmd.out, "[%g, %g], ", [integer[cp.ext.start]], [integer[cp.ext.length]]];
IO.PutF[cmd.out, "[%g, %g]\n", [integer[cp.ver.start]], [integer[cp.ver.length]]];
};
Commander.Register["DebugExpandName", DebugExpandName];
END.