TIPMatcher.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Doug Wyatt, April 14, 1985 10:22:37 pm PST
Russ Atkinson (RRA) October 21, 1985 10:09:08 pm PDT
DIRECTORY
Ascii USING [BS, CR, DEL, ESC, LF, SP, TAB],
ClassIncreek USING [Acceptance, ActionBody, ActionKind, CopyIncreek, GetAction, GetPositionFrom, GetTime, Increek, NewStdIncreek, SetAtLatest, SetMouseGrain, ViewPosition],
Interminal USING [DownUp, KeyName, KeyState, MousePosition],
Process USING [Abort, GetCurrent],
RefTab USING [Create, Fetch, Ref, Store],
TIPPrivate USING [stdChar, stdCoords, stdTime, TIPButtonProc, TIPClient, TIPClientRec, TIPNotifyProc, TIPParseInfo, TIPParseInfoRec],
TIPTables USING [TIPChoice, TIPChoiceSeries, TIPKeyState, TIPTableImplRep],
TIPUser USING [TIPPredicate, TIPScreenCoords, TIPScreenCoordsRec, TIPTable, TIPTableRep];
TIPMatcher: CEDAR PROGRAM
IMPORTS ClassIncreek, Process, RefTab, TIPPrivate
EXPORTS TIPUser, TIPPrivate
= { OPEN TIPUser, TIPTables, TIPPrivate;
TIPTableImpl: TYPE ~ REF TIPTableImplRep;
TIPTableImplRep: PUBLIC TYPE ~ TIPTables.TIPTableImplRep;
mouseGrainCreek: ClassIncreek.Increek ← NIL; -- only for setting recording grain
transparentTIPTable: TIPTable ~ MakeTransparentTIPTable[];
MakeTransparentTIPTable: PROC RETURNS [TIPTable] = {
impl: TIPTableImpl ~ NEW[TIPTableImplRep ← [variants: transparent[]]];
RETURN[NEW[TIPTableRep ← [impl: impl]]];
};
TransparentTIPTable: PUBLIC PROC RETURNS [table: TIPTable] = {
RETURN[transparentTIPTable];
};
DiscardTypeAhead: PUBLIC SAFE PROC [user: TIPClient] = TRUSTED {
discard any mouse/keyboard events not yet processed
ClassIncreek.SetAtLatest[user.parseInfo.inCreek];
};
ResetTIPContext: PUBLIC PROC [user: TIPClient, table: TIPTable, notify: TIPNotifyProc, interrupt: BOOLFALSE] = TRUSTED {
user.parseInfo.tableHead ← table;
user.notifyProc ← notify;
ClassIncreek.SetMouseGrain[mouseGrainCreek, IF table=NIL THEN 50 ELSE table.mouseTicks, 1];
IF interrupt THEN Process.Abort[user.matcher];
};
InterruptTIP: PUBLIC UNSAFE PROC [self: TIPClient] = UNCHECKED {Process.Abort[self.matcher]};
Forces TIP interpreter to top level state.
Experts/hackers only, please.
MatchProcess: PUBLIC PROC [user: TIPClient] = TRUSTED {
creekAction: ClassIncreek.ActionBody;
results: LIST OF REF ANY;
keep two copies of screen coords so that client can overwrite passed copy
privateTSC: TIPScreenCoords ← NEW[TIPScreenCoordsRec ← [0, 0, FALSE]];
userTSC: TIPScreenCoords ← NEW[TIPScreenCoordsRec];
UNTIL user.matcher=NIL DO -- until TIP client instance is flushed...
ENABLE ABORTED => LOOP;
Aborted is expected to be caught in these two situations:
1) when DestroyClient is called to flush the tip process (inCreek=NIL)
2) InterruptTIP is called to change the table and flush pending state
someday should also catch ClassIncreek.IncreekError => flush type-ahead
in case ring buffer wraps
save away our state for the buttonProc below
mouse moves also depend on this!
ClassIncreek.CopyIncreek[user.parseInfo.localCreek, user.parseInfo.inCreek];
get a top-level action trigger
creekAction ← ClassIncreek.GetAction[self: user.parseInfo.inCreek,
waitMode: forever, acceptance: clicksAndMotion];
The following special test and dispatch is for Cedar window management.
The parseInfo.localCreek contains the Inscript state at the start of the
mouse event and is passed to the buttonProc. The buttonProc is required to
adjust the passed Increek to reflect the event that gets parsed. If no
event was successfully parsed, the buttonProc should return FALSE.
IF user.buttonProc#NIL THEN WITH action: creekAction SELECT FROM
mousePosition, deltaMouse => {
p: Interminal.MousePosition ~ ClassIncreek.GetPositionFrom[user.parseInfo.inCreek].mousePosition;
userTSC^ ← privateTSC^ ← [mouseX: p.mouseX, mouseY: p.mouseY, color: p.color];
IF user.buttonProc[userTSC, motion, user.parseInfo.localCreek] THEN {
[] ← ClassIncreek.CopyIncreek[user.parseInfo.inCreek,
user.parseInfo.localCreek];
LOOP;
};
};
keyUp => SELECT action.value FROM Red, Yellow, Blue => {
userTSC^ ← privateTSC^;
IF user.buttonProc[userTSC, buttonUp, user.parseInfo.localCreek] THEN {
[] ← ClassIncreek.CopyIncreek[user.parseInfo.inCreek,
user.parseInfo.localCreek];
LOOP;
};
}; ENDCASE;
keyDown => SELECT action.value FROM Red, Yellow, Blue => {
userTSC^ ← privateTSC^;
IF user.buttonProc[userTSC, buttonDown, user.parseInfo.localCreek] THEN {
[] ← ClassIncreek.CopyIncreek[user.parseInfo.inCreek,
user.parseInfo.localCreek];
LOOP;
};
}; ENDCASE;
ENDCASE;
IF user.parseInfo.tableHead#NIL THEN WITH action: creekAction SELECT FROM
mousePosition, deltaMouse, keyUp, keyDown =>
results ← MatchEvent[user.parseInfo, creekAction];
ENDCASE => ERROR; -- unexpected ActionKind
IF results#NIL THEN user.notifyProc[results];
ENDLOOP;
};
ParseOneEvent: PUBLIC SAFE PROC [parseInfo: TIPParseInfo] RETURNS [result: LIST OF REF ANY] = TRUSTED {
RETURN[MatchEvent[parseInfo, ClassIncreek.GetAction[self: parseInfo.inCreek, waitMode: forever, acceptance: clicksAndMotion]]];
};
the top level parser goes through the list of TIPTables, picks up the relevant ones (the ones not to be ignored), and parses the appropriate entry:
MatchEvent: PROC [parseInfo: TIPParseInfo, creekAction: ClassIncreek.ActionBody] RETURNS [result: LIST OF REF ANYNIL] = TRUSTED {
increek: ClassIncreek.Increek;
increekAction: ClassIncreek.ActionBody;
firstAccess,
copied: BOOL;
advanced: BOOL;
tells whether the increek on top of the stack has been changed at all
used to eliminate unnecessary copying of increeks
stackPointer: CARDINAL;
ClearIncreekStack: PROC[] = TRUSTED INLINE {
stackPointer ← 0;
};
PushIncreek: PROC[] = TRUSTED INLINE {
ClassIncreek.CopyIncreek[parseInfo.creekStack[stackPointer], increek];
stackPointer ← stackPointer + 1;
advanced ← FALSE;
};
CopyTopIncreek: PROC = TRUSTED INLINE {
IF advanced THEN {
ClassIncreek.CopyIncreek[increek, parseInfo.creekStack[stackPointer]];
advanced ← FALSE;
};
};
PopIncreek: PROC = TRUSTED INLINE {
stackPointer ← stackPointer - 1;
};
unless a second access to the increek actions, MatchEvent deals with parseInfo.inCreek
later, parseInfo.localCreek is the increek which to be considered
PushIncreek and CopyTopIncreek use parseInfo.localCreek as implicit parameter,
assuming that increek, the pointer to the 'interesting' increek, equals localCreek
GetIncreekAction: PROC [acceptance: ClassIncreek.Acceptance ← clicks] = TRUSTED INLINE {
IF firstAccess
THEN {
firstAccess ← FALSE;
increek ← parseInfo.inCreek;
increekAction ← creekAction; -- the parameter of MatchEvent
copied ← FALSE;
}
ELSE {
IF ~copied THEN {
copied ← TRUE;
ClassIncreek.CopyIncreek[parseInfo.localCreek, parseInfo.inCreek];
increek ← parseInfo.localCreek;
};
increekAction ← ClassIncreek.GetAction[self: increek,
waitMode: forever,
acceptance: acceptance];
advanced ← TRUE;
};
};
MatchChoice: PROC [choice: TIPChoice] RETURNS [result: LIST OF REF ANYNIL] = TRUSTED {
valid: BOOLTRUE; -- go ahead, you're about to match an event
FOR terms: TIPChoice ← choice, terms.rest UNTIL ~valid OR terms=NIL DO
WITH term: terms.first SELECT FROM
keyTrigger => {
GetIncreekAction[clicks];
valid ← WITH ca: increekAction SELECT FROM
keyDown => (term.keyState.key=ca.value AND
term.keyState.state=down),
keyUp => (term.keyState.key=ca.value AND
term.keyState.state=up),
ENDCASE => FALSE; -- suprise action from Increek
};
mouseTrigger => {
GetIncreekAction[clicksAndMotion];
valid ← WITH increekAction SELECT FROM
mousePosition => TRUE,
deltaMouse => TRUE,
ENDCASE => FALSE;
};
timeTrigger => {
IF firstAccess THEN ERROR; -- time events can't be first
copy local creek for checking time since no "putback"
ClassIncreek.CopyIncreek[parseInfo.timeCreek, increek];
increekAction ← ClassIncreek.GetAction[self: parseInfo.timeCreek,
waitMode: timed, waitInterval: term.mSecs,
acceptance: clicks];
valid ← WITH ca: increekAction SELECT FROM
timedOut => term.flavor=gt,
ENDCASE => term.flavor=lt;
};
keyEnable => {
creekKeyState: Interminal.KeyState ← ClassIncreek.GetPositionFrom[increek].keyState;
valid ← (term.keyState.state = creekKeyState.bits[term.keyState.key]);
};
key2Enable => {
creekKeyState: Interminal.KeyState ← ClassIncreek.GetPositionFrom[increek].keyState;
valid ← (term.keyState1.state = creekKeyState.bits[term.keyState1.key])
OR (term.keyState2.state = creekKeyState.bits[term.keyState2.key]);
};
keyEnableList => {
creekKeyState: Interminal.KeyState ← ClassIncreek.GetPositionFrom[increek].keyState;
valid ← FALSE;
FOR lst: LIST OF TIPKeyState ← term.lst, lst.rest UNTIL lst=NIL DO
IF lst.first.state = creekKeyState.bits[lst.first.key] THEN {
valid ← TRUE; EXIT };
ENDLOOP;
};
predEnable => {
predRef: REF;
found: BOOL;
predicate: REF TIPUser.TIPPredicate;
[found, predRef] ← RefTab.Fetch[predTable, term.predicate];
IF found
THEN {
predicate ← NARROW[predRef];
valid ← predicate^[] }
ELSE { valid ← FALSE };
};
char => stdChar^ ← AsciiAction[increek, increekAction];
coords => {
mp: Interminal.MousePosition ~ ClassIncreek.GetPositionFrom[increek].mousePosition;
stdCoords^ ← [mouseX: mp.mouseX, mouseY: mp.mouseY, color: mp.color];
};
time => {
stdTime^ ← ClassIncreek.GetTime[increek];
};
nested => {
PushIncreek[];
FOR choices: TIPChoiceSeries ← term.statement, choices.rest UNTIL choices=NIL DO
result ← MatchChoice[choices.first];
IF result#NIL THEN RETURN[result];
CopyTopIncreek[];
ENDLOOP;
PopIncreek[];
valid ← FALSE;
};
result => {
IF copied THEN ClassIncreek.CopyIncreek[parseInfo.inCreek, increek];
RETURN[term.list];
};
ENDCASE => ERROR;
ENDLOOP;
};
actionKind: ClassIncreek.ActionKind ← creekAction.kind;
IF parseInfo.tableHead=transparentTIPTable THEN { -- just pass creek and action through
result ← LIST[parseInfo.inCreek, NEW[ClassIncreek.ActionBody ← creekAction]];
RETURN;
};
FOR table: TIPTable ← parseInfo.tableHead, IF table.opaque THEN NIL ELSE table.link UNTIL table=NIL DO
impl: TIPTableImpl ~ table.impl;
SELECT actionKind FROM -- for efficiency
mousePosition => IF impl.ignore.move THEN LOOP;
deltaMouse => IF impl.ignore.move THEN LOOP;
keyDown => IF impl.ignore.down THEN LOOP;
keyUp => IF impl.ignore.up THEN LOOP;
ENDCASE;
firstAccess ← TRUE;
ClearIncreekStack[];
WITH t: impl SELECT FROM
fast => {
GetIncreekAction[];
WITH action: increekAction SELECT FROM
mousePosition => result ← MatchChoice[t.mouse];
deltaMouse => result ← MatchChoice[t.mouse];
keyUp => result ← MatchChoice[t.keyUp[action.value]];
keyDown => result ← MatchChoice[t.keyDown[action.value]];
ENDCASE;
IF result#NIL THEN RETURN[result];
};
small => {
FOR choices: TIPChoiceSeries ← t.all, choices.rest
UNTIL choices=NIL DO
result ← MatchChoice[choices.first];
IF result#NIL THEN RETURN[result];
firstAccess ← TRUE;
ClearIncreekStack[];
ENDLOOP;
};
ENDCASE;
ENDLOOP;
};
KeyItem: TYPE ~ RECORD[normal, shift: CHAR] ← [0C, 0C];
nullKeyItem: KeyItem ~ [0C, 0C];
DefaultingKeyItem: TYPE ~ KeyItem ← nullKeyItem;
KeyTable: TYPE ~ ARRAY Interminal.KeyName OF DefaultingKeyItem;
keyTable: REF KeyTable ~ NEW[KeyTable ← [
ESC: [Ascii.ESC, Ascii.ESC], -- Alto ESC (upper left), DLion CENTER (top row, left end)
One: ['1, '!], -- 1 and !
Two: ['2, '@], -- 2 and @
Three: ['3, '#], -- 3 and #
Four: ['4, '$], -- 4 and $
Five: ['5, '%], -- 5 and %
Six: ['6, '~], -- 6 and ~
Seven: ['7, '&], -- 7 and &
Eight: ['8, '*], -- 8 and *
Nine: ['9, '(], -- 9 and (
Zero: ['0, ')], -- 0 and )
Dash: ['-, '—], -- Alto - and —, DLion -
Equal: ['=, '+], -- = and +
BackSlash: ['\\, '|], -- Alto \ and |, DLion DEFAULTS (top row, right end)
LF: [Ascii.LF, Ascii.LF], -- Alto LF (upper right), DLion COPY (left group)
DEL: [Ascii.DEL, Ascii.DEL], -- Alto DEL, DLion DELETE (left group)
TAB: [Ascii.TAB, Ascii.TAB], -- Alto TAB, DLion <paratab> (large key left of Q)
Q: ['q, 'Q],
W: ['w, 'W],
E: ['e, 'E],
R: ['r, 'R],
T: ['t, 'T],
Y: ['y, 'Y],
U: ['u, 'U],
I: ['i, 'I],
O: ['o, 'O],
P: ['p, 'P],
LeftBracket: ['[, '{], -- [ and {
RightBracket: ['], '}], -- ] and }
Arrow: ['←, '^], -- Alto ← and ^, DLion open quotes
BS: [Ascii.BS, Ascii.BS], -- Alto BS (upper right), DLion ← (large key, upper right)
A: ['a, 'A],
S: ['s, 'S],
D: ['d, 'D],
F: ['f, 'F],
G: ['g, 'G],
H: ['h, 'H],
J: ['j, 'J],
K: ['k, 'K],
L: ['l, 'L],
SemiColon: [';, ':], --; and :
Quote: ['\', '\"], -- ' and " (close quotes on DLion)
Return: [Ascii.CR, Ascii.CR], -- Alto RETURN, DLion <newpara> (double-height key, right side)
Z: ['z, 'Z],
X: ['x, 'X],
C: ['c, 'C],
V: ['v, 'V],
B: ['b, 'B],
N: ['n, 'N],
M: ['m, 'M],
Comma: [',, '<], -- , and <
Period: ['., '>], -- . and >
Slash: ['/, '?], -- / and ?
Space: [Ascii.SP, Ascii.SP], -- the space bar
Spare1: ['\201, '\204],
Spare2: ['\202, '\205],
Spare3: ['\203, '\206]
]];
AsciiAction: PROC [inCreek: ClassIncreek.Increek, creekAction: ClassIncreek.ActionBody] RETURNS [c: CHAR] = TRUSTED {
p: ClassIncreek.ViewPosition = ClassIncreek.GetPositionFrom[inCreek];
WITH action: creekAction SELECT FROM
keyDown => {
kI: KeyItem = keyTable[action.value];
char: CHAR ← kI.normal;
IF kI=nullKeyItem THEN ERROR; -- not a character
SELECT Interminal.DownUp[down] FROM
p.keyState.bits[Ctrl] => char ← VAL[ORD[char] MOD 40B];
p.keyState.bits[LeftShift], p.keyState.bits[RightShift] => char ← kI.shift;
p.keyState.bits[Lock] => IF char IN['a..'z] THEN char ← kI.shift;
ENDCASE;
RETURN[char];
};
ENDCASE => ERROR; -- why are they asking for a char?
};
TIPUser facilities
predTable: PUBLIC RefTab.Ref ← RefTab.Create[]; -- table for user defined predicates
CreateClient: PUBLIC PROC [notify: TIPNotifyProc ← NIL, buttons: TIPButtonProc ← NIL] RETURNS [self: TIPClient] = TRUSTED {
self ← NEW[TIPClientRec ← [
notifyProc: notify,
buttonProc: buttons,
parseInfo: CreateParseInfo[],
matcher:
]];
self.matcher ← LOOPHOLE[Process.GetCurrent[]];
so is never NIL when MatchProcess begins
self.matcher ← FORK MatchProcess[self];
};
DestroyClient: PUBLIC PROC [self: TIPClient] = TRUSTED {
self.matcher ← NIL; -- force matcher to terminate
};
CreateParseInfo: PUBLIC PROC [parseTable: TIPTable ← NIL] RETURNS [new: TIPParseInfo] = TRUSTED {
new ← NEW[TIPParseInfoRec ← [
inCreek: ClassIncreek.NewStdIncreek[],
localCreek: ClassIncreek.NewStdIncreek[],
timeCreek: ClassIncreek.NewStdIncreek[],
creekStack: ALL[ClassIncreek.NewStdIncreek[]],
tableHead: parseTable
]];
};
PushTIPTable: PUBLIC PROC [user: TIPClient, table: TIPTable, opaque: BOOL] = {
t: TIPTable;
FOR t ← table, t.link UNTIL t.link=NIL DO ENDLOOP;
t.link ← user.parseInfo.tableHead;
user.parseInfo.tableHead ← table;
table.opaque ← opaque;
};
PopTIPTable: PUBLIC PROC [user: TIPClient] RETURNS [old: TIPTable] = {
garbage collector will get old table unless the client keeps a reference
IF (old←user.parseInfo.tableHead)#NIL THEN
user.parseInfo.tableHead ← user.parseInfo.tableHead.link;
};
RegisterTIPPredicate: PUBLIC PROC [key: ATOM, p: TIPPredicate] = {
a user-defined predicate may be included in the enables list of a TIPTable via this
association mechanism.
[] ← RefTab.Store[predTable, key, NEW[TIPPredicate ← p]];
};
Initialization
TRUSTED {
mouseGrainCreek ← ClassIncreek.NewStdIncreek[]; -- only for setting recording grain
ClassIncreek.SetMouseGrain[mouseGrainCreek, 100, 1];
};
}.