InitialCommandsImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
L. Stewart, January 17, 1984 5:03 pm
Russ Atkinson, April 23, 1985 12:28:45 pm PST
Paul Rovner, November 29, 1983 8:51 am
Doug Wyatt, May 25, 1984 2:52:01 pm PDT
DIRECTORY
BasicTime USING [FromPupTime, GetClockPulses, GMT, Now, Period, Pulses, PulsesToMicroseconds, PulsesToSeconds],
Booting USING [Boot, Checkpoint],
Commander USING [CommandObject, CommandProc, CommandProcHandle, CommandProcObject, Handle, Lookup, Register],
CommandTool USING [AddProcToList, AddSearchRule, ArgumentVector, DoCommand, Failed, GetProp, Parse, ParseToList, RemoveProcFromList],
CommandToolLookup USING [DoLookup, FindMatchingCommands],
Convert USING [AppendChar, Error, IntFromRope, RopeFromTime],
EditedStream USING [Rubout],
FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, ResolveRelativePath, StripVersionNumber],
FS USING [EnumerateForNames, Error, NameProc, StreamOpen],
IO USING [Close, EndOf, EndOfStream, Error, GetBlock, GetLine, PutBlock, PutChar, PutF, PutF1, PutFR, PutRope, STREAM],
List USING [AList, Assoc, Memb, PutAssoc],
Process USING [Abort, CheckForAbort, GetCurrent, Pause, SecondsToTicks],
Real USING [Fix, RealException],
Rope USING [Concat, Equal, Fetch, Find, IsEmpty, Length, Match, ROPE],
RopeList USING [DReverse],
SafeStorage USING [NWordsAllocated],
SystemVersion USING [bootFileDate, release],
TiogaMenuOps USING [Open],
UserCredentials USING [Get],
ViewerClasses USING [Viewer],
VMStatistics USING [pageFaults];
InitialCommandsImpl: CEDAR MONITOR
IMPORTS BasicTime, Booting, Commander, CommandTool, CommandToolLookup, Convert, EditedStream, FileNames, FS, IO, List, Process, Real, Rope, RopeList, SafeStorage, SystemVersion, TiogaMenuOps, UserCredentials, VMStatistics
= BEGIN
ProcAny: TYPE = PROC ANY RETURNS ANY;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Date: Commander.CommandProc = {
RETURN[NIL, Convert.RopeFromTime[from: BasicTime.Now[], includeDayOfWeek: TRUE]];
};
User: Commander.CommandProc = {
user: ROPE ← UserCredentials.Get[].name;
IF user.Length[] = 0 THEN user ← "??";
RETURN[NIL, user];
};
Version: Commander.CommandProc = TRUSTED {
PutVersion[cmd.out];
};
PutVersion: PROC [out: STREAM] = {
IO.PutF[out, "Cedar %g.%g",
[integer[SystemVersion.release.major]], [integer[SystemVersion.release.minor]]];
IF SystemVersion.release.patch # 0 THEN
IO.PutF1[out, ".%g", [integer[SystemVersion.release.patch]]];
IO.PutF1[out, " of %t\n", [time[BasicTime.FromPupTime[SystemVersion.bootFileDate]]]];
};
Fail: Commander.CommandProc = {
RETURN[$Failure, "This Command Always Fails"];
};
HelpCommand: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
full: BOOL ← cmd.procData.clientData = $Full;
out: STREAM ← cmd.out;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
IF argv.argc <= 1 THEN {msg ← "Usage: Help pattern ...\n"; GO TO oops};
FOR i: NAT IN [1..argv.argc) DO
arg: ROPE ← argv[i];
matchList: LIST OF ROPE
IF full
THEN CommandToolLookup.DoLookup[cmd, arg].paths
ELSE CommandToolLookup.FindMatchingCommands[arg, FALSE, CommandTool.GetProp[cmd, $SearchRules]].paths;
IF matchList = NIL THEN {
IO.PutF1[out, "No matching commands for %g\n", [rope[arg]] ];
LOOP;
};
FOR each: LIST OF ROPE ← matchList, each.rest WHILE each # NIL DO
name: ROPE ← each.first;
pData: Commander.CommandProcHandle ← Commander.Lookup[name];
IO.PutF1[out, "%-20g", [rope[name]] ];
IF pData = NIL THEN {
Maybe this is a file, really
SELECT TRUE FROM
NOT full => IO.PutRope[out, " {helpless}\n"];
Rope.Match["*.load*", name, FALSE] => IO.PutRope[out, " {load file}\n"];
Rope.Match["*.cm*", name, FALSE] => IO.PutRope[out, " {command file}\n"];
ENDCASE => IO.PutRope[out, " {helpless}\n"];
LOOP;
};
IO.PutRope[out, " "];
IO.PutRope[out, pData.doc];
IO.PutRope[out, "\n"];
ENDLOOP;
IF matchList.rest = NIL THEN {
Exact match, so try to print the implementing procedure as well
pData: Commander.CommandProcHandle ← Commander.Lookup[matchList.first];
IF pData # NIL AND pData.proc # NIL THEN {
new: REF ProcAny ← NEW[ProcAny ← pData.proc];
IO.PutF1[out, "{implementor: %g}\n", [refAny[new]] ];
};
};
ENDLOOP;
EXITS oops => result ← $Failure;
};
Unregister: Commander.CommandProc = {
procData: Commander.CommandProcHandle;
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
IF argv.argc = 2
THEN {
procData ← Commander.Lookup[argv[1]];
IF procData # NIL AND procData.proc # NIL
THEN Commander.Register[key: argv[1], proc: NIL, doc: NIL]
ELSE cmd.out.PutF1["Unregister: %g not found\n", [rope[argv[1]]]];
}
ELSE cmd.out.PutRope["Usage: Unregister commandname\n"];
EXITS oops => result ← $Failure;
};
Open: Commander.CommandProc = {
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
IF argv.argc < 2 THEN {OpenViewer[NIL, cmd.out]; RETURN};
FOR i: NAT IN [1..argv.argc) DO
list: LIST OF ROPE ← ExpandStar[argv[i]];
WHILE list # NIL DO
name: ROPE ← FileNames.ResolveRelativePath[list.first];
OpenViewer[name, cmd.out];
list ← list.rest;
ENDLOOP;
ENDLOOP;
EXITS oops => result ← $Failure;
};
ExpandStar: PROC [token: ROPE] RETURNS [LIST OF ROPE] = {
IF Rope.Find[token, "*"] # -1 THEN {
stripVersion: BOOL;
listOfTokens: LIST OF ROPENIL;
ConsProc: FS.NameProc = {
fullFName ← FileNames.ConvertToSlashFormat[fullFName];
IF stripVersion THEN fullFName ← FileNames.StripVersionNumber[fullFName];
listOfTokens ← CONS[fullFName, listOfTokens]; -- on front of list
RETURN[TRUE];
};
IF token.Find["!"] = -1 THEN token ← Rope.Concat[token, "!H"];
stripVersion ← token.Find["!H", 0, FALSE] # -1;
FS.EnumerateForNames[pattern: FileNames.ResolveRelativePath[token], proc: ConsProc
! FS.Error => IF error.group # bug THEN CONTINUE];
RETURN[RopeList.DReverse[listOfTokens]];
}
ELSE RETURN[LIST[token]];
};
New: Commander.CommandProc = {
OpenViewer[NIL, cmd.out];
};
Comment: Commander.CommandProc = {
};
OpenViewer: PROC [name: ROPE, out: STREAM] = {
viewer: ViewerClasses.Viewer;
IF Rope.IsEmpty[name] THEN name ← FileNames.CurrentWorkingDirectory[];
viewer ← TiogaMenuOps.Open[name];
IF viewer = NIL THEN out.PutF1["\tViewer file not found: %g\n", [rope[name]]]
ELSE out.PutF1["\tCreated Viewer: %g\n", [rope[viewer.name]]];
};
statBeforeRef: Commander.CommandProcHandle
NEW[Commander.CommandProcObject ← [StatBefore]];
statAfterRef: Commander.CommandProcHandle
NEW[Commander.CommandProcObject ← [StatAfter]];
StatDataObject: TYPE = RECORD [
cmd: Commander.Handle ← NIL,
startTime: BasicTime.GMT,
startPulses: BasicTime.Pulses,
startFaults: INT,
startWords: INT
];
MaxStats: NAT = 10;
StatArray: TYPE = RECORD [
a: SEQUENCE length: [0..MaxStats+1) OF StatDataObject
];
stats: REF StatArray ← NEW[StatArray[MaxStats]];
StatBefore: ENTRY Commander.CommandProc = {
ENABLE UNWIND => NULL;
FOR i: NAT IN [0..MaxStats) DO
IF stats.a[i].cmd = NIL THEN {
stats.a[i] ← [
cmd: cmd,
startTime: BasicTime.Now[],
startPulses: BasicTime.GetClockPulses[],
startFaults: VMStatistics.pageFaults,
startWords: SafeStorage.NWordsAllocated[] ];
EXIT;
};
ENDLOOP;
result ← $Preserve;
};
StatAfter: ENTRY Commander.CommandProc = {
ENABLE UNWIND => NULL;
pulses: BasicTime.Pulses = BasicTime.GetClockPulses[];
now: BasicTime.GMT = BasicTime.Now[];
wordsNow: INT = SafeStorage.NWordsAllocated[];
faultsNow: INT = VMStatistics.pageFaults;
FOR i: NAT IN [0..MaxStats) DO
statObj: StatDataObject = stats.a[i];
IF statObj.cmd = cmd THEN {
seconds: INT ← BasicTime.Period[statObj.startTime, now];
nw: INT = wordsNow - statObj.startWords;
nf: INT = faultsNow - statObj.startFaults;
stats.a[i].cmd ← NIL;
cmd.out.PutRope[" {"];
SELECT seconds FROM
< 0 => cmd.out.PutRope["?? seconds"];
>= 60 => {
Long elapsed time, so use low precision clock to avoid overflow
cmd.out.PutF1["%r seconds", [integer[seconds]]];
};
ENDCASE => {
High precision elapsed time
tt: REAL ← BasicTime.PulsesToSeconds[pulses - statObj.startPulses];
fraction: INT ← 0;
seconds ← Real.Fix[tt
! Real.RealException => { seconds ← LAST[INT]; CONTINUE; }];
fraction ← Real.Fix[(tt - seconds) * 100.0
! Real.RealException => { fraction ← 0; CONTINUE; }];
cmd.out.PutF["%d.%02d seconds", [integer[seconds]], [integer[fraction]]]
};
IF nw > 0 AND nw < 10000000 THEN cmd.out.PutF1[", %d words", [integer[nw]]];
IF nf > 0 AND nf < 10000000 THEN cmd.out.PutF1[", %d page faults", [integer[nf]]];
cmd.out.PutRope["}\n"];
EXIT;
};
ENDLOOP;
Now we have to preserve the result, or else we will not preserve $Failure!
result ← $Preserve;
};
Statistics: Commander.CommandProc = {
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
which: {on, off, swap} ← swap;
IF argv.argc > 1 THEN {
arg: ROPE ← argv[1];
SELECT TRUE FROM
Rope.Equal[arg, "on", FALSE] => which ← on;
Rope.Equal[arg, "off", FALSE] => which ← off;
ENDCASE => {msg ← "Illegal option.\n"; GO TO oops};
};
WITH List.Assoc[key: $Before, aList: cmd.propertyList] SELECT FROM
rbl: LIST OF REF ANY => {
IF List.Memb[ref: statBeforeRef, list: rbl] THEN {
can turn off if we want to
SELECT which FROM
swap, off => {
cmd.propertyList ← CommandTool.RemoveProcFromList[aList: cmd.propertyList, listKey: $Before, proc: statBeforeRef];
cmd.propertyList ← CommandTool.RemoveProcFromList[aList: cmd.propertyList, listKey: $After, proc: statAfterRef];
GO TO showOff;
};
ENDCASE;
GO TO showOn;
};
};
ENDCASE;
SELECT which FROM
swap, on => {
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Before, proc: statBeforeRef];
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $After, proc: statAfterRef];
GO TO showOn;
};
ENDCASE;
GO TO showOff;
EXITS
showOff => msg ← "Statistics off.\n";
showOn => msg ← "Statistics on.\n";
oops => result ← $Failure;
};
Echo: Commander.CommandProc = {
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd, TRUE
! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
FOR i: NAT IN [1..argv.argc) DO
cmd.out.PutRope[argv[i]];
cmd.out.PutChar[' ];
Process.CheckForAbort[];
ENDLOOP;
cmd.out.PutChar['\n];
EXITS oops => result ← $Failure;
};
PrintSearchRules: Commander.CommandProc = {
rules: LIST OF REF ANY
NARROW[List.Assoc[key: $SearchRules, aList: cmd.propertyList]];
cmd.out.PutRope["( "];
IF rules = NIL THEN cmd.out.PutChar[' ];
WHILE rules # NIL DO
cmd.out.PutRope[NARROW[rules.first, ROPE]];
rules ← rules.rest;
cmd.out.PutChar[' ];
Process.CheckForAbort[];
ENDLOOP;
cmd.out.PutRope[")\n"];
};
SetSearchRules: Commander.CommandProc = {
args: LIST OF ROPE;
dir: ROPE;
first: CHAR;
args ← CommandTool.ParseToList[cmd, TRUE
! CommandTool.Failed => {msg ← errorMsg; GO TO oops}].list;
IF cmd.procData.clientData = $SetSearchRules THEN
cmd.propertyList ← List.PutAssoc[key: $SearchRules, val: NIL, aList: cmd.propertyList];
WHILE args # NIL DO
dir ← args.first;
dir ← FileNames.ResolveRelativePath[dir];
IF dir.Length[] < 3 THEN {
msg ← Rope.Concat["Directory name too short: ", dir]; GO TO oops};
first ← dir.Fetch[0];
IF first # '[ AND first # '/ THEN {
msg ← Rope.Concat["Bad directory name: ", dir]; GO TO oops};
CommandTool.AddSearchRule[cmd: cmd, dir: dir, append: TRUE];
args ← args.rest;
Process.CheckForAbort[];
ENDLOOP;
EXITS oops => result ← $Failure;
};
Sleep: Commander.CommandProc = {
seconds: INT;
secondsCard: CARDINAL;
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
IF argv.argc # 2 THEN {msg ← "Usage: Sleep seconds"; GO TO oops};
seconds ← Convert.IntFromRope[argv[1] ! Convert.Error => {msg ← "Bad arg"; GO TO oops}];
IF seconds NOT IN [0..LAST[CARDINAL]] THEN {msg ← "Bad arg"; GO TO oops};
secondsCard ← seconds;
Process.Pause[Process.SecondsToTicks[seconds]];
EXITS oops => result ← $Failure;
};
Indent: Commander.CommandProc = {
depth: INT;
depthNAT: NAT;
spaces: REF TEXT;
line: REF TEXTNEW[TEXT[200]];
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
IF argv.argc > 2 THEN {msg ← "Usage: Indent depth"; GO TO oops};
IF argv.argc < 2
THEN {
depth ← 8;
depthNAT ← 8;
}
ELSE {
depth ← Convert.IntFromRope[argv[1] ! Convert.Error => {msg ← "Bad arg"; GO TO oops}];
IF depth NOT IN [0..80] THEN {msg ← "depth should be in [0..80]"; GO TO oops};
depthNAT ← depth;
};
spaces ← NEW[TEXT[depthNAT]];
FOR i: NAT IN [0..depthNAT) DO spaces[i] ← ' ; ENDLOOP;
spaces.length ← depthNAT;
DO
IF cmd.in.EndOf[ ! IO.Error => EXIT] THEN EXIT;
line ← cmd.in.GetLine[buffer: line !
IO.EndOfStream => GOTO Finished;
IO.Error => EXIT;
EditedStream.Rubout => GOTO Finished;
];
cmd.out.PutBlock[block: spaces ! IO.Error => EXIT];
cmd.out.PutBlock[block: line ! IO.Error => EXIT];
cmd.out.PutChar['\n ! IO.Error => EXIT];
ENDLOOP;
EXITS
Finished => NULL;
oops => result ← $Failure;
};
Tee: Commander.CommandProc = {
fileStream: STREAMNIL;
block: REF TEXTNEW[TEXT[512]];
{
ENABLE UNWIND => IF fileStream # NIL THEN fileStream.Close[];
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
IF argv.argc > 2 THEN {msg ← "Usage: Tee filename"; GO TO oops};
IF argv.argc = 2 THEN
fileStream ← FS.StreamOpen[fileName: argv[1], accessOptions: $create
! FS.Error => IF error.group # $bug THEN {msg ← error.explanation; GO TO oops}];
DO
count: NAT;
block.length ← 0;
IF cmd.in.EndOf[] THEN EXIT;
count ← cmd.in.GetBlock[block: block !
IO.EndOfStream => EXIT;
IO.Error => EXIT;
EditedStream.Rubout => EXIT;
];
IF count = 0 THEN EXIT;
IF fileStream # NIL THEN fileStream.PutBlock[block: block ! IO.Error => EXIT];
cmd.out.PutBlock[block: block ! IO.Error => EXIT];
Process.CheckForAbort[];
ENDLOOP;
fileStream.Close[ ! IO.Error => CONTINUE];
};
EXITS oops => RETURN[$Failure, msg];
};
Abort: Commander.CommandProc = TRUSTED {
Process.Abort[LOOPHOLE[Process.GetCurrent[], UNSPECIFIED]];
};
Shift: Commander.CommandProc = {
result ← CommandTool.DoCommand[commandLine: cmd.commandLine, parent: cmd];
};
ShiftInterp: Commander.CommandProc = {
result ← CommandTool.DoCommand[commandLine: cmd.commandLine, parent: cmd];
};
Time: Commander.CommandProc = {
start: BasicTime.Pulses ← BasicTime.GetClockPulses[];
stop: BasicTime.Pulses;
microseconds, seconds: LONG CARDINAL;
digit: CARDINAL;
any: BOOLFALSE;
text: REF TEXTNEW[TEXT[8]];
result ← CommandTool.DoCommand[commandLine: cmd.commandLine, parent: cmd];
stop ← BasicTime.GetClockPulses[];
microseconds ← BasicTime.PulsesToMicroseconds[stop - start];
seconds ← microseconds / 1000000;
microseconds ← microseconds MOD 1000000;
THROUGH [0..6) DO
microseconds ← microseconds * 10;
digit ← microseconds / 1000000;
microseconds ← microseconds MOD 1000000;
IF NOT any THEN {
text ← Convert.AppendChar[to: text, from: '., quote: FALSE];
any ← TRUE;
};
text ← Convert.AppendChar[to: text, from: digit + '0, quote: FALSE];
IF microseconds = 0 THEN EXIT;
ENDLOOP;
RETURN [NIL, IO.PutFR["Running time: %r%g\n", [cardinal[seconds]], [text[text]]]];
};
Checkpoint: Commander.CommandProc = {
errmsg: ROPE;
out: STREAM = cmd.out;
IO.PutF1[out, "Creating checkpoint at %g\n", [time[BasicTime.Now[]]]];
IO.PutF1[out, " made by %g\n", [rope[UserCredentials.Get[].name]]];
errmsg ← Booting.Checkpoint[];
IF errmsg.Length[] # 0 THEN RETURN[$Failure, errmsg];
IO.PutF[out, "Rollback at %g\n User: %g\n Version: ",
[time[BasicTime.Now[]]], [rope[UserCredentials.Get[].name]]];
PutVersion[cmd.out];
};
Rollback: Commander.CommandProc = {
RETURN[$Failure, Booting.Boot[[self[]], [r: TRUE]]];
};
Init: PROC = {
Register: PROC [name: ROPE, proc: Commander.CommandProc, doc: ROPE, data: REFNIL, un: BOOLFALSE] = {
name ← Rope.Concat["///Commands/", name];
Commander.Register[name, proc, doc, data, NOT un];
};
Register["-", Comment, "Comment", NIL, TRUE];
Register["--", Comment, "Comment", NIL, TRUE];
Register["/", Comment, "Comment", NIL, TRUE];
Register["//", Comment, "Comment", NIL, TRUE];
Register["?", HelpCommand, "List registered commands"];
Register["??", HelpCommand, "List registered commands (including those not yet loaded)", $Full];
Register["Help", HelpCommand, "List registered commands"];
Register["HelpFull", HelpCommand, "List registered commands (including those not yet loaded)", $Full];
Register["Abort", Abort, "Abort -- Raises ABORTED"];
Register["AddSearchRules", SetSearchRules, "Add command search rules: AddSearchRules list-of-directories"];
Register["Checkpoint", Checkpoint, "Create a checkpoint"];
Register["Date", Date, "Print date and time"];
Register["Echo", Echo, "Print command line"];
Register["Fail", Fail, "This command always fails"];
Register["Indent", Indent, "Indent n -- Indent n spaces"];
Register["New", New, "Open an empty viewer"];
Register["Open", Open, "Open fileName - open a viewer"];
Register["PrintSearchRules", PrintSearchRules, "Print command search rules"];
Register["Rollback", Rollback, "Roll back to previous checkpoint"];
Register["SetSearchRules", SetSearchRules, "Set command search rules: SetSearchRules directory*", $SetSearchRules];
Register["Shift", Shift, "Shift command-line (uninterpreted)", NIL, TRUE];
Register["ShiftInterp", ShiftInterp, "ShiftInterp command-line (interpreted)"];
Register["Sleep", Sleep, "Sleep n -- pause for n seconds"];
Register["Statistics", Statistics, "Turn statistics printing on or off"];
Register["Tee", Tee, "Tee file -- copy standard input to file"];
Register["Time", Time, "Time command-line (uninterpreted)", NIL, TRUE];
Register["Unregister", Unregister, "Unregister a command"];
Register["User", User, "Print name of logged in user"];
Register["Version", Version, "Print Cedar version number"];
};
Init[];
END.
April 4, 1983 4:42 pm, Stewart, Created
April 20, 1983 7:10 pm, Stewart, add commandfile arguments and auto commandfiles
September 7, 1983 11:10 am, Stewart, Cedar 5
October 24, 1983 9:42 am, Stewart, New CommandTool
November 3, 1983 9:37 am, Stewart, Bulletproofing
November 3, 1983 9:37 am, Stewart, add Checkpoint and Rollback
January 14, 1984 8:18 pm, Stewart, new Open