DIRECTORY
AMBridge,
CedarScanner,
FileIO,
Interpreter,
IO,
Menus,
Rope,
RTBasic,
ViewerClasses;
MenuParserImpl: CEDAR PROGRAM
IMPORTS AMBridge, CedarScanner, FileIO, Interpreter, IO, Rope EXPORTS Menus =
BEGIN OPEN CedarScanner;
global data:
t: Token;
menus: LIST OF Menus.Menu ← NIL;
globalRope: Rope.ROPE;
errorLog: Rope.ROPE;
syntaxErrorFound: SIGNAL;
the user entry
ParseDescription:
PUBLIC
PROC [def: Rope.
ROPE, prevlist:
LIST
OF Menus.Menu ←
NIL, errorFile: Rope.
ROPE ←
NIL]
RETURNS [
LIST
OF Menus.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.
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.
ROPE]
RETURNS [
BOOLEAN] = {
RETURN[Rope.Equal[r, CurrentContents[], FALSE] ];
};
CurrentContents:
PROC []
RETURNS [Rope.
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.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.
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.
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.
ROPE]
RETURNS[answer:
LIST
OF Rope.
ROPE] = {
myToken: Token;
answer ← NIL;
myToken ← GetToken[[GetForNamesFromRope, string], 0];
WHILE myToken.kind = tokenID
DO
answer ← CONS[ContentsFromToken[[GetForNamesFromRope, string], myToken], answer];
myToken ← GetToken[[GetForNamesFromRope, string], myToken.next];
ENDLOOP;
RETURN[answer];
};
internal parsing routines
ParseRelabel:
PROC [] = {
assumes that the first '[' hasn't been read
firstParm: Rope.ROPE ← NIL;
secondParm: Rope.ROPE ← NIL;
menuname: Rope.ROPE ← NIL;
oldname: Rope.ROPE;
newname: Rope.ROPE;
GetNext[tokenSINGLE, "["];
GetNext[tokenID];
firstParm ← CurrentContents[];
GetNext[tokenID];
secondParm ← CurrentContents[];
GetNext;
IF t.kind = tokenID
THEN {
menuname ← firstParm;
oldname ← secondParm;
newname ← CurrentContents[];
GetNext[tokenSINGLE, "]"];
}
ELSE {
the menuname is nil
menuname ← NIL;
oldname ← firstParm;
newname ← secondParm;
};
now affect the data structure
FOR l:
LIST
OF Menus.Menu ← menus, l.rest
UNTIL l =
NIL
DO
IF menuname =
NIL
OR Rope.Equal[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 Menus.Menu ← NIL;
newMenu: REF Menus.Menu ← NEW[Menus.Menu];
GetNext[tokenID];
newMenu.name ← CurrentContents[];
GetNext;
ParseMenuParms[newMenu];
MustBe["["];
ParseEntries[newMenu];
MustBe["]"];
FOR l:
LIST
OF Menus.Menu ← menus, l.rest
UNTIL l =
NIL
DO
IF Rope.Equal[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:
REF Menus.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:
REF Menus.Menu] = {
newEntry: REF Menus.Entry;
WHILE ContentsAre["["]
DO
newEntry ← NEW[Menus.Entry];
GetNext[tokenID];
newEntry.name ← CurrentContents[];
ParseEntryParms[newEntry];
MustBe["=>"];
GetNext;
IF ContentsAre["["] THEN newEntry.actions ← GetComplex[]
ELSE {
newEntry.actions ← LIST[ Menus.EntryNotifyRecord[trigger: LIST[all], notifyData: GetListOfRefAny[]] ];
};
add entry to the menu, at the end of the list:
IF newMenu.entries = NIL THEN newMenu.entries ← LIST[newEntry^]
ELSE {
prev: LIST OF Menus.Entry ← NIL;
FOR l:
LIST
OF Menus.Entry ← newMenu.entries, l.rest
UNTIL l =
NIL
DO
prev ← l;
ENDLOOP;
prev.rest ← LIST[newEntry^];
};
MustBe["]"];
GetNext[];
ENDLOOP;
MustBe["]"];
};
ParseEntryParms:
PROC [newEntry:
REF Menus.Entry] = {
assumes first token NOT read for it by the caller.
GetNext[];
WHILE t.kind = tokenID
DO
IF ContentsAre["guarded"]
THEN {
GetNext[tokenSINGLE, ":"];
newEntry.guarded ← GetBoolean[];
}
ELSE
IF ContentsAre["display"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext;
IF t.kind = tokenROPE OR t.kind = tokenID THEN newEntry.displayData𡤌urrentContents[]
ELSE IF ContentsAre["{"] THEN newEntry.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[answer:
LIST
OF Menus.EntryNotifyRecord] = {
newEntryNotifyRec: REF Menus.EntryNotifyRecord;
assumes first token already read for it by the caller.
answer ← NIL;
MustBe["["];
WHILE ContentsAre["["]
DO
newEntryNotifyRec ← NEW[Menus.EntryNotifyRecord];
newEntryNotifyRec.trigger ← GetTriggers[];
ParseTriggerParms[newEntryNotifyRec];
MustBe["=>"];
GetNext;
newEntryNotifyRec.notifyData ← GetListOfRefAny[];
MustBe["]"];
add newEntryNotifyRec to answer, at the end of the list:
IF answer = NIL THEN answer ← LIST[newEntryNotifyRec^]
ELSE {
prev: LIST OF Menus.EntryNotifyRecord ← NIL;
FOR l:
LIST
OF Menus.EntryNotifyRecord ← answer, l.rest
UNTIL l =
NIL
DO
prev ← l;
ENDLOOP;
prev.rest ← LIST[newEntryNotifyRec^];
};
GetNext;
ENDLOOP;
MustBe["]"];
RETURN[answer];
};
GetTriggers:
PROC []
RETURNS [l:
LIST
OF Menus.MenuEntryTrigger] = {
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.
r: Rope.ROPE;
l ← NIL;
GetNext;
IF ContentsAre["["]
THEN {
GetNext[tokenID];
WHILE t.kind = tokenID
DO
order for this list doesn't matter, so I can just CONS it onto the front
r ← CurrentContents[];
SELECT
TRUE
FROM
Rope.Equal["notrigger", r, FALSE] => NULL;
Rope.Equal["all", r, FALSE] => l ← CONS[all, l];
Rope.Equal["leftup", r, FALSE] => l ← CONS[leftup, l];
Rope.Equal["middleup", r, FALSE] => l ← CONS[middleup, l];
Rope.Equal["rightup", r, FALSE] => l ← CONS[rightup, l];
Rope.Equal["shiftleftup", r, FALSE] => l ← CONS[shiftleftup, l];
Rope.Equal["shiftmiddleup", r, FALSE] => l ← CONS[shiftmiddleup, l];
Rope.Equal["shiftrightup", r, FALSE] => l ← CONS[shiftrightup, l];
Rope.Equal["allnonshifts", r,
FALSE] => {
l ← CONS[leftup, l];
l ← CONS[middleup, l];
l ← CONS[rightup, l]
};
Rope.Equal["allshifts", r,
FALSE] => {
l ← CONS[shiftleftup, l];
l ← CONS[shiftmiddleup, l];
l ← CONS[shiftrightup, l]
};
Rope.Equal["allleft", r,
FALSE] => {
l ← CONS[leftup, l];
l ← CONS[shiftleftup, l];
};
Rope.Equal["allmiddle", r,
FALSE] => {
l ← CONS[middleup, l];
l ← CONS[shiftmiddleup, l];
};
Rope.Equal["allright", r,
FALSE] => {
l ← CONS[rightup, l];
l ← CONS[shiftrightup, l];
};
ENDCASE => SyntaxError["Was expecting a trigger keyword ('all', 'leftup', 'leftshiftup', etc)"];
GetNext;
ENDLOOP;
MustBe["]"];
}
ELSE
IF t.kind = tokenID
THEN {
r ← CurrentContents[];
SELECT
TRUE
FROM
Rope.Equal["notrigger", r, FALSE] => NULL;
Rope.Equal["all", r, FALSE] => l ← CONS[all, l];
Rope.Equal["leftup", r, FALSE] => l ← CONS[leftup, l];
Rope.Equal["middleup", r, FALSE] => l ← CONS[middleup, l];
Rope.Equal["rightup", r, FALSE] => l ← CONS[rightup, l];
Rope.Equal["shiftleftup", r, FALSE] => l ← CONS[shiftleftup, l];
Rope.Equal["shiftmiddleup", r, FALSE] => l ← CONS[shiftmiddleup, l];
Rope.Equal["shiftrightup", r, FALSE] => l ← CONS[shiftrightup, l];
Rope.Equal["allnonshifts", r,
FALSE] => {
l ← CONS[leftup, l];
l ← CONS[middleup, l];
l ← CONS[rightup, l]
};
Rope.Equal["allshifts", r,
FALSE] => {
l ← CONS[shiftleftup, l];
l ← CONS[shiftmiddleup, l];
l ← CONS[shiftrightup, l]
};
Rope.Equal["allleft", r,
FALSE] => {
l ← CONS[leftup, l];
l ← CONS[shiftleftup, l];
};
Rope.Equal["allmiddle", r,
FALSE] => {
l ← CONS[middleup, l];
l ← CONS[shiftmiddleup, l];
};
Rope.Equal["allright", r,
FALSE] => {
l ← CONS[rightup, l];
l ← CONS[shiftrightup, l];
};
ENDCASE => SyntaxError["Was expecting a trigger keyword ('all', 'leftup', 'leftshiftup', etc)"];
}
ELSE SyntaxError["Was Expecting a trigger keyword or list of them surrounded by brackets '[' (triggers are 'all', 'leftup', 'leftshiftup', etc)"];
};
TriggerFromRope:
PROC [r: Rope.
ROPE, l:
LIST
OF Menus.MenuEntryTrigger] = {
the Trigger(s) the rope indicates are CONSed onto the front of the list
};
ParseTriggerParms:
PROC [newEntryNotifyRec:
REF Menus.EntryNotifyRecord] = {
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];
newEntryNotifyRec.popupDoc ← CurrentContents[];
}
ELSE
IF ContentsAre["makeActive"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenROPE];
newEntryNotifyRec.makeActive ← GetNamesFromRope[CurrentContents[]];
}
ELSE
IF ContentsAre["makeInActive"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenROPE];
newEntryNotifyRec.makeInActive ← GetNamesFromRope[CurrentContents[]];
}
ELSE
IF ContentsAre["toggle"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext[tokenROPE];
newEntryNotifyRec.toggle ← GetNamesFromRope[CurrentContents[]];
}
ELSE
IF ContentsAre["guardResponse"]
THEN {
GetNext[tokenSINGLE, ":"];
GetNext;
IF t.kind = tokenROPE THEN newEntryNotifyRec.guardResponse ← CurrentContents[]
ELSE newEntryNotifyRec.guardResponse ← GetGuardResponseProc[];
}
ELSE SyntaxError["Was expecting a trigger parameter ('popUpDoc', 'makeActive', 'makeInActive', 'toggle', or 'guardResponse')"];
GetNext;
ENDLOOP; -- while
};
GetListOfRefAny:
PROC []
RETURNS [answer:
LIST
OF
REF
ANY] = {
reads until it hits a ']' — assumes caller has read the first token
r: LIST OF REF ANY ← NIL; -- the reversed list — order matters here
answer ← NIL;
WHILE
NOT (ContentsAre["]"]
OR ContentsAre["}"])
DO
SELECT t.kind
FROM
tokenINT => r ← CONS[NEW[INTEGER ← IntFromToken[[Get,NIL], t]], r];
tokenREAL => r ← CONS[NEW[REAL ← RealFromToken[[Get,NIL], t]], r];
tokenROPE => r ← CONS[RopeFromToken[[Get,NIL], t], r]; -- Rope is already a REF
tokenATOM => r ← CONS[NEW[ATOM ← AtomFromToken[[Get,NIL], t]], r];
ENDCASE => SyntaxError["Was expecting a valid token for a LIST OF REF ANY"];
GetNext;
ENDLOOP;
now build the list we really want to return, which has the proper order:
FOR l:
LIST
OF
REF
ANY ← r, l.rest
UNTIL l =
NIL
DO
answer ← CONS[l.first, answer];
ENDLOOP;
};
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
RETURN[ NARROW[GeneralGetProc[], REF ViewerClasses.NotifyProc]^ ];
};
GetDisplayProc:
PROC []
RETURNS [
REF Menus.DrawingRec] = {
RETURN[
NEW [Menus.DrawingRec ← [NARROW[GeneralGetProc[], REF Menus.DrawingProc] ^, GetListOfRefAny[] ] ]
];
};
GetQualifiedName:
PROC []
RETURNS [Rope.
ROPE] = {
assumes first token not read, doesn't read past where it needs to
firstPart: Rope.ROPE;
secondPart: Rope.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.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.