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.ROPENIL] 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.ROPENIL, first: BOOLFALSE] = {
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.ROPENIL] = {
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.ROPENIL;
secondParm: Rope.ROPENIL;
menuname: Rope.ROPENIL;
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;
};
MustBe["]"];
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: BOOLEANFALSE;
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 ANYNIL; -- 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.