Last Edited by: Teitelman, June 20, 1983 9:17 am
DIRECTORY
ActionAreasDefs USING [RestoreActionArea],
AMTypes USING [TVToName],
BBAction USING [Abort],
BBContext USING [GetContents],
BBEval USING [EvalHead],
BBNub USING [FindWorld, TurnADeafEar],
IO USING [AppendStreams, ChangeDeliverWhen, CharProc, CharsAvail, Close, ControlX, CR, CreateDribbleStream, CreateEditedStream, CreateRefStreamProcs, CreateProcsStream, CurrentPosition, DeliverWhenProc, EndOfStream, EraseChar, Error, ESC, Flush, GetBufferContents, GetChar, GetCedarToken, GetSequence, int, LookupData, NewLine, noWhereStream, PFStarCodeProc, PeekChar, PutChar, PutF, PutRope, Reset, RIS, rope, ROPE, SetEcho, SetPFStarCodeProc, Signal, StoreData, STREAM, SyntaxError, TAB, UserAborted],
IOExtras USING [GetCedarScannerToken, noInputStream, WaitUntilCharsAvail],
List USING [DotCons, DRemove],
MessageWindow USING [Append, Blink],
Process USING [Detach, GetCurrent, SetPriority, priorityNormal, Yield],
ProcessProps USING [AddPropList],
Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, IsEmpty, Length, Substr],
SafeStorage USING [NewZone],
TiogaOps USING [LastLocWithin, PutTextKey, ViewerDoc, Location],
TTYIO USING [CreateTTYHandleFromStreams],
TypeScript USING [Create],
UserCredentials USING [GetUserCredentials, SetUserCredentials],
UserExec USING [AcquireResource, AskUser, DoIt, ErrorThisEvent, ExecHandle, ExecRecord, Expression, HistoryEvent, ReleaseResource, ResetUserResponse, RopeSubst, Viewer],
UserExecExtras USING [CreateEvent, NewErrorThisEvent],
UserExecPrivate USING [ActionAreaData, Advice, AdviceRecord, execMenu, CreateExecLogFile, CloseExecLogFile, EvalHeadData, ExecPrivateRecord, GetEvalHead, GlobalExecState, MethodList, execHandleList, HistoryError, HistoryEventPrivateRecord, ForAllSplitViewers, SplitViewerProc],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChanged, ProfileChangedProc, Token],
ViewerAbort USING [UserAbort, SetUserAbort, ResetUserAbort],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream, NotAllowed],
ViewerOps USING [AddProp, DestroyViewer, PaintViewer, SetMenu],
ViewerTools USING [GetSelectedViewer, SetSelection],
WorldVM USING [World, LocalWorld]
;
UserExecImpl: CEDAR MONITOR
IMPORTS ActionAreasDefs, AMTypes, BBAction, BBContext, BBNub, IO, List, IOExtras, MessageWindow, Process, ProcessProps, Rope, SafeStorage, TiogaOps, TTYIO, TypeScript, UserCredentials, UserExec, UserExecExtras, UserExecPrivate, UserProfile, ViewerAbort, ViewerIO, ViewerOps, ViewerTools, WorldVM
EXPORTS UserExec, UserExecExtras, UserExecPrivate
SHARES ViewerIO
ViewerIO: NotAllowed
= BEGIN OPEN IO, UserExec, UserExecPrivate;
connecting concrete and opaque types
ExecPrivateRecord: PUBLIC TYPE = UserExecPrivate.ExecPrivateRecord;
HistoryEventPrivateRecord: PUBLIC TYPE = UserExecPrivate.HistoryEventPrivateRecord;
signals
GoToNextEvent: PUBLIC ERROR = CODE; -- used by history
ParseFailed: PUBLIC ERROR [expr: Expression, msg: ROPE] = CODE;
EvaluationFailed: PUBLIC ERROR [expr: Expression, msg: ROPE] = CODE;
ErrorThisEvent: PUBLIC ERROR[event: HistoryEvent, msg: ROPE] = CODE;
NewErrorThisEvent: PUBLIC ERROR[event: HistoryEvent, msg: ROPE, offender: ROPENIL] = CODE;
GoToSleep: PUBLIC ERROR = CODE;
InvalidExecHandle: PUBLIC ERROR[exec: ExecHandle, msg: ROPE] = CODE;
global constants
version: PUBLIC ROPE ← "*mCedar Executive June 20, 1983 9:14 am. Type ? for Commands.\n\n";
execHandleList: PUBLIC LIST OF ExecHandle ← NIL;
methodList: PUBLIC UserExecPrivate.MethodList ← NIL;
Zone: PUBLIC ZONE ← SafeStorage.NewZone[];
creating, starting, and destroying execs
CreateUserExecutive: PUBLIC PROCEDURE [name: ROPENIL, viewer: Viewer ← NIL, paint: BOOLTRUE, iconic: BOOLFALSE, msg: ROPENIL, startListening: BOOLTRUE] RETURNS[exec: ExecHandle] = {
exec ← CreateUserExec[name: name, viewer: viewer, paint: paint, iconic: iconic, msg: IF msg = NIL THEN version ELSE msg];
IF startListening THEN StartListening[exec];
};
SetEditableTypescripts: UserProfile.ProfileChangedProc = {
useViewerEditedStream ← UserProfile.Boolean["EditTypeScripts", TRUE];
};
useViewerEditedStream: BOOL;
idNumber: CARDINAL ← 0;
CreateUserExec: PROCEDURE [name: ROPENIL, viewer: Viewer ← NIL, paint: BOOLTRUE, iconic: BOOLFALSE, msg: ROPENIL] RETURNS[exec: ExecHandle] = {
in, out, log, execDotWStream: STREAM;
private: REF ExecPrivateRecord;
id: ROPE = ComputeExecName[idNumber];
SetPFStuff: PROC [stream: STREAM] = {
[] ← stream.CurrentPosition[]; -- makes out keep track of position.
stream.SetPFStarCodeProc['n, NewLine];
FOR i: INT IN [0..Rope.Length[looks]) DO
stream.SetPFStarCodeProc[Rope.Fetch[looks, i], ChangeFont];
ENDLOOP;
};
IF idNumber = 0 -- first time -- THEN SetPFStuff[IO.noWhereStream];
idNumber ← idNumber + 1;
private ← Zone.NEW[ExecPrivateRecord ← [
-- deliverWhen: DefaultDeliverWhen,
historyList: NIL,
id: id,
process: NULL,
execDotW: NULL
]];
exec ← Zone.NEW[ExecRecord ← [
viewer: NIL,
privateStuff: private
]];
IF name = NIL THEN name ← CaptionForExec[exec];
IF viewer = NIL THEN viewer ← TypeScript.Create[info: [name: name, iconic: iconic, column: right], paint: paint];
ViewerOps.SetMenu[viewer: viewer, menu: execMenu, paint: paint];
[in, out] ← ViewerIO.CreateViewerStreams[name: viewer.name, viewer: viewer, editedStream: useViewerEditedStream];
IF NOT useViewerEditedStream THEN in ← IO.CreateEditedStream[in, out];
SetPFStuff[out];
IF (log ← CreateExecLogFile[exec]) # NIL THEN {
out ← IO.CreateDribbleStream[out, log];
out.SetPFStarCodeProc['n, LogNewLine]; -- font changes just go to the viewer stream, not to dribble stream
IO.StoreData[self: in, key: $EXEC, data: exec]; -- another stream (an EchoWhenDeliverStream) will be built on top of in, but in is the edited stream upon which the deliverwhenproc has to work.
in ← CreateEchoWhenDeliverStream[in, log];
[] ← IO.SetEcho[in, out]; -- characters read from the viewer by the edited stream continued to be echoed to the viewer output stream, rather than the dribble stream. When they are delivered, then they are also echoed to the log. This is so any edits are seen in the log.
};
private.in ← in;
private.out ← out;
exec.viewer ← viewer;
[] ← IO.ChangeDeliverWhen[in, ExecDeliverWhen];
[execDotWStream,] ← ViewerIO.CreateViewerStreams[name: viewer.name, viewer: viewer, editedStream: FALSE]; -- in is an editedStream. Need a stream for input upon which to build a TTYHandle for execDotW.
[] ← execDotWStream.SetEcho[out];
TRUSTED {private.execDotW ← TTYIO.CreateTTYHandleFromStreams[execDotWStream, out]};
IO.StoreData[self: in, key: $EXEC, data: exec]; -- so ExecDeliverWhen can find which exec this stream is connected to.
IO.StoreData[self: out, key: $EXEC, data: exec]; -- for consistency
out.PutF[msg];
{AddIt: ENTRY PROC = {execHandleList ← CONS[exec, execHandleList]};
AddIt[];
};
}; -- of CreateUserExec
Echo When Deliver Stream
EchoedWhenDelivered: TYPE = RECORD[echoTo: IO.STREAM];
CreateEchoWhenDeliverStream: PROCEDURE [in, echoTo: IO.STREAM] RETURNS [IO.STREAM] = {
RETURN[IO.CreateProcsStream[
streamProcs: IO.CreateRefStreamProcs[getChar: EchoWhenDeliverGetChar, name: "Echo When Delivered"],
backingStream: in,
streamData: NEW[EchoedWhenDelivered ← [echoTo]]
]];
}; -- of CreateEchoWhenDeliverStream
EchoWhenDeliverGetChar: PROC[self: IO.STREAM] RETURNS[char: CHAR] = {
data: REF EchoedWhenDelivered = NARROW[self.streamData];
char ← self.backingStream.GetChar[];
data.echoTo.PutChar[char];
}; -- of EchoWhenDeliverGetChar
CaptionForExec: PUBLIC PROC [exec: ExecHandle, paint: BOOLTRUE] RETURNS[caption: ROPE] = {
private: REF ExecPrivateRecord = exec.privateStuff;
viewer: Viewer = exec.viewer;
actionAreaData: REF ActionAreaData ← private.actionAreaData;
ropeForIcon, r: ROPE;
r ← ropeForIcon ← Rope.Concat[private.id, ": "];
IF private.execState = suspended THEN r ← Rope.Concat[r, "suspended "]
ELSE IF actionAreaData = NIL OR actionAreaData.action # NIL THEN NULL
ELSE IF actionAreaData.booted THEN ": booted "
ELSE IF actionAreaData.aborted THEN r ← Rope.Concat[r, "aborted "]
ELSE r ← Rope.Concat[r, "proceeded "];
IF actionAreaData # NIL THEN TRUSTED { -- action area
caption ← Rope.Cat["Action Area ", r, actionAreaData.abbrevMsg];
IF actionAreaData.world # WorldVM.LocalWorld[] THEN caption ← Rope.Concat["WorldSwap ", caption];
ropeForIcon ← Rope.Cat[ropeForIcon, actionAreaData.iconMsg];
}
ELSE {
caption ← Rope.Cat["Work Area ", r];
IF NOT private.evalMode THEN {
caption ← Rope.Concat[caption, "Executive"];
ropeForIcon ← Rope.Concat[ropeForIcon, "Executive"];
}
ELSE {
evalHead: BBEval.EvalHead = UserExecPrivate.GetEvalHead[expr: NIL, exec: exec];
evalHeadData: REF UserExecPrivate.EvalHeadData = NARROW[evalHead.data];
caption ← Rope.Concat[caption, "Interpreter"];
ropeForIcon ← Rope.Concat[ropeForIcon, "Interpreter"];
IF private.spawnedActionArea # NIL THEN NULL -- if suspended, no point in including default context in caption
ELSE IF evalHead.globalContext # NIL THEN caption ← Rope.Cat[caption, " {global context: ", AMTypes.TVToName[BBContext.GetContents[evalHead.globalContext].gf], "}"]
ELSE IF evalHeadData.defaultInterface # NIL THEN caption ← Rope.Cat[caption, " {default interface: ", evalHeadData.defaultInterface, "}"];
};
};
IF viewer # NIL THEN {
viewerProc: UserExecPrivate.SplitViewerProc = TRUSTED {
viewer.name ← caption;
ViewerOps.AddProp[viewer, $IconLabel, ropeForIcon];
IF paint THEN ViewerOps.PaintViewer[viewer: viewer, hint: IF viewer.iconic THEN all ELSE caption];
};
UserExecPrivate.ForAllSplitViewers[viewer, viewerProc];
};
};
ComputeExecName: PROC [n: INT] RETURNS [r: ROPE] = {
m: INT;
DO
m ← n/26;
IF m > 0 THEN
{r ← Rope.Concat[r, Rope.FromChar['A + m]];
n ← n MOD 26;
}
ELSE
{r ← Rope.Concat[r, Rope.FromChar['A + n]];
EXIT};
ENDLOOP;
};
StartListening is a separate procedure in order to give client a chance to print something to the exec, or change some menus, before the thing takes off
StartListening: PUBLIC ENTRY PROC [exec: ExecHandle] = TRUSTED { -- Process
private: REF ExecPrivateRecord = exec.privateStuff;
IF private.execState = notListening OR private.execState = dormant THEN {
private.execState ← listening; -- only want this done once. that is why it is monitored, and we set the state before releasing the monitor
Process.Detach[FORK ReadEvalPrint[exec]];
};
};
DestroyExec: PUBLIC PROC [exec: ExecHandle] = {
private: REF ExecPrivateRecord ← exec.privateStuff;
viewer: Viewer = exec.viewer;
RemoveExec: ENTRY PROC = TRUSTED { -- LOOPHOLE for Polymorphism
ENABLE UNWIND => NULL;
UserExecPrivate.execHandleList ← LOOPHOLE[List.DRemove[exec, LOOPHOLE[UserExecPrivate.execHandleList, LIST OF REF ANY]]];
UserExecPrivate.CloseExecLogFile[exec];
};
IF private.execState = destroyed THEN RETURN; -- already destroyed
private.execState ← destroyed;
private.in.Close[! IO.Error => IF ec = StreamClosed THEN CONTINUE];
private.out.Close[! IO.Error => IF ec = StreamClosed THEN CONTINUE];
RemoveExec[];
IF private.actionAreaData # NIL AND private.actionAreaData.action # NIL THEN {
actionAreaData: REF UserExecPrivate.ActionAreaData = private.actionAreaData;
IF ViewerTools.GetSelectedViewer[] = viewer AND actionAreaData.viewer # NIL THEN -- not sure latter can fail, but it did, causing an addressfault
ViewerTools.SetSelection[actionAreaData.viewer, NIL];
IF private.spawnedActionArea # NIL THEN {
DestroyExec[private.spawnedActionArea];
private.spawnedActionArea ← NIL;
};
IF actionAreaData.parentExec # NIL THEN ActionAreasDefs.RestoreActionArea[actionAreaData.parentExec];
IF actionAreaData.action.status = pendingOut THEN BBAction.Abort[actionAreaData.action];
};
TRUSTED {FREE [@private.actionAreaData]; FREE[@private]};
};
Manipulating execHandles
GetPrivateStuff: PUBLIC PROC [exec: ExecHandle] RETURNS[REF ExecPrivateRecord] = {
private: REF ExecPrivateRecord;
IF exec = NIL THEN ERROR InvalidExecHandle[exec: exec, msg: "Non-NIL exec handle required"];
private ← exec.privateStuff;
IF private.execState = destroyed THEN InvalidExecHandle[exec: exec, msg: "exec handle has been destroyed"];
RETURN[private];
};
GetExecHandle: PUBLIC PROCEDURE [id: ROPENIL, viewer: Viewer ← NIL, process: UNSAFE PROCESSNIL] RETURNS[exec: ExecHandle] = TRUSTED { -- Process
current: UNSAFE PROCESSNIL;
Search: ENTRY PROC RETURNS [exec: ExecHandle] = TRUSTED {
FOR l: LIST OF ExecHandle ← UserExecPrivate.execHandleList, l.rest UNTIL l = NIL DO
private: REF UserExecPrivate.ExecPrivateRecord = l.first.privateStuff;
IF id # NIL AND Rope.Equal[private.id, id] THEN RETURN[l.first];
IF viewer # NIL THEN {
v: Viewer ← l.first.viewer;
DO
IF v = viewer THEN RETURN[l.first];
IF (v ← v.link) = NIL OR (v = l.first.viewer) THEN EXIT;
ENDLOOP;
};
IF process # NIL AND private.process = process THEN RETURN[l.first];
IF current # NIL AND private.process = current THEN RETURN[l.first];
ENDLOOP;
RETURN[NIL];
};
IF id = NIL AND viewer = NIL AND process = NIL THEN current ← Process.GetCurrent[];
exec ← Search[];
RETURN[
IF exec # NIL THEN exec
ELSE IF current # NIL -- i.e. called with all three arguments NIL -- THEN GetDefaultExecHandle[]
ELSE NIL];
};
GetDefaultExecHandle: PUBLIC PROCEDURE RETURNS[exec: ExecHandle] = {
CreateIt: PROC RETURNS[exec: ExecHandle] = {
defaultExec ← CreateUserExecutive[msg: Rope.Substr[base: version, len: Rope.Find[version, "."]], paint: FALSE, iconic: TRUE, startListening: FALSE];
private ← defaultExec.privateStuff;
private.out.PutRope["\nThis is a \"deaf\" executive: it does not listen to the keyboard, but instead is used only for the execution of events from an application program.\n"];
defaultExec.viewer.name ← UserExec.RopeSubst[old: "Executive", new: "Default Executive", base: defaultExec.viewer.name];
ViewerOps.PaintViewer[defaultExec.viewer, all];
private.in ← IOExtras.noInputStream;
RETURN[defaultExec];
};
private: REF UserExecPrivate.ExecPrivateRecord;
IF defaultExec = NIL THEN RETURN[CreateIt[]];
private ← defaultExec.privateStuff;
IF private.execState = destroyed THEN RETURN[CreateIt[]];
RETURN[defaultExec];
};
defaultExec: PUBLIC UserExec.ExecHandle ← NIL;
GetWorld: PUBLIC PROCEDURE RETURNS[WorldVM.World] = TRUSTED { -- Process
exec: ExecHandle = GetExecHandle[process: Process.GetCurrent[]];
private: REF UserExecPrivate.ExecPrivateRecord;
IF exec = NIL THEN RETURN[WorldVM.LocalWorld[]];
private ← GetPrivateStuff[exec];
RETURN[IF private.actionAreaData # NIL THEN private.actionAreaData.world
ELSE WorldVM.LocalWorld[]];
};
NewLine: IO.PFStarCodeProc -- [stream: STREAM, char: CHAR] -- = {
IO.NewLine[stream]
};
LogNewLine: IO.PFStarCodeProc -- [stream: STREAM, char: CHAR] -- = {
IF stream.backingStream.CurrentPosition[] # 0 THEN stream.PutChar['\n];
-- reason for this is because the CR is put to the viewer and the log separately (the viewer when typed, the log when edited stream passes it up), but without this, would get extra crs because this stream does not know that position has changed.
};
AdviseExec: PUBLIC PROC [exec: ExecHandle, before, after: PROC[data: REF ANY], data: REF ANY] = {
private: REF UserExecPrivate.ExecPrivateRecord = GetPrivateStuff[exec];
private.advice ← CONS[
NEW[UserExecPrivate.AdviceRecord ← [before: before, after: after, data: data]
],
private.advice];
};
world swap debug
SetWorldSwapDebug: UserProfile.ProfileChangedProc -- [reason: ProfileChangeReason] -- = {
new: BOOLEAN = UserProfile.Boolean[key: "WorldSwapDebug", default: FALSE];
IF reason = firstTime OR new # worldSwapDebug THEN
{worldSwapDebug ← new;
SetupWorldSwapDebug[];
};
};
worldSwapDebug: BOOLEAN;
world: WorldVM.World;
SetupWorldSwapDebug: PROC = {
IF NOT worldSwapDebug THEN world ← BBNub.FindWorld[name: "Local", new: TRUE]
ELSE IF world # NIL THEN BBNub.TurnADeafEar[world];
};
Read/Eval/Print Loop
ReadEvalPrint: PROC [exec: ExecHandle] = {
private: REF ExecPrivateRecord = exec.privateStuff;
event: HistoryEvent;
TypeOfAskUser: TYPE = PROC [msg: ROPE, in, out: IO.STREAM, defaultKey: ATOM, keyList: LIST OF ATOM, timeout: INT] RETURNS [value: ATOM];
askUser: TypeOfAskUser = {RETURN[UserExec.AskUser[msg: msg, timeout: timeout, defaultKey: defaultKey, exec: exec, keyList: keyList]]};
inner: PROC = {
DO
ENABLE {
IO.Error => IF ec = StreamClosed THEN {
IF exec.viewer = NIL OR exec.viewer.destroyed THEN GOTO Destroy;
}; -- user destroyed the viewer, then somebody tried to do input or output
ABORTED => { -- breakpoint or signal, then aborted
IF exec.privateStuff = NIL OR private.execState = destroyed THEN GOTO Destroy;
WasAborted[exec, ""];
LOOP;
};
IO.UserAborted => { -- ctrl-del or clicked stop
WasAborted[exec, msg];
LOOP;
};
};
IF private.execState = destroyed THEN {
IF NOT exec.viewer.destroyed THEN ViewerOps.DestroyViewer[exec.viewer];
EXIT;
};
event ← GetEvent[exec];
private.eventState ← running;
ProcessEvent[event, exec ! GoToSleep => {
private.execState ← dormant;
ReleaseStreams[exec];
GOTO Sleep;
}
];
ENDLOOP;
EXITS
Destroy => DestroyExec[exec];
Sleep => NULL;
};
TRUSTED {
private.process ← Process.GetCurrent[];
Process.SetPriority[Process.priorityNormal]; -- if exec was created via New button, then would be running at process foreground
};
private.execState ← listening;
ProcessProps.AddPropList[
LIST[List.DotCons[$AskUser, NEW[TypeOfAskUser ← askUser]]],
inner]; -- "redefines" IO.AskUser to call this AskUser. Primarily so Bringover, SModel etc. use UserExec. AskUser when running in workarea
};
GetEvent: PROC [exec: ExecHandle] RETURNS [event: HistoryEvent] = {
private: REF ExecPrivateRecord = exec.privateStuff;
input: ROPE;
in, out, bottom: STREAM;
charsAvail: BOOLFALSE;
UserExec.ResetUserResponse[exec.viewer];
FOR lst: LIST OF Advice ← private.advice, lst.rest UNTIL lst = NIL DO
IF lst.first.before # NIL THEN lst.first.before[lst.first.data];
ENDLOOP;
bottom ← private.in;
UNTIL bottom.backingStream = NIL DO
bottom ← bottom.backingStream;
ENDLOOP;
DO
eventNum: INTIF private.continuation THEN private.eventNum - 1 ELSE private.eventNum;
All: IO.CharProc = {
include ← TRUE;
quit ← NOT in.CharsAvail[]; -- basically want the entire buffer. Use DeliverWhen to decide.
};
CharsAvail: PROC RETURNS [BOOL] = { -- the reason for this (kluge) rather than checking just bottom.charsAvail is that there may be characters available at a higher level, in particular this occurs when you do an AppendStreams to stuff characters to implement a control-x.
stream: IO.STREAM ← private.in;
UNTIL stream = NIL DO
IF stream.CharsAvail[] THEN RETURN[TRUE];
stream ← stream.backingStream;
ENDLOOP;
RETURN[FALSE];
};
IF NOT private.dontPrompt THEN Prompt[eventNum, exec];
private.eventState ← readyForNext;
-- IF NOT private.continuation THEN -- ReleaseStreams[exec];
{
ENABLE {
IO.Signal => CHECKED {
SELECT ec FROM
Rubout => {
out.PutRope[" XXX\n"];
private.dontPrompt ← FALSE;
in.Reset[];
[] ← in.SetEcho[out];
LOOP;
}; -- reprints event number.
ENDCASE};
IO.SyntaxError => IF stream = in THEN
{out.PutF["*n*eSyntax Error: %g", rope[msg]]; Reset[in]; LOOP};
ViewerIO.NotAllowed => GOTO NotAllowed; -- must unwind before calling WasAborted or else could cause monitor lock
};
IF private.continuation THEN [in, out] ← GetStreams[exec]
ELSE
IF NOT CharsAvail[] THEN IOExtras.WaitUntilCharsAvail[bottom]; -- gives somebody else a chance to get in.
[in, out] ← AcquireStreams[exec];
IF NOT CharsAvail[] THEN { -- between the time that charas were available and AcquireStreams returns, no more chars. Means that the character was really typed to somebody else and has been consumed, e.g. an event ran in this exec that asked for confirmation. WaitUntilCharsAvail woke up when user typed Y, but AcquireStreams did not return until after the streams were released. At that point, the character had already been read. So we are back where we were.
private.dontPrompt ← TRUE;
LOOP;
};
private.eventState ← receivingInput;
[] ← in.PeekChar[]; -- wait for first character, thereby guaranteeing that deliverWhenProc has returned TRUE.
input ← IO.GetSequence[in, All]; -- now get everything.
EXITS
NotAllowed => {
WasAborted[exec, "buffered characters and document don't agree"];
LOOP;
};
};
IF input = NIL THEN ERROR;
EXIT;
ENDLOOP;
private.lastLine ← input;
IF private.continuation THEN
{event ← private.historyList.first;
event.input ← input;
private.continuation ← FALSE;
}
ELSE event ← UserExecExtras.CreateEvent[exec, input];
private.dontPrompt ← FALSE;
out.PutF["*s"]; -- return to system font
RETURN[event];
}; -- of GetEvent
ProcessEvent: PROC [event: HistoryEvent, exec: ExecHandle] = {
private: REF ExecPrivateRecord = exec.privateStuff;
abortMsg: ROPE;
IF event.input.IsEmpty[] THEN ERROR;
BEGIN -- inner block
ENABLE {
UserExec.ErrorThisEvent => GOTO Exit;
UserExecExtras.NewErrorThisEvent => GOTO Exit;
IO.UserAborted => {-- user typed control-del
abortMsg ← msg;
GOTO Aborted;
};
ABORTED => {
abortMsg ← "";
GOTO Aborted;
};
UserExecPrivate.HistoryError => {
out: STREAM = GetStreams[exec].out;
SELECT ec FROM
NotFound => out.PutRope["not found"];
UseWhat => out.PutRope["Use What?"]; -- from ParseUseCommand
UseForWhat => out.PutRope["For What?"];-- from ParseUseCommand
UseHuh => out.PutRope["Huh?"];-- from ParseUseCommand
ENDCASE => ERROR;
GOTO Exit;
};
UNWIND => NULL;
}; -- of Catch Phrases
FOR lst: LIST OF Advice ← private.advice, lst.rest UNTIL lst = NIL DO
IF lst.first.after # NIL THEN lst.first.after[lst.first.data];
ENDLOOP;
ResetUserAbort[exec]; -- if it wasn't seen before, then somebody missed it. dont want to abort the next thing.
[] ← UserExec.DoIt[event.input, exec, event];
EXITS -- still inside inner block
Aborted => WasAborted[exec, abortMsg];
Exit => NULL;
END; -- of inner block
}; -- of ProcessEvent
Prompt: PUBLIC PROC [eventNum: INT, exec: ExecHandle] = {
private: REF ExecPrivateRecord = GetPrivateStuff[exec];
viewer: Viewer;
loc: TiogaOps.Location;
viewer ← exec.viewer;
private.out.Flush[];
loc ← TiogaOps.LastLocWithin[TiogaOps.ViewerDoc[viewer]]; -- location of the current end of typescript. Don't do PutTextKey until some characters are printed, because characters woould be inserted in front of this place, i.e. the addr would always refer to the end of the document.
private.out.PutF["*n*p&%d *u", int[eventNum]];
TiogaOps.PutTextKey[node: loc.node, where: loc.where, key: exec]; -- saves sticky addr for beginning of next event. However, don't know yet who is going to get the event, i.e. somebody might call DoIt from outside.
IF NOT private.evalMode THEN RETURN;
viewer.class.notify[viewer, LIST["← "]]; not synchronized with typeahead.
AppendStreams[in: private.in, from: IO.RIS["← "]];
};
WasAborted: PROC [exec: ExecHandle, msg: ROPE] = {
private: REF ExecPrivateRecord = exec.privateStuff;
IF private.execState = destroyed THEN RETURN;
ResetUserAbort[exec];
[] ← private.in.SetEcho[NIL];
private.in.Reset[]; -- clears the edited stream buffer. Note that clicking STOP will clear everything that has been typed ahead up until that point. Can't wait until here, because means user can't start typing as soon as he clicks stop. This means that CTRL-DEL and stop behave differently, i.e. CTRL-DEL will not clear any typeahead.
[] ← private.in.SetEcho[private.out];
private.continuation ← FALSE;
private.dontPrompt ← FALSE;
private.out.PutF["*n*maborted %g\n*s", rope[msg]];
};
GetExecFromStream: PUBLIC PROC [stream: STREAM] RETURNS[UserExec.ExecHandle] = {
RETURN[NARROW[IO.LookupData[stream, $EXEC]]];
};
ExecDeliverWhen: PUBLIC IO.DeliverWhenProc -- PROC[char: CHAR, stream: STREAM] RETURNS[BOOL] -- = {
exec: ExecHandle = GetExecFromStream[stream];
private: REF ExecPrivateRecord = GetPrivateStuff[exec];
yes: BOOLEANFALSE;
SELECT char FROM
'^ => 
SELECT private.lastChar FROM
SP, '\t, '\n => NULL;
ENDCASE => char ← NUL; -- ^ only has its escape interpretation when preceded by white space, because ^ is a legitimate way to end a mesa expression.
CR, TAB, ControlX, '? =>
SELECT private.lastChar FROM
'\\ => NULL;
ENDCASE => {
r: ROPE = IO.GetBufferContents[private.in];
IF NOT (yes ← IsWellFormed[Rope.Substr[base: r, len: Rope.Length[r] - 1]]) THEN MessageWindow.Append["Event not terminated, continue with input", TRUE];
};
ESC => {
r: ROPE = IO.GetBufferContents[private.in];
IF Rope.IsEmpty[r] THEN ERROR; -- in this case, stream is supposed to take care of supplying characters.
IF Rope.Equal[r, "← \033"] THEN {
private.in.EraseChar[char];
private.in.EraseChar[' ];
private.in.EraseChar['←];
AppendStreams[in: private.in, from: IO.RIS[Rope.Substr[base: private.lastLine, len: Rope.Length[private.lastLine] - 1]]];-- ignore the last character since it activated.
}
ELSE SELECT Rope.Fetch[r, Rope.Length[r] - 2] FROM -- -1 is the ESC
IN ['A..'Z], IN['a..'z], IN['0..'9] => yes ← TRUE;
ENDCASE => {
private.in.EraseChar[char];
MessageWindow.Append["No match", TRUE];
MessageWindow.Blink[];
};
};
ENDCASE;
private.lastChar ← char;
RETURN[yes];
};
the purpose of this procedure is to determine whether the last character in the rope really terminates the rope, viewed as a sequence of characters, or is a part of a token itself, e.g. to distinguish ← foo? from ←"foo?".
IsWellFormed: PROC [rope: ROPE] RETURNS[BOOLEAN] = {
stream: STREAM;
stream ← IO.RIS[rope]; -- since streams already acquired, we might consider saving a scratch stream and using it.
IF Rope.IsEmpty[rope] OR NOT Rope.Equal["←", IO.GetCedarToken[stream ! IO.EndOfStream => GOTO Yes]] THEN GOTO Yes;
DO
IOExtras.GetCedarScannerToken[stream, NIL !
IO.SyntaxError => GOTO No;
IO.EndOfStream => GOTO Yes
];
ENDLOOP;
EXITS
Yes => RETURN[TRUE];
No => RETURN[FALSE];
};
Acquiring Streams
AcquireExec: PUBLIC PROC [exec: ExecHandle] RETURNS[newlyAcquired: BOOL] = TRUSTED { -- Process
private: REF ExecPrivateRecord = GetPrivateStuff[exec];
process: UNSAFE PROCESS = Process.GetCurrent[];
thisExec: ExecHandle = GetExecHandle[process: process];
IF private.execOwner = process THEN { -- already acquired by this process
private.execOwnerCount ← private.execOwnerCount + 1;
RETURN[FALSE];
};
newlyAcquired ← UserExec.AcquireResource[resource: exec, owner: NIL, exec: thisExec]; -- the exec argument is where abort is checked.
private.execOwner ← process;
private.execOwnerCount ← 1;
};
ReleaseExec: PUBLIC PROC [exec: ExecHandle] RETURNS[released: BOOL] = TRUSTED {
private: REF ExecPrivateRecord = exec.privateStuff; -- don't use GetPrivateStuff because exec may be destroyed
process: UNSAFE PROCESS = Process.GetCurrent[];
IF private.execState = destroyed THEN RETURN[FALSE];
IF private.execOwner # process OR private.execOwnerCount = 0 THEN RETURN[FALSE];
private.execOwnerCount ← private.execOwnerCount - 1;
IF private.execOwnerCount = 0 THEN {
private.execOwner ← NIL;
IF NOT UserExec.ReleaseResource[resource: exec] THEN ERROR;
Process.Yield[];
RETURN[TRUE];
}
ELSE RETURN[FALSE];
};
AcquireStreams: PUBLIC PROC [exec: ExecHandle] RETURNS [in, out: STREAM] = {
private: REF ExecPrivateRecord = GetPrivateStuff[exec];
[] ← AcquireExec[exec: exec];
in ← private.in;
out ← private.out;
};
ReleaseStreams: PUBLIC PROC [exec: ExecHandle] = TRUSTED { -- Process
private: REF ExecPrivateRecord = exec.privateStuff; -- don't use GetPrivateStuff because exec may be destroyed
owner: UNSAFE PROCESS = private.execOwner;
process: UNSAFE PROCESS = Process.GetCurrent[];
IF owner = NIL THEN RETURN; -- to make it convenient to simply call ReleaseStreams from Doit, rather than checking
IF owner # process THEN RETURN;
[] ← ReleaseExec[exec: exec];
};
GetStreams: PUBLIC PROC [exec: ExecHandle] RETURNS [in, out: STREAM] = TRUSTED { -- Process
IF exec = NIL THEN RETURN[in: IOExtras.noInputStream, out: IO.noWhereStream]
ELSE {
private: REF ExecPrivateRecord = GetPrivateStuff[exec];
process: UNSAFE PROCESS = Process.GetCurrent[];
IF private.execOwner = process THEN NULL -- already owns it
ELSE IF private.execOwner = NIL AND (
exec = defaultExec -- anybody can have this -- OR
private.execState # listening) -- e.g. not started yet
THEN ERROR InvalidExecHandle[exec, "Streams Not Acquired"];
in ← private.in;
out ← private.out;
};
};
Passwords
GetNameAndPassword: PUBLIC PROC RETURNS[name, password: ROPE] = {
[name, password] ← UserCredentials.GetUserCredentials[];
};
SetNameAndPassword: PUBLIC PROC [name, password: ROPE] = {
IF Rope.Find[name, "."] = -1 THEN name ← Rope.Concat[name, ".pa"]; -- should I still be doing this.
UserCredentials.SetUserCredentials[name, password];
UserProfile.ProfileChanged[edit];
};
Aborting
UserAbort: PUBLIC PROC [exec: ExecHandle] RETURNS [BOOLEAN] = {
RETURN[exec # NIL AND exec.viewer # NIL AND NOT exec.viewer.destroyed AND ViewerAbort.UserAbort[exec.viewer]];
};
SetUserAbort: PUBLIC PROC [exec: ExecHandle] = {
IF exec # NIL AND exec.viewer # NIL AND NOT exec.viewer.destroyed THEN ViewerAbort.SetUserAbort[exec.viewer];
};
ResetUserAbort: PUBLIC PROC [exec: ExecHandle] = {
IF exec # NIL AND exec.viewer # NIL AND NOT exec.viewer.destroyed THEN ViewerAbort.ResetUserAbort[exec.viewer];
};
UserAborted: PUBLIC PROC [exec: ExecHandle, msg: ROPENIL] = {
IO.UserAborted[exec, msg];
};
Changing Fonts
ChangeFont: IO.PFStarCodeProc -- [stream: STREAM, char: CHAR] -- = {
viewer: Viewer = ViewerIO.GetViewerFromStream[stream];
looks: ROPE;
IF viewer = NIL THEN RETURN;
SELECT char FROM
'u => looks ← userLooks; -- what the user types in
's => looks ← systemLooks;
'e => looks ← errorLooks;
'm => looks ← messageLooks;
'p => looks ← promptLooks;
ENDCASE;
stream.PutF["%l", rope[looks]];
};
userLooks, systemLooks, errorLooks, messageLooks, promptLooks: ROPE;
looks: ROPE ← "usemp"; -- those characters to make into * commands for pf.
SetLooks: UserProfile.ProfileChangedProc -- [reason: ProfileChangeReason] -- = {
userLooks ← Rope.Concat[" ", UserProfile.Token["Looks.typein", " "]];
systemLooks ← Rope.Concat[" ", UserProfile.Token["Looks.output", " "]];
errorLooks ← Rope.Concat[" ", UserProfile.Token["Looks.errors", "ib"]];
messageLooks ← Rope.Concat[" ", UserProfile.Token["Looks.messages", "i"]];
promptLooks ← Rope.Concat[" ", UserProfile.Token["Looks.prompt", "b"]];
};
Initialization
UserProfile.CallWhenProfileChanges[SetWorldSwapDebug];
UserProfile.CallWhenProfileChanges[SetLooks];
UserProfile.CallWhenProfileChanges[SetEditableTypescripts];
END. -- of UserExecImpl
Edited on January 26, 1983 12:01 am, by Teitelman
updated time
changes to: version, SetBackingFile
Edited on January 29, 1983 3:00 pm, by Teitelman
changes to: DIRECTORY, CreateUserExec
Edited on February 27, 1983 2:53 pm, by Teitelman
changes to: version, IsWellFormed
Edited on March 2, 1983 12:00 am, by Teitelman
changes to: CreateUserExec
Edited on March 3, 1983 11:27 am, by Teitelman
changes to: version, CaptionForExec
Edited on March 7, 1983 12:48 pm, by Teitelman
changes to: version, CaptionForExec, CreateUserExec, EchoedWhenDelivered, CreateEchoWhenDeliverStream, EchoWhenDeliverGetChar, CaptionForExec, ComputeExecName, StartListening, DestroyExec, CreateUserExec, EchoedWhenDelivered, CreateEchoWhenDeliverStream, EchoWhenDeliverGetChar, CreateUserExec, EchoedWhenDelivered, DIRECTORY, NewErrorThisEvent, DirectoryNotFound, ProcessEvent, ReadEvalPrint, ReadEvalPrint
Edited on March 10, 1983 3:01 am, by Teitelman
changes to: DIRECTORY, EXPORTS, version, GetWorld, NewLine
Edited on March 11, 1983 6:24 pm, by Teitelman
changes to: version, DestroyExec, WasAborted
Edited on March 28, 1983 8:14 pm, by Teitelman
changes to: DIRECTORY, version, viewerProc (local of CaptionForExec), CaptionForExec, DefaultDeliverWhen, IMPORTS, DefaultDeliverWhen, version, DefaultDeliverWhen, versi
Edited on April 7, 1983 1:02 pm, by Teitelman
changes to: version
Edited on April 11, 1983 1:43 pm, by Teitelman
changes to: DIRECTORY, version, ReadEvalPrint
Edited on April 15, 1983 3:30 pm, by Teitelman
changes to: DIRECTORY, IMPORTS, ReadEvalPrint, askUser (local of ReadEvalPrint), inner (local of ReadEvalPrint), version, StartListening, GetEvent, GetEvent, inner (local of ReadEvalPrint), version, GetEvent, GetEvent, All (local of GetEvent), version, GetEvent, WasAborte
Edited on April 18, 1983 9:11 pm, by Teitelman
changes to: AcquireExec, ReleaseExec, ReleaseExec, ReleaseStreams, GetStreams, DIRECTORY, ErrorThisEvent, version, ProcessEvent, DestroyExec, NewErrorThisEvent, foo, GetEvent, DIRECTORY, IMPORTS, ReadEvalPrint, StartListening, DIRECTORY, StartListening, ReadEvalPrint, ReleaseExec
Edited on April 20, 1983 6:07 pm, by Teitelman
changes to: version, foo, foo, DIRECTORY, IMPORTS, ReadEvalPrint, ReadEvalPrint, GetEvent
Edited on May 3, 1983 8:48 pm, by Teitelman
changes to: version, inner (local of ReadEvalPrint)
Edited on May 9, 1983 4:56 pm, by Teitelman
changes to: version
Edited on May 12, 1983 1:11 am, by Teitelman
changes to: version
Edited on May 13, 1983 11:02 am, by Teitelman
changes to: version, methodList, ProcessEvent
Edited on May 23, 1983 6:33 pm, by Teitelman
changes to: version
Edited on May 27, 1983 11:57 am, by Teitelman
changes to: GetEvent, CharsAvail (local of GetEvent), CharsAvail (local of GetEvent)
Edited on June 3, 1983 9:56 am, by Teitelman
changes to: HistoryEventPrivateRecord, version, DestroyExec
Edited on June 13, 1983 3:00 pm, by Teitelman
changes to: WasAborted, version
Edited on June 20, 1983 9:14 am, by Teitelman
changes to: DIRECTORY, IMPORTS, version, DestroyExec