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: BOOL ← FALSE];
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: BOOL ← TRUE];
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: BOOL ← TRUE]
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];