UserExec.tioga, .press describes the UserExecutive as seen by the user. This interface describes how a program can interact with the UserExecutive.
Edited by Teitelman on April 13, 1983 2:29 pm
DIRECTORY
Atom USING [PropList],
IO USING [STREAM, UserAborted],
Rope USING [ROPE],
RTBasic USING [TV, Type],
Spell USING [SpellingList, SpellingGenerator, Filter, Modes],
ViewerClasses USING [Viewer]
;
UserExec: CEDAR DEFINITIONS IMPORTS IO =
BEGIN
Types
Synonym Types
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
TV: TYPE = RTBasic.TV;
Type: TYPE = RTBasic.Type;
Viewer: TYPE = ViewerClasses.Viewer;
ExecHandle: TYPE = REF ExecRecord;
ExecRecord: TYPE = RECORD [
viewer: Viewer,
privateStuff: REF ExecPrivateRecord
];
ExecPrivateRecord: TYPE;
HistoryList: TYPE = LIST OF HistoryEvent;
HistoryEvent: TYPE = REF HistoryEventRecord;
HistoryEventRecord: TYPE = RECORD[
input: ROPENIL,
commandLine: ROPENIL, -- input with firstToken and any comments removed, *'s expanded, etc.
commandLineStream: STREAMNIL,
expression: Expression ← NIL,
subEvents: LIST OF HistoryEvent ← NIL,
props: Atom.PropList ← NIL,
tryHard: BOOLFALSE, -- true for events ending in !. used by interpreter as well as some other events, e.g. list files! means use spelling correction
dontCorrect: BOOLEANFALSE,
privateStuff: REF HistoryEventPrivateRecord ← NIL
];
HistoryEventPrivateRecord: TYPE;
Expression: TYPE = REF ExpressionRecord;
ExpressionRecord: TYPE = RECORD[
rope: ROPE,
value: TVNIL,
numRtns: INTEGER ← 0,
dontCorrect: BOOLEANFALSE,
correctionMade: BOOLFALSE,
privateStuff: REF ExpressionPrivateRecord ← NIL
];
ExpressionPrivateRecord: TYPE;
CommandProc: TYPE = PROC [event: HistoryEvent, exec: ExecHandle, clientData: REF ANYNIL] RETURNS[ok: BOOLEANTRUE, msg: ROPENIL];
msg is for inclusion in the history, e.g. it can be used to include various information, such as how many files were transferred, what got compiled, etc, or the reason that the command failed. Commands can also indicate failure by raising ErrorThisEvent (see below).
TransformProc: TYPE = PROC [event: HistoryEvent, exec: ExecHandle, clientData: REF ANYNIL] RETURNS[result: ROPENIL];
for commands which operate by producing a command which is then executed, e.g. history commands, alias, @ etc. Registering such commands as TransformProc (rather than as a CommandProc which uses DoIt to execute the command that it produces) makes ^X work automatically.
MethodProc: TYPE = PROC [event: HistoryEvent, exec: ExecHandle, clientData: REF ANYNIL] RETURNS[handled: BOOLEANFALSE];
Signals
InvalidExecHandle: ERROR [exec: ExecHandle, msg: ROPE];
ErrorThisEvent: ERROR [event: HistoryEvent, msg: ROPE];
For use from inside of (or under) a commandProc. Equivalent to returning [FALSE, msg] as the value of the commandProc.
ParseFailed: ERROR [expr: Expression, msg: ROPE];
EvaluationFailed: ERROR [expr: Expression, msg: ROPE];
See section on Interpreting Expressions below.
Manipulating ExecHandles: CreateUserExecutive, GetExecHandle, FindExecFromViewer
CreateUserExecutive: PROCEDURE [name: ROPENIL, viewer: Viewer ← NIL, paint: BOOLTRUE, iconic: BOOLFALSE, msg: ROPENIL, startListening: BOOLTRUE] RETURNS[exec: ExecHandle];
used to create a new exec. caption is the caption at the top of the window/viewer. name = NIL defaults to "Work Area id: Executive/Interpreter". If viewer is supplied, it must be of type Typescript.TS. If viewer is not supplied, one is created, iconic if iconic = TRUE. msg is the herald that is printed in the executive. If NIL, it defaults to a standard message. (use "" if you don't want anything printed). If startListening = TRUE, calls StartListening.
StartListening: PROC [exec: ExecHandle];
Makes exec start listening to user, nop if that has already been done once.
StartListening is a separate procedure in order to give the client a chance to do something with the exec before it exec goes into its normal read-eval-print loop. Not starting the executive at all also enables a client to use this execHandle, e.g. to pass it into DoIt to execute events, without having to worry about whether or not a user is typing to it.
GetExecHandle: PROCEDURE [id: ROPENIL, viewer: Viewer ← NIL, process: UNSAFE PROCESS ← NIL] RETURNS[exec: ExecHandle];
If id # NIL, finds exec with corresponding id (id is what appears before the : in the caption, e.g. A, B, etc.), otherwise returns NIL.
If viewer # NIL, finds exec with corresponding, otherwise returns NILviewer.
If process # NIL, finds exec running under corresponding process, otherwise returns NIL.
If process = NIL, find exec running under current process, otherwise returns GetDefaultExecHandle.
In other words, GetExecHandle[] is equivalent to {exec: ExecHandle ← GetExecHandle[process: Process.GetCurrent[]]; RETURN[IF exec # NIL THEN exec ELSE GetDefaultExecHandle]};
GetDefaultExecHandle: PROCEDURE RETURNS[exec: ExecHandle];
Registered Commands, Transformations, and Methods
Registration and implementation
RegisterCommand: PROC [name: ROPE, proc: CommandProc, briefDoc, doc: ROPENIL, clientData: REF ANYNIL];
registers a command and corresponding procedure. Subsequently, when the user types name params{cr}, proc will be called with the corresponding event and execHandle and the specified clientData.
briefDoc is a brief description of the command presented when user types ?
doc is more complete description supplied when user types name?. Note: RegisterCommand does NOT call the proc.
For discussion of how to write a CommandProc, see Implementing Registered Commands below.
RegisterTransformation: PROC [name: ROPE, proc: TransformProc, briefDoc, doc: ROPENIL, clientData: REF ANYNIL];
used to register a transformation, i.e. a command which operates by producing another command, e.g. history commands such as Redo, Use, @ command, etc. Aliases are also implemented this way.
RegisterMethod: PROC [name: ROPE, proc: MethodProc, doc: ROPENIL, clientData: REF ANYNIL];
used to register a method, i.e. a way of handling of classes of input, e.g. to make typing &.mesa open a viewer on the corresponding file. ? and ^X are both implemented via RegisterMethod. When the corresponding procedure is called, event.commandLine will be the entire line.
Invoking the executive from a program
For most applications, commandProcs, aliases, etc., will be invoked as a result of user typein. However, occasionally it is useful to be able to invoke some executive function from a program. The following procedure, DoIt, enables this.
DoIt: PROCEDURE [input: ROPE, exec: ExecHandle ← GetDefaultExecHandle[], partOf: HistoryEvent ← NIL];
"does" input. Input can correspond to a single event or a multiple event. If partOf is non-NIL, any events created will be nested under partOf. (For example, history commands and the implementation of command files use this argument.)
Note that only one process can be running in an exec at a time. Thus, if some other process calls DoIt with a particular exec handle, it will wait until the event that is currently being executed, if any, has completed. (If DoIt is called from within the same process, e.g. a CommandProc turns around and calls DoIt on some rope it has manufactured, this is ok. If however the commandProc forks a process which in turn calls DoIt, this latter call will wait until the event finishes. The intent is to avoid intermixing input/output from several operations in what is essentially a sequential facility.)
StuffIt: PROC [exec: ExecHandle, rope: ROPE, list: LIST OF REF ANYNIL];
Loads the executive's input buffer with rope. (list argument permits specifying other than characters, e.g. commands to change looks, etc.).
StuffIt is one way a commandProc can compute input to be taken as the next event, e.g. control-X expansion and the Perhaps-You-Mean feature both use this facility. (Note that this method automatically takes care of synchronization problems, since the input will not be executed until the current event(s) complete.) StuffIt is also useful for defining menu buttons (Menus.ClickProc) which are implemented in terms of a UserExec command, e.g. Eval, Compile, Redo, Set (break point) menu buttons all use StuffIt.
Implementing Registered Commands
General comments
The arguments to the commandProc are the corresponding event, from which the commandLine can be obtained (everything in the input following the commandProc up to and including the CR or ; that terminated the command), as well the commandLineStream, which is a rope input stream whose contents are the commandLine (provided for convenience). (Note that the input field of the event contains the entire command as it was input, i.e. it includes the name of the command, as well as any comments that might have appeared in the command.)
If the commandProc needs to perform output or obtain input, it obtains these streams via the procedure GetStreams, described below. If a lengthy operation is involved, it is good policy to check periodically to see whether the user has indicated a desire to abort. Use CheckForAbort to accomplish this. The spelling corrector can be conveniently accessed via the procedures GetTheOne and GetTheFile, also described below, and any other confirmation/interaction with the user can be done via AskUser, since this will correctly handle interactions with user type ahead, aborting, etc.
Here is an example of a CommandProc that names a desktop. Other examples may be found in UserExecOpsImpl.mesa and ViewerExecOpsImpl.mesa
NameDesktop: CommandProc = {
name: ROPE = IO.GetToken[event.commandLineStream, IO.IDProc];
IF NOT name.IsEmpty[name] THEN
{VirtualDesktops.SetName[name: name];
UserExec.GetStreams[exec].out.PutF["This virtual desktop has been named: %g\n", rope[name]];
RETURN[TRUE];
}
ELSE RETURN[FALSE];
};
GetStreams: PROC [exec: ExecHandle ← GetExecHandle[]] RETURNS [in, out: STREAM];
returns the in and out stream of the corresponding exec. This is the way a commandProc obtains input and output streams should it need them.
The in stream of any exec (except the default exec) is a Edited Viewers Stream (see ViewerIO.mesa) attached to the viewer for the corresponding exec. in is set up to echo to out, a Viewers Output Stream. If you have an application which requires reading from the typescript directly without echoing or buffering, i.e. getting access to each and every character as it is typed, call GetStreams to obtain in, and then read from in.backingStream, which is a Viewers Input Stream. Note: the input stream for the default executive is a stream for which EndOf is always TRUE, CharsAvail is always FALSE, and any attempt to obtain input, e.g. GetChar, will raise UserAborted, because the default executive does not listen to the keyboard.
If you call GetStreams when you are not running under a CommandProc, GetStreams will raise InvalidExecHandle in order to forestall the possibility of output from your procedure being intermixed with output from an event that may be executing in that executive. If you have such an application, i.e. one obtains its input/output streams via GetStreams and may be called when not under a commandProc...
then you must worry about synchronizing your use of these streams with events that may be running in the executive. To do this, you must acquire the streams via AcquireStreams and release them with ReleaseStreams when you are finished with the streams. You can call AcquireStreams more than once from the same process without causing a wait, but you must call ReleaseStreams as many times as you call AcquireStreams in order for the streams to be released.
AcquireStreams: PROC [exec: ExecHandle] RETURNS [in, out: STREAM];
ReleaseStreams: PROC [exec: ExecHandle];
Aborting: UserAbort, UserAborted, ErrorThisEvent
CheckForAbort: PROC [exec: ExecHandle, msg: ROPENIL]
= INLINE {IF exec # NIL AND UserAbort[exec] THEN UserAborted[exec, msg]};
UserAborted: PROC [exec: ExecHandle, msg: ROPENIL]
= INLINE {ERROR IO.UserAborted[exec, msg]};
UserAbort: PROC [exec: ExecHandle] RETURNS [abort: BOOLEAN];
returns TRUE if the user has requested an abort of the operation running in exec. The client program is responsible for effecting the abort, which is typically performed by raising the signal UserAborted.
SetUserAbort: PROC [exec: ExecHandle];
sets the user abort, i.e. provides a programmable way to abort (the operation currently running in) an exec.
ResetUserAbort: PROC [exec: ExecHandle];
turn the abort bit off.
Confirmation: AskUser, Confirm
AskUser provides for a standard way of interacting with the user to obtain one of a small, specified set of responses, such as Yes or No, or Yes, No, All, or Quit. The interaction can be through an execHandle, in which case the user can respond either by typing or via the corresponding menu buttons which will be posted, and the problem of type ahead is handled automatically, or through a viewer, in which case the user can respond via the menu, or, if neither an execHandle or viewer is available, confirm using just the mouse for the simple case of Yes/No only.
AskUser: PROC [msg: ROPE, timeout: INT ← -1, defaultKey: ATOMNIL, exec: ExecHandle ← NIL, viewer: Viewer ← NIL, keyList: LIST OF ATOMNIL] RETURNS[value: ATOM];
provides a standard way of asking a user to respond/confirm to a proposed operation using either the specified exec, or viewer. keyList = NIL => LIST[$Yes, $No].
If exec # NIL, the confirmation message is printed in the executive, and menu buttons for each of the keys posted in the menu for that exec. For example, Bringover calls Askuser with LIST[$Yes, $No, $All, $Quit]. If the user has not typed ahead, he can then respond using the keyboard by typing the first character (case does not matter) of one of the keys. Typing anything else, e.g. ?, will cause the user to be prompted with his options and allow him to respond once again. Clicking Stop will raise IO.UserAborted. If the user has typed ahead, the typeahead is retained, and the user can only respond using the menu buttons.
If exec = NIL but viewer # NIL, the procedure is the same except that the message is printed in the MessageWindow, and the user can only confirm using buttons.
If both exec = NIL and viewer = NIL, then if keyList = NIL, i.e. the client simply wants a yes no confirmation, the message is posted in the message window and MessageWindow.Confirm is used for confirmation, which then gets translated back into $Yes or $No. Otherwise, AskUser simply returns NIL.
timeout is number of milliseconds to wait before timeout. defaultKey is the value returned in case of a timeout. timeout = -1 means do not timeout.
Confirm: PROC [msg: ROPE, timeout: INT ← -1, defaultConfirm: BOOLFALSE, exec: ExecHandle, viewer: Viewer ← NIL] RETURNS[BOOLEAN]
= INLINE {RETURN[AskUser[msg: msg, timeout: timeout, defaultKey: IF defaultConfirm THEN $Yes ELSE $No, exec: exec, viewer: viewer] = $Yes]};
confirming/rejecting ahead
If the implementor wants the user to be able to respond ahead, e.g. reject, for operations involving a lengthy computation, SetupAskUser can be called as soon as the need for a response is detected, but before the actual operation has completed. SetupAskUser will post the buttons, and the implementor can check whether the user has responded ahead using GetUserResponse defined below. In this way, the implementor can decide to stop pursuing an operation which the user has already rejected. Note that if the implementor does call SetupAskUser, he is also responsible for calling FinishAskUser to take down the buttons).
For aesthetic reasons, the implementor may also want to call SetupAskUser in the case where a number of interactions are anticipated, e.g. a bringover. This will allow the confirmation menu to stay in place throughout the operation, rather than continually popping up and being erased.
SetupAskUser: PROC [viewer: Viewer, keyList: LIST OF ATOMNIL];
Posts the menu buttons for keyList.
FinishAskUser: PROC [viewer: Viewer, keyList: LIST OF ATOMNIL];
take down menu.
GetUserResponse: PROC [viewer: Viewer] RETURNS [hasResponded: BOOL, value: ATOM];
SetUserResponse: PROC [viewer: Viewer, value: ATOM];
ResetUserResponse: PRIVATE PROC [viewer: Viewer];
Interpreting expressions
Note that the client can always invoke the interpreter by calling the procedure DoIt described above. The procedures described below allow more direct access to the interpreter, while still permitting for any error correction and type coercion.
CreateExpr: PROCEDURE [rope: ROPE, dontCorrect: BOOLFALSE] RETURNS [Expression];
EvalExpr: PROCEDURE [expr: Expression, exec: ExecHandle ← NIL, viewer: ViewerClasses.Viewer ← NIL] RETURNS[value: TV, numRtns: INT];
Evaluates the rope and stores the result in the expression as well as returning it. If a correction was made, correctionMade will be set to TRUE and expr.rope will have been updated. Confirmation is handled via AskUser. May raise ParseFailed, EvaluationFailed, or IO.UserAborted.
Login and password
GetNameAndPassword: PROC RETURNS [name, password: ROPE];
SetNameAndPassword: PROC [name, password: ROPE];
Login: PROC [in, out: STREAM] RETURNS[name, password: ROPE];
prompts user for name and password. echoes password characters with *.
Invoking spelling corrector via execHandle or viewer: GetTheOne, GetMatchingList, GetTheFile, GetMatchingFileList
The procedures described below enable invoking the spelling corrector using an execHandle or viewer for feedback and confirmation. If client has an execHandle or viewer in hand, this is the right way to call the spelling corrector. The execHandle is used to obtain streams for output. If exec = NIL, output will go to the message window (see discussion ) The exec and viewer arguments are passed to AskUser if confirmation is required. Aborting can be performed by using the STOP button. The event argument should be supplied when called from a CommandProc and is used to suppress corrections for those cases where correction has been disabled for the entire event, e.g. when executing events from a command file.
GetTheOne: PROCEDURE [
unknown: ROPE,
spellingList: Spell.SpellingList ← NIL,
generator: Spell.SpellingGenerator ← NIL,
event: HistoryEvent,
exec: ExecHandle,
viewer: Viewer ← NIL,
filter: Spell.Filter ← NIL,
modes: Spell.Modes ← NIL
]
RETURNS [correct: ROPE];
GetMatchingList: PROCEDURE [
unknown: ROPE,
spellingList: Spell.SpellingList ← NIL,
generator: Spell.SpellingGenerator ← NIL,
event: HistoryEvent,
exec: ExecHandle,
viewer: Viewer ← NIL,
filter: Spell.Filter ← NIL,
modes: Spell.Modes ← NIL
]
RETURNS [matches: LIST OF ROPE];
GetTheFile: PROC [
file: ROPE,
defaultExt: ROPENIL,
event: HistoryEvent,
exec: ExecHandle,
viewer: Viewer ← NIL,
modes: Spell.Modes ← NIL
]
RETURNS [correct: ROPE];
First checks to see if file (or file.defaultExt) exists, and if so, returns immediately.
GetMatchingFileList: PROC [
file: ROPE,
defaultExt: ROPENIL,
event: HistoryEvent,
exec: ExecHandle,
viewer: Viewer ← NIL,
modes: Spell.Modes ← NIL
] RETURNS[matches: LIST OF ROPE];
sharing global resources: AcquireResource, ReleaseResource
AcquireResource and ReleaseResource together comprise a package used for sharing/locking a global resource between several processes. For example, this package is used to make sure that the compiler is not run simultaneously by more than one exec, by having the compiler call AcquireResource[$Compiler, "Compiler"] before attempting to run.
If one process) has already acquired (the) resource, an attempt to acquire it by another process will print a suitable message, and cause the process to wait for the resource to be freed, i.e. AcquireResource will not return until the resource is available. However, while waiting for the resource to be available, the user can abort by clicking STOP in the corresponding exec, and AcquireResource will call IO.UserAborted.
Where there is only one instance of a particular resource, resource is typically an atom, e.g. $Compiler, $Bringover, etc. However, resource can be an arbitrary REF ANY, e.g. a stream, viewer, execHandle etc. GetStreams, described above, calls AcquireResource with a stream as the resource. The decision about whether this resource is available or not is made by comparing resource with those that are currently not available, have been acquired but not released, using =.
owner is used to identify who currently owns the resource in the message that is printed when waiting. For example, it is not possible for two commands registered via the old Exec interface to run simultaneously, because there is only one global exec handle. In order to prevent two such commands from executing at the same time, AcquireResource is called with resource = $ExecDotW, and owner = name of the command, so that AcquireResource can print out who it is that owns the resource, e.g. BringOver, SModel, etc. If owner = NIL, no message is printed.
If AcquireResource is called on the same resource from the same process, it is effectively a NOP, i.e. the process does not wait. This enables an application not to have to keep track of whether or not it has already acquired the resource at some other level. However, it is necessary that each AcquireResource be matched by a corresponding ReleaseResource: the resource will not actually be freed until a ReleaseResource has been done for each AcquireResource.
AcquireResource: PROC [resource: REF ANY, owner: ROPE, exec: ExecHandle ← NIL] RETURNS[newlyAcquired: BOOL];
value is TRUE if this call just acquired resource, FALSE if resource was already acquired. If owner # NIL, and the resource is busy, a message is printed. If owner = NIL, no message.
ReleaseResource: PROC [resource: REF ANY, doWhenReleased: PROC[REF ANY] ← NIL] RETURNS[released: BOOL];
Value is true if this call released the resource, FALSE if there is yet an unmatched AcquireResource. If the resource is to be released, and doWhenReleased # NIL, then doWhenReleased is called just before the broadcast that tells everybody else to wake up and notice that the resource is now free.
miscellaneous
CheckForFile: PROC [file: ROPE] RETURNS [found: BOOLEAN];
returns TRUE if file is on the local disk
RopeSubst: PROC [old, new, base: ROPE, case: BOOLEANFALSE, allOccurrences: BOOLEANTRUE] RETURNS[value: ROPE];
if old is not found in base, then value = base.
if alloccurrences THEN substitute for each occurrence of old, otherwise only for first.
ProcessPutProp: PROC [process: UNSAFE PROCESS, prop: REF ANY, value: REF ANY];
ProcessGetProp: PROC [process: UNSAFE PROCESS, prop: REF ANY] RETURNS[value: REF ANY];
NameProcess: PROC [name: ROPE, process: UNSAFE PROCESS]
= INLINE {ProcessPutProp[process, $Name, name]};
END. -- NewUserExec
Converting from 3.5
The following are some suggestions about how to convert from 3.5.
If you import UserExecUtilities, all of these procedures are now in UserExec.
If you imported UserExecExtras, the procedures that you are using are probably in UserExec. If not, see me.
If you implement your own CommandProc, make the following changes:
exec.commandLine => event.commandLine.
exec.commandLineStream => event.commandLineStream.
exec.in => UserExec.GetStreams[exec].in
exec.out => UserExec.GetStreams[exec].out
If you have been using any other fields from exec, see me.
Change UserExec.GetExecHandle[] to UserExec.GetExecHandle[returnDefault: TRUE] if you always want an exec handle returned. If you are calling GetExecHandle from a context in which you expect to be running under an exec, then it is probably best to continue calling just GetExecHandle[].