CommandToolImpl.mesa
Larry Stewart, December 16, 1983 4:45 pm
Russ Atkinson, April 20, 1983 8:25 pm
Mike Schroeder, November 18, 1983 1:04 pm
Paul Rovner, November 30, 1983 5:07 pm
DIRECTORY
AMFiles USING [pathPrefixes],
BcdDefs USING [NullVersion, VersionStamp, BcdBase, VersionID],
Buttons USING [ButtonProc, Create],
Commander USING [CommandObject, CommandProcHandle, CommandProcObject, CommandProc, Handle, Register],
CommandExtras USING [IsUninterpreted],
CommandTool,
FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, Directory, FileWithSearchRules, IsADirectory, ResolveRelativePath],
FS USING [Close, Error, GetName, nullOpenFile, Open, OpenFile, StreamOpen, Read],
IO USING [Close, EndOf, Error, Flush, GetChar, int, noInputStream, noWhereStream, PutChar, PutF, PutFR, PutRope, RIS, rope, RopeFromROS, ROS, SetIndex, STREAM],
IOClasses USING [CreatePipe],
List USING [AList, Assoc, Length, PutAssoc],
Loader USING [Error, Instantiate, IRItem, Start],
LoadState USING [local, Acquire, Release, ConfigInfo, EnumerateConfigs, nullConfig, ConfigID],
PrincOps USING [ControlModule],
Process USING [Detach, GetPriority, Priority, priorityNormal, SetPriority],
ProcessExtras USING [CheckForAbort],
ProcessProps USING [GetPropList, PushPropList],
ReadEvalPrint USING [ClientProc, CreateStreamEvaluator, CreateViewerEvaluator, Handle, MainLoop, RObject],
Rope USING [Cat, Compare, Concat, Fetch, Find, FromChar, Index, IsEmpty, Length, ROPE, Substr],
RopeList USING [CopyTopList, DAppend, Memb],
RuntimeError USING [UNCAUGHT],
UserProfile USING [Line],
ViewerIO USING [CreateViewerStreams],
VM USING [Interval, Allocate, AddressForPageNumber, Free];
CommandToolImpl:
CEDAR
MONITOR
IMPORTS
AMFiles, Buttons, Commander, CommandExtras, CommandTool, FileNames, FS, IO, IOClasses, List, Loader, LoadState, Process, ProcessExtras, ProcessProps, ReadEvalPrint, Rope, RopeList, RuntimeError, UserProfile, ViewerIO, VM
EXPORTS CommandTool
= BEGIN
defaultPrompt: Rope.ROPE = "%% ";
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
];
MyCommandLookupHandle: Commander.CommandProcHandle ← NEW[Commander.CommandProcObject ← [CommandTool.LookupWithSearchRules, "LookupWithSearchRules", "DefaultCommandLookup"]];
MyFileLookupHandle: Commander.CommandProcHandle ← NEW[Commander.CommandProcObject ← [CommandTool.CommandFileWithSearchRules, "CommandFileWithSearchRules", "DefaultCommandFileLookup"]];
MyLoadLookupHandle: Commander.CommandProcHandle ← NEW[Commander.CommandProcObject ← [CommandTool.LoadAndRunWithSearchRules, "LoadAndRunWithSearchRules", "DefaultLoadFileLookup"]];
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.
EachCommand:
PUBLIC ReadEvalPrint.ClientProc = {
cmd: Commander.Handle;
propertyList: List.AList ← NIL;
errorOut: IO.STREAM ← NIL;
remainingCommands: Rope.ROPE;
someExpansion: BOOL;
expansion: BOOL ← FALSE;
cth: Commander.Handle;
pipePushStream: IO.STREAM ← NIL;
pipePullStream: IO.STREAM ← NIL;
lastControls: Controls ← [];
controls: Controls ← [];
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
If what is left is too short, then we are done.
IF remainingCommands.Length[] <= 1
THEN {
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
RETURN["[[Last command fed a pipe, output discarded]]"];
};
RETURN;
};
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 {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
errorMsg ← Rope.Concat[errorMsg, "\n Previous command fed a pipe, output discarded."];
};
result ← errorMsg;
GOTO Die;
}];
expansion ← expansion OR someExpansion;
Try looking up the command.
cmd.procData ← NIL;
IF cmd.command.Length[] > 0 THEN LookupCommand[cmd];
IF cmd.procData = NIL THEN controls.uninterpreted ← TRUE
ELSE {
controls.uninterpreted ← cmd.procData.uninterpreted;
controls.uninterpreted ← CommandExtras.IsUninterpreted[cmd.procData];
controls.starExpand ← cmd.procData.starExpand;
controls.starExpand ← FALSE;
};
remainingCommands ← Rope.Cat[cmd.commandLine, Rope.FromChar[controls.terminator], remainingCommands];
IF controls.uninterpreted
THEN {
eol: INT ← Rope.Index[s1: remainingCommands, pos1: 0, s2: "\n", case: FALSE];
cmd.commandLine ← Rope.Substr[base: remainingCommands, start: 0, len: eol + 1];
remainingCommands ← Rope.Substr[base: remainingCommands, start: eol + 1];
controls.terminator ← '\n;
}
Use Pass1 to get the command arguments
ELSE {
[first: cmd.commandLine, rest: remainingCommands, terminator: controls.terminator, someExpansion: someExpansion] ← CommandTool.Pass1[initial: remainingCommands, nameOnly:
FALSE ! CommandTool.Failed => {
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
errorMsg ← Rope.Concat[errorMsg, "\n Previous command fed a pipe, output discarded."];
};
result ← errorMsg;
GOTO Die;
}];
expansion ← expansion OR someExpansion;
};
Now if there was no command, try again
IF cmd.procData =
NIL
THEN {
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
RETURN["[[Last command fed a pipe, output discarded.]]"];
};
lastControls ← controls;
ProcessExtras.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 {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
RETURN["[[Cannot pipe into an uninterpreted command]]"];
};
}
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 => {
Check for open pipes!
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
};
RETURN[Rope.Cat["[[Unknown terminating character: ", Rope.FromChar[controls.terminator], "]]"]];
};
{
ENABLE CommandTool.Failed => {
result ← errorMsg;
CONTINUE;
};
CommandTool.AmpersandSubstitution[cmd];
CommandTool.DollarSubstitution[cmd];
IF controls.starExpand THEN CommandTool.StarExpansion[cmd];
[inRedirected: controls.inRedirected, outRedirected: controls.outRedirected] ← CommandTool.IORedirection[cmd];
};
IF result #
NIL
THEN {
Check for open pipes!
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
};
result ← Rope.Cat["[[", result, "]]"];
GOTO Die; -- 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 {
Check for open pipes!
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
};
RETURN["[[Cannot redirect output and have a pipe]]"]; -- discards remaining commands on line
};
IF lastControls.pipe
AND controls.inRedirected
THEN {
Check for open pipes!
IF pipePullStream #
NIL
THEN {
pipePullStream.Close[! IO.Error => CONTINUE; ];
pipePullStream ← NIL;
};
RETURN["[[Previous command was a pipe, cannot redirect input]]"]; -- 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: IO.STREAM;
viewerIn: IO.STREAM;
Create a viewer for the command and set up streams
[in: viewerIn, out: viewerOut] ← ViewerIO.CreateViewerStreams[name: cmd.command];
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];
IF cmd.in = NIL THEN cmd.in ← viewerIn;
IF cmd.out = NIL THEN cmd.out ← viewerOut;
};
Check that the error streams are set up:
If a new viewer is created above, then they are set
Else cmd.err is set earlier to be cth.err and $ErrorInputStream is unchanged from the cth property list
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 {
[push: pipePushStream, pull: pipePullStream] ← IOClasses.CreatePipe[];
cmd.out ← pipePushStream;
};
}; -- end of interpreted section
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.PutRope["[["];
h.out.PutRope[cmd.command];
h.out.PutRope[Rope.Substr[cmd.commandLine, 0, cmd.commandLine.Length[] - 1]];
h.out.PutRope["]]\n"];
};
The next line is just bulletproofing. This program tries pretty hard to avoid having a NIL property list. Having a NIL property list isn't really bad (and this code doesn't break), but it may break improperly written client code that deals with the property list.
IF cmd.propertyList = NIL THEN cmd.propertyList ← List.PutAssoc[key: $Prompt, val: h.prompt, aList: cmd.propertyList];
{
savedOutStream: IO.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.ROPE => h.prompt ← rope;
ENDCASE => h.prompt ← defaultPrompt;
ProcessExtras.CheckForAbort[];
lastControls ← controls;
Start again with the rest of the command line.
IF controls.quitOnFailure
AND List.Assoc[key: $Result, aList: cmd.propertyList] = $Failure
THEN {
IF pipePullStream # NIL THEN ERROR; -- can't happen!
RETURN["[[Command failed]]"];
};
ENDLOOP; -- bottom of the loop which executes multiple commands in a line from ReadEvalPrint
EXITS
Die => NULL;
};
The Commander.Handle passed to LookupCommand has nothing filled in except err, command, and propertyList
LookupCommand:
PUBLIC
PROC [cmd: Commander.Handle] = {
abmsg: Rope.ROPE ← NIL;
printedAMessage: BOOL ← FALSE;
previousAmbiguity: BOOL ← FALSE;
foundProcData: Commander.CommandProcHandle ← NIL;
foundCommand: Rope.ROPE ← NIL;
foundCommandLine: Rope.ROPE ← NIL;
origCommand: Rope.ROPE ← cmd.command;
origCommandLine: Rope.ROPE ← cmd.commandLine;
savedResult: REF ANY;
TestLookupDone:
PROC [result:
REF, msg: Rope.
ROPE]
RETURNS [stop:
BOOL] = {
ambiguous: BOOL ← result = $Ambiguous;
stop ← FALSE;
IF msg.Length[] > 0
THEN {
-- print any message
printedAMessage ← TRUE;
cmd.err.PutRope[msg];
IF msg.Fetch[msg.Length[] - 1] # '\n THEN cmd.err.PutChar['\n];
};
IF cmd.procData =
NIL
THEN {
-- nothing found
IF ambiguous THEN previousAmbiguity ← TRUE
ELSE {
Remember a previous ambiguous search!
IF previousAmbiguity THEN cmd.propertyList ← List.PutAssoc[key: $Result, val: $Ambiguous, aList: cmd.propertyList];
};
previousAmbiguity means that only exact searches should succeed from now on
RETURN[stop: FALSE];
};
If we get here, then cmd.procData # NIL
IF cmd.procData.proc =
NIL
THEN {
cmd.procData ← NIL;
We found a deleted command, so forget about it, ignoring even that it was found through a unique pattern match, if it was.
RETURN[stop: FALSE];
};
If we get here, then a valid command has been found
IF
NOT previousAmbiguity
THEN {
-- first time we've found anything!
foundProcData ← cmd.procData; -- save it
foundCommand ← cmd.command;
foundCommandLine ← cmd.commandLine;
If we find a unique match, then save it for later use, but keep looking in hopes an exact match will turn up.
IF ambiguous
THEN {
-- restore original command and CommandLine
previousAmbiguity ← TRUE;
cmd.procData ← NIL;
cmd.command ← origCommand;
cmd.commandLine ← origCommandLine;
RETURN[stop: FALSE]; -- keep looking, but only for an exact match
};
RETURN[stop: TRUE]; -- this is exact, so stop
}
ELSE {
-- previousAmbiguity = TRUE
IF (
NOT ambiguous)
OR (foundProcData #
NIL)
THEN {
-- remember the data only if it is the first or this is an exact match.
foundProcData ← cmd.procData;
foundCommand ← cmd.command;
foundCommandLine ← cmd.commandLine;
};
IF ambiguous
THEN {
once previousAmbiguity is set, then only an exact match is good enough, and this one is not exact. Keep trying.
cmd.procData ← NIL;
cmd.command ← origCommand;
cmd.commandLine ← origCommandLine;
RETURN[stop: FALSE];
};
We have found an exact match even though previous searches were ambiguous, this is the one we want!
RETURN[stop: TRUE];
};
};
IF List.Assoc[key: $Lookup, aList: cmd.propertyList] =
NIL
THEN {
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyCommandLookupHandle, append: TRUE];
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyLoadLookupHandle, append: TRUE];
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyFileLookupHandle, append: TRUE];
};
savedResult ← List.Assoc[key: $Result, aList: cmd.propertyList];
CommandTool.CallList[property: $Lookup, cmd: cmd, proc: TestLookupDone !
ABORTED => {abmsg ← " . . . Aborted\n"; CONTINUE};
UNWIND => {abmsg ← " . . . Unwound\n"; CONTINUE};
];
cmd.propertyList ← List.PutAssoc[key: $Result, val: savedResult, aList: cmd.propertyList];
IF abmsg #
NIL
THEN {
cmd.err.PutRope[abmsg ! RuntimeError.UNCAUGHT => CONTINUE];
cmd.propertyList ← List.PutAssoc[key: $Result, val: $Failure, aList: cmd.propertyList];
RETURN;
};
cmd.procData ← foundProcData;
cmd.command ← foundCommand;
cmd.commandLine ← foundCommandLine;
IF cmd.procData =
NIL
THEN {
IF
NOT printedAMessage
THEN {
cmd.err.PutRope["[["];
cmd.err.PutRope[cmd.command];
cmd.err.PutRope[" . . . not found]]\n"];
};
RETURN;
};
};
Base:
PROC [list: List.AList, cmd: Commander.Handle, detached:
BOOL] = {
innerExecute:
PROC = {
{
ENABLE {
UNWIND => {
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];
};
cmd.started ← FALSE;
};
};
result: REF ANY;
msg: Rope.ROPE;
originalPropertyList: List.AList ← cmd.propertyList;
[result: result, msg: msg] ← cmd.procData.proc[cmd: cmd];
IF msg #
NIL
THEN {
cmd.out.PutRope[msg];
IF msg.Fetch[msg.Length[] - 1] # '\n THEN cmd.out.PutChar['\n];
};
IF NOT detached THEN cmd.propertyList ← List.PutAssoc[key: $Result, val: result, aList: originalPropertyList];
}; -- END of enable
}; -- END of innerExecute
Main code of Base
IF list # NIL THEN ProcessProps.PushPropList[list, innerExecute]
ELSE innerExecute[];
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];
};
cmd.started ← FALSE;
};
ExecuteCommand:
PUBLIC
PROC [cmd: Commander.Handle, background:
BOOL] = {
Mark the commander handle itself!
cmd.started ← TRUE;
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];
};
};
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.
DoCommand:
PUBLIC
PROC [commandLine: Rope.
ROPE, parent: Commander.Handle]
RETURNS [result:
REF
ANY] = {
rep: ReadEvalPrint.Handle ← NEW[ReadEvalPrint.RObject ← [NIL, NIL, NIL, NIL, FALSE, NIL, NIL, NIL, NIL, NIL, TRUE, NIL, NIL, NIL, NIL, NIL, NIL]];
rep: ReadEvalPrint.Handle;
oldREP: ReadEvalPrint.Handle;
cll: INT ← commandLine.Length[];
msg: Rope.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];
};
rep.prompt ← NARROW[List.Assoc[key: $Prompt, aList: parent.propertyList]];
oldREP ← NARROW[List.Assoc[key: $ReadEvalPrintHandle, aList: parent.propertyList]];
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];
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 ← List.Assoc[key: $Result, aList: parent.propertyList];
};
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.
DoCommandRope:
PUBLIC
PROC [commandLine, in: Rope.
ROPE ←
NIL, parent: Commander.Handle]
RETURNS [out: Rope.
ROPE, result:
REF
ANY] = {
outS: IO.STREAM ← IO.ROS[];
rep: ReadEvalPrint.Handle ← NEW[ReadEvalPrint.RObject ← [NIL, NIL, NIL, NIL, FALSE, NIL, NIL, NIL, NIL, NIL, TRUE, NIL, NIL, NIL, NIL, NIL, NIL]];
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[List.Assoc[key: $ReadEvalPrintHandle, aList: parent.propertyList]];
rep.prompt ← NARROW[List.Assoc[key: $Prompt, aList: parent.propertyList]];
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;
parent.out.PutRope[EachCommand[h: rep, command: commandLine]];
result ← List.Assoc[key: $Result, aList: parent.propertyList];
out ← IO.RopeFromROS[outS ! IO.Error => CONTINUE];
outS.Close[];
};
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.
IF readProfile = TRUE then initial commands will be read from item InitialCommands in the user profile.
If newViewer = TRUE, then initial commands will be read from CommandToolStartup.cm in the user's home directory.
Create is Called in the following circumstances:
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 parents environment: copyProps = FALSE
Create:
PROC [parentCommander: Commander.Handle, newViewer:
BOOL, fork:
BOOL, copyProps:
BOOL, readProfile:
BOOL ←
FALSE] = {
cmd: Commander.Handle ← NEW[Commander.CommandObject];
readEvalPrint: ReadEvalPrint.Handle;
originalReadEvalPrint: ReadEvalPrint.Handle;
oldPriority: Process.Priority ← Process.GetPriority[];
prompt: Rope.ROPE;
errorInputStream: IO.STREAM;
Set process priority to normal for the execution of the create code (and to start off the new commander).
TRUSTED{Process.SetPriority[Process.priorityNormal]};
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 cmd.propertyList ← NIL
ELSE {
cmd.propertyList ← 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.
cmd.propertyList ← CommandTool.CopyAList[cmd.propertyList];
copy certain top level properties which are known to be lists
cmd.propertyList ← CommandTool.CopyListOfRefAny[key: $Before, aList: cmd.propertyList];
cmd.propertyList ← CommandTool.CopyListOfRefAny[key: $After, aList: cmd.propertyList];
cmd.propertyList ← CommandTool.CopyListOfRefAny[key: $Lookup, aList: cmd.propertyList];
cmd.propertyList ← CommandTool.CopyListOfRefAny[key: $SearchRules, aList: cmd.propertyList];
In this case, save the parentCommander as the $ParentCommander property of the new commander property list.
cmd.propertyList ← List.PutAssoc[key: $ParentCommander, val: parentCommander, aList: cmd.propertyList];
};
};
Use the parent's prompt if there is one.
prompt ← NARROW[List.Assoc[key: $Prompt, aList: cmd.propertyList]];
IF prompt.Length[] = 0
THEN {
cmd.propertyList ← List.PutAssoc[key: $Prompt, val: defaultPrompt, aList: cmd.propertyList];
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: cmd.propertyList]];
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: cmd.propertyList]];
};
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.
cmd.propertyList ← List.PutAssoc[key: $ErrorInputStream, val: errorInputStream, aList: cmd.propertyList];
cmd.propertyList ← List.PutAssoc[key: $ReadEvalPrintHandle, val: readEvalPrint, aList: cmd.propertyList];
If there are no SearchRules, put ///Commands/ on it.
{
maybeList: REF ANY ← List.Assoc[key: $SearchRules, aList: cmd.propertyList];
list: LIST OF REF ANY ← NIL;
IF ISTYPE[maybeList, LIST OF REF ANY] THEN list ← NARROW[maybeList];
IF List.Length[list] = 0
THEN {
rope: Rope.ROPE ← "///Commands/";
cmd.propertyList ← List.PutAssoc[key: $SearchRules, val: LIST[rope], aList: cmd.propertyList];
};
};
If there is no Lookup, add our own Lookup procs to the list
{
maybeList: REF ANY ← List.Assoc[key: $Lookup, aList: cmd.propertyList];
list: LIST OF REF ANY ← NIL;
IF ISTYPE[maybeList, LIST OF REF ANY] THEN list ← NARROW[maybeList];
IF List.Length[list] = 0
THEN {
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyCommandLookupHandle, append: TRUE];
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyLoadLookupHandle, append: TRUE];
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $Lookup, proc: MyFileLookupHandle, append: TRUE];
};
};
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;
};
TRUSTED {
processProperties: List.AList;
Get the process properties list
processProperties ← 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];
};
Process.SetPriority[oldPriority];
};
};
CommandToolBase:
PROC [props: List.AList, rep: ReadEvalPrint.Handle, readProfile:
BOOL, readInit:
BOOL] = {
inner:
PROC = {
cmd: Commander.Handle ← NARROW[rep.clientData];
IF readProfile
THEN {
initCommandLine: Rope.ROPE ← UserProfile.Line[key: "CommandTool.BootCommands"];
IF NOT initCommandLine.IsEmpty[] THEN [] ← DoCommand[commandLine: initCommandLine, parent: cmd];
};
IF readInit
THEN {
startupCommandLine: Rope.ROPE ← UserProfile.Line[key: "CommandTool.EachCommandToolCommands"];
IF NOT startupCommandLine.IsEmpty[] THEN [] ← DoCommand[commandLine: startupCommandLine, parent: cmd];
};
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: IO.STREAM;
commandFileName: Rope.ROPE;
failMsg: Rope.ROPE ← NIL;
count: NAT;
source: BOOL ← cmd.procData.clientData = $Source;
prompt: Rope.ROPE;
argv ← CommandTool.Parse[cmd: cmd ! CommandTool.Failed => { msg ← errorMsg; CONTINUE; }];
IF argv = NIL THEN RETURN[$Failure, msg];
IF argv.argc = 1
THEN {
Makes no sense to source an empty command file
IF source THEN RETURN[$Failure, "Usage: Source commandFileName {argument list}"];
Next line is the case of creating a brand-new independent commander
Create[parentCommander: cmd, newViewer: TRUE, copyProps: TRUE, fork: TRUE];
RETURN;
};
commandFileName ← argv[1];
commandFileName ← FileNames.FileWithSearchRules[root: commandFileName, defaultExtension: ".cm", requireExtension: FALSE, searchRules: List.Assoc[key: $SearchRules, aList: cmd.propertyList]].fullPath;
IF commandFileName.IsEmpty[] THEN RETURN[$Failure, Rope.Concat["Cannot open command file ", argv[1]]];
commandStream ←
FS.StreamOpen[fileName: commandFileName !
FS.Error =>
IF error.group = $user THEN {msg ← error.explanation; CONTINUE; }];
IF msg.Length[] > 0 THEN RETURN[$Failure, msg];
IF commandStream = NIL THEN RETURN[$Failure, Rope.Concat["Cannot open command file ", argv[1]]];
Scan command file for reasonableness
count ← 0;
WHILE
NOT commandStream.EndOf[]
DO
c: CHAR ← commandStream.GetChar[];
IF c = 0C
OR c >= 200C
THEN {
commandStream.Close[];
RETURN[$Failure, Rope.Concat[commandFileName, " appears to be a binary file"]];
};
count ← count + 1;
IF count > 256
THEN {
ProcessExtras.CheckForAbort[];
count ← 0;
};
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[List.Assoc[key: $Prompt, aList: cmd.propertyList]];
IF Rope.Find[s1: prompt, s2: ">"] # -1 THEN prompt ← Rope.Concat[">", prompt]
ELSE prompt ← "> ";
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 ← List.Assoc[key: $Result, aList: cmd.propertyList];
};
RunCommand: Commander.CommandProc =
TRUSTED {
argv: CommandTool.ArgumentVector;
bcdName: Rope.ROPE;
startIfUnbound: BOOL ← FALSE;
runAgain: BOOL ← FALSE;
errMsg: Rope.ROPE;
error: BOOL;
argv ← CommandTool.Parse[cmd ! CommandTool.Failed => { msg ← errorMsg; CONTINUE; }];
IF argv = NIL THEN RETURN[$Failure, msg];
FOR i:
NAT
IN [1..argv.argc)
DO
IF argv[i].Fetch[0] = '-
THEN {
FOR j:
INT
IN [1..argv[i].Length[])
DO
SELECT argv[i].Fetch[j]
FROM
's => startIfUnbound ← TRUE;
'a => runAgain ← TRUE;
ENDCASE;
ENDLOOP;
LOOP;
};
bcdName ← argv[i];
[errMsg: errMsg, error: error] ← Run[bcdName: FileNames.ResolveRelativePath[argv[i]], runEvenIfAlreadyRun: runAgain, runEvenIfUnbound: startIfUnbound];
IF
NOT errMsg.IsEmpty[]
THEN {
cmd.out.PutRope[errMsg];
cmd.out.PutChar['\n];
};
IF error THEN EXIT;
ENDLOOP;
IF error THEN result ← $Failure;
};
Load and start a BCD. If there is no .bcd on bcdName one will be appended. errMsg will always either return "Loaded and 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.
Run:
PUBLIC
PROC [bcdName: Rope.
ROPE, runEvenIfAlreadyRun:
BOOL ←
FALSE, runEvenIfUnbound:
BOOL ←
FALSE]
RETURNS [errMsg: Rope.ROPE ← NIL, error: BOOL ← FALSE] = TRUSTED {
name: Rope.ROPE;
f: FS.OpenFile ← FS.nullOpenFile;
length: INT;
duplicateFound: BOOL ← FALSE;
unboundImports: LIST OF Loader.IRItem ← NIL;
cm: PrincOps.ControlModule;
bcdName ← FileNames.ResolveRelativePath[bcdName];
length ← Rope.Length[bcdName];
IF length < 5 OR (Rope.Compare[Rope.Substr[bcdName, length - 4, 4], ".bcd", FALSE] # equal AND Rope.Find[bcdName, "!", MAX[0, length-6]] = -1) THEN bcdName ← Rope.Concat[bcdName, ".bcd"];
TRUSTED {
ENABLE {
Loader.Error => {
SELECT type
FROM
invalidBcd => errMsg ← "InvalidBcd[";
fileNotFound => errMsg ← "FileNotFound[";
versionMismatch => errMsg ← "VersionMismatch[";
loadStateFull => errMsg ← "LoadStateFull[";
insufficientVM => errMsg ← "InsufficientVM[";
ENDCASE => ERROR;
errMsg ← Rope.Cat[errMsg, message, "]"];
error ← TRUE;
CONTINUE;
};
FS.Error => {
errMsg ← error.explanation;
Can't set error here due to name conflict, thus the error ← TRUE after the FS.Open below.
CONTINUE;
};
};
f ← FS.Open[bcdName];
IF errMsg # NIL THEN error ← TRUE
ELSE {
IF
NOT runEvenIfAlreadyRun
THEN {
LoadState.local.Acquire[];
{
ENABLE
UNWIND => LoadState.local.Release[];
BcdVersion:
SAFE
PROC[file:
FS.OpenFile]
RETURNS [version: BcdDefs.VersionStamp ← BcdDefs.NullVersion] = TRUSTED{
bcdSpace: VM.Interval = VM.Allocate[count: 1];
bcd: BcdDefs.BcdBase ← LOOPHOLE[VM.AddressForPageNumber[bcdSpace.page]];
FS.Read[file: file, from: 0, nPages: 1, to: LOOPHOLE[bcd]];
IF bcd.versionIdent = BcdDefs.VersionID
AND
NOT bcd.definitions
AND bcd.spare1
THEN version ← bcd.version; -- else error, which will be reported later
VM.Free[bcdSpace];
};
lookAtConfig:
SAFE
PROC [config: LoadState.ConfigID]
RETURNS [stop:
BOOL ←
FALSE] =
TRUSTED{
IF bcdVersion = LoadState.local.ConfigInfo[config].bcd.version THEN RETURN[TRUE];
};
bcdVersion: BcdDefs.VersionStamp = BcdVersion[f];
IF bcdVersion # BcdDefs.NullVersion
AND LoadState.local.EnumerateConfigs[newestFirst, lookAtConfig]
# LoadState.nullConfig
THEN duplicateFound ← TRUE;
}; -- end ENABLE UNWIND => LoadState.local.Release[];
LoadState.local.Release[];
}; -- end IF NOT runEvenIfAlreadyRun THEN {
IF errMsg=
NIL
AND
NOT duplicateFound
THEN
[cm, unboundImports] ← Loader.Instantiate[f !
ABORTED => {
errMsg ← "Execution Aborted in ";
error ← TRUE;
CONTINUE;
}];
IF
NOT error
THEN {
name: Rope.ROPE ← FileNames.ConvertToSlashFormat[FS.GetName[f].fullFName];
name ← FileNames.Directory[name];
AddNewUniqueDebugRule[name];
};
IF unboundImports #
NIL
THEN {
errMsg ← "There are unbound imports: ";
FOR l:
LIST
OF Loader.IRItem ← unboundImports, l.rest
UNTIL l =
NIL
DO
errMsg ← Rope.Concat[errMsg, IO.PutFR["[%g,%d] ", IO.rope[l.first.interfaceName], IO.int[l.first.index]]];
ProcessExtras.CheckForAbort[];
ENDLOOP;
IF runEvenIfUnbound THEN Loader.Start[cm];
};
};
IF error = FALSE AND errMsg=NIL AND NOT duplicateFound THEN Loader.Start[cm];
IF errMsg=
NIL
THEN {
fullFName, attachedTo: Rope.ROPE;
[fullFName, attachedTo] ← FS.GetName[f];
name ← IF attachedTo # NIL THEN attachedTo ELSE fullFName;
};
};
IF f # NIL THEN FS.Close[f];
IF errMsg =
NIL
THEN {
errMsg ← IF duplicateFound THEN Rope.Cat["Previously loaded and run: ", name, "\n"] ELSE Rope.Cat["Loaded and ran: ", name, "\n"];
}
ELSE errMsg ← Rope.Concat[errMsg, "\n"];
};
PrintDebugSearchRules:
ENTRY Commander.CommandProc = {
rules: LIST OF Rope.ROPE ← AMFiles.pathPrefixes;
cmd.out.PutRope["( "];
IF rules = NIL THEN cmd.out.PutChar[' ];
WHILE rules #
NIL
DO
cmd.out.PutRope[rules.first];
rules ← rules.rest;
cmd.out.PutChar[' ];
ProcessExtras.CheckForAbort[];
ENDLOOP;
cmd.out.PutRope[")\n"];
};
SetDebugSearchRules:
ENTRY Commander.CommandProc = {
args: LIST OF Rope.ROPE;
newList: LIST OF Rope.ROPE ← NIL;
CommandTool.StarExpansion[cmd];
args ← CommandTool.ParseToList[cmd ! CommandTool.Failed => { msg ← errorMsg; CONTINUE; }].list;
IF args = NIL THEN RETURN[$Failure, msg];
newList ← args;
WHILE newList #
NIL
DO
newList.first ← FileNames.ConvertToSlashFormat[FileNames.ResolveRelativePath[newList.first]];
IF NOT FileNames.IsADirectory[newList.first] THEN RETURN[$Failure, Rope.Concat[newList.first, " is not a directory\n"]];
IF newList.first.Length[] < 3 THEN RETURN[$Failure, Rope.Concat["Directory name too short: ", newList.first]];
newList ← newList.rest;
ProcessExtras.CheckForAbort[];
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;
ProcessExtras.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;
ProcessExtras.CheckForAbort[];
ENDLOOP;
};
AMFiles.pathPrefixes ← newList;
};
AddNewUniqueDebugRule:
ENTRY
PROC [rule: Rope.
ROPE] = {
newRules: LIST OF Rope.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;
};
Init:
PROC = {
[] ← 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 {-s} {-a} xxx.bcd* - Load and Start one or more .bcds. -s => ignore unbound, -a ignore previously loaded", 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];
Create[parentCommander: NIL, newViewer: TRUE, copyProps: TRUE, fork: TRUE, readProfile: TRUE];
};
main program for Commander
Init[];
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