DIRECTORY
AMEvents USING [CallDebugger],
Atom USING [MakeAtom, GetProp, PutProp],
CIFS USING [Error],
ConvertUnsafe USING [AppendRope, ToRope],
Directory USING [Lookup, GetProps, Error],
File USING [Capability, nullCapability],
FileIO USING [Open, OpenFailed],
IO USING [CR, EndOf, EndOfStream, SyntaxError, Close, Flush, GetBlock, GetIndex, GetLength, int, GetCedarToken, PutChar, PutF, PutRope, ROPE, rope, RIS, SetIndex, SP, STREAM, TAB, GetToken, WhiteSpace, SkipOver, UserAborted],
IOExtras USING [GetCedarScannerToken, FromTokenProc],
Loader USING [Instantiate, Start, Error],
PrincOps USING [ControlModule],
Process USING [GetCurrent],
Rope USING [Cat, Concat, Equal, Fetch, Find, IsEmpty, Length, Replace, Substr, Text],
RopeInline USING [NewText],
RTProcess USING [GetTotalPageFaults, StartWatchingFaults],
SafeStorage USING [NWordsAllocated],
ShowTime USING [GetMark, Microseconds, Show],
System USING [GreenwichMeanTime],
Time USING [Current],
TiogaOps USING [AddLooks, CreateSimplePattern, FirstChild, GetRope, GoToPreviousCharacter, InsertChar, IsComment, Location, Next, NodeSearch, Paste, Pattern, Ref, SaveSpanForPaste, ViewerDoc, SelectPoint, LastLocWithin],
TiogaExtraOps USING [GetFile],
UserExec USING [HistoryEvent, ExecHandle, CheckForAbort, UserAbort, UserAborted, GetTheFile, GetStreams, ErrorThisEvent, GetDefaultExecHandle],
UserExecExtras USING [CreateEvent, NewErrorThisEvent],
UserExecPrivate USING [AcquireExec, EventFailed, ReleaseExec, Prompt, UpdateFrameCache, ExecPrivateRecord, StripComments, CreateSubEvent, methodList, MethodList, GetPrivateStuff, BlinkIcon, HistoryEventPrivateRecord],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc],
ViewerClasses USING [Viewer],
ViewerOps USING [FindViewer]
;
processing individual events
DoIt:
PUBLIC
PROC [input:
ROPE, exec: ExecHandle ← UserExec.GetDefaultExecHandle[], partOf: HistoryEvent ←
NIL] = {
private: REF UserExecPrivate.ExecPrivateRecord;
execOwner: UNSAFE PROCESS;
event, subEvent: HistoryEvent;
privateEvent, privateSubEvent: REF UserExecPrivate.HistoryEventPrivateRecord;
abortIfSubEventFails: BOOLEAN;
start, current: INT ← 0;
length: INT;
mark: ShowTime.Microseconds ← 0;
t0: System.GreenwichMeanTime;
nWords: LONG CARDINAL ← 0;
nFaults: INT ← 0;
Release:
PROC =
TRUSTED {
-- Process
IF exec #
NIL
AND private.execState # destroyed
AND UserExecPrivate.
ReleaseExec[exec]
THEN {
IF private.process = Process.GetCurrent[] THEN ERROR;
private.eventState ← readyForNext;
IF private.execState = listening THEN UserExecPrivate.Prompt[private.eventNum, exec];
};
};
{
ENABLE
UNWIND => Release[];
ShowStats:
PROC = {
IF showStats
AND exec #
NIL
AND
NOT private.dontPrompt
THEN
{
out: STREAM = UserExec.GetStreams[exec].out;
nw: INT ← SafeStorage.NWordsAllocated[] - nWords;
t: System.GreenwichMeanTime;
seconds: CARDINAL;
nf: INT;
Put: PROC [char: CHAR] RETURNS[BOOL ← FALSE] = {out.PutChar[char]};
out.PutF["*n {"];
TRUSTED {t ← Time.Current[]};
seconds ← t - t0;
IF seconds > 60
THEN {
out.PutF["%d minutes, %d seconds", int[seconds/60], int[seconds MOD 60]];
}
ELSE {
ShowTime.Show[from: mark, p: Put, places: 2];
out.PutRope[" seconds"];
};
IF nw > 0 AND nw < 10000000 THEN out.PutF[", %d words", int[nw]];
TRUSTED {nf ← RTProcess.GetTotalPageFaults[] - nFaults};
IF nf > 0 AND nf < 10000000 THEN out.PutF[", %d page faults", int[nf]];
out.PutChar['}];
};
};
IF exec #
NIL
THEN {
private ← UserExecPrivate.GetPrivateStuff[exec];
execOwner ← private.execOwner;
IF UserExecPrivate.
AcquireExec[exec]
THEN
TRUSTED {
-- Process
IF private.process = Process.GetCurrent[] THEN ERROR; -- since Read Eval Print acquires exec first, this must be an event coming in from somewhere else.
IF private.execState = notListening
OR private.execState = dormant
THEN {
IF partOf # NIL THEN privateEvent ← partOf.privateStuff;
UserExecPrivate.Prompt[
IF partOf # NIL THEN privateEvent.eventNum ELSE private.eventNum, -- if partof was specified, then use its eventNumber.
exec];
}
ELSE IF private.eventState = readyForNext THEN private.eventState ← running
ELSE ERROR;
UserExec.GetStreams[exec].out.PutRope[input];
};
};
event ← IF partOf # NIL THEN partOf ELSE UserExecExtras.CreateEvent[exec, input];
subEvent ← event;
privateEvent ← event.privateStuff;
privateSubEvent ← subEvent.privateStuff;
length ← Rope.Length[input];
break input up into segments delimited by cr or ;
DO
stream: STREAM;
substr: ROPE ← NIL;
whiteSpace: BOOLEAN ← TRUE;
UserExec.CheckForAbort[exec];
IF current = length THEN EXIT;
start ← current;
WHILE current < length
DO
char: CHARACTER ← Rope.Fetch[input, current];
TerminatesAtCurrent:
PROC
RETURNS[
BOOL] = {
count: INT ← start;
fromTokenProc: IOExtras.FromTokenProc = {
count ← count + token.next;
IF token.kind = tokenCOMMENT
AND closure.proc[closure.data, token.next - 1] = '\n
THEN count ← count - 1; -- cr is (incorrectly) included in comment. without this check, test would say that CR is not a legitimate terminator in this case
};
stream ← RIS[input, stream];
IO.SetIndex[stream, start];
IF NOT Rope.Equal[IO.GetCedarToken[stream! IO.EndOfStream => GOTO Yes], "←"] THEN RETURN[TRUE];
IO.SetIndex[stream, start];
DO
IOExtras.GetCedarScannerToken[stream, fromTokenProc !
IO.SyntaxError => CONTINUE
];
-- current is one beyond the character just read. We want to know if that character, e.g. CR could be the terminator. Therefore, we want to test and see if the scan stopped just before that character.
IF count = current - 1 THEN GOTO Yes -- stopped just before the desired place.
ELSE IF count >= current THEN GOTO No;
ENDLOOP;
EXITS
No => RETURN[FALSE];
Yes => RETURN[TRUE];
};
current ← current + 1;
SELECT char
FROM
SP => IF whiteSpace THEN start ← current; -- necessary (aesthetic) for compound events
'' => current ← current + 1; -- from the standpoint of terminating the input, ignore next character. Whether ' or ^ is interpreted as special for command line will have to wait until we decide if this is a command or an expression.
'^ =>
SELECT Rope.Fetch[input, current - 2] FROM
SP, '\n, '\t => current ← current + 1; -- compromise. ^{cr} could end a line consisting of a mesa expression. Therefore, only give ^ its special interpretation (a la Alto exec) when preceded by white space.
ENDCASE;
'\\ => current ← current + 1; -- from the standpoint of terminating the input, ignore next character.
CR, TAB => {
IF whiteSpace THEN {start ← current; LOOP}
ELSE IF start = 0 AND current = length THEN substr ← input -- typical case
ELSE IF current = length OR TerminatesAtCurrent[] THEN substr ← Rope.Substr[base: input, start: start, len: current - start]
ELSE LOOP;
abortIfSubEventFails ← FALSE;
EXIT;
otherwise, keep going, e.g. CR is in middle of rope. current = length check says if this is the end of the rope, no point in calling ProperlyTerminated
};
'; => {
IF current = length
OR TerminatesAtCurrent[]
THEN {
substr ← Rope.Concat[Rope.Substr[base: input, start: start, len: current - start - 1], "\n"]; -- convert ; to CR so commands can all assume they are given a single line terminated by a CR.
abortIfSubEventFails ← TRUE;
EXIT;
};
otherwise, keep going, e.g. ; is in middle of rope.
};
ENDCASE => whiteSpace ← FALSE;
REPEAT
FINISHED => substr ← Rope.Substr[input, start, current]; -- e.g. terminate in ?
ENDLOOP; -- of WHILE loop that breaks into segments.
process substr
BEGIN
ENABLE {
IO.UserAborted => {
privateEvent.state ← aborted;
privateSubEvent.state ← aborted;
ShowStats[];
}; -- let error go through
UserExec.ErrorThisEvent => {
IF NOT Rope.IsEmpty[msg] THEN UserExec.GetStreams[exec].out.PutF["*n*e%g*s", rope[msg]];
privateEvent.state ← causedAnError;
privateSubEvent.state ← causedAnError;
IF exec.viewer.iconic THEN UserExecPrivate.BlinkIcon[exec.viewer];
ShowStats[];
IF NOT abortIfSubEventFails THEN LOOP; -- and go on with next subevent.
}; -- otherwise let error go through.
UserExecExtras.NewErrorThisEvent => {
IF NOT Rope.IsEmpty[msg] THEN UserExec.GetStreams[exec].out.PutF["*n*e%g*s", rope[msg]];
privateEvent.state ← causedAnError;
privateSubEvent.state ← causedAnError;
UserExecPrivate.EventFailed[event: event, offender: offender];
IF exec.viewer.iconic THEN UserExecPrivate.BlinkIcon[exec.viewer];
ShowStats[];
IF NOT abortIfSubEventFails THEN LOOP; -- and go on with next subevent.
}; -- otherwise let error go through.
UNWIND => {
privateEvent.state ← causedAnError;
privateSubEvent.state ← causedAnError;
IF exec.viewer.iconic AND private.execState # dormant THEN UserExecPrivate.BlinkIcon[exec.viewer];
};
};
inRopeStream: STREAM = RIS[rope: substr];
line: ROPE;
firstToken: ROPE;
firstToken ← IO.GetToken[inRopeStream];
IF Rope.IsEmpty[firstToken]
THEN
{IF event # NIL AND event.subEvents = NIL AND current = length AND exec # NIL THEN private.continuation ← TRUE;
LOOP; -- white space on line in compound event.
};
IF event #
NIL
AND (event.subEvents #
NIL
OR current # length
OR input # event.input)
THEN {
-- second case says this is a compound event. third says came from command file, where input field is @file and input (obtained from private.input) is the expansion
subEvent ← UserExecPrivate.CreateSubEvent[event: event, input: substr];
privateSubEvent ← subEvent.privateStuff;
UserExec.GetStreams[exec].out.PutF["*n"]; -- if previous event caused some output, want this to go on fresh line. See comments in userexecimpl regarding LogNewLine as to why just don't call NewLine directly.
};
line ← UserExecPrivate.StripComments[event, substr];
IF line # substr
THEN
-- some comments
{[] ← RIS[rope: line, oldStream: inRopeStream];
IO.SkipOver[inRopeStream, IO.WhiteSpace];
IF inRopeStream.EndOf[] THEN {privateSubEvent.state ← completed; LOOP}; -- nothing there but a comment.
};
subEvent.commandLine ← line;
subEvent.commandLineStream ← inRopeStream;
mark ← ShowTime.GetMark[];
nWords ← SafeStorage.NWordsAllocated[];
TRUSTED {t0 ← Time.Current[]; nFaults ← RTProcess.GetTotalPageFaults[]};
IF privateSubEvent.showInput THEN UserExec.GetStreams[exec].out.PutF["*n>%g", rope[line]];
FOR lst: UserExecPrivate.MethodList ← UserExecPrivate.methodList, lst.rest
UNTIL lst =
NIL
DO
subEvent.commandLine ← line;
[] ← RIS[rope: line, oldStream: subEvent.commandLineStream]; -- load commandLineStream. Have to do it each time because the method might read from the commandLine before it determines that it is not applicable.
IF lst.first.proc[event: subEvent, exec: exec, clientData: lst.first.clientData]
THEN
EXIT;
REPEAT
FINISHED => ERROR UserExecExtras.NewErrorThisEvent[event: subEvent, msg: Rope.Concat[firstToken, " not a command.\n"], offender: firstToken];
ENDLOOP; -- methodlist loop
END; -- of process substr.
ShowStats[];
privateSubEvent.state ← completed;
ENDLOOP; -- substr loop
privateEvent.state ← completed;
Release[];
};
}; -- DoIt
EventFailed:
PUBLIC
PROC[event: HistoryEvent, msg:
ROPE ← NIL, offender:
ROPE ←
NIL] = {
privateEvent: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff;
IF NOT Rope.IsEmpty[msg] THEN ERROR UserExecExtras.NewErrorThisEvent[event, msg, offender]
ELSE privateEvent.offender ← offender;
};
RopeFromCMFile:
PUBLIC
PROC [file:
ROPE, event: HistoryEvent, exec: ExecHandle]
RETURNS[contents:
ROPE, name:
ROPE] =
{
ENABLE FileIO.OpenFailed => {
SELECT why
FROM
fileNotFound =>
{
name ← UserExec.GetTheFile[file: file, defaultExt: "cm", event: event, exec: exec];
IF name # NIL THEN {contents ← RopeFromFile[name]; CONTINUE};
name ← UserExec.GetTheFile[file: file, defaultExt: "commands", event: event, exec: exec];
IF name # NIL THEN {contents ← RopeFromFile[name]; CONTINUE};
};
illegalFileName => NULL;
ENDCASE => REJECT;
ERROR UserExecExtras.NewErrorThisEvent[event: event, msg: Rope.Concat[fileName, " not found"], offender: fileName];
length: INT;
IF Rope.Fetch[file, 0] = '@ THEN file ← Rope.Substr[file, 1]; -- strip off @
length ← Rope.Length[file];
IF length = 0 THEN RETURN;
IF Rope.Fetch[file, length - 1] = '@ THEN file ← Rope.Substr[base: file, len: length - 1]; -- strip off trailing @
first try just filename
name ← file;
contents ← NIL;
contents ← RopeFromFile[fileName: file ! FileIO.OpenFailed =>
IF why = fileNotFound
AND Rope.Find[file, "."] = -1
THEN
{name ← Rope.Concat[file, ".cm"]; CONTINUE}
];
IF contents # NIL THEN RETURN;
no extension specified, try .cm
contents ← RopeFromFile[fileName: name ! FileIO.OpenFailed =>
IF why= fileNotFound
THEN
{name ← Rope.Concat[file, ".commands"]; CONTINUE}
];
IF contents # NIL THEN RETURN;
no extension specified, try .commands
contents ← RopeFromFile[fileName: name];
};
Think about using RopeIO
RopeFromFile:
PROC [fileName:
ROPE]
RETURNS [value:
ROPE] =
TRUSTED {
-- LOOPHOLE
handle: IO.STREAM ← FileIO.Open[fileName: fileName, accessOptions: read, createOptions: oldOnly];
length: LONG INTEGER ← handle.GetLength[];
text: Rope.Text ← RopeInline.NewText[length];
[] ← handle.GetBlock[LOOPHOLE[text, REF TEXT]];
IO.Close[handle];
RETURN[text];
};
StripComments:
PUBLIC
PROC [event: UserExec.HistoryEvent, rope:
ROPE]
RETURNS [
ROPE] = {
pos1, pos2: INT ← 0;
WHILE (pos1 ← Rope.Find[s1: rope, s2: "//", pos1: pos1]) # -1 DO
pos2 ← Rope.Find[s1: rope, s2: "\n", pos1: pos1];
IF pos2 = -1 THEN pos2 ← Rope.Length[rope];
rope ← Rope.Replace[base: rope, start: pos1, len: pos2 - pos1 + 1];
ENDLOOP;
IF Rope.Find[s1: rope, s2: "//"] # -1 THEN UserExec.ErrorThisEvent[event, "// convention for comments no longer implemented. Use -- instead."];
pos1 ← 0;
WHILE (pos1 ← Rope.Find[s1: rope, s2: "--", pos1: pos1]) # -1
DO
pos3: INT ← Rope.Find[s1: rope, s2: "--", pos1: pos1 + 2];
pos2 ← Rope.Find[s1: rope, s2: "\n", pos1: pos1];
IF pos2 = -1 THEN pos2 ← Rope.Length[rope];
IF pos3 = -1 THEN pos3 ← Rope.Length[rope] ELSE pos3 ← pos3 + 2;
rope ← Rope.Replace[base: rope, start: pos1,
len: MIN[pos2, pos3] - pos1];
ENDLOOP;
RETURN[rope];
};
CorrectionDisabled:
PUBLIC PROC[event: HistoryEvent]
RETURNS[disabled:
BOOL] = {
RETURN[IF event = NIL THEN FALSE ELSE event.dontCorrect];
};
August 20, 1982 1:37 pm fixed LoadAndGo to use Loader.Instantiate and Loader.Start instead of Runtime.RunConfig.