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:
BOOL ←
FALSE] =
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
ANY ←
NIL] =
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
ANY ←
NIL] =
TRUSTED {
valid: BOOL ← TRUE; -- 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];
};
}.