MenuParserImpl.mesa; Written by Randy Pausch
Last edited by Doug Wyatt, October 12, 1983 3:12 pm
DIRECTORY
AMBridge USING [SomeRefFromTV],
Atom USING [MakeAtom],
CedarScanner,
FileIO USING [Open],
Interpreter USING [Evaluate],
IO USING [Flush, int, NUL, PutF, rope, STREAM],
Menus,
Rope USING [Cat, Equal, Fetch, Length, ROPE, Substr],
RTBasic USING [TV],
ViewerClasses USING [NotifyProc];
MenuParserImpl: CEDAR PROGRAM
IMPORTS AMBridge, Atom, CedarScanner, FileIO, Interpreter, IO, Rope
EXPORTS Menus
= BEGIN OPEN CedarScanner, Menus;
ROPE: TYPE = Rope.ROPE;
global data:
t: Token;
menus: LIST OF Menu ← NIL;
globalRope: ROPE;
errorLog: ROPE;
syntaxErrorFound: SIGNAL;
the user entry
ParseDescription:
PUBLIC
PROC [def:
ROPE, prevlist:
LIST
OF Menu ←
NIL, errorFile:
ROPE ←
NIL]
RETURNS [
LIST
OF Menu] = {
ENABLE syntaxErrorFound => GOTO SyntaxErrorExit;
errorLog ← IF errorFile = NIL THEN "menus.errlog" ELSE errorFile;
globalRope ← def;
GetNext[first: TRUE];
menus ← prevlist;
WHILE t.kind # tokenEOF
DO
SELECT t.kind
FROM
tokenID => {
MustBe["Relabel"];
ParseRelabel[];
};
tokenSINGLE => {
MustBe["["];
ParseMenu[];
};
ENDCASE => SyntaxError["Was expecting keyword 'relabel' or '[' to start menu definition"];
MustBe["]"];
GetNext;
ENDLOOP;
RETURN[menus];
EXITS SyntaxErrorExit => { RETURN[NIL] };
};
utilty procedures:
Get: GetProc = {
[data: REF, index: INT] RETURNS [CHAR]
length: INTEGER ← Rope.Length[globalRope];
IF index >= length THEN RETURN[IO.NUL]
ELSE RETURN Rope.Fetch[globalRope, index];
};
SyntaxError:
PROC [msg:
ROPE] = {
this routine is called any time a syntax error is discovered.
file: IO.STREAM ← FileIO.Open[errorLog, overwrite];
IO.PutF[file, "MenuParse ErrorLog:\n\n"];
IO.PutF[file, "Syntax error found at token beginning in position [%d]\n", IO.int[t.start] ];
IF msg # NIL THEN IO.PutF[file, "TYPE OF ERROR: [%g]\n\n\n", IO.rope[msg] ];
IO.Flush[file];
SIGNAL syntaxErrorFound;
};
ContentsAre:
PROC [r:
ROPE]
RETURNS [
BOOLEAN] = {
RETURN[Rope.Equal[r, CurrentContents[], FALSE] ];
};
CurrentContents:
PROC []
RETURNS [
ROPE] = {
one special case: A rope literal would normally return with the quote marks still in the string
IF t.kind = tokenROPE
THEN {
dummy: ROPE ← ContentsFromToken[[Get,NIL], t];
RETURN[Rope.Substr[dummy, 1, Rope.Length[dummy] - 2] ];
}
ELSE RETURN[ContentsFromToken[[Get,NIL], t] ];
};
GetNext:
PROC[kind:TokenKind ← tokenERROR, string:
ROPE ←
NIL, first:
BOOL ←
FALSE] = {
t ← GetToken[[Get, NIL], IF first THEN 0 ELSE t.next];
WHILE t.kind = tokenCOMMENT
DO
t ← GetToken[[Get, NIL], t.next];
ENDLOOP;
IF kind # tokenERROR
THEN {
IF t.kind # kind
THEN {
IF string # NIL THEN SyntaxError[Rope.Cat["Was Expecting the token ->", string, "<-"]]
ELSE SyntaxError[Rope.Cat["Was expecting ",
SELECT kind
FROM
tokenID => "an identifier, like a menu name, or an entry name, for example",
tokenINT => "an integer",
tokenREAL => "a real number",
tokenROPE => "a quoted string",
tokenATOM => "an atom",
tokenSINGLE => "a single character token, like '[' or '{', for example",
tokenDOUBLE => "a double character token, like '=>', for example",
ENDCASE => "a different kind of token"]];
};
IF string # NIL THEN IF NOT Rope.Equal[string, CurrentContents[], FALSE ] THEN SyntaxError[Rope.Cat["Was Expecting the token ->", string, "<-"]];
};
};
MustBe:
PROC[string:
ROPE ←
NIL] = {
IF NOT Rope.Equal[string, CurrentContents[], FALSE] THEN SyntaxError[Rope.Cat["Was Expecting the token ->", string, "<-"]];
};
GetForNamesFromRope: GetProc = {
[data: REF, index: INT] RETURNS [CHAR]
length: INTEGER ← Rope.Length[NARROW[data]];
IF index >= length THEN RETURN[IO.NUL]
ELSE RETURN Rope.Fetch[NARROW[data], index];
};
GetNamesFromRope:
PROC[string:
ROPE]
RETURNS[answer:
LIST
OF MenuName ←
NIL] = {
myToken: Token ← GetToken[[GetForNamesFromRope, string], 0];
WHILE myToken.kind = tokenID
DO
name: ROPE = ContentsFromToken[[GetForNamesFromRope, string], myToken];
answer ← CONS[MakeMenuName[name], answer];
myToken ← GetToken[[GetForNamesFromRope, string], myToken.next];
ENDLOOP;
RETURN[answer];
};
MakeMenuName:
PROC[rope:
ROPE]
RETURNS[MenuName] = {
RETURN[Atom.MakeAtom[rope]] };
MenuNameEqual: PROC[a, b: ATOM] RETURNS[BOOL] = INLINE { RETURN[a=b] };
internal parsing routines
ParseRelabel:
PROC [] = {
assumes that the first '[' hasn't been read
firstParm: ROPE ← NIL;
secondParm: ROPE ← NIL;
menuname: MenuName ← NIL;
oldname: ROPE;
newname: ROPE;
GetNext[tokenSINGLE, "["];
GetNext[tokenID];
firstParm ← CurrentContents[];
GetNext[tokenID];
secondParm ← CurrentContents[];
GetNext;
IF t.kind = tokenID
THEN {
menuname ← MakeMenuName[firstParm];
oldname ← secondParm;
newname ← CurrentContents[];
GetNext[tokenSINGLE, "]"];
}
ELSE {
menuname ← NIL;
oldname ← firstParm;
newname ← secondParm;
};
now affect the data structure
FOR l:
LIST
OF Menu ← menus, l.rest
UNTIL l =
NIL
DO
IF menuname=
NIL
OR MenuNameEqual[menuname, l.first.name]
THEN
FOR e:
LIST
OF Menus.Entry ← l.first.entries, e.rest
UNTIL e =
NIL
DO
IF Rope.Equal[e.first.name, oldname] THEN e.first.displayData ← newname;
ENDLOOP;
ENDLOOP;
};
ParseMenu:
PROC = {
replacement: BOOLEAN ← FALSE;
prev: LIST OF Menu ← NIL;
newMenu: Menu ← NEW[Menus.MenuRec];
GetNext[tokenID];
newMenu.name ← MakeMenuName[CurrentContents[]];
GetNext;
ParseMenuParms[newMenu];
MustBe["["];
ParseEntries[newMenu];
MustBe["]"];
FOR l:
LIST
OF Menu ← menus, l.rest
UNTIL l =
NIL
DO
IF MenuNameEqual[newMenu.name, l.first.name]
THEN {
l.first ← newMenu;
replacement ← TRUE;
};
prev ← l;
ENDLOOP;
IF
NOT replacement
THEN {
IF menus = NIL THEN menus ← LIST[newMenu]
ELSE prev.rest ← LIST[newMenu];
};
};
ParseMenuParms:
PROC [newMenu: Menu] = {
assumes first token already read for it by the caller.
WHILE t.kind = tokenID
DO
IF ContentsAre["breakBefore"]
THEN {
GetNext[tokenSINGLE, ":"];
newMenu.breakBefore ← GetBoolean[];
}
ELSE
IF ContentsAre["breakAfter"]
THEN {
GetNext[tokenSINGLE, ":"];
newMenu.breakAfter ← GetBoolean[];
}
ELSE
IF ContentsAre["beginsActive"]
THEN {
GetNext[tokenSINGLE, ":"];
newMenu.beginsActive ← GetBoolean[];
}
ELSE
IF ContentsAre["notifyProc"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenSINGLE, "{"];
newMenu.notify ← GetNotifyProc[];
}
ELSE SyntaxError["Was expecting a menu parameter keyword ('breakBefore', 'breakAfter', 'beginsActive', or 'notifyProc')"];
GetNext;
ENDLOOP; -- while
};
ParseEntries:
PROC [newMenu: Menu] = {
Item: TYPE = Entry;
list, tail: LIST OF Item ← NIL;
Append:
PROC[item: Item] = {
prev: LIST OF Item = tail; tail ← LIST[item];
IF prev=NIL THEN list ← tail ELSE prev.rest ← tail;
};
WHILE ContentsAre["["]
DO
entry: Entry = NEW[EntryRec];
GetNext[tokenID];
entry.name ← CurrentContents[];
ParseEntryParms[entry];
MustBe["=>"];
GetNext;
IF ContentsAre["["] THEN entry.actions ← GetComplex[]
ELSE {
action: Action = NEW[ActionRec ← [triggers: allTriggers, input: GetListOfRefAny[]]];
entry.actions ← LIST[action];
};
Append[entry];
MustBe["]"];
GetNext[];
ENDLOOP;
newMenu.entries ← list;
MustBe["]"];
};
ParseEntryParms:
PROC [entry: Entry] = {
assumes first token NOT read for it by the caller.
GetNext[];
WHILE t.kind = tokenID
DO
IF ContentsAre["guarded"]
THEN {
GetNext[tokenSINGLE, ":"];
entry.guarded ← GetBoolean[];
}
ELSE
IF ContentsAre["display"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext;
IF t.kind = tokenROPE OR t.kind = tokenID THEN entry.displayData ← CurrentContents[]
ELSE IF ContentsAre["{"] THEN entry.displayData ← GetDisplayProc[]
ELSE SyntaxError["Was Expecting an word, quoted string, or {...} for display: parameter"];
}
ELSE SyntaxError["Was expecting an entry keyword parameter ('guarded' or 'display')"];
GetNext;
ENDLOOP; -- while
};
GetComplex:
PROC []
RETURNS[
LIST
OF Action] = {
assumes first token already read for it by the caller.
Item: TYPE = Action;
list, tail: LIST OF Item ← NIL;
Append:
PROC[item: Item] = {
prev: LIST OF Item = tail; tail ← LIST[item];
IF prev=NIL THEN list ← tail ELSE prev.rest ← tail;
};
MustBe["["];
WHILE ContentsAre["["]
DO
action: Action = NEW[ActionRec];
action.triggers ← GetTriggers[];
ParseTriggerParms[action];
MustBe["=>"];
GetNext;
action.input ← GetListOfRefAny[];
MustBe["]"];
Append[action];
GetNext;
ENDLOOP;
MustBe["]"];
RETURN[list];
};
SetTriggersFromRope:
PROC[triggers: TriggerSet, r:
ROPE]
RETURNS[TriggerSet] = {
SELECT
TRUE
FROM
Rope.Equal["none", r, FALSE] => NULL;
Rope.Equal["all", r, FALSE] => triggers ← ALL[TRUE];
Rope.Equal["leftUp", r, FALSE] => triggers[leftUp] ← TRUE;
Rope.Equal["middleUp", r, FALSE] => triggers[middleUp] ← TRUE;
Rope.Equal["rightUp", r, FALSE] => triggers[rightUp] ← TRUE;
Rope.Equal["shiftLeftUp", r, FALSE] => triggers[shiftLeftUp] ← TRUE;
Rope.Equal["shiftMiddleUp", r, FALSE] => triggers[shiftMiddleUp] ← TRUE;
Rope.Equal["shiftRightUp", r, FALSE] => triggers[shiftRightUp] ← TRUE;
Rope.Equal["allNonShifts", r,
FALSE] =>
triggers[leftUp] ← triggers[middleUp] ← triggers[rightUp] ← TRUE;
Rope.Equal["allShifts", r,
FALSE] =>
triggers[shiftLeftUp] ← triggers[shiftMiddleUp] ← triggers[shiftRightUp] ← TRUE;
Rope.Equal["allLeft", r,
FALSE] =>
triggers[leftUp] ← triggers[shiftLeftUp] ← TRUE;
Rope.Equal["allMiddle", r,
FALSE] =>
triggers[middleUp] ← triggers[shiftMiddleUp] ← TRUE;
Rope.Equal["allRight", r,
FALSE] =>
triggers[rightUp] ← triggers[shiftRightUp] ← TRUE;
ENDCASE => SyntaxError["Was expecting a trigger keyword ('all', 'leftUp', 'leftshiftup', etc)"];
RETURN[triggers];
};
GetTriggers:
PROC []
RETURNS [TriggerSet] = {
NOTE: triggers can either be a single ID, or '[' ID* ']' — we do NOT assume that the caller has read our first token! We DO NOT eat the next token, either.
triggers: TriggerSet ← ALL[FALSE];
GetNext;
IF ContentsAre["["]
THEN {
GetNext[tokenID];
WHILE t.kind = tokenID
DO
triggers ← SetTriggersFromRope[triggers, CurrentContents[]];
GetNext;
ENDLOOP;
MustBe["]"];
}
ELSE
IF t.kind = tokenID
THEN {
triggers ← SetTriggersFromRope[triggers, CurrentContents[]];
}
ELSE SyntaxError["Was Expecting a trigger keyword or list of them surrounded by brackets '[' (triggers are 'all', 'leftUp', 'shiftLeftUp', etc)"];
RETURN[triggers];
};
ParseTriggerParms:
PROC [action: Action] = {
Does NOT assume first token already read for it by the caller.
GetNext;
WHILE t.kind = tokenID
DO
IF ContentsAre["popUpDoc"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenROPE];
action.popupDoc ← CurrentContents[];
}
ELSE
IF ContentsAre["makeActive"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenROPE];
action.makeActive ← GetNamesFromRope[CurrentContents[]];
}
ELSE
IF ContentsAre["makeInactive"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenROPE];
action.makeInactive ← GetNamesFromRope[CurrentContents[]];
}
ELSE
IF ContentsAre["toggle"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenROPE];
action.toggle ← GetNamesFromRope[CurrentContents[]];
}
ELSE
IF ContentsAre["guardResponse"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext;
IF t.kind = tokenROPE THEN action.guardResponse ← CurrentContents[]
ELSE action.guardResponse ← GetGuardResponseProc[];
}
ELSE SyntaxError["Was expecting a trigger parameter ('popUpDoc', 'makeActive', 'makeInactive', 'toggle', or 'guardResponse')"];
GetNext;
ENDLOOP; -- while
};
GetListOfRefAny:
PROC []
RETURNS [
LIST
OF
REF
ANY] = {
reads until it hits a ']' — assumes caller has read the first token
Item: TYPE = REF ANY;
list, tail: LIST OF Item ← NIL;
Append:
PROC[item: Item] = {
prev: LIST OF Item = tail; tail ← LIST[item];
IF prev=NIL THEN list ← tail ELSE prev.rest ← tail;
};
WHILE
NOT (ContentsAre["]"]
OR ContentsAre["}"])
DO
SELECT t.kind
FROM
tokenINT => Append[NEW[INTEGER ← IntFromToken[[Get,NIL], t]]];
tokenREAL => Append[NEW[REAL ← RealFromToken[[Get,NIL], t]]];
tokenROPE => Append[RopeFromToken[[Get,NIL], t]];
tokenATOM => Append[AtomFromToken[[Get,NIL], t]];
ENDCASE => SyntaxError["Was expecting a valid token for a LIST OF REF ANY"];
GetNext;
ENDLOOP;
RETURN[list];
};
GetGuardResponseProc:
PROC []
RETURNS [
REF Menus.UnGuardRec] = {
RETURN[
NEW [Menus.UnGuardRec ← [NARROW[GeneralGetProc[], REF Menus.UnGuardProc] ^, GetListOfRefAny[] ] ]
];
};
GetNotifyProc:
PROC []
RETURNS [answer: ViewerClasses.NotifyProc] = {
answer ← NARROW[GeneralGetProc[], REF ViewerClasses.NotifyProc]^;
MustBe["}"]; -- all the other guys call GetListOfRefAny, but notify procs specs don't get parms
};
GetDisplayProc:
PROC []
RETURNS [
REF Menus.DrawingRec] = {
RETURN[
NEW [Menus.DrawingRec ← [NARROW[GeneralGetProc[], REF Menus.DrawingProc] ^, GetListOfRefAny[] ] ]
];
};
GetQualifiedName:
PROC []
RETURNS [
ROPE] = {
assumes first token not read, doesn't read past where it needs to
firstPart: ROPE;
secondPart: ROPE;
GetNext[tokenID];
firstPart ← CurrentContents[];
GetNext[tokenSINGLE, "."];
GetNext[tokenID];
secondPart ← CurrentContents[];
RETURN[Rope.Cat[firstPart, ".", secondPart] ];
};
GeneralGetProc:
PROC []
RETURNS [
REF
ANY] = {
assumes the "{" read by caller, doesn't read past where it needs to
qualifiedName: ROPE;
temp: RTBasic.TV;
myRef: REF ANY;
MustBe["{"];
qualifiedName ← GetQualifiedName[];
GetNext; -- GetListOfRefAny will always be called right after me, and wants first token read
[temp, ----, ----] ← Interpreter.Evaluate[qualifiedName];
TRUSTED {myRef ← AMBridge.SomeRefFromTV[temp]};
RETURN[myRef];
};
GetBoolean:
PROC []
RETURNS [answer:
BOOLEAN] = {
GetNext[tokenID];
IF ContentsAre["TRUE"] THEN RETURN[TRUE]
ELSE IF ContentsAre["FALSE"] THEN RETURN[FALSE]
ELSE SyntaxError["Was expecting to find a boolean keyword (either 'TRUE' or 'FALSE')"];
};
END.