DIRECTORY
CedarScanner,
FileIO,
IO,
Menus,
Rope,
ViewerClasses;
MenuParser: CEDAR PROGRAM
IMPORTS FileIO, IO, CedarScanner, Rope =
BEGIN OPEN CedarScanner;
global data:
t: Token;
menus: LIST OF Menus.Menu ← NIL;
globalRope: Rope.ROPE;
errorLog: IO.STREAM;
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];
};
Debug: PROC [msg: Rope.ROPE] = {
IO.PutF[errorLog, "%g\n", IO.rope[msg]];
IO.Flush[errorLog];
}; -- Debug
SyntaxError: PROC = {
this routine is called any time a syntax error is discovered.
IO.PutF[errorLog, "Syntax error found at token beginning in position [%d]\n", IO.int[t.start]];
ERROR;
};
CurrentContentsAre: PROC [r: Rope.ROPE] RETURNS [BOOLEAN] = {
RETURN[Rope.Equal[r, ContentsFromToken[[Get,NIL], t] ] ];
};
CurrentContents: PROC [] RETURNS [Rope.ROPE] = {
RETURN[ContentsFromToken[[Get,NIL], t] ];
};
ContentsAre: PROC [r: Rope.ROPE] RETURNS [BOOLEAN] = {
RETURN[ Rope.Equal[r, ContentsFromToken[[Get,NIL],t], FALSE] ];
};
GetNext: PROC[kind:TokenKind ← tokenERROR, string: Rope.ROPENIL, first: BOOLFALSE] = {
t ← GetToken[[Get, NIL], IF first THEN 0 ELSE t.next];
Debug[Rope.Cat["Just Got Token -->", ContentsFromToken[[Get, NIL], t], "<--"] ];
IF kind # tokenERROR THEN {
IF t.kind # kind THEN SyntaxError;
IF string # NIL THEN IF NOT Rope.Equal[string, ContentsFromToken[[Get, NIL], t]] THEN SyntaxError;
};
};
MustBe: PROC[string: Rope.ROPENIL] = {
IF NOT Rope.Equal[string, ContentsFromToken[[Get, NIL], t]] THEN SyntaxError;
};
SeparateQualifiedName: PROC[name:Rope.ROPE] RETURNS[first:Rope.ROPE, second:Rope.ROPE] = {
periodIndex: INTEGER ← Rope.Index[s1: name, s2: "."];
IF periodIndex = Rope.Length[name] THEN SyntaxError;
first ← Rope.Substr[name, 0, periodIndex];
second ← Rope.Substr[name, periodIndex + 1, Rope.Length[name] - periodIndex -1];
RETURN[first, second];
};
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[RopeFromToken[[GetForNamesFromRope, string], myToken], answer];
myToken ← GetToken[[GetForNamesFromRope, string], myToken.next];
ENDLOOP;
RETURN[answer];
};
the user entry
ParseDescription: PROC [def: Rope.ROPE, prevlist: LIST OF Menus.Menu ← NIL, errorFile: Rope.ROPE ← "menuParse.errlog"] RETURNS [LIST OF Menus.Menu] = {
errorLog ← FileIO.Open[errorFile, overwrite];
Debug["starting"];
globalRope ← def;
IO.Put[errorLog, IO.rope["Menu ErrorLog:\n\n"] ];
GetNext[first: TRUE];
menus ← prevlist;
WHILE t.kind # tokenEOF DO
SELECT t.kind FROM
tokenID => {
MustBe["Relabel"];
ParseRelabel[];
};
tokenSINGLE => {
MustBe["["];
ParseMenu[];
};
ENDCASE => SyntaxError;
GetNext;
ENDLOOP;
RETURN[menus];
};
internal parsing routines
ParseRelabel: PROC [] = {
menuname: Rope.ROPENIL;
module: Rope.ROPE;
oldname: Rope.ROPE;
newname: Rope.ROPE;
GetNext[tokenID, "["];
GetNext[tokenROPE];
oldname ← CurrentContents[];
[module, oldname] ← SeparateQualifiedName[oldname];
GetNext[tokenROPE];
newname ← CurrentContents[];
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.name ← 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.first ← 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;
GetNext;
ENDLOOP; -- while
};
ParseEntries: PROC [newMenu: REF Menus.Menu] = {
newEntry: REF Menus.Entry;
WHILE CurrentContentsAre["["] DO
newEntry ← NEW[Menus.Entry];
GetNext[tokenID];
newEntry.name ← CurrentContents[];
ParseEntryParms[newEntry];
GetNext[tokenDOUBLE, "=>"];
GetNext;
IF CurrentContentsAre["["] THEN newEntry.actions ← GetComplex[]
ELSE {
MustBe["=>"];
newEntry.actions ← LIST[ Menus.EntryNotifyRecord[trigger: LIST[all], notifyData: GetListOfRefAny[]] ];
GetNext[tokenSINGLE, "]"];
};
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^];
};
ENDLOOP;
};
ParseEntryParms: PROC [newEntry: REF Menus.Entry] = {
assumes first token already read for it by the caller.
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 THEN newEntry.displayData ← CurrentContents[]
ELSE newEntry.displayData ← GetDisplayProc[];
}
ELSE SyntaxError;
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 CurrentContentsAre["["] DO
newEntryNotifyRec ← NEW[Menus.EntryNotifyRecord];
newEntryNotifyRec.trigger ← GetTriggers[];
ParseTriggerParms[newEntryNotifyRec];
MustBe["=>"];
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;
RETURN[answer];
};
GetTriggers: PROC [] RETURNS [answer: 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.
answer ← NIL;
GetNext;
IF CurrentContentsAre["["] THEN {
GetNext[tokenID];
WHILE t.kind = tokenID DO
order for this list doesn't matter, so I can just CONS it onto the front
TriggerFromRope[CurrentContents[], answer];
ENDLOOP;
MustBe["]"];
}
ELSE IF t.kind = tokenID THEN TriggerFromRope[CurrentContents[], answer]
ELSE SyntaxError;
};
TriggerFromRope: PROC [r: Rope.ROPE, l: LIST OF Menus.MenuEntryTrigger] = {
the Trigger(s) the rope indicates are CONSed onto the front of the list
SELECT TRUE FROM
Rope.Equal["notrigger", r] => NULL;
Rope.Equal["all", r] => l ← CONS[all, l];
Rope.Equal["leftup", r] => l ← CONS[leftup, l];
Rope.Equal["middleup", r] => l ← CONS[middleup, l];
Rope.Equal["rightup", r] => l ← CONS[rightup, l];
Rope.Equal["shiftleftup", r] => l ← CONS[shiftleftup, l];
Rope.Equal["shiftmiddleup", r] => l ← CONS[shiftmiddleup, l];
Rope.Equal["shiftrightup", r] => l ← CONS[shiftrightup, l];
Rope.Equal["allnonshifts", r] => {
l ← CONS[leftup, l];
l ← CONS[middleup, l];
l ← CONS[rightup, l]
};
Rope.Equal["allshifts", r] => {
l ← CONS[shiftleftup, l];
l ← CONS[shiftmiddleup, l];
l ← CONS[shiftrightup, l]
};
ENDCASE => SyntaxError;
};
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;
GetNext;
ENDLOOP; -- while
};
GetListOfRefAny: PROC [] RETURNS [answer: LIST OF REF ANY] = {
reads until it hits a ']' — assumes caller has NOT read the first token
r: LIST OF REF ANYNIL; -- the reversed list — order matters here
answer ← NIL;
MustBe["=>"];
GetNext;
WHILE NOT CurrentContentsAre["]"] 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];
tokenCOMMENT => NULL;
ENDCASE => SyntaxError;
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] = {
runtime stuff
ERROR;
};
GetNotifyProc: PROC [] RETURNS [ViewerClasses.NotifyProc] = {
runtime stuff
ERROR;
};
GetDisplayProc: PROC [] RETURNS [REF Menus.DrawingRec] = {
runtime stuff
ERROR;
};
GetBoolean: PROC [] RETURNS [answer: BOOLEAN] = {
GetNext[tokenID];
IF ContentsAre["TRUE"] THEN RETURN[TRUE]
ELSE IF ContentsAre["FALSE"] THEN RETURN[FALSE]
ELSE SyntaxError;
};
END.