procedures and data relating to the handling of registered commands in the user exec
Last Edited by: teitelman, May 3, 1983 8:35 pm
DIRECTORY
Atom USING [GetPName],
CIFS USING [GetFC, Open, read],
Commander USING [CommandProc, CommandObject, Enumerate, Lookup, Register, Handle],
ConvertUnsafe USING [AppendRope],
Exec USING [commandLine],
File USING [Capability, nullCapability],
FileIO USING [Open, OpenFailed],
Generator USING [Handle, Produce],
IO USING [AppendStreams, ChangeDeliverWhen, Close, CreateFilterCommentsStream, CR, DeliverWhenProc, EndOf, EveryThing, GetOutputStreamRope, LookupData, PeekChar, PutChar, PutF, PutRope, RemoveData, rope, ROPE, RIS, ROS, SetEcho, SetIndex, SP, StoreData, STREAM, BreakProc, CharProc, GetSequence, GetToken, GetRefAny, IDProc, SkipOver, WhiteSpace],
List USING [DotCons, Nconc1, Sort],
MessageWindow USING [Append],
ProcessProps USING [AddPropList],
CommandProcOps USING [PutProperty],
Rope USING [Cat, Concat, Equal, Fetch, Find, IsEmpty, Length, Replace, Substr],
RTFiles USING [GetMapList],
Spell USING [GeneratorFromEnumerator, SpellingGenerator, IsAPattern, Modes],
TTY USING [Handle],
SymTab USING [Create, Fetch, Store, Ref],
UnsafeStorage USING [GetSystemUZone],
UserExec USING [DoIt, AcquireResource, CommandProc, ExecHandle, GetMatchingFileList, GetStreams, GetTheOne, MethodProc, ReleaseResource, ErrorThisEvent, AcquireStreams, ReleaseStreams, TransformProc, HistoryEvent],
UserExecExtras USING [CorrectionDisabled],
UserExecPrivate USING [CommandRecord, commandString, EventFailed, ExecDeliverWhen, ExecPrivateRecord, GetExecFromStream, HistoryEventPrivateRecord, FileNotFound, methodList, MethodRecord, PrintCommand, RopeFromCMFile, RunBCDFile, StripComments, w, Zone, GetPrivateStuff],
UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Token],
VersionMapOps USING [SourceFileList, FindSource]
;
UserExecRegCmdsImpl: CEDAR PROGRAM
IMPORTS Atom, CIFS, Commander, CommandProcOps, ConvertUnsafe, Exec, FileIO, Generator, IO, List, MessageWindow, ProcessProps, Rope, RTFiles, Spell, SymTab, UnsafeStorage, UserExec, UserExecExtras, UserExecPrivate, UserProfile, VersionMapOps
EXPORTS UserExec, UserExecPrivate
= BEGIN OPEN IO;
connecting concrete and opaque types
ExecPrivateRecord:
PUBLIC
TYPE = UserExecPrivate.ExecPrivateRecord;
to access execDotW in CallRegisteredProc, continuation, dontPrompt, and deliverWhen in ReadQuietly, and deliverWhen in StuffBufferDeliverWhen, and currentEvent (to get dontCorrect) in LookupComand
HistoryEventPrivateRecord:
PUBLIC
TYPE = UserExecPrivate.HistoryEventPrivateRecord;
executing registered commands: CallRegisteredProc, SetUpCommandLine, ExpandCommandLine, DoesUserMean, StuffBuffer
CallRegisteredProc:
PUBLIC
PROC [command:
ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle]
RETURNS[handled:
BOOLEAN ←
TRUE] = {
commandLineStream: STREAM = event.commandLineStream;
registration: REF UserExecPrivate.CommandRecord ← GetRegistrationData[command];
commanderProc: Commander.CommandProc = Commander.Lookup[command].proc;
nameOfBCD: ROPE;
IF registration = NIL THEN RETURN[CallCommanderProc[command, event, exec]];
IF NOT Rope.Equal[registration.name, "←"] THEN SetUpCommandLine[event: event, exec: exec];
IF (nameOfBCD ← registration.nameOfBCD) #
NIL
AND commanderProc = undefined
THEN
-- check for commanderProc because the file may have been run and the command registered with commander directl, e.g. Walnut
{
doc: ROPE = registration.documentation;
error: ROPE;
notFound: BOOL ← FALSE;
error ← UserExecPrivate.RunBCDFile[fileName: nameOfBCD, out: UserExec.GetStreams[exec].out ! UserExecPrivate.FileNotFound => {
error ← Rope.Concat[nameOfBCD, " must be on your local disk to execute this command"];
notFound ← TRUE;
CONTINUE;
}
].error;
IF notFound
THEN
TRUSTED {
-- what errors to catch?
lst: VersionMapOps.SourceFileList;
cap: File.Capability ← File.nullCapability;
MessageWindow.Append["Loading Version Map...", TRUE];
lst ← VersionMapOps.FindSource[short: nameOfBCD, mapList: RTFiles.GetMapList[]];
IF lst #
NIL
THEN {
MessageWindow.Append[Rope.Cat["Loading ", lst.first.name, "..."]];
cap ← CIFS.GetFC[CIFS.Open[name: lst.first.name, mode: CIFS.read]];
};
IF cap # File.nullCapability THEN error ← UserExecPrivate.RunBCDFile[fileName: lst.first.name, fileCapability: cap, out: UserExec.GetStreams[exec].out].error;
};
IF error # NIL THEN UserExecPrivate.EventFailed[event: event, msg: error, offender: nameOfBCD];
registration ← GetRegistrationData[name: registration.name];
registration.nameOfBCD ← NIL;
IF registration.commandProc =
NIL
AND registration.transformProc =
NIL
AND registration.oldStyleProc =
NIL
AND Commander.Lookup[command].proc = undefined
THEN {
UserExec.GetStreams[exec].out.PutF["*n*m%g was run and did not register any commands*s\n", rope[nameOfBCD]];
RETURN[TRUE];
};
IF registration # NIL AND registration.documentation = NIL THEN registration.briefDocumentation ← registration.documentation ← doc;
RETURN[CallRegisteredProc[command, event, exec]];
};
IF registration.commandProc #
NIL
THEN
-- if there is a UserExec.commandProc, use it even though there is a commander CommandProc, because it may (probably) have more functionality
{
ok: BOOL;
msg: ROPE;
inner: Commander.CommandProc = {
[ok, msg] ← registration.commandProc[event, exec, registration.clientData];
};
PushPropsAndCallProc[inner, command, event, exec];
IF NOT ok THEN ERROR UserExec.ErrorThisEvent[event, msg]; -- msg should be stored in history, even on success.
}
ELSE
IF registration.oldStyleProc #
NIL
THEN
{
Done: PROC = {[] ← UserExec.ReleaseResource[$Exec]};
[] ← UserExec.AcquireResource[resource: $Exec, owner: registration.name, exec: exec];
TRUSTED {
ENABLE UNWIND => Done[];
private: REF ExecPrivateRecord = exec.privateStuff;
inner: Commander.CommandProc =
TRUSTED {
registration.oldStyleProc[]
};
UserExecPrivate.w ← private.execDotW;
UserExecPrivate.commandString.length ← 0;
IF Rope.Length[event.commandLine] > UserExecPrivate.commandString.maxlength
THEN
-- long command file. make string larger.
Exec.commandLine.s ← UserExecPrivate.commandString ← UnsafeStorage.GetSystemUZone[].NEW[StringBody[Rope.Length[event.commandLine]]];
ConvertUnsafe.AppendRope[to: UserExecPrivate.commandString, from: event.commandLine];
Exec.commandLine.i ← 0;
PushPropsAndCallProc[inner, command, event, exec];
Done[];
};
}
ELSE IF registration.transformProc #
NIL
THEN {
input: ROPE = registration.transformProc[event, exec, registration.clientData];
privateEvent: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff;
IF Rope.Equal[input, event.input] THEN ERROR UserExec.ErrorThisEvent[event: event, msg: "You are looping"];
privateEvent.showInput ← TRUE;
UserExec.DoIt[input, exec, event];
}
ELSE IF commanderProc # undefined THEN RETURN[CallCommanderProc[command, event, exec]] -- command had registrationData, but nothing to do. Occurs for commands obtained from catalogue which when run, register themselves with commander only, e.g. smodel
ELSE RETURN[FALSE];
RETURN[TRUE];
};
SetUpCommandLine:
PROC [event: UserExec.HistoryEvent, exec: UserExec.ExecHandle] = {
commandLine: ROPE ← event.commandLine;
pos: INT;
commandLine ← ExpandCommandLine[line: commandLine, event: event, exec: exec];
discard \\'s
WHILE (pos ← Rope.Find[s1: commandLine, s2: "\\"]) # -1
DO
commandLine ← Rope.Replace[base: commandLine, start: pos, len: 1];
ENDLOOP;
event.commandLine ← commandLine;
[] ← RIS[rope: commandLine, oldStream: event.commandLineStream]; -- for commands registered by UserExec.RegisterCommand
}; -- of SetUpCommandLine
ExpandCommandLine:
PUBLIC
PROC [line:
ROPE, modes: Spell.Modes ←
NIL, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle]
RETURNS [
ROPE] = {
inRopeStream: STREAM;
pos: INT;
replace any @file with corresponding rope
WHILE (pos ← Rope.Find[s1: line, s2: "@"]) # -1
AND (pos = 0
OR Rope.Fetch[line, pos - 1] # '\\)
DO
fileName: ROPE;
inRopeStream ← RIS[rope: line, oldStream: inRopeStream];
inRopeStream.SetIndex[pos];
fileName ← IO.GetToken[inRopeStream, IO.IDProc];
line ← Rope.Replace[base: line, start: pos, len: Rope.Length[fileName], with: UserExecPrivate.RopeFromCMFile[file: fileName, event: event, exec: exec].contents];
ENDLOOP;
line ← UserExecPrivate.StripComments[event, line];
comments in the input line are stripped in Doit. reason for this check is to handle lines that were manufactured, e.g. by CommandsFrom.
discard ^s when followed by CR
pos ← 0;
WHILE (pos ← Rope.Find[s1: line, s2: "^", pos1: pos]) # -1 DO
IF Rope.Fetch[line, pos + 1] = CR AND pos # 1 AND Rope.Fetch[line, pos - 1] = SP THEN line ← Rope.Replace[base: line, start: pos, len: 2];
pos ← pos + 1;
ENDLOOP;
IF Spell.IsAPattern[line]
THEN
{
r: ROPE;
asFile: BOOLEAN ← FALSE;
inRopeStream ← RIS[rope: line, oldStream: inRopeStream];
WHILE Rope.Length[r ← IO.GetToken[inRopeStream, IO.IDProc]] # 0
DO
IF Spell.IsAPattern[r]
THEN
{outRopeStream: STREAM = IO.ROS[];
FOR l:
LIST
OF
ROPE ←
-- IF ~asFile THEN LookupCommands[r, exec] ELSE-- UserExec.GetMatchingFileList[file: r, event: event, exec: exec, modes: modes], l.rest
UNTIL l =
NIL
DO
outRopeStream.PutRope[l.first];
IF l.rest # NIL THEN outRopeStream.PutChar[SP];
ENDLOOP;
line ← Rope.Replace[base: line, start: Rope.Find[line, r], len: Rope.Length[r], with: outRopeStream.GetOutputStreamRope[]];
};
asFile ← TRUE;
ENDLOOP;
};
RETURN[line];
}; -- of ExpandCommandLine
GetRestOfStream:
PUBLIC
PROC [in:
STREAM, includeLast:
BOOL ←
TRUE]
RETURNS[value:
ROPE] = {
value ← IO.GetSequence[in, IO.EveryThing];
IF NOT includeLast THEN value ← Rope.Substr[base: value, len: Rope.Length[value] - 1];
};
DoesUserMean:
PUBLIC
PROC [rope:
ROPE, exec: UserExec.ExecHandle, intro:
ROPE ←
NIL] = {
out: STREAM = UserExec.GetStreams[exec: exec].out;
out.PutRope[IF intro = NIL THEN "perhaps you mean:\n" ELSE intro];
out.PutRope[rope];
ReadQuietly[rope, exec];
};
used to make what appears on the screen and input buffer agree. loads buffer with rope, turns off echoing, delivering, and allows characters to be read until no more left, at which point turns everything back on.
ReadQuietly:
PUBLIC
PROC [rope:
ROPE, exec: UserExec.ExecHandle] = {
in: STREAM;
private: REF ExecPrivateRecord = UserExecPrivate.GetPrivateStuff[exec];
private.continuation ← TRUE; -- tells exec not to increment event number and to reuse event.
private.dontPrompt ← TRUE;
IF
NOT rope.IsEmpty[]
THEN {
in ← UserExec.AcquireStreams[exec: exec].in;
[] ← in.SetEcho[NIL]; -- turn off echoing.
in.StoreData[key: $Count, data: NEW[INT ← rope.Length[]]];
[] ← IO.ChangeDeliverWhen[in, StuffBufferDeliverWhen]; -- turns echoing back on as soon as all characters consumed.
AppendStreams[in, RIS[rope]];
};
};
turns echoing back on when all characters that were stuffed have been read.
StuffBufferDeliverWhen: IO.DeliverWhenProc --
PROC[char:
CHAR, stream:
STREAM]
RETURNS[
BOOL] -- =
{
exec: UserExec.ExecHandle = UserExecPrivate.GetExecFromStream[stream];
count: REF INT;
private: REF UserExecPrivate.ExecPrivateRecord = UserExecPrivate.GetPrivateStuff[exec];
in, out: STREAM;
[in, out] ← UserExec.GetStreams[exec: exec];
count ← NARROW[in.LookupData[$Count]];
count^ ← count^ - 1;
IF count^ <= 0
THEN {
[] ← IO.ChangeDeliverWhen[in, UserExecPrivate.ExecDeliverWhen];
[] ← in.SetEcho[out];
in.RemoveData[key: $Count];
UserExec.ReleaseStreams[exec: exec];
};
RETURN[FALSE];
};
Registration: RegisterCommand, RegisterTransformation, RegisterMethod, Register
RegisterCommand:
PUBLIC
PROC [name:
ROPE, proc: UserExec.CommandProc, briefDoc, doc:
ROPE ←
NIL, clientData:
REF
ANY ←
NIL] = {
Register[name: name, proc: proc, briefDoc: briefDoc, doc: doc, clientData: clientData];
};
RegisterTransformation:
PUBLIC PROC [name:
ROPE, proc: UserExec.TransformProc, briefDoc, doc:
ROPE ←
NIL, clientData:
REF
ANY ←
NIL]= {
Register[name: name, transformProc: proc, briefDoc: briefDoc, doc: doc, clientData: clientData];
};
RegisterMethod:
PUBLIC
PROC [name:
ROPE, proc: UserExec.MethodProc, doc:
ROPE ←
NIL, clientData:
REF
ANY ←
NIL] = {
r: REF UserExecPrivate.MethodRecord ← NIL;
IF name #
NIL
THEN
FOR lst:
LIST
OF
REF UserExecPrivate.MethodRecord ← UserExecPrivate.methodList, lst.rest
UNTIL lst =
NIL
DO
IF Rope.Equal[lst.first.name, name] THEN {r ← lst.first; EXIT}
ENDLOOP;
IF r =
NIL
THEN
TRUSTED { -- Loophole for polymorphism
r ← UserExecPrivate.Zone.NEW[UserExecPrivate.MethodRecord ← []];
UserExecPrivate.methodList ← LOOPHOLE[List.Nconc1[LOOPHOLE[UserExecPrivate.methodList, LIST OF REF ANY], r]];
}; -- important that methods be executed in order defined
r^ ← [proc: proc, name: name, doc: doc, clientData: clientData];
};
Register:
PUBLIC
PROC [
name: ROPE ← NIL,
proc: UserExec.CommandProc ← NIL,
oldStyleProc: UNSAFE PROCEDURE ← NIL,
transformProc: UserExec.TransformProc ← NIL,
briefDoc, doc: ROPE ← NIL,
nameOfBCD: ROPE ← NIL,
clientData: REF ANY ← NIL,
fromCatalogue: BOOL ← FALSE
] = {
compare: List.CompareProc -- [ref1: REF ANY, ref2: REF ANY] RETURNS [Environment.Comparison] -- = {
RETURN[Rope.Compare[
s1: NARROW[ref1, REF UserExecPrivate.CommandRecord].name,
s2: NARROW[ref2, REF UserExecPrivate.CommandRecord].name,
case: FALSE]];
};
i: INT ← Rope.Find[s1: name, s2: "."];
r: REF UserExecPrivate.CommandRecord;
commanderProc: Commander.CommandProc;
IF i # -1 THEN name ← Rope.Substr[name, 0, i]; -- old style registration was to register mumble.~
commanderProc ← Commander.Lookup[key: name].proc;
Commander.Register[key: name, proc: IF commanderProc = NIL THEN undefined ELSE commanderProc, doc: briefDoc]; -- if happen to be registering a command with same name as one already registered in commander, leave the commander one alone so when run from command tool, it will continue to work the same as before
r ← GetRegistrationData[name];
IF r =
NIL
THEN {
r ← UserExecPrivate.Zone.NEW[UserExecPrivate.CommandRecord ← []];
RegisterData[name, r];
}
ELSE IF (r.commandProc #
NIL
OR r.oldStyleProc #
NIL)
AND nameOfBCD #
NIL
THEN {
-- command already registered with procedure is being reregistered indirectly with nameOfBCD. In this case, user should have to explicitly run the bcd.
IF doc # NIL THEN r.documentation ← doc;
RETURN;
};
r^ ← [name: name, commandProc: proc, transformProc: transformProc, briefDocumentation: IF briefDoc = NIL THEN doc ELSE briefDoc, documentation: IF doc = NIL THEN briefDoc ELSE doc, oldStyleProc: oldStyleProc, nameOfBCD: nameOfBCD, clientData: clientData, fromCatalogue: fromCatalogue];
{
SortCommands: ENTRY PROCEDURE = TRUSTED { -- Loophole for polymorphism
ENABLE UNWIND => NULL;
UserExecPrivate.commandList ← LOOPHOLE[List.Sort[LOOPHOLE[UserExecPrivate.commandList], compare]]
};
}; -- Register
registrationTable: SymTab.Ref ← SymTab.Create[30, FALSE];
GetRegistrationData:
PUBLIC PROC [name:
ROPE]
RETURNS[
REF UserExecPrivate.CommandRecord] = {
RETURN[NARROW[SymTab.Fetch[registrationTable, name].val, REF UserExecPrivate.CommandRecord]];
};
RegisterData:
PUBLIC
PROC [command:
ROPE, val:
REF
ANY] = {
[] ← SymTab.Store[x: registrationTable, key: command, val: val];
};
Lookup
LookupCommand:
PUBLIC PROC [name:
ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle ←
NIL]
RETURNS[fullName:
ROPE] = {
command: ROPE = name;
out: STREAM;
matches: LIST OF ROPE;
matches ← LookupCommands[name, event, exec];
IF matches #
NIL
THEN
TRUSTED { -- LOOPHOLE for polymorphism
ENABLE UNWIND => IF out # NIL THEN UserExec.ReleaseStreams[exec];
IF matches.rest = NIL THEN RETURN[matches.first] ;
out ← UserExec.AcquireStreams[exec].out;
out.PutF["*n%g is ambiguous. It matches with:\n", rope[name]];
FOR l:
LIST
OF
ROPE ←
LOOPHOLE[List.Sort[
LOOPHOLE[matches]]], l.rest
UNTIL l =
NIL
DO
[] ← UserExecPrivate.PrintCommand[name: l.first, event: event, exec: exec];
ENDLOOP;
UserExec.ReleaseStreams[exec];
RETURN[NIL];
};
};
LookupCommands:
PROC [command:
ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle ←
NIL]
RETURNS[val:
LIST
OF
ROPE ←
NIL] = {
r: ROPE;
search:
PROC[name:
ROPE, proc: Commander.CommandProc, doc:
ROPE]
RETURNS[stop:
BOOL] = {
IF Rope.Equal[s1: command, s2: name, case:
FALSE]
THEN {
val ← LIST[name];
RETURN[TRUE];
}
ELSE IF Rope.Find[s1: name, s2: command, case: FALSE] = 0 THEN val ← CONS[name, val];
RETURN[FALSE];
};
Correct:
PROC = {
ENABLE UNWIND => NULL;
r ← UserExec.GetTheOne[unknown: command, generator: commandGenerator, event: event, exec: exec];
};
IF Commander.Lookup[command].proc # NIL THEN RETURN[LIST[command]]
ELSE IF exec = NIL OR UserExecExtras.CorrectionDisabled[event: event] THEN RETURN[NIL];
[] ← Commander.Enumerate[search];
IF val # NIL THEN RETURN;
Correct[];
RETURN[IF r # NIL THEN LIST[r] ELSE NIL];
};
Dealing With Commander
CallCommanderProc:
PROC[command:
ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle]
RETURNS[handled:
BOOL] = {
commandProc: Commander.CommandProc = Commander.Lookup[command].proc;
IF commandProc = NIL THEN RETURN[FALSE];
PushPropsAndCallProc[commandProc, command, event, exec];
RETURN[TRUE];
};
PushPropsAndCallProc:
PROC [proc: Commander.CommandProc, command:
ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle] = {
in, out: IO.STREAM;
commanderHandle: Commander.Handle;
inner:
PROCEDURE = {
proc[commanderHandle];
};
[in, out] ← UserExec.GetStreams[exec];
commanderHandle ←
NEW[Commander.CommandObject ← [
in: in, out: out, err: out,
commandLine: event.commandLine,
command: command,
propertyList: NIL
]];
CommandProcOps.PutProperty[handle: commanderHandle, key: $UserExec, val: exec, thisEventOnly: TRUE];
CommandProcOps.PutProperty[handle: commanderHandle, key: $HistoryEvent, val: event, thisEventOnly: TRUE];
ProcessProps.AddPropList[
LIST[List.DotCons[$CommanderHandle, commanderHandle]],
inner];
};
undefined: Commander.CommandProc = {
cmd.out.PutRope["This command can only be executed in a Work Area."];
};
LookupEnumerate:
PROC [self: Generator.Handle] = {
proc:
PROC[name:
ROPE, proc: Commander.CommandProc, doc:
ROPE]
RETURNS[stop:
BOOL] = {
RETURN[Generator.Produce[self, name]]; -- somebody terminated the generator.
};
[] ← Commander.Enumerate[proc];
};
commandGenerator: PUBLIC Spell.SpellingGenerator ← Spell.GeneratorFromEnumerator[enumerator: LookupEnumerate, clientData: NIL];
Initialization
GetRegisteredCommands: UserProfile.ProfileChangedProc = {
GetCommands:
PROC [name:
ROPE, fromCatalogue:
BOOL] = {
GetRope:
PROC
RETURNS [
ROPE] = {
whiteSpace: IO.BreakProc = {
RETURN[IF char = CR THEN break ELSE IO.WhiteSpace[char]]
};
ref: REF ANY ← IO.GetRefAny[source ! ANY => GOTO Exit];
IF ref = NIL THEN RETURN[NIL];
IO.SkipOver[source, whiteSpace]; -- skips spaces, tabs, etc., but not CR.
WITH ref
SELECT
FROM
a: ATOM => RETURN[Atom.GetPName[a]];
r: ROPE => RETURN[r];
ENDCASE => ERROR; -- ERROR TO ERROR LOG.
EXITS
Exit => RETURN[NIL]
};
source: STREAM ← NIL;
IF name #
NIL
THEN {
IF Rope.Find[name, "\n"] = -1
THEN
-- r is name of file
source ← FileIO.Open[fileName: name, accessOptions: read, createOptions: oldOnly !
FileIO.OpenFailed => IF why = fileNotFound THEN CONTINUE]
ELSE source ← IO.RIS[name];
};
IF source #
NIL
THEN {
source ← IO.CreateFilterCommentsStream[source];
WHILE
NOT source.EndOf[]
DO
name, nameOfBCD, documentation: ROPE ← NIL;
name ← GetRope[];
IF name = NIL THEN EXIT;
IF source.PeekChar[] # CR THEN documentation ← GetRope[];
IF source.PeekChar[] # CR THEN nameOfBCD ← GetRope[];
IF nameOfBCD = NIL THEN nameOfBCD ← name;
IF Rope.Find[s1: nameOfBCD, s2: "."] = -1
THEN nameOfBCD ← Rope.Concat[nameOfBCD, ".bcd"];
Register[name: name, briefDoc: documentation, doc: documentation, nameOfBCD: nameOfBCD, fromCatalogue: fromCatalogue];
ENDLOOP;
IO.Close[source];
};
};
GetCommands["RegisteredCommands.Catalogue", TRUE];
GetCommands[UserProfile.Token["RegisteredCommands"], FALSE];
};
UserProfile.CallWhenProfileChanges[GetRegisteredCommands];
END. -- of UserExecRegCmdsImpl
Commander NOtes:
Event argument to GetTheOne etc for supporessing correction, could be done via property list mechanism.
When I get around to it, can deimplement UserExecAcquireResource.
Larry and I spent an interesting hour discussing the technical aspects of how we could have the best of both worlds, namely that clients could register themselves with the Commander and would be able to be invoked in leaner meaner cedar, but magically, when running in the full Cedar world, would behave as they now do. I then went off and spent several hours examining in detail the difference between the Commander interface and UserExec interface with an eye to implementing the latter in terms of the former, and trying to identify what functionality, if any would be lost. This process has led me to a clear(er) espousal of the differences (actually fairly small) between Commander and UserExec, and to a specific proposal consisting of two small changes to the Commander interface which would accomplish all of the desired goals. I submit both espousal and proposal for your consideration.
A Commander CommandProc traffics in an object called a Handle, which basically consists of input and output streams, the commandLine and commandName, and a catch all propertyList. UserExec CommandProcs are more complicated. They take three arguments, an event and a handle, and a clientData field. Basically, the event corresponds to, and contains the commandLine/command information, and the handle contains the stream information (which must be accessed via a procedure rather than directly - more on this later). The clientData field contains information particular to the command so that the same commandProc can be used to implement several different commands, as is the case with the expansion of aliases, treatment of commands registered in catalogue, etc. This sort of technique is used in other interfaces such as Menus, and was originally championed by Russ.
UserExec CommandProcs also return two values, one which says that everything was ok, and the other a rope. In most cases these are TRUE and NIL. One could imagine an implementation in which for the exceptional case, the client called a procedure which recorded the relevant information in the event, or raised a signal, etc. In other words, it would be possible to eliminate this difference and have a UserExec.CommandProc not return a value and still retain all of the current functionality.
So we are left with two issues: why does the UserExec.CommandProc need two pieces of information and the Commander only one, and why are these two pieces of information so much more complicated than the Commander.Handle, which consists of only six fields? Let's consider the latter question first, since it really is a red herring.
The UserExec ExecHandle is actually pretty simple. It just consists of a viewer and a privateData field. The event also has a private data field, but it in addition exposes some other information which I thought might be useful to some clients, such as an expression field which helps when dealing with interpreter, e.g. you can see if any corrections were made, a dontCorrect field, so that clients that call the spelling corrector etc. don't have to worry about those situations where they shouldn't, e.g. the run command does not attempt correction when it is executing an event from a command file, whereas it does when coming from the keyboard, etc. I could also have provided this information via procedures for those exceptional clients that needed it. We all know that, in general, procedural interfaces allow the implementor more freedom to change his representation without affecting his clients, but make it slightly more cumbersome for the clients to access the relevant information.
The main reason that the UserExec type structure looks so complicated and the Commander so simple is that the behind the scenes implementation information in the Commander is stored on the property list of the handle, rather than in a declared field in the record. In the same way as the private data field in the UserExec represents an attempt to provide for more interface stability by using an opaque type in order not to commit to a particular representation, the Commander goes one step further by removing all mention of this information from the interface. This gives maximal freedom to the implementor while running the risk of postponing certain errors to runtime that would normally be caught by the compiler. Had I used the same approach for the UserExec, the data structure for an event and a handle would look pretty much the same as it does for Commander.
So the main difference is the division of the information needed by the commandProc into two arguments, the event and the handle, and the fact that the streams have to be obtained via a procedure (both changes made for 4.0). What does this buy?
The main area I was attacking was to make it possible for a client to invoke a command proc when the client didn't have an execHandle in hand (e.g. from Walnut, Russ's compiletool, or some other tool based application). I did not want to force the client to create an execHandle, complete with historylist, viewer, symbol table, etc., because in most of these applications, there would be no output/input unless something went wrong. Another related issue was that a command proc running under a particular execHandle ought to be able to FORK its execution, which might not complete until after several more events were executed under the same handle. The latter was not possible previously because all event-related information, such as the commandLine, was stored in the UserExec Handle, and got overwritten with each event. (Interestingly enough, this shortfall was first pointed out to me by Larry Stewart.) Given the possibility of events executing asynchronously and simultaneously under the same handle, I wanted some method of synchronizing, i.e. locking, the input/output streams. The procedural access to the streams provides a way of making sure these two events don't intermix their input/output by assigning the streams to one and only one event. Furthermore, I only assign those streams when they are needed so that if an event does not require input/output it can execute "silently" at the same time as another event. However, Larry pointed out that one could accomplish the same thing with a special kind of stream which did acquired the underlying streams when they were first needed. I wish I had thought of that earlier, and will use this idea in accomodating to the Commander interface which requires that the streams be available through the handle directly.
So, the bottom line is, there are very few unresolved differences between the two ways of doing business. In fact, we can get the best of both worlds, as follows (each of these changes are independent and obtains the indicated benefit. They can be accepted or rejected independently.):
(1)
Change Commander.Register by adding a clientData field argument which is passed to the CommandProc.
This change enables the UserExec to share the same registry with the Commander, i.e. UserExec.RegisterCommand will no longer have to exist, and the UserExec will not have to maintain its own internal data structures for command registry, yet will still be able to support the registeredcommands catalogue, aliases, etc.
(2)
Change the type of CommandProc by adding an event argument, where event is defined trivially as a REF RECORD[commandLine: Rope.ROPE, command: Rope.ROPE, propertyList: List.AList],
i.e. split the information currently in the CommandObject into two pieces, one of which contains information global to the handle, and the second which contains the information that is particular to this event. This will allow UserExec.DoIt to continue working as it now does, and will even allow operations executed in the commandTool in leaner meaner cedar to FORK if they so desire. (I don't know if the commander currently has any event specific information other than the commandLine, e.g. the start time, number words allocated etc., but I could easily imagine a client wanting to store such information on a property list, and not want it to be overwritten by the next event.)
What I could do with this is to store the UserExec Handle on the commander handle's property list, and similarly store the HistoryEvent on the Commander Events property list. I would provide procedural interfaces in the UserExec for obtaining this information for those clients who need to invoke operations available only through the UserExec interface, such as DoIt, or GetTheFile, etc, and to allow me to register all of my commandprocs as CommanderProcs. All would be wonderful!
One more minor change is necessary to AskUser due to its special nature. The current version of AskUser provided by UserExec correctly handles type ahead in the presence of an AskUser interaction by posting a menu and ignoring the keyboard. In the new scheme of things, AskUser is being moved to IO because bringover/smodel use it, and they are in the kernel, whereas the UserExec is not. Therefore, a simpler version of AskUser was implemented which takes two streams as arguments and does all of its interactions with the keyboard. How can we arrange things so that when called from a WorkArea in a full Cedar, AskUser, i.e. smodel, bringover, etc., will automatically behave as they do now?
(3) Instead of taking two streams,
change IOMisc.AskUser to take a Commander.Handle (actually a REF ANY which is NARROWED to a Commander.Handle in order to avoid interface circularities), which contains these streams.
Inside of this AskUser, we look on the property list of the commander handle for the property $AskUser. If found, its value is (NARROWED to) a procedure of the same type as AskUser and called. Otherwise, perform the simple minded AskUser which does not handle typeahead, menus, etc. In the full Cedar system, the UserExec will set up this property so that when invoked in a WorkArea, the fancy AskUser is called, on the same arguments, and it then obtains the UserExec Handle from the same property list. I have already implemented this and it works fine.
I understand that Paul has already committed, i.e. compiled some of his code to use depend on Commander and IOMisc.AskUser. I would be willing to remake those components with the new interface if it would make his response more positive. I think that there are a lot of benefits to be obtained with the above proposal.
warren
Edited on March 24, 1983 11:49 am, by Teitelman
any error coming out of running registered command, included aborted, was causing it to attempt to load version maps.
changes to: CallRegisteredProc, CallRegisteredProc
Edited on March 25, 1983 1:43 pm, by Teitelman
changes to: GetCommands (local of GetRegisteredCommands) added .bcd to name obtained from registered commands catalogue when no extension specified., UserProfile, END
Edited on April 7, 1983 3:22 pm, by Teitelman
changes to: RegisterCommanderProc, CommanderProcRecord, CallCommanderProc, END, DIRECTORY, IMPORTS, CallRegisteredProc, LookupGenerate, CallRegisteredProc, IsACommanderProc (local of CallRegisteredProc), RegisterCommanderStuff, register (local of RegisterCommanderStuff), RegisterCommanderStuff, UserProfile, DIRECTORY, IMPORTS, IsACommanderProc (local of CallRegisteredProc), CallRegisteredProc, DIRECTORY, CallRegisteredProc, RegisterCommanderStuff, register (local of RegisterCommanderStuff), EndOps, UserProfile, DIRECTORY, IMPORTS, UserProfile, register (local of RegisterCommanderStuff), register (local of RegisterCommanderStuff
Edited on April 19, 1983 2:57 pm, by Teitelman
changes to: CallRegisteredProc, CallCommanderProc, registrationTable, GetRegistrationData, Register, proc (local of LookupEnumerate), Register, LookupCommand, DIRECTORY, LookupCommand, DIRECTORY, Register, GetRegistrationData, RegisterData, DIRECTORY, IMPORTS, CallCommanderPro
Edited on April 21, 1983 12:47 am, by Teitelman
changes to: DIRECTORY, IMPORTS, CallCommanderProc, CallRegisteredProc, inner (local of CallCommanderProc), IMPORTS, CallCommanderProc, inner (local of CallRegisteredProc), CallRegisteredProc, CallCommanderProc, inner (local of CallRegisteredProc), CallRegisteredProc, inner (local of CallRegisteredProc), inner (local of PushPropsAndCallProc), PushPropsAndCallProc, inner (local of CallCommanderProc), CallCommanderProc
Edited on May 3, 1983 8:35 pm, by Teitelman
changes to: CallRegisteredProc