CommandToolImpl.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
Spreitzer, May 23, 1984 2:41:20 pm PDT
Russ Atkinson (RRA) March 14, 1986 1:34:34 pm PST
DIRECTORY
AMFiles USING [pathPrefixes],
BcdDefs USING [NullVersion, VersionStamp, BcdBase, VersionID],
Buttons USING [ButtonProc, Create],
CedarProcess USING [DoWithPriority],
Commander USING [CommandObject, CommandProcHandle, CommandProcObject, CommandProc, Handle, Register],
CommandTool USING [AmpersandSubstitution, ArgumentVector, CallList, CommandFile, ConvertToSlashFormat, CopyAList, CopyListOfRefAny, DollarSubstitution, ExecuteCommand, Failed, GetProp, Insulate, IORedirection, Parse, ParseToList, Pass1, PutLocalProperty, ResolveRelativePath, StarExpansion],
CommandToolLookup USING [DoLookup, FindMatchingCommands, FindMatchingFiles, ShowAmbiguous],
EndOps USING [Register],
FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, Directory, IsADirectory, ResolveRelativePath],
FS USING [Close, Error, GetName, nullOpenFile, Open, OpenFile, StreamOpen, Read],
IO USING [Close, EndOf, Error, Flush, GetChar, GetInfo, noInputStream, noWhereStream, PutChar, PutF, PutF1, PutFR1, PutRope, RIS, RopeFromROS, ROS, SetIndex, STREAM],
IOClasses USING [CreatePipe],
List USING [AList, Assoc, PutAssoc],
Loader USING [Error, Instantiate, IRItem, Start],
LoadState USING [Acquire, ConfigID, ConfigInfo, EnumerateConfigs, local, nullConfig, Release],
Menus USING [CreateEntry, InsertMenuEntry, MenuProc],
PrincOps USING [ControlModule, NullControl],
Process USING [Abort, CheckForAbort, Detach, GetCurrent, InvalidProcess],
ProcessProps USING [AddPropList, GetPropList, PushPropList],
ReadEvalPrint USING [ClientProc, CreateStreamEvaluator, CreateViewerEvaluator, Handle, MainLoop, RObject],
Rope USING [Cat, Concat, Equal, Fetch, Flatten, Find, FromChar, Index, IsEmpty, Length, Match, ROPE, Substr],
RopeList USING [CopyTopList, DAppend, Memb],
UserCredentials USING [Get],
ViewerClasses USING [Viewer],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
ViewerOps USING [PaintViewer],
VM USING [AddressForPageNumber, Allocate, Free, Interval];
CommandToolImpl:
CEDAR
MONITOR
IMPORTS AMFiles, Buttons, CedarProcess, Commander, CommandTool, CommandToolLookup, EndOps, FileNames, FS, IO, IOClasses, List, Loader, LoadState, Menus, Process, ProcessProps, ReadEvalPrint, Rope, RopeList, UserCredentials, ViewerIO, ViewerOps, VM
EXPORTS CommandTool
= BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
defaultPrompt: ROPE ← "%l%% %l";
Controls:
TYPE =
RECORD [
terminator: CHAR ← '\n,
uninterpreted: BOOL ← FALSE,
quitOnFailure: BOOL ← FALSE,
background: BOOL ← FALSE,
createViewer: BOOL ← FALSE,
pipe: BOOL ← FALSE,
inRedirected: BOOL ← FALSE,
outRedirected: BOOL ← FALSE,
starExpand: BOOL ← FALSE
];
EachCommand:
PUBLIC ReadEvalPrint.ClientProc = {
EachCommand handles a line of commands from read eval print up to the point that the commander handle is completely constructed. It then turns over control to ExecuteCommand.
cmd: Commander.Handle;
propertyList: List.AList ← NIL;
errorOut: STREAM ← NIL;
remainingCommands: ROPE;
someExpansion: BOOL;
expansion: BOOL ← FALSE;
cth: Commander.Handle;
pipePullStream: STREAM ← NIL;
lastControls: Controls ← [];
controls: Controls ← [];
{
-- solely for exits clauses
cth ← NARROW[h.clientData];
IF cth = NIL THEN ERROR; -- there must be one!
If this is not a viewer ReadEvalPrint, then assume it doesn't echo
IF h.viewer = NIL THEN h.out.PutRope[command];
propertyList ← cth.propertyList;
errorOut ← cth.err;
Enter a loop in which we do each of the commands represented by the line we got from ReadEvalPrint.
remainingCommands ← command;
DO
result ← NIL;
IF Rope.IsEmpty[remainingCommands]
THEN {
If what is left is too short, then we are done.
IF pipePullStream # NIL THEN result ← "[[Last command fed a pipe, output discarded]]";
GOTO PipeClose;
};
Allocate the Handle for the next command and fill in the parts that are immediately clear
cmd ← NEW[Commander.CommandObject ← []];
cmd.err ← errorOut;
cmd.propertyList ← propertyList;
controls ← [];
First use Pass1 to find the command name
[first: cmd.command, rest: remainingCommands, terminator: controls.terminator, someExpansion: someExpansion]
← CommandTool.Pass1[initial: remainingCommands, nameOnly:
TRUE
! CommandTool.Failed => {
IF pipePullStream #
NIL
THEN
errorMsg ← Rope.Concat[
errorMsg, "\n Previous command fed a pipe, output discarded."];
GOTO PipeClose;
}];
expansion ← expansion OR someExpansion;
Try looking up the command.
cmd.procData ← NIL;
IF cmd.command.IsEmpty[]
THEN {
controls.uninterpreted ← FALSE;
controls.starExpand ← FALSE;
}
ELSE {
LookupCommand[cmd];
IF cmd.procData =
NIL
THEN controls.uninterpreted ← TRUE
ELSE {
controls.uninterpreted ← NOT cmd.procData.interpreted;
controls.starExpand ← FALSE;
};
};
remainingCommands ← Rope.Cat[cmd.commandLine, Rope.FromChar[controls.terminator], remainingCommands];
IF controls.uninterpreted
THEN {
pos: INT ← Rope.Index[s1: remainingCommands, pos1: 0, s2: "\n", case: FALSE] + 1;
line: ROPE ← Rope.Substr[base: remainingCommands, start: 0, len: pos];
IF Rope.Length[line] < pos
THEN {
No \n present, so add one to keep things happy
line ← Rope.Concat[line, "\n"];
remainingCommands ← NIL;
}
ELSE
Remove the line from the remaining commands
remainingCommands ← Rope.Substr[base: remainingCommands, start: pos];
cmd.commandLine ← line;
controls.terminator ← '\n;
}
ELSE {
Use Pass1 to get the command arguments
[first: cmd.commandLine, rest: remainingCommands, terminator: controls.terminator, someExpansion: someExpansion] ← CommandTool.Pass1[initial: remainingCommands, nameOnly:
FALSE ! CommandTool.Failed => {
IF pipePullStream # NIL THEN errorMsg ← Rope.Concat[errorMsg, "\n Previous command fed a pipe, output discarded."];
result ← errorMsg;
GOTO PipeClose;
}];
expansion ← expansion OR someExpansion;
};
Now if there was no command, try again
IF cmd.procData =
NIL
THEN {
IF pipePullStream #
NIL
THEN {
result ← "[[Last command fed a pipe, output discarded.]]";
GOTO PipeClose;
};
lastControls ← controls;
Process.CheckForAbort[];
LOOP;
};
If the command is uninterpreted, we are done what if the previous command was a pipe
IF controls.uninterpreted
THEN {
IF pipePullStream #
NIL
THEN {
result ← "[[Cannot pipe into an uninterpreted command]]";
GOTO PipeClose;
};
}
ELSE {
If we get here, then do command processing
Process the terminating character
SELECT controls.terminator
FROM
'\n => NULL;
'; => controls.quitOnFailure ← TRUE;
'& => controls.background ← controls.createViewer ← TRUE;
'| => controls.background ← controls.pipe ← TRUE;
ENDCASE => {
result ← IO.PutFR1["[[Unknown terminating character: %g]]", [character[controls.terminator]] ];
GOTO PipeClose;
};
{
ENABLE CommandTool.Failed => {
result ← errorMsg;
GO TO failed;
};
CommandTool.AmpersandSubstitution[cmd];
CommandTool.DollarSubstitution[cmd];
IF controls.starExpand THEN CommandTool.StarExpansion[cmd];
[inRedirected: controls.inRedirected, outRedirected: controls.outRedirected] ← CommandTool.IORedirection[cmd];
EXITS failed => {};
};
IF result #
NIL
THEN {
result ← Rope.Cat["[[", result, "]]"];
GOTO PipeClose; -- discards remaining commands
};
Now set up the remaining streams according to lastControls and controls
First check for the error cases.
IF controls.pipe
AND controls.outRedirected
THEN {
result ← "[[Cannot redirect output and have a pipe]]";
GOTO PipeClose; -- discards remaining commands on line
};
IF lastControls.pipe
AND controls.inRedirected
THEN {
result ← "[[Previous command was a pipe, cannot redirect input]]";
GOTO PipeClose; -- discards remaining commands on line
};
Create a viewer when terminator = '& except when both input and output are redirected or when last terminator was '| and output is redirected. When a viewer is created, it always gets the error streams. The in and out streams are set if they were not set before (by redirection).
controls.createViewer ← controls.terminator = '& AND NOT ((controls.inRedirected AND controls.outRedirected) OR (lastControls.pipe AND controls.outRedirected));
Create a viewer if appropriate.
IF controls.createViewer
THEN {
viewerOut: STREAM;
viewerIn: STREAM;
Create a viewer for the command and set up streams
[in: viewerIn, out: viewerOut] ← ViewerIO.CreateViewerStreams[
name: Rope.Flatten[Rope.Cat[cmd.command, " ", cmd.commandLine], 0, 256]];
cmd.err ← viewerOut;
In the case that we alter the $ErrorInputStream, it is done as a local property, so that it will pop back later.
cmd.propertyList ← CommandTool.PutLocalProperty[key: $ErrorInputStream, val: viewerIn, aList: cmd.propertyList, origList: cmd.propertyList];
{
v: ViewerClasses.Viewer ← ViewerIO.GetViewerFromStream[viewerOut];
Menus.InsertMenuEntry[v.menu, Menus.CreateEntry["STOP!", StopHit, cmd] ];
ViewerOps.PaintViewer[v, menu];
};
IF cmd.in = NIL THEN cmd.in ← viewerIn;
IF cmd.out = NIL THEN cmd.out ← viewerOut;
};
Finish the in and out streams
IF lastControls.pipe
THEN {
cmd.in ← pipePullStream;
pipePullStream ← NIL; -- note that the pipe is finished!
};
IF controls.pipe
THEN {
pipePushStream: STREAM;
[push: pipePushStream, pull: pipePullStream] ← IOClasses.CreatePipe[];
cmd.out ← pipePushStream;
pipePushStream ← NIL;
};
};
Any remaining NIL streams should come from the commander streams
IF cmd.in = NIL THEN cmd.in ← CommandTool.Insulate[cth.in];
IF cmd.out = NIL THEN cmd.out ← CommandTool.Insulate[cth.out];
If @files are involved, then print the present command
IF expansion
THEN
h.out.PutF["[[%g%g]]\n", [rope[cmd.command]], [rope[Rope.Substr[cmd.commandLine, 0, cmd.commandLine.Length[] - 1]]]];
{
Actually execute the command in here.
savedOutStream: STREAM ← cmd.out;
ch: Commander.Handle ← cmd;
cmd.out ← h.out;
CommandTool.CallList[property: $Before, cmd: cmd, proc: NIL];
cmd.out ← savedOutStream;
IF controls.background THEN ch ← NEW[Commander.CommandObject ← cmd^];
ExecuteCommand[cmd: cmd, background: controls.background];
ch.out ← h.out;
CommandTool.CallList[property: $After, cmd: ch, proc: NIL];
};
If the command has changed the prompt, let ReadEvalPrint know about it.
WITH List.Assoc[key: $Prompt, aList: propertyList]
SELECT
FROM
rope: ROPE => h.prompt ← rope;
ENDCASE => h.prompt ← defaultPrompt;
Process.CheckForAbort[];
lastControls ← controls;
Start again with the rest of the command line.
IF controls.quitOnFailure
AND CommandTool.GetProp[cmd, $Result] = $Failure
THEN {
IF pipePullStream # NIL THEN ERROR; -- can't happen!
RETURN["[[Command failed]]"];
};
ENDLOOP;
EXITS
PipeClose => {
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
};
};
};
};
LookupCommand:
PUBLIC
PROC [cmd: Commander.Handle] = {
The Commander.Handle passed to LookupCommand has nothing filled in except err, command, and propertyList. LookupCommand fills in procData & commandLine, and alters command.
err: STREAM = cmd.err;
name: ROPE = cmd.command;
list: LIST OF ROPE;
[list, cmd.procData] ← CommandToolLookup.DoLookup[cmd, name];
SELECT
TRUE
FROM
list = NIL => {IO.PutF1[err, "[[%g not found]]\n", [rope[name]] ]; GO TO fail};
list.rest # NIL => {CommandToolLookup.ShowAmbiguous[err, list]; GO TO fail};
cmd.procData =
NIL => {
We found a command to execute
cmd.procData ←
NEW[Commander.CommandProcObject ← [
ReallyLoadAndRun,
"Command not yet loaded",
NEW[SavedCommandObject
← [command: name, commandFileName: list.first, runCommandAfter: Rope.Match["*.load*", list.first, FALSE]]]]];
};
ENDCASE;
cmd.command ← list.first;
EXITS
fail => cmd.procData ← NIL;
};
SavedCommand: TYPE = REF SavedCommandObject;
SavedCommandObject:
TYPE =
RECORD [
command: ROPE ← NIL,
commandFileName: ROPE ← NIL,
runCommandAfter: BOOL ← FALSE
];
commandFileProcData: Commander.CommandProcHandle ←
NEW[Commander.CommandProcObject ← [CommandTool.CommandFile]];
ReallyLoadAndRun: Commander.CommandProc = {
procData.clientData is a SavedCommand, load the .load file whose name is in commandLine and then Lookup and execute the saved command.
sc: SavedCommand ← NARROW[cmd.procData.clientData];
commandFileName: ROPE ← sc.commandFileName;
originalCommandLine: ROPE ← cmd.commandLine;
oldOut: IO.STREAM;
oldIn: IO.STREAM;
loadFileDirectory: ROPE;
inner:
PROC = {
ENABLE UNWIND => {cmd.out ← oldOut; cmd.in ← oldIn};
[result, msg] ← CommandTool.CommandFile[cmd];
cmd.out ← oldOut;
cmd.in ← oldIn;
};
commandFileName ← FileNames.ConvertToSlashFormat[commandFileName];
loadFileDirectory ← FileNames.Directory[path: commandFileName];
cmd.command ← "CommandTool";
cmd.commandLine ← Rope.Concat[commandFileName, originalCommandLine];
cmd.procData ← commandFileProcData;
oldOut ← cmd.out;
cmd.out ← CommandTool.Insulate[cmd.err];
oldIn ← cmd.in;
cmd.in ← CommandTool.Insulate[oldIn];
set the working directory for this call (and this call only)
ProcessProps.AddPropList[
List.PutAssoc[key: $WorkingDirectory, val: loadFileDirectory, aList: NIL],
inner];
IF result = $Failure THEN RETURN[result, msg];
cmd.command ← sc.command;
cmd.commandLine ← originalCommandLine;
IF sc.runCommandAfter
THEN {
cmd.procData ← CommandToolLookup.FindMatchingCommands[cmd.command, FALSE, CommandTool.GetProp[cmd, $SearchRules]].data;
IF cmd.procData #
NIL
AND cmd.procData.proc #
NIL
THEN {
Success. So pass along the result of executing the command.
CommandTool.ExecuteCommand[cmd, FALSE];
result ← CommandTool.GetProp[cmd, $Result];
}
ELSE RETURN[$Failure, ".load file failed to register command"];
};
};
StopHit: Menus.MenuProc =
TRUSTED {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
WITH clientData
SELECT
FROM
cmd: Commander.Handle => {
WITH CommandTool.GetProp[cmd, $ForkedProcess]
SELECT
FROM
rp:
REF
PROCESS => {
Process.Abort[rp^ ! Process.InvalidProcess => CONTINUE];
};
ENDCASE;
};
ENDCASE;
};
Base:
PROC [list: List.AList, cmd: Commander.Handle, detached:
BOOL] = {
cleanup:
PROC = {
Now clean up the streams. This will do nothing if the streams are the ones created above by Insulate, but if the streams have been changed by IO redirection, then these calls will clean them up.
IF cmd.in #
NIL
THEN
cmd.in.Close[! IO.Error => CONTINUE];
IF cmd.out #
NIL
THEN {
cmd.out.Flush[! IO.Error => CONTINUE];
cmd.out.Close[! IO.Error => CONTINUE];
};
IF cmd.err #
NIL
THEN {
cmd.err.Flush[! IO.Error => CONTINUE];
};
IF detached
THEN
Show that this command handle has finished
cmd.propertyList ← NIL;
};
innerExecute:
PROC = {
ENABLE UNWIND => cleanup[];
result: REF ANY;
msg: ROPE;
out: STREAM ← cmd.out;
originalPropertyList: List.AList ← cmd.propertyList;
[result: result, msg: msg] ← cmd.procData.proc[cmd: cmd
! ABORTED => IF detached THEN IO.PutRope[out, "\n-- Aborting ... "]
];
IF msg #
NIL
THEN {
out.PutRope[msg];
IF msg.Fetch[msg.Length[] - 1] # '\n THEN out.PutChar['\n];
};
IF detached
AND
IO.GetInfo[out].class # $Pipe
THEN IO.PutRope[out, IF result = $Failure THEN "-- Failed!\n" ELSE "-- Done.\n"]
ELSE cmd.propertyList ← List.PutAssoc[key: $Result, val: result, aList: originalPropertyList];
};
IF detached
THEN
TRUSTED {
Put on a local property for the executing process.
rp: REF PROCESS ← NEW[PROCESS ← LOOPHOLE[Process.GetCurrent[], PROCESS]];
cmd.propertyList ← CommandTool.PutLocalProperty[key: $ForkedProcess, val: rp, aList: cmd.propertyList, origList: cmd.propertyList];
};
Main code of Base
{
Perform the requested command. If there is a property list, then use the one given, otherwise inherit the process properties from the current process. If we ever get an IO.Error due to the input or output being closed, just terminate the command.
ENABLE
IO.Error => {
IF ec = StreamClosed
AND (stream = cmd.in
OR stream = cmd.out)
THEN
GO TO closed;
};
IF list #
NIL
THEN ProcessProps.PushPropList[list, innerExecute]
ELSE innerExecute[];
EXITS closed => {};
};
Now clean up the streams. This will do nothing if the streams are the ones created above by Insulate, but if the streams have been changed by IO redirection, then these calls will clean them up.
cleanup[];
};
ExecuteCommand:
PUBLIC
PROC [cmd: Commander.Handle, background:
BOOL] = {
IF background
THEN {
Make a copy of the process properties list of the caller.
processProperties: List.AList ← CommandTool.CopyAList[ProcessProps.GetPropList[]];
Set the $CommanderHandle property.
processProperties ← List.PutAssoc[key: $CommanderHandle, val: cmd, aList: processProperties];
TRUSTED { Process.Detach[FORK Base[list: processProperties, cmd: cmd, detached: background]]; };
}
ELSE {
Set the $CommanderHandle property.
[] ← List.PutAssoc[key: $CommanderHandle, val: cmd, aList: ProcessProps.GetPropList[]];
Base[list: NIL, cmd: cmd, detached: background];
};
};
DoCommand:
PUBLIC
PROC [commandLine:
ROPE, parent: Commander.Handle]
RETURNS [result:
REF
ANY] = {
Execute the given commandLine. Parent will be used to provide streams and the property list. cmd.command and cmd.commandLine will be overwritten. This is particularly useful for commands which wish to execute their own command lines as commands: DoCommand[cmd.commandLine, cmd]; DoCommand is a simple interface to EachCommand.
rep: ReadEvalPrint.Handle;
oldREP: ReadEvalPrint.Handle;
cll: INT ← commandLine.Length[];
msg: ROPE;
rep ← NEW[ReadEvalPrint.RObject ← [menuHitQueue: NIL]];
IF cll = 0 THEN RETURN[NIL];
IF commandLine.Fetch[cll - 1] # '\n THEN commandLine ← Rope.Concat[commandLine, "\n"];
IF parent =
NIL
THEN {
parent ← NEW[Commander.CommandObject ← []];
parent.err ← IO.noWhereStream;
parent.out ← IO.noWhereStream;
parent.in ← IO.noInputStream;
parent.propertyList ← List.PutAssoc[
key: $ErrorInputStream, val: IO.noInputStream, aList: parent.propertyList];
parent.propertyList ← List.PutAssoc[
key: $Prompt, val: defaultPrompt, aList: parent.propertyList];
parent.propertyList ← List.PutAssoc[
key: $SearchRules,
val: ConsRope[
IO.PutFR1["///Users/%g", [rope[UserCredentials.Get[].name]]],
ConsRope["///Commands/", ConsRope["///"]]],
aList: parent.propertyList];
};
rep.prompt ← NARROW[CommandTool.GetProp[parent, $Prompt]];
oldREP ← NARROW[CommandTool.GetProp[parent, $ReadEvalPrintHandle]];
IF rep.prompt.IsEmpty[] THEN rep.prompt ← defaultPrompt;
rep.out ← parent.out;
rep.clientData ← parent;
rep.viewer ← IF oldREP # NIL THEN oldREP.viewer ELSE NIL;
IF rep.viewer #
NIL
THEN {
parent.out.PutF[rep.prompt, [rope["b"]], [rope["B"]] ];
parent.out.PutRope[commandLine];
};
msg ← EachCommand[h: rep, command: commandLine];
parent.out.PutRope[msg];
IF msg.Length[] > 0 AND msg.Fetch[msg.Length[] - 1] # '\n THEN parent.out.PutRope["\n"];
result ← CommandTool.GetProp[parent, $Result];
};
ConsRope:
PROC [r:
ROPE, list:
LIST
OF
REF
ANY ←
NIL]
RETURNS [
LIST
OF
REF
ANY] = {
RETURN [CONS[r, list]];
};
DoCommandRope:
PUBLIC
PROC [commandLine, in:
ROPE ←
NIL, parent: Commander.Handle]
RETURNS [out:
ROPE, result:
REF
ANY] = {
Execute the given commandLine. (The command name must be the first token on the commandLine). The in, out, and err streams connected to the corresponding ropes. (Calls EachCommand) The property list and error streams come from parent.
outS: STREAM ← IO.ROS[];
rep: ReadEvalPrint.Handle;
oldREP: ReadEvalPrint.Handle ← NIL;
cmd: Commander.Handle ← NEW[Commander.CommandObject ← []];
rep ← NEW[ReadEvalPrint.RObject ← [menuHitQueue: NIL]];
cmd.in ← IO.RIS[in];
cmd.out ← CommandTool.Insulate[outS];
IF parent #
NIL
THEN {
cmd.err ← parent.err;
cmd.propertyList ← parent.propertyList;
oldREP ← NARROW[CommandTool.GetProp[parent, $ReadEvalPrintHandle]];
rep.prompt ← NARROW[CommandTool.GetProp[parent, $Prompt]];
rep.out ← parent.out;
}
ELSE {
cmd.propertyList ← NIL;
cmd.err ← IO.noWhereStream;
rep.out ← IO.noWhereStream;
cmd.propertyList ← List.PutAssoc[key: $ErrorInputStream, val: IO.noInputStream, aList: cmd.propertyList];
cmd.propertyList ← List.PutAssoc[key: $Prompt, val: defaultPrompt, aList: cmd.propertyList];
};
IF rep.prompt.IsEmpty[] THEN rep.prompt ← defaultPrompt;
rep.clientData ← cmd;
rep.viewer ← IF oldREP # NIL THEN oldREP.viewer ELSE NIL;
rep.out.PutRope[EachCommand[h: rep, command: commandLine]];
result ← CommandTool.GetProp[parent, $Result];
out ← IO.RopeFromROS[outS ! IO.Error => CONTINUE];
outS.Close[];
};
Create:
PROC [parentCommander: Commander.Handle, newViewer:
BOOL, fork:
BOOL, copyProps:
BOOL, readProfile:
BOOL ←
FALSE] = {
Create has become quite an involved process. It can be invoked either from the message window or from another commander. It creates a new instance of the commander. It also copies the process property list and creates a new commander handle property list.
readProfile = TRUE =>
read initial commands from profile item CommandTool.BootCommands
(see CommandToolBase for the code to do this)
newViewer = TRUE AND NOT readProfile =>
read initial commands from profile item CommandTool.EachCommandToolCommands
Create is called in the following circumstances:
For the first top-level command tool: newViewer = TRUE, readProfile = TRUE
To create a new top level command tool: newViewer = TRUE
To execute a command file in a protected environment: fork = TRUE
To execute a command file in the parent's environment: copyProps = FALSE
cmd: Commander.Handle ← NEW[Commander.CommandObject];
readEvalPrint: ReadEvalPrint.Handle;
originalReadEvalPrint: ReadEvalPrint.Handle;
prompt: ROPE;
errorInputStream: STREAM;
props: List.AList ← NIL;
inner:
PROC = {
If we create a new viewer, then necessarily fork a process!
fork ← fork OR newViewer;
If we start a new process, then necessarily copy properties!
copyProps ← copyProps OR fork;
IF parentCommander #
NIL
THEN {
props ← parentCommander.propertyList;
IF copyProps
THEN {
Make a copy of the parentCommander property list as the starting place for the property list for our new commander.
props ← CommandTool.CopyAList[props];
copy certain top level properties which are known to be lists
props ← CommandTool.CopyListOfRefAny[key: $Before, aList: props];
props ← CommandTool.CopyListOfRefAny[key: $After, aList: props];
props ← CommandTool.CopyListOfRefAny[key: $SearchRules, aList: props];
In this case, save the parentCommander as the $ParentCommander property of the new commander property list.
props ← List.PutAssoc[key: $ParentCommander, val: parentCommander, aList: props];
};
};
Use the parent's prompt if there is one.
prompt ← NARROW[List.Assoc[key: $Prompt, aList: props]];
IF prompt.Length[] = 0
THEN {
props ← List.PutAssoc[key: $Prompt, val: defaultPrompt, aList: props];
prompt ← defaultPrompt;
};
save the old originalReadEvalPrint, if the command is source, it will be needed later
IF
NOT copyProps
THEN
originalReadEvalPrint ← NARROW[List.Assoc[key: $ReadEvalPrintHandle, aList: props]];
Create a ReadEvalPrint object using the right prompt and working directory.
IF newViewer
OR parentCommander =
NIL
THEN {
readEvalPrint ← ReadEvalPrint.CreateViewerEvaluator[clientProc: EachCommand, prompt: prompt, info: [name: Rope.Concat["CommandTool: WD = ", FileNames.CurrentWorkingDirectory[]], column: right, iconic: FALSE], edited: TRUE, deliverWhen: NIL, clientData: cmd, topLevel: TRUE];
cmd.err ← readEvalPrint.out;
errorInputStream ← readEvalPrint.in;
}
ELSE {
readEvalPrint ← ReadEvalPrint.CreateStreamEvaluator[clientProc: EachCommand, prompt: prompt, in: parentCommander.in, out: parentCommander.out, deliverWhen: NIL, clientData: cmd, topLevel: FALSE];
cmd.err ← parentCommander.err;
Hope there is an $ErrorInputStream property!
errorInputStream ← NARROW[List.Assoc[key: $ErrorInputStream, aList: props]];
};
Record the REP streams in the Commander Handle.
cmd.in ← readEvalPrint.in;
cmd.out ← readEvalPrint.out;
Record the input stream for use by error handlers. This is here until Commander is revised.
props ← List.PutAssoc[key: $ErrorInputStream, val: errorInputStream, aList: props];
props ← List.PutAssoc[key: $ReadEvalPrintHandle, val: readEvalPrint, aList: props];
WITH List.Assoc[key: $SearchRules, aList: props]
SELECT
FROM
list:
LIST
OF
REF
ANY => {
There is a $SearchRules list, so do nothing
};
ENDCASE => {
When there is no SearchRules list, add our own SearchRules procs to the list
rope: ROPE ← "///Commands/";
props ← List.PutAssoc[key: $SearchRules, val: LIST[rope], aList: props];
};
IF parentCommander #
NIL
THEN {
cmd.commandLine ← parentCommander.commandLine;
cmd.command ← parentCommander.command;
cmd.procData ← parentCommander.procData;
}
ELSE {
cmd.commandLine ← NIL;
cmd.command ← NIL;
cmd.procData ← NIL;
};
cmd.propertyList ← props;
TRUSTED {
processProperties: List.AList ← ProcessProps.GetPropList[];
Make a copy of the process properties list of the caller.
IF copyProps THEN processProperties ← CommandTool.CopyAList[processProperties];
Now set the CommanderHandle property of the new process
processProperties ← List.PutAssoc[key: $CommanderHandle, val: cmd, aList: processProperties];
IF fork
THEN
TRUSTED {
Process.Detach[FORK CommandToolBase[
props: IF copyProps THEN processProperties ELSE NIL,
rep: readEvalPrint, readProfile: readProfile, readInit: newViewer]];
}
ELSE CommandToolBase[
props: IF copyProps THEN processProperties ELSE NIL, rep: readEvalPrint,
readProfile: readProfile, readInit: newViewer];
Restore smashed things
IF parentCommander #
NIL
AND
NOT copyProps
THEN {
parentCommander.propertyList ← List.PutAssoc[
key: $ReadEvalPrintHandle,
val: originalReadEvalPrint,
aList: parentCommander.propertyList];
[] ← List.PutAssoc[
key: $CommanderHandle, val: parentCommander, aList: processProperties];
};
};
};
CedarProcess.DoWithPriority[priority: normal, action: inner];
};
CommandToolBase:
PROC
[props: List.AList, rep: ReadEvalPrint.Handle, readProfile:
BOOL, readInit:
BOOL] = {
inner:
PROC = {
cmd: Commander.Handle ← NARROW[rep.clientData];
SELECT
TRUE
FROM
readProfile =>
[] ← DoCommand["///Commands/NoteBootCommands\n", cmd];
readInit =>
[] ← DoCommand["///Commands/NotePerCommandTool\n", cmd];
ENDCASE;
ReadEvalPrint.MainLoop[h: rep, forkAndDetach: FALSE, properties: NIL];
};
IF props # NIL THEN ProcessProps.PushPropList[props, inner] ELSE inner[];
};
CreateCommanderButtonProc: Buttons.ButtonProc = {
Create a brand-new independent commander
Create[parentCommander: NIL, newViewer: TRUE, copyProps: TRUE, fork: TRUE];
};
CommandFile:
PUBLIC Commander.CommandProc = {
argv: CommandTool.ArgumentVector;
commandStream: STREAM;
commandFileName: ROPE;
source: BOOL ← cmd.procData.clientData = $Source;
prompt: ROPE;
list: LIST OF ROPE ← NIL;
argv ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => { msg ← errorMsg; GO TO oops; }];
IF argv.argc = 1
THEN {
Makes no sense to source an empty command file
IF source THEN {msg ← "Usage: Source commandFileName {argument list}"; GO TO oops};
Next line is the case of creating a brand-new independent commander
Create[parentCommander: cmd, newViewer: TRUE, copyProps: TRUE, fork: TRUE];
RETURN;
};
list ← CommandToolLookup.FindMatchingFiles[root: argv[1], defaultExtension: ".cm", requireExact: FALSE, searchRules: CommandTool.GetProp[cmd, $SearchRules]];
SELECT
TRUE
FROM
list =
NIL => {
msg ← IO.PutFR1["[[Command file '%g' not found]]\n", [rope[argv[1]]] ];
GO TO oops;
};
list.rest #
NIL => {
err: STREAM ← cmd.err;
IO.PutRope[err, "[[Ambiguous command files:\n"];
FOR each:
LIST
OF
ROPE ← list, each.rest
WHILE each #
NIL
DO
IO.PutF1[err, " %g\n", [rope[each.first]] ];
ENDLOOP;
IO.PutRope[err, " ]]\n"];
GO TO oops;
};
ENDCASE;
commandFileName ← list.first;
commandStream ←
FS.StreamOpen[fileName: commandFileName
! FS.Error => IF error.group # bug THEN {msg ← error.explanation; GO TO oops; }];
Scan command file for reasonableness
WHILE
NOT commandStream.EndOf[]
DO
c: CHAR ← commandStream.GetChar[];
IF c = 0C
OR c >= 200C
THEN {
commandStream.Close[];
msg ← Rope.Concat[commandFileName, " appears to be a binary file"];
GO TO oops;
};
Process.CheckForAbort[];
ENDLOOP;
commandStream.SetIndex[0];
cmd.propertyList ← CommandTool.PutLocalProperty[key: $CommandFileArguments, val: argv, aList: cmd.propertyList, origList: cmd.propertyList];
Get the old prompt. If it has a '> in it, append one, otherwise set the prompt to "> "
prompt ← NARROW[CommandTool.GetProp[cmd, $Prompt]];
IF Rope.Find[s1: prompt, s2: ">"] # -1
THEN prompt ← Rope.Concat[">", prompt]
ELSE prompt ← "%l%l> ";
cmd.propertyList ← CommandTool.PutLocalProperty[key: $Prompt, val: prompt, aList: cmd.propertyList, origList: cmd.propertyList];
cmd.propertyList ← CommandTool.PutLocalProperty[key: $Result, val: NIL, aList: cmd.propertyList, origList: cmd.propertyList];
cmd.in ← commandStream;
Create a new commander, but let it use the present viewer. If the command was source, then don't copy properties or create a new process. source xxx& is dangerous!
Create[parentCommander: cmd, newViewer: FALSE, copyProps: NOT source, fork: FALSE];
commandStream.Close[];
result ← CommandTool.GetProp[cmd, $Result];
EXITS oops => result ← $Failure;
};
RunCommand: Commander.CommandProc =
TRUSTED {
startIfUnbound: BOOL ← TRUE;
runAgain: BOOL ← FALSE;
loadOnly: BOOL ← FALSE;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => { msg ← errorMsg; GO TO oops; }];
IF argv = NIL THEN RETURN[$Failure, msg];
FOR i:
NAT
IN [1..argv.argc)
DO
arg: ROPE ← argv[i];
error: BOOL ← FALSE;
errMsg: ROPE;
cm: PrincOps.ControlModule ← PrincOps.NullControl;
IF Rope.Fetch[arg, 0] = '-
THEN {
FOR j:
INT
IN [1..Rope.Length[arg])
DO
SELECT Rope.Fetch[arg, j]
FROM
'd => startIfUnbound ← FALSE;
'a => runAgain ← TRUE;
'l => loadOnly ← TRUE;
ENDCASE;
ENDLOOP;
LOOP;
};
[errMsg: errMsg, error: error] ← RunInternal[bcdName: FileNames.ResolveRelativePath[arg], runEvenIfAlreadyRun: runAgain, runEvenIfUnbound: startIfUnbound, loadOnly: loadOnly];
IF NOT errMsg.IsEmpty[] THEN IO.PutRope[cmd.out, errMsg];
IF error THEN {result ← $Failure; RETURN};
ENDLOOP;
EXITS oops => result ← $Failure;
};
Run:
PUBLIC
PROC [bcdName:
ROPE, runEvenIfAlreadyRun, runEvenIfUnbound:
BOOL ←
FALSE]
RETURNS [errMsg:
ROPE ←
NIL, error:
BOOL ←
FALSE] = {
Load and start a BCD. If there is no .bcd on bcdName one will be appended. errMsg will always either return "Ran: XXX" or an error message, depending on the value of error. If there is no .bcd on bcdName one will be appended. If runEvenIfAlreadyRun is FALSE, then a BCD will only be run if it has not already been run by someone else. If runEvenIfAlreadyRun is TRUE, then the bcd will be run regardless. Unless runEvenIfUnbound is TRUE, the BCD will only be STARTed if there were no unbound imports.
[errMsg: errMsg, error: error] ← RunInternal[bcdName, runEvenIfAlreadyRun, runEvenIfUnbound, FALSE];
};
RunInternal:
PROC [bcdName:
ROPE, runEvenIfAlreadyRun, runEvenIfUnbound, loadOnly:
BOOL ←
FALSE]
RETURNS [errMsg:
ROPE ←
NIL, error:
BOOL ←
FALSE] = {
bcdVersion: BcdDefs.VersionStamp ← BcdDefs.NullVersion;
cm: PrincOps.ControlModule ← PrincOps.NullControl;
f: FS.OpenFile ← FS.nullOpenFile;
ros: STREAM = IO.ROS[];
put1: PROC [r1: ROPE ← NIL] = {IO.PutRope[ros, r1]};
put2: PROC [r1,r2: ROPE ← NIL] = {IO.PutRope[ros, r1]; IO.PutRope[ros, r2]};
shortName: ROPE ← bcdName ← FileNames.ResolveRelativePath[bcdName];
SELECT
TRUE
FROM
Rope.Match["*.bcd", bcdName, FALSE] => {};
Rope.Match["*!*", bcdName, TRUE] => {};
ENDCASE => bcdName ← Rope.Concat[bcdName, ".bcd"];
{
-- start scope for exits
unboundImports: LIST OF Loader.IRItem ← NIL;
name, fullFName, attachedTo: ROPE ← NIL;
f ←
FS.Open[bcdName
! FS.Error => {IO.PutRope[ros, error.explanation]; GO TO failed}
];
[fullFName, attachedTo] ← FS.GetName[f];
name ← IF attachedTo # NIL THEN attachedTo ELSE fullFName;
IF
NOT runEvenIfAlreadyRun
THEN
TRUSTED {
Check to see if this version has already been run. If so, we do not run this bcd again, but we have not really failed, either.
duplicateFound: BOOL ← FALSE;
LoadState.local.Acquire[];
{
ENABLE
UNWIND => LoadState.local.Release[];
lookAtConfig:
SAFE
PROC [config: LoadState.ConfigID]
RETURNS [stop:
BOOL ←
FALSE] =
TRUSTED {
IF bcdVersion = LoadState.local.ConfigInfo[config].bcd.version THEN RETURN[TRUE];
};
bcdSpace: VM.Interval = VM.Allocate[count: 1];
{
Read in the BCD from the file and grab the version
bcd: BcdDefs.BcdBase ← LOOPHOLE[VM.AddressForPageNumber[bcdSpace.page]];
FS.Read[file: f, from: 0, nPages: 1, to: LOOPHOLE[bcd] ! FS.Error => GO TO nope];
IF bcd.versionIdent = BcdDefs.VersionID
AND
NOT bcd.definitions
AND bcd.spare1
THEN
bcdVersion ← bcd.version; -- else error, which will be reported later
EXITS nope => {};
};
VM.Free[bcdSpace];
IF bcdVersion # BcdDefs.NullVersion
AND LoadState.local.EnumerateConfigs[newestFirst, lookAtConfig] # LoadState.nullConfig
THEN duplicateFound ← TRUE;
};
LoadState.local.Release[];
IF duplicateFound
THEN {
put2["Previously loaded and run: ", name];
GO TO done;
};
};
TRUSTED {
ENABLE {
Loader.Error => {
SELECT type
FROM
invalidBcd => put1["InvalidBcd["];
fileNotFound => put1["FileNotFound["];
versionMismatch => put1["VersionMismatch["];
loadStateFull => put1["LoadStateFull["];
insufficientVM => put1["InsufficientVM["];
ENDCASE => ERROR;
put2[message, "]"];
GO TO failed;
};
ABORTED => {
put2["Loading aborted in ", name];
GO TO failed;
};
};
[cm, unboundImports] ← Loader.Instantiate[f];
};
AddNewUniqueDebugRule[
FileNames.Directory[FileNames.ConvertToSlashFormat[fullFName]]];
IF unboundImports #
NIL
THEN {
put1["\n-- Unbound imports { "];
FOR l:
LIST
OF Loader.IRItem ← unboundImports, l.rest
UNTIL l =
NIL
DO
IO.PutF[ros, "[%g,%d] ", [rope[l.first.interfaceName]], [integer[l.first.index]] ];
ENDLOOP;
put1["}"];
IF NOT runEvenIfUnbound THEN GO TO failed;
};
At this point we really can start the bcd, no longer under protection of the ENABLE Loader.Error (the idea is to allow errors from user programs to be properly reported).
Process.CheckForAbort[];
IF
NOT loadOnly
THEN {
Loader.Start[cm];
put2["Ran: ", name];
cm ← PrincOps.NullControl;
}
ELSE {
put2["Loaded: ", shortName];
put2[", from: ", name];
AddControlModule[cm, shortName];
};
EXITS
failed => error ← TRUE;
done => {};
};
IF f # FS.nullOpenFile THEN FS.Close[f];
put1["\n"];
errMsg ← IO.RopeFromROS[ros];
};
AddControlModule:
ENTRY
PROC [cm: PrincOps.ControlModule, name:
ROPE] =
INLINE {
ENABLE UNWIND => NULL;
IF cm # PrincOps.NullControl
THEN
unstartedList ← CONS[ [cm, name], unstartedList];
};
RemControlModule:
ENTRY
PROC [name:
ROPE]
RETURNS [cm: PrincOps.ControlModule ← PrincOps.NullControl] =
INLINE {
ENABLE UNWIND => NULL;
lag: UnstartedList ← NIL;
FOR each: UnstartedList ← unstartedList, each.rest
WHILE each #
NIL
DO
IF Rope.Equal[each.first.name, name,
FALSE]
THEN {
IF lag = NIL THEN unstartedList ← each.rest ELSE lag.rest ← each.rest;
RETURN [each.first.cm];
};
lag ← each;
ENDLOOP;
};
unstartedList: UnstartedList ← NIL;
UnstartedList: TYPE = LIST OF UnstartedEntry;
UnstartedEntry:
TYPE =
RECORD [
cm: PrincOps.ControlModule,
name: ROPE];
StartCommand: Commander.CommandProc = {
Starts the last named module loaded by Load or Run that was loaded but NOT started. Can only start things listed here, since we otherwise do not know what the control list is.
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => { msg ← errorMsg; GO TO oops; }];
FOR i:
NAT
IN [1..argv.argc)
DO
name: ROPE ← argv[1];
cm: PrincOps.ControlModule ← RemControlModule[name];
IF cm = PrincOps.NullControl
THEN {
msg ← "Error; no such unstarted module.\n";
GO TO oops};
TRUSTED {Loader.Start[cm]};
IO.PutF1[cmd.out, "Started %g\n", [rope[name]] ];
ENDLOOP;
EXITS oops => result ← $Failure;
};
LoadCommand: Commander.CommandProc = {
Loads a module but does NOT start it.
cmd.commandLine ← Rope.Concat["-l ", cmd.commandLine];
[result, msg] ← RunCommand[cmd];
};
PrintDebugSearchRules:
ENTRY Commander.CommandProc = {
ENABLE UNWIND => NULL;
rules: LIST OF ROPE ← AMFiles.pathPrefixes;
out: STREAM = cmd.out;
IO.PutRope[out, "( "];
IF rules = NIL THEN IO.PutChar[out, ' ];
WHILE rules #
NIL
DO
IO.PutRope[out, rules.first];
rules ← rules.rest;
IO.PutChar[out, ' ];
ENDLOOP;
IO.PutRope[out, ")\n"];
};
SetDebugSearchRules:
ENTRY Commander.CommandProc = {
ENABLE UNWIND => NULL;
args: LIST OF ROPE;
newList: LIST OF ROPE ← NIL;
CommandTool.StarExpansion[cmd];
args ← CommandTool.ParseToList[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}].list;
newList ← args;
WHILE newList #
NIL
DO
newList.first ← FileNames.ConvertToSlashFormat[FileNames.ResolveRelativePath[newList.first]];
IF
NOT FileNames.IsADirectory[newList.first]
THEN {
msg ← Rope.Concat[newList.first, " is not a directory\n"];
GO TO failed
};
IF newList.first.Length[] < 3
THEN
{
msg ← Rope.Concat["Directory name too short: ", newList.first];
GO TO failed
};
newList ← newList.rest;
ENDLOOP;
newList ← NIL;
IF cmd.procData.clientData = $SetSearchRules
THEN {
WHILE args #
NIL
DO
IF
NOT RopeList.Memb[newList, args.first,
FALSE]
THEN
newList ← RopeList.DAppend[newList, LIST[args.first]];
args ← args.rest;
Process.CheckForAbort[];
ENDLOOP;
IF
NOT RopeList.Memb[newList, "///",
FALSE]
THEN
newList ← RopeList.DAppend[newList, LIST["///"]];
}
ELSE {
newList ← RopeList.CopyTopList[AMFiles.pathPrefixes];
WHILE args #
NIL
DO
IF
NOT RopeList.Memb[newList, args.first,
FALSE]
THEN
newList ← RopeList.DAppend[newList, LIST[args.first]];
args ← args.rest;
Process.CheckForAbort[];
ENDLOOP;
};
AMFiles.pathPrefixes ← newList;
EXITS failed => result ← $Failed;
};
AddNewUniqueDebugRule:
ENTRY
PROC [rule:
ROPE] =
INLINE {
ENABLE UNWIND => NULL;
newRules: LIST OF ROPE ← NIL;
rule ← CommandTool.ConvertToSlashFormat[CommandTool.ResolveRelativePath[rule]];
IF RopeList.Memb[AMFiles.pathPrefixes, rule, FALSE] THEN RETURN;
newRules ← RopeList.CopyTopList[AMFiles.pathPrefixes];
IF RopeList.Memb[newRules, rule, FALSE] THEN RETURN;
newRules ← RopeList.DAppend[newRules, LIST[rule]];
AMFiles.pathPrefixes ← newRules;
};
CreateFirst:
PROC = {
Create[parentCommander: NIL, newViewer: TRUE, copyProps: TRUE, fork: TRUE, readProfile: TRUE];
};
{
main program for Commander
[] ← Buttons.Create[info: [name: "Cmd"], proc: CreateCommanderButtonProc, fork: TRUE, documentation: "Create a Commander viewer"];
Commander.Register[key: "///Commands/Source", proc: CommandFile, doc: "Source commandFileName {argument list}, execute command file in current context", clientData: $Source];
Commander.Register[key: "///Commands/CommandTool", proc: CommandFile, doc: "Create a new Command Tool"];
Commander.Register[key: "///Commands/Cmd", proc: CommandFile, doc: "Create a new Command Tool"];
Commander.Register[key: "///Commands/Run", proc: RunCommand, doc: "Run -d -a {file}* - Load and Start one or more .bcds. -d => don't START if unbound, -a => ignore previously loaded, -l => load only", clientData: NIL];
Commander.Register[key: "///Commands/Load", proc: LoadCommand, doc: "Load -a {file}* - Load one or more .bcds. -a => ignore previously loaded", clientData: NIL];
Commander.Register[key: "///Commands/Start", proc: StartCommand, doc: "Start a previously loaded bcd", clientData: NIL];
Commander.Register[key: "///Commands/AddDebugSearchRules", proc: SetDebugSearchRules, doc: "Add debugger command search rules: AddDebugSearchRules list-of-directories"];
Commander.Register[key: "///Commands/PrintDebugSearchRules", proc: PrintDebugSearchRules, doc: "Print debugger command search rules"];
Commander.Register[key: "///Commands/SetDebugSearchRules", proc: SetDebugSearchRules, doc: "Set debugger command search rules: SetDebugSearchRules list-of-directories", clientData: $SetSearchRules];
EndOps.Register[CreateFirst];
};
END.
March 27, 1983 3:27 pm, Stewart, Created from ChatImpl
April 20, 1983 8:26 pm, Russ Atkinson, added Process Props stuff
September 9, 1983 11:33 am, Stewart, Cedar 5
October 5, 1983 4:23 pm, Stewart, New command line processing
October 19, 1983 5:17 pm, Stewart, Additional work
December 2, 1983 8:03 pm, Stewart, Bug fixes and more bulletproofing
December 12, 1983 12:43 pm, Stewart, FileNames, better default $Lookup
December 15, 1983 11:09 am, Stewart, User profile and CommandToolStartup.cm
December 15, 1983 5:44 pm, Stewart, Debug search rules
January 14, 1984 7:44 pm, Stewart, Run defaults now start even if unbound, bugs fixed
May 23, 1984 2:40:31 pm PDT, Spreitzer, Made initial create last thing done, via EndOps.