CommanderOpsImpl.mesa
Copyright Ó 1989, 1990, 1991 by Xerox Corporation. All rights reserved.
Michael Plass, July 23, 1992 10:39 am PDT
Last changed by Pavel on April 30, 1990 1:29 pm PDT
Swinehar, December 11, 1990 3:31 pm PST
Willie-s, July 9, 1992 12:53 pm PDT
DIRECTORY Atom, Basics, BasicTime, List, CommanderBackdoor, CommanderRegistry, CommanderOps, Commander, CommanderSys, Convert, IO, ProcessProps, RefText, Rope, UXStrings;
CommanderOpsImpl: CEDAR MONITOR
IMPORTS Atom, BasicTime, List, Commander, CommanderRegistry, CommanderSys, Convert, IO, ProcessProps, RefText, Rope, UXStrings
EXPORTS CommanderOps, CommanderBackdoor
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
ArgumentVector: TYPE ~ REF ArgumentVectorRep;
ArgumentVectorRep: TYPE ~ CommanderOps.ArgHandleObject;
QuoteTreatment: TYPE ~ CommanderOps.QuoteTreatment;
CommandToolData: TYPE ~ CommanderBackdoor.CommandToolData;
PrivateDataRep: PUBLIC TYPE ~ RECORD [
parseRecord: REF ParseRecord ¬ NIL,
nestedMsg: ROPE ¬ NIL -- used for inhibiting unwanted repetition of messages
];
Properties maintained or used by this module:
Commander properties:
Prompt
Result
Msg
CommandFileArgumentVector
DebugUNCAUGHT
Process Properties:
CommanderHandle
StdIn
StdOut
ErrOut
Constants
doubleQuote: CHAR = '"; doubleQuoteRope: ROPE = Rope.FromChar[doubleQuote];
openParen: CHAR = '(; openParenRope: ROPE = Rope.FromChar[openParen];
closeParen: CHAR = '); closeParenRope: ROPE = Rope.FromChar[closeParen];
Errors
Failed: PUBLIC ERROR [errorMsg: ROPE] ~ CODE;
Tokenizing
scratchRIS: IO.STREAM ¬ NIL;
ObtainRIS: ENTRY PROC [rope: ROPE] RETURNS [ris: IO.STREAM] = {
ris ¬ IO.RIS[rope, scratchRIS];
scratchRIS ¬ NIL;
};
ReleaseRIS: ENTRY PROC [stream: IO.STREAM] = { scratchRIS ¬ stream };
seprChars: REF READONLY TEXT ¬ " \t\r\l";
breakChars: REF READONLY TEXT ¬ "\"@|(,){;}";
CmdTokenBreak: PROC [char: CHAR] RETURNS [IO.CharClass] = {
FOR i: NAT IN [0..breakChars.length) DO
IF char = breakChars[i] THEN RETURN [break]
ENDLOOP;
FOR i: NAT IN [0..seprChars.length) DO
IF char = seprChars[i] THEN RETURN [sepr]
ENDLOOP;
RETURN [other]
};
Token: TYPE ~ CommanderOps.Token;
nullToken: Token ~ CommanderOps.nullToken;
GetCmdToken: PUBLIC PROC [stream: IO.STREAM] RETURNS [token: Token ¬ nullToken] = {
skip: INT ¬ 0;
token.start ¬ IO.GetIndex[stream];
[token: token.value, charsSkipped: skip] ¬ IO.GetTokenRope[stream, CmdTokenBreak ! IO.EndOfStream => CONTINUE];
token.start ¬ token.start + skip;
token.literal ¬ token.value;
SELECT TRUE FROM
Rope.Equal[token.value, doubleQuoteRope] => {
IO.Backup[self: stream, char: doubleQuote];
{ startIndex: INT ~ token.start ¬ IO.GetIndex[stream];
rope: ROPE ~ IO.GetRopeLiteral[stream ! IO.Error, IO.EndOfStream => GOTO MisMatch];
length: INT ~ IO.GetIndex[stream] - startIndex;
token.value ¬ rope;
IO.SetIndex[stream, startIndex];
token.literal ¬ IO.GetRope[stream, length];
};
};
Rope.Equal[token.value, openParenRope] => {
startIndex: INT ~ token.start ¬ IO.GetIndex[stream]-1;
DO
-- This allocates rather awfully; clean up when possible. - mfp
subtoken: Token ~ GetCmdToken[stream];
IF subtoken.literal = NIL THEN ERROR Failed["Unmatched parentheses in command line"];
IF Rope.Equal[subtoken.literal, closeParenRope] THEN {
length: INT ~ IO.GetIndex[stream] - startIndex;
IO.SetIndex[stream, startIndex];
token.literal ¬ IO.GetRope[stream, length];
token.value ¬ Rope.Substr[token.literal, 1, length-2];
EXIT;
};
ENDLOOP;
};
Rope.Equal[token.literal, "$"] => {
IF NOT IO.EndOf[stream] THEN SELECT IO.PeekChar[stream] FROM
doubleQuote, openParen => {
rest: Token ~ GetCmdToken[stream];
token.literal ¬ token.value ¬ Rope.Concat[token.literal, rest.literal];
};
ENDCASE;
};
ENDCASE => NULL;
EXITS MisMatch => ERROR Failed["Mismatched double quotes in command line"]
};
Parsing (Public)
ParseToList: PUBLIC PROC [cmd: Commander.Handle, quoteTreatment: QuoteTreatment] RETURNS [list: LIST OF ROPE, length: NAT ¬ 0] ~ {
head: LIST OF ROPE ~ LIST[ArgN[cmd, 0, quoteTreatment]];
last: LIST OF ROPE ¬ head;
DO
arg: ROPE ~ NextArgument[cmd, quoteTreatment];
IF arg = NIL THEN EXIT;
last ¬ last.rest ¬ LIST[arg];
length ¬ length + 1;
ENDLOOP;
list ¬ head.rest;
head.rest ¬ NIL;
};
Parse: PUBLIC PROC [cmd: Commander.Handle, quoteTreatment: QuoteTreatment] RETURNS [argv: ArgumentVector] ~ {
n: NAT ~ NumArgs[cmd];
argv ¬ NEW[ArgumentVectorRep[n]];
argv[0] ¬ ArgN[cmd, 0, quoteTreatment];
FOR i: NAT IN [1..n) DO
IF (argv[i] ¬ NextArgument[cmd, quoteTreatment]) = NIL THEN ERROR;
ENDLOOP;
IF NextArgument[cmd, quoteTreatment] # NIL THEN ERROR;
};
ParseToTList: PROC [cmd: Commander.Handle] RETURNS [list: LIST OF Token, length: NAT ¬ 0] ~ {
head: LIST OF Token ~ LIST[nullToken];
last: LIST OF Token ¬ head;
cmds: IO.STREAM ¬ ObtainRIS[cmd.commandLine];
DO
token: Token ~ GetCmdToken[cmds];
IF token.value = NIL THEN EXIT;
last ¬ last.rest ¬ LIST[token];
length ¬ length + 1;
ENDLOOP;
list ¬ head.rest;
head.rest ¬ NIL;
ReleaseRIS[cmds];
};
ParseRecord: TYPE = RECORD [
argumentList: LIST OF Token ¬ NIL,
The arguments to the command.
argumentPointer: INT ¬ 0,
position of next argument to fetch
argumentListTail: LIST OF Token ¬ NIL,
List of unfetched arguments
commandLine: ROPE
used to check if the command line has been parsed already for the current command
];
GetParseRecord: PROC [cmd: Commander.Handle] RETURNS [pr: REF ParseRecord] ~ {
data: CommandToolData ¬ GetCommandToolData[cmd];
private: REF PrivateDataRep ¬ data.private;
pr ¬ private.parseRecord;
IF pr = NIL OR pr.commandLine # cmd.commandLine THEN {
pr ¬ NEW[ParseRecord ¬ []];
pr.argumentListTail ¬ pr.argumentList ¬ ParseToTList[cmd].list;
pr.commandLine ¬ cmd.commandLine;
private.parseRecord ¬ pr;
};
};
NumArgs: PUBLIC PROC [cmd: Commander.Handle] RETURNS [n: INT ¬ 1] ~ {
pp: REF ParseRecord ¬ GetParseRecord[cmd];
FOR tail: LIST OF Token ¬ pp.argumentList, tail.rest UNTIL tail = NIL DO n ¬ n + 1 ENDLOOP;
};
NextArgument: PUBLIC PROC [cmd: Commander.Handle, quoteTreatment: QuoteTreatment] RETURNS [arg: ROPE ¬ NIL] ~ {
pp: REF ParseRecord ¬ GetParseRecord[cmd];
IF pp.argumentListTail = NIL THEN RETURN;
arg ¬ SELECT quoteTreatment FROM
stripQuotes => pp.argumentListTail.first.value,
leaveQuotes => pp.argumentListTail.first.literal,
ENDCASE => ERROR;
pp.argumentListTail ¬ pp.argumentListTail.rest;
pp.argumentPointer ¬ pp.argumentPointer + 1;
};
ArgN: PUBLIC PROC [cmd: Commander.Handle, n: INT, quoteTreatment: QuoteTreatment] RETURNS [arg: ROPE ¬ NIL] ~ {
pp: REF ParseRecord ¬ GetParseRecord[cmd];
IF n = 0 THEN {
pp.argumentListTail ¬ pp.argumentList;
pp.argumentPointer ¬ 0;
RETURN [cmd.command]
};
n ¬ n - 1;
IF n < 0 THEN RETURN [NIL];
IF n < pp.argumentPointer THEN {
pp.argumentListTail ¬ pp.argumentList;
pp.argumentPointer ¬ 0;
};
UNTIL n = pp.argumentPointer DO
IF pp.argumentListTail = NIL THEN RETURN [NIL];
pp.argumentListTail ¬ pp.argumentListTail.rest;
pp.argumentPointer ¬ pp.argumentPointer + 1;
ENDLOOP;
IF pp.argumentListTail = NIL THEN RETURN [NIL];
arg ¬ SELECT quoteTreatment FROM
stripQuotes => pp.argumentListTail.first.value,
leaveQuotes => pp.argumentListTail.first.literal,
ENDCASE => ERROR;
pp.argumentListTail ¬ pp.argumentListTail.rest;
pp.argumentPointer ¬ pp.argumentPointer + 1;
};
Properties and Substitution
PutProp: PUBLIC PROC [cmd: Commander.Handle, key, val: REF] ~ {
IF cmd # NIL THEN {
new: Atom.PropList ¬ List.PutAssoc[key: key, val: val, aList: cmd.propertyList];
IF new # cmd.propertyList THEN ERROR Failed["PutProp failed"];
};
};
GetProp: PUBLIC PROC [cmd: Commander.Handle, key: REF] RETURNS [value: REF] ~ {
value ¬ CommanderRegistry.GetProp[cmd, key];
Strictly, I suppose we should register the rest of this stuff back with CommanderRegistry, but it hardly seems worth it to me - mfp
IF value = NIL AND cmd # NIL THEN value ¬ List.Assoc[key: key, aList: cmd.propertyList];
IF value = NIL THEN value ¬ ProcessProps.GetProp[key];
IF value = NIL THEN WITH key SELECT FROM
atom: ATOM => { value ¬ CommanderSys.GetEnv[Atom.GetPName[atom]] };
ENDCASE => NULL;
};
DoSubstitutions: PROC [cmd: Commander.Handle, rope: ROPE] RETURNS [ROPE] ~ {
size: INT ¬ Rope.Size[rope];
index: INT ¬ FindTerminator[rope, INT.LAST, "$", 0]+1;
length: NAT ¬ 0;
FOR cur: INT ¬ index, FindTerminator[rope, INT.LAST, "$", index]+1 UNTIL cur >= size DO
numeric: BOOL ¬ TRUE;
subst: ROPE ¬ NIL;
IF cur < size AND (SELECT Rope.Fetch[rope, cur] FROM doubleQuote, openParen => TRUE ENDCASE => FALSE)
THEN {
res: REF;
commandLine: ROPE ¬ NIL; {
ris: IO.STREAM ~ ObtainRIS[rope];
IO.SetIndex[ris, cur];
commandLine ¬ GetCmdToken[ris].value;
length ¬ IO.GetIndex[ris] - cur;
ReleaseRIS[ris];
};
[subst, res] ¬ DoCommandRope[commandLine: commandLine, parent: cmd];
IF res = $Failure THEN Failed[NIL];
subst ¬ RemoveEOL[subst];
}
ELSE {
text: REF TEXT ¬ RefText.ObtainScratch[100];
text.length ¬ 0;
WHILE INT[cur + text.length] < size DO
c: CHAR ~ Rope.Fetch[rope, cur + text.length];
IF c IN ['A..'Z] OR c IN ['a..'z] OR c IN ['0..'9] OR c = '←
THEN {
text ¬ RefText.AppendChar[text, c];
numeric ¬ numeric AND c IN ['0..'9];
}
ELSE EXIT;
ENDLOOP;
IF numeric
THEN {
i: CARD ¬ CARD.LAST;
i ¬ Convert.CardFromRope[RefText.TrustTextAsRope[text] ! Convert.Error => CONTINUE];
WITH GetProp[cmd, $CommandFileArgumentVector] SELECT FROM
argv: ArgumentVector => {
IF i+1 < argv.argc THEN {
subst ¬ argv[i+1];
};
};
ENDCASE => NULL;
}
ELSE {
atom: ATOM ~ Atom.MakeAtomFromRefText[text];
WITH GetProp[cmd, atom] SELECT FROM
rope: ROPE => subst ¬ rope;
ENDCASE => NULL;
};
length ¬ text.length;
RefText.ReleaseScratch[text];
};
IF subst # NIL
THEN {
rope ¬ Rope.Replace[base: rope, start: cur-1, len: length+1, with: subst];
index ¬ cur-1 + Rope.Size[subst];
size ¬ Rope.Size[rope];
}
ELSE { index ¬ cur + length };
ENDLOOP;
RETURN [rope]
};
FindTerminator: PROC [rope: ROPE, maxNest: INT, terminators: REF TEXT, start: INT ¬ 0] RETURNS [INT] = {
Returns the index of the first terminator, skipping quoted strings.
IsTerminator: PROC [c: CHAR] RETURNS [BOOL] ~ INLINE {
FOR k: NAT IN [0..terminators.length) DO
IF c = terminators[k] THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE]
};
end: INT ~ Rope.Size[rope];
i: INT ¬ start;
state: {outside, insideQuotes} ¬ outside;
nest: INT ¬ 0;
WHILE i < end DO
c: CHAR ~ rope.Fetch[i];
SELECT state FROM
outside => {
SELECT c FROM
openParen => { nest ¬ nest + 1 };
closeParen => { nest ¬ nest - 1 };
doubleQuote => { state ¬ insideQuotes };
ENDCASE => IF nest <= maxNest AND IsTerminator[c] THEN EXIT;
};
insideQuotes => {
IF c = '\\
THEN { i ¬ i + 1 }
ELSE IF c = doubleQuote THEN {
IF end > (i + 1) AND rope.Fetch[i+1] = doubleQuote
THEN { i ¬ i + 1 }
ELSE { state ¬ outside };
};
};
ENDCASE => ERROR;
i ¬ i + 1;
ENDLOOP;
IF state = insideQuotes THEN ERROR Failed["Mismatched double quotes in command line"];
RETURN [i]
};
Execution
GetPrivate: PROC [cmd: Commander.Handle] RETURNS [REF PrivateDataRep] ~ {
IF cmd # NIL THEN {
pd: CommandToolData ~ GetCommandToolData[cmd];
IF pd # NIL THEN RETURN [pd.private];
};
RETURN [NIL]
};
PutMsg: PROC [cmd: Commander.Handle, rope: ROPE] ~ {
data: CommandToolData ~ GetCommandToolData[cmd];
private: REF PrivateDataRep ¬ GetPrivate[cmd];
parentPrivate: REF PrivateDataRep ¬ GetPrivate[data.parentCommander];
IF rope # NIL AND cmd # NIL AND (private=NIL OR private.nestedMsg # rope) THEN {
len: INT ~ Rope.Size[rope];
IO.PutRope[cmd.err, rope];
IF len = 0 OR Rope.Fetch[rope, len-1] # '\n THEN { IO.PutRope[cmd.err, "\n"] };
};
IF parentPrivate # NIL THEN parentPrivate.nestedMsg ¬ rope;
};
EnsureWhitespace: PROC [rope: ROPE] RETURNS [ROPE] ~ {
pre: ROPE ~ IF Rope.Match[" *", rope] THEN NIL ELSE " ";
post: ROPE ~ IF Rope.Match["*\n", rope] THEN NIL ELSE "\n";
RETURN [Rope.Cat[pre, rope, post]]
};
RemoveEOL: PROC [rope: ROPE] RETURNS [ROPE] ~ {
IF Rope.Match["*\n", rope] THEN rope ¬ Rope.Substr[rope, 0, Rope.Size[rope]-1];
RETURN [IF rope = NIL THEN "" ELSE rope]
};
DoCommand: PUBLIC PROC [commandLine: ROPE, parent: Commander.Handle] RETURNS [result: REF] ~ {
cmd: Commander.Handle ~ CreateFromStreams[parentCommander: parent];
msg: ROPE ¬ NIL;
[result: result, msg: msg] ¬ ExecuteCommand[cmd, commandLine];
};
DoCommandRope: PUBLIC PROC [commandLine, in: ROPE ¬ NIL, parent: Commander.Handle]
RETURNS [out: ROPE, result: REF] ~ {
outStream: IO.STREAM ¬ IO.ROS[];
cmd: Commander.Handle ~ CreateFromStreams[in: IO.RIS[in], out: outStream, parentCommander: parent];
msg: ROPE ¬ NIL;
[result: result, msg: msg] ¬ ExecuteCommand[cmd, commandLine];
out ¬ IO.RopeFromROS[outStream];
};
ProcessPropsDiffer: PROC [cmd: Commander.Handle] RETURNS [List.AList] ~ {
differ: List.AList ¬ NIL;
Do: PROC [key, val: REF] ~ {
IF ProcessProps.GetProp[key] # val THEN differ ¬ Atom.PutPropOnList[propList: differ, prop: key, val: val];
};
Do[$CommanderHandle, cmd];
Do[$StdIn, cmd.in];
Do[$StdOut, cmd.out];
Do[$ErrOut, cmd.err];
RETURN [differ]
};
CommandWithProcessProps: PROC [cmd: Commander.Handle] RETURNS [result: REF, msg: ROPE] ~ {
commandToolData: CommandToolData ~ GetCommandToolData[cmd];
differ: List.AList ¬ ProcessPropsDiffer[cmd];
IF commandToolData = NIL OR commandToolData.verbose THEN {
IO.PutRope[cmd.err, " *** "];
IO.PutRope[cmd.err, cmd.command];
IO.PutRope[cmd.err, cmd.commandLine];
};
IF differ = NIL
THEN { [result: result, msg: msg] ¬ cmd.procData.proc[cmd] }
ELSE {
Inner: PROC ~ { [result: result, msg: msg] ¬ cmd.procData.proc[cmd] };
ProcessProps.AddPropList[propList: differ, inner: Inner];
};
};
ExecuteCommand: PUBLIC PROC [cmd: Commander.Handle, wholeCommandLine: ROPE]
RETURNS [result: REF ¬ NIL, msg: ROPE ¬ NIL] ~ {
residual: ROPE ¬ wholeCommandLine;
data: CommandToolData ~ GetCommandToolData[cmd];
WHILE residual # NIL DO
data.private.nestedMsg ¬ NIL;
BEGIN
ENABLE {
Failed => { msg ¬ errorMsg; GOTO Failure };
};
line: ROPE ~ residual;
commandLineIndex: INT ¬ 0;
command: ROPE ¬ NIL; {
cmds: IO.STREAM ¬ ObtainRIS[line];
command ¬ IO.GetTokenRope[cmds, CmdTokenBreak ! IO.EndOfStream => CONTINUE].token;
commandLineIndex ¬ IO.GetIndex[cmds];
ReleaseRIS[cmds];
cmds ¬ NIL;
};
IF command = NIL THEN RETURN;
residual ¬ NIL; -- in case CommandSetup raises an error
residual ¬ CommandSetup[cmd, line, command, commandLineIndex];
[result: result, msg: msg] ¬ CommandWithProcessProps[cmd];
EXITS
Failure => { result ¬ $Failure };
END;
PutMsg[cmd, msg];
PutProp[cmd, $Result, result];
PutProp[cmd, $Msg, msg];
ENDLOOP;
};
DoLookup: PROC [cmd: Commander.Handle, command: ROPE] ~ {
unabbreviated: ROPE ¬ command;
cmd.procData ¬ Commander.Lookup[command];
IF cmd.procData = NIL THEN {
result: REF ¬ NIL;
[result: result] ¬ ExecuteBasicCommand[cmd: cmd, command: "PreRegister", commandLine: command !
Failed => ERROR Failed[Rope.Cat["Failed in PreRegister ", command, ": ", errorMsg]]
];
WITH result SELECT FROM
rope: ROPE => unabbreviated ¬ rope;
ENDCASE => NULL;
cmd.procData ¬ Commander.Lookup[unabbreviated];
IF cmd.procData = NIL THEN {
ERROR Failed[Rope.Cat["PreRegister ", command, " failed to register ", unabbreviated]];
};
};
cmd.command ¬ unabbreviated;
};
CommandSetup: PROC [cmd: Commander.Handle, wholeCommandLine, command: ROPE, commandLineIndex: INT] RETURNS [residual: ROPE ¬ NIL] ~ {
Sets up cmd, including PreRegister if needed; raises Failed if there is a problem.
DoLookup[cmd, command];
IF NOT cmd.procData.interpreted
THEN { cmd.commandLine ¬ EnsureWhitespace[Rope.Substr[base: wholeCommandLine, start: commandLineIndex]] }
ELSE {
redirectFrom: ROPE ¬ NIL;
redirectTo: ROPE ¬ NIL;
append: BOOL ¬ FALSE;
skip: INT ¬ 0;
NoteRedirection: PROC [pos: INT, in: BOOL] ~ {
SELECT Rope.Fetch[wholeCommandLine, pos-1] FROM
' , '\t => {
ris: IO.STREAM ~ ObtainRIS[wholeCommandLine];
fName: ROPE;
IF in AND redirectFrom # NIL THEN ERROR Failed["Error: Multiple input redirection"];
IF NOT in AND redirectTo # NIL THEN ERROR Failed["Error: Multiple output redirection"];
IO.SetIndex[ris, pos + 1];
IF NOT in AND NOT IO.EndOf[ris] AND IO.PeekChar[ris] = '> THEN {
append ¬ TRUE;
[] ¬ IO.GetChar[ris];
};
fName ¬ GetCmdToken[ris].literal;
IF fName = NIL THEN ERROR Failed["Error: Invalid redirection syntax"];
fName ¬ DoSubstitutions[cmd, fName];
IF in THEN redirectFrom ¬ fName ELSE redirectTo ¬ fName;
wholeCommandLine ¬ Rope.Replace[base: wholeCommandLine, start: pos, len: IO.GetIndex[ris]-pos];
length ¬ Rope.Size[wholeCommandLine];
ReleaseRIS[ris];
};
ENDCASE => { skip ¬ 1 }; -- was not a redirection because no leading whitespace
};
length: INT ¬ Rope.Size[wholeCommandLine];
break: INT ¬ -1;
pipe: BOOL ¬ FALSE;
FOR cur: INT ¬ commandLineIndex, FindTerminator[wholeCommandLine, 0, "|;<>", cur+skip] WHILE cur < length DO
skip ¬ 0;
SELECT Rope.Fetch[wholeCommandLine, cur] FROM
'; => { break ¬ cur; EXIT };
'| => {
break ¬ cur; pipe ¬ TRUE;
IF redirectTo # NIL THEN ERROR Failed["Error: Multiple output redirection"];
EXIT;
};
'< => { NoteRedirection[pos: cur, in: TRUE] };
'> => { NoteRedirection[pos: cur, in: FALSE] };
ENDCASE => { skip ¬ 1 }; -- should never happen
ENDLOOP;
IF break < 0 THEN break ¬ length;
IF break < length THEN residual ¬ Rope.Substr[base: wholeCommandLine, start: break];
IF pipe OR redirectTo # NIL OR redirectFrom # NIL
THEN {
DoLookup[cmd, "RedirectIO"];
cmd.commandLine ¬ Rope.Cat[
IF redirectFrom # NIL THEN Rope.Concat[" -from ", redirectFrom] ELSE NIL,
IF redirectTo # NIL THEN Rope.Concat[IF append THEN " -append " ELSE " -to ", redirectTo] ELSE NIL,
IF pipe THEN " -pipe" ELSE NIL,
EnsureWhitespace[Rope.Substr[base: wholeCommandLine, start: 0, len: break]]];
}
ELSE {
cmd.commandLine ¬ DoSubstitutions[cmd, EnsureWhitespace[Rope.Substr[base: wholeCommandLine, start: commandLineIndex, len: break-commandLineIndex]]];
};
};
};
ExecuteBasicCommand: PROC [cmd: Commander.Handle, command, commandLine: ROPE] RETURNS [result: REF, msg: ROPE] ~ {
Executes with a minimum of bells&whistles; raises Failed if there is a problem.
cmd.command ¬ command;
cmd.commandLine ¬ EnsureWhitespace[commandLine];
cmd.procData ¬ Commander.Lookup[command];
IF cmd.procData = NIL THEN {
ERROR Failed[Rope.Concat["Unknown command: ", command]];
};
[result: result, msg: msg] ¬ CommandWithProcessProps[cmd];
IF result = $Failure THEN ERROR Failed[msg];
};
Read-Eval-Print Loop
looksItalic: ROPE ~ "ABCDEFGHiJKLMNOPQRSTUVWXYZ";
PutItalics: PROC [stream: IO.STREAM, rope: ROPE] ~ {
IO.PutF[stream, "%l%g%l\n",
[rope[looksItalic]],
[rope[rope]],
[rope["I"]]
];
};
BeforeProcessing: PROC [cmd: Commander.Handle, startTime: CommanderSys.EGMT, commandLine: ROPE] ~ {
data: CommandToolData ~ GetCommandToolData[cmd];
IF data.recent = NIL THEN data.recent ¬ NEW[CommanderBackdoor.CommandHistoryElement];
data.recent­ ¬ [
commandNumber: data.recent.commandNumber+1,
wholeCommandLine: commandLine,
result: NIL,
msg: NIL,
startTime: startTime.gmt,
seconds: 0,
allocations: 0,
pageFaults: 0,
subHistory: NIL
];
IF data.Before # NIL THEN data.Before[cmd];
};
AfterProcessing: PROC [cmd: Commander.Handle, startTime: CommanderSys.EGMT, result: REF, msg: ROPE] ~ {
data: CommandToolData ~ GetCommandToolData[cmd];
endTime: CommanderSys.EGMT ~ CommanderSys.ExtendedNow[];
intSeconds: INT ~ BasicTime.Period[from: startTime.gmt, to: endTime.gmt];
microseconds: INT ~ endTime.usecs - startTime.usecs;
seconds: REAL ~ REAL[intSeconds] + REAL[microseconds]/1.0E+6;
IF data.recent = NIL THEN data.recent ¬ NEW[CommanderBackdoor.CommandHistoryElement];
data.recent.result ¬ result;
data.recent.msg ¬ msg;
data.recent.seconds ¬ seconds;
data.recent.subHistory ¬ data.childHistory;
IF data.keepHistory THEN {
data.history ¬ CONS[data.recent­, data.history];
data.childHistory ¬ NIL;
IF data.parentCommander # NIL THEN GetCommandToolData[data.parentCommander].childHistory ¬ data.history;
};
IF data.After # NIL THEN data.After[cmd];
};
ReadEvalPrintLoop: PUBLIC PROC [cmd: Commander.Handle] RETURNS [hadFailure: BOOL ¬ FALSE] ~ {
data: CommandToolData ~ GetCommandToolData[cmd];
REPLoop: PROC ~ {
IF data.parentCommander # NIL THEN data.history ¬ GetCommandToolData[data.parentCommander].childHistory;
DO
SetProcess[cmd, CommanderSys.CurrentProcess[]];
IF data.Prompt # NIL THEN data.Prompt[cmd];
BEGIN
result: REF ¬ NIL;
msg: ROPE ¬ NIL;
line: ROPE ¬ IO.GetLineRope[stream: cmd.in !
IO.EndOfStream => EXIT;
IO.Rubout => GOTO Rubout;
];
startTime: CommanderSys.EGMT ~ CommanderSys.ExtendedNow[];
BeforeProcessing[cmd, startTime, line];
[result: result, msg: msg] ¬ ExecuteCommand[cmd: cmd, wholeCommandLine: data.recent.wholeCommandLine !
UNWIND => {
AfterProcessing[cmd, startTime, $Failure, "Aborted"];
};
];
AfterProcessing[cmd, startTime, result, msg];
IF data.statistics THEN {
IO.PutF[cmd.out, " %l{ %6.2f sec }%l\n",
[rope["i"]],
[real[data.recent.seconds]],
[rope["I"]]
]
};
IF result = $Failure THEN {
hadFailure ¬ TRUE;
IF data.stopOnFailure THEN EXIT;
};
EXITS
Rubout => { PutItalics[cmd.err, " -- <DEL>"]; IO.Reset[cmd.in] };
END;
ENDLOOP;
};
ProcessUNCAUGHT: PROC [sig: ROPE] RETURNS [reject: BOOL] ~ {
DO
action: ErrorAction;
commandLine: ROPE;
[action, commandLine] ¬ ErrorPrompt[AdamOrEve[cmd], sig];
SELECT action FROM
continue => RETURN [reject: FALSE];
reject => RETURN [reject: TRUE];
command => [] ¬ ExecuteCommand[cmd, commandLine];
ENDCASE => ERROR;
ENDLOOP;
};
REPBase: PROC ~ {
DO
ok: BOOL ¬ TRUE;
aborted: BOOL ¬ FALSE;
ok ¬ CommanderSys.UNCAUGHTProtect[REPLoop, ProcessUNCAUGHT !
IO.Error => {
IF stream = cmd.in OR stream = cmd.err THEN CONTINUE
};
ABORTED => { SetProcess[cmd, NIL]; aborted ¬ TRUE; ok ¬ FALSE; CONTINUE }
];
IF ok THEN EXIT;
IF aborted THEN { PutItalics[cmd.err, " -- Aborted"]; IO.Reset[cmd.in] };
ENDLOOP;
SetProcess[cmd, NIL];
};
Action: PROC ~ IF AlreadyProtected[cmd] THEN REPLoop ELSE REPBase;
differ: List.AList ~ ProcessPropsDiffer[cmd];
IF differ = NIL
THEN Action[]
ELSE { ProcessProps.AddPropList[propList: differ, inner: Action] };
};
CommanderBackdoor Procedures
AdamOrEve: PUBLIC PROC [cmd: Commander.Handle] RETURNS [Commander.Handle] ~ {
THROUGH [0..1000) DO
data: CommandToolData ~ GetCommandToolData[cmd];
IF data = NIL OR data.parentCommander = NIL THEN RETURN [cmd];
cmd ¬ data.parentCommander;
ENDLOOP;
ERROR; -- paranoia against circularities
};
AbortCommander: PUBLIC ENTRY PROC [cmd: Commander.Handle] ~ {
ENABLE UNWIND => NULL;
data: CommandToolData ~ GetCommandToolData[cmd];
IF data # NIL AND data.process # NIL AND data.process­ # NIL THEN {
CommanderSys.AbortProcess[data.process­];
};
};
SetProcess: PUBLIC ENTRY PROC [cmd: Commander.Handle, process: PROCESS] ~ {
data: CommandToolData ~ GetCommandToolData[cmd];
IF data # NIL AND data.process # NIL THEN { data.process­ ¬ process };
};
AlreadyProtected: ENTRY PROC [cmd: Commander.Handle] RETURNS [BOOLEAN] ~ {
data: CommandToolData ~ GetCommandToolData[cmd];
RETURN [data # NIL AND data.process # NIL AND data.process­ = CommanderSys.CurrentProcess[]];
};
GetCommandToolData: PUBLIC PROC [cmd: Commander.Handle] RETURNS [CommandToolData] ~ {
WITH GetProp[cmd, cmd] SELECT FROM
data: CommandToolData => RETURN [data];
ENDCASE => RETURN [NIL];
};
Backstop Error Catcher
ErrorAction: TYPE ~ {continue, reject, command};
ErrorPrompt: PROC [cmd: Commander.Handle, sig: ROPE] RETURNS [errorAction: ErrorAction ¬ continue, commandLine: ROPE ¬ NIL] ~ {
ENABLE IO.Error, IO.EndOfStream, IO.Rubout => GOTO SkipIt;
in: IO.STREAM ~ cmd.in;
out: IO.STREAM ~ cmd.err;
IO.PutRope[out, "\n*** Uncaught ERROR or SIGNAL: "];
IO.PutRope[out, sig];
WITH GetProp[cmd, "DebugUNCAUGHT"] SELECT FROM
rope: ROPE => {
SELECT TRUE FROM
Rope.Equal[rope, "n", FALSE] => RETURN [continue];
Rope.Equal[rope, "y", FALSE] => RETURN [reject];
ENDCASE => NULL;
};
ENDCASE => NULL;
IO.PutRope[out, "\n*** Do you want to try to debug this? (y, n, s <flags>, ! <command>, or ?) "];
IO.Flush[out];
SELECT IO.PeekChar[in] FROM
'\r, '\n => [] ¬ IO.GetChar[in];
ENDCASE;
DO
line: ROPE ~ IO.GetLineRope[in];
c: CHAR ~ IF Rope.IsEmpty[line] THEN '? ELSE Rope.Fetch[line, 0];
SELECT c FROM
'n, 'N => RETURN [continue];
'y, 'Y => RETURN [reject];
's, 'S => RETURN [command, Rope.Concat["StackTrace", Rope.Substr[line, 1]]];
'! => RETURN [command, Rope.Substr[line, 1]];
ENDCASE => IO.PutRope[out, "\n*** Type 'y' to REJECT the signal and land in the system debugger, 'n' to get back to top level, 's <flags>' to invoke the StackTrace command, '! <command>' to execute a commander command and return to the error prompt: "];
ENDLOOP;
EXITS
SkipIt => RETURN [continue];
};
Creation
defaultPrompt: ROPE ¬ "Commander %l%% %l";
DefaultPrompter: PROC [cmd: Commander.Handle] ~ {
prompt: ROPE ~ WITH GetProp[cmd, $Prompt] SELECT FROM
rope: ROPE => rope,
ENDCASE => defaultPrompt;
IO.PutF[cmd.err, prompt, [rope["b"]], [rope["B"]]];
};
CreateFromStreams: PUBLIC PROC [in, out, err: IO.STREAM ¬ NIL, parentCommander: Commander.Handle ¬ NIL] RETURNS [cmd: Commander.Handle] ~ {
data: CommandToolData ¬ NEW[CommanderBackdoor.CommandToolDataRep];
init: PROC [cmd: Commander.Handle] ¬ NIL;
oldPropList: Atom.PropList ¬ NIL;
data.private ¬ NEW[PrivateDataRep];
cmd ¬ NEW[Commander.CommandObject];
IF parentCommander = NIL
THEN {
data.Prompt ¬ DefaultPrompter;
data.keepHistory ¬ TRUE;
}
ELSE {
p: CommandToolData ¬ GetCommandToolData[parentCommander];
IF in = NIL THEN in ¬ parentCommander.in;
IF out = NIL THEN out ¬ parentCommander.out;
IF err = NIL THEN err ¬ parentCommander.err;
IF p # NIL THEN {
data.Lookup ¬ p.Lookup;
data.verbose ¬ p.verbose;
init ¬ p.InitChild;
data.process ¬ p.process;
data.keepHistory ¬ p.keepHistory;
};
data.parentCommander ¬ parentCommander;
oldPropList ¬ parentCommander.propertyList;
};
IF data.process = NIL THEN data.process ¬ NEW[PROCESS ¬ NIL];
IF in = NIL THEN in ¬ IO.noInputStream;
IF out = NIL THEN out ¬ IO.noWhereStream;
IF err = NIL THEN err ¬ out;
cmd.in ¬ in;
cmd.out ¬ out;
cmd.err ¬ err;
cmd.propertyList ¬ CONS[NEW[Atom.DottedPairNode ¬ [key: cmd, val: data]], oldPropList];
IF init # NIL THEN init[cmd];
};
C Language Hook
XRDoCommanderCommands: PROC [commandLine: POINTER TO Basics.RawChars] RETURNS [BOOL] ~ {
A simple (minded?) hook for doing a command from a c program. No frills; useful for installation code inside packaged worlds.
cmd: Commander.Handle ~ CreateFromStreams[in: IO.RIS[UXStrings.ToRope[commandLine]]];
RETURN ReadEvalPrintLoop[cmd];
};
ExternalNames: PROC = TRUSTED MACHINE CODE {
"^ExternalNames\n";
"XRDoCommanderCommands XR𡤍oCommanderCommands\n";
};
ExternalNames[];
END.
¬ &v ¬ ViewerIO.CreateViewerStreams[name: "CommanderOps"];
¬ CommanderOpsImpl.ReadEvalPrintLoop[CommanderOpsImpl.CreateFromStreams[in: &v.in, out: &v.out]]