ConfigAndMesaDeps.Mesa
Last Edited by: Spreitzer, July 24, 1985 4:16:30 pm PDT
Carl Hauser, April 11, 1985 3:52:50 pm PST
DIRECTORY BasicTime, BcdDefs, BCDery, FS, IO, IOClasses, List, ListerUtils, MakeDo, RedBlackTree, Rope, TimeStamp;
ConfigAndMesaDeps:
CEDAR
PROGRAM
IMPORTS BasicTime, BCDery, FS, IO, IOClasses, List, ListerUtils, MakeDo, RedBlackTree, Rope =
BEGIN OPEN MakeDo;
GotoSyntaxError: ERROR = CODE;
ROPE: TYPE = Rope.ROPE;
StampRef:
TYPE =
REF StampRep; StampRep:
TYPE =
RECORD [
created: BasicTime.GMT,
stamp: TimeStamp.Stamp];
SourceData:
TYPE =
REF SourceDataRep; SourceDataRep:
TYPE =
RECORD [
bcdName: ROPE,
sourceType: SourceType,
supports: SupportList,
sourceNode, bcdNode: Node,
sourceStamp: Stamp ← TimeStamp.Null,
bcdCreateTime: BasicTime.GMT,
cmd: ROPE ← NIL
];
SourceType: TYPE = {Mesa, Config};
SupportList:
TYPE =
LIST
OF Support; Support:
TYPE =
RECORD [
node: Node,
version: Stamp];
ParseData:
TYPE =
REF ParseDataRep; ParseDataRep:
TYPE =
RECORD [
source: ROPE,
sourceType: SourceType,
stamp: BasicTime.GMT ← BasicTime.nullGMT,
refdModules: RopeList ← NIL];
parses: RedBlackTree.Table ← RedBlackTree.Create[GetParseKey, CompareParses];
configAndMesaClass: CommandClass ←
NEW [CommandClassRep ← [
NotCurrent: SourceNotCurrent,
Rederive: RederiveSource,
UpdateTime: FileTime,
Explain: ExplainSource]];
debugging: BOOLEAN ← FALSE;
GetParseKey: RedBlackTree.GetKey --
PROC [data: UserData]
RETURNS [Key] -- = {
RETURN[ data ] };
CompareParses: RedBlackTree.Compare
--
PROC [k: Key, data: UserData]
RETURNS [Basics.Comparison]
-- = {
GetKey:
PROC [ra:
REF
ANY]
RETURNS [r:
ROPE] = {
r ←
WITH ra
SELECT
FROM
x: ParseData => x.source,
x: ROPE => x,
ENDCASE => ERROR};
RETURN [GetKey[k].Compare[s2: GetKey[data], case: FALSE]]};
SourceFind:
PROC [resultName:
ROPE, finderData:
REF
ANY]
RETURNS [found:
BOOLEAN, sought: Node, makes, from, why: NodeList, cmd:
ROPE, class: CommandClass, foundData:
REF
ANY]
-- FinderProc -- =
BEGIN
bcdExpanded, shortName, baseName, sourceName, bcdName, configName, switches: ROPE;
bcdCP: FS.ComponentPositions;
sourceNode, bcdNode, configNode: Node;
bcdCreateTime: BasicTime.GMT;
md: SourceData;
sourceType: SourceType;
NoteDep:
PROC [fileName:
ROPE] =
{from ←
CONS[
GetNode[FS.ExpandName[fileName.Cat[".BCD"]].fullFName],
from]};
[bcdExpanded, bcdCP, ] ← FS.ExpandName[resultName];
IF
NOT (found ←
bcdExpanded.Substr[start: bcdCP.ext.start, len: bcdCP.ext.length].
Equal[s2: "BCD", case: FALSE]
OR (bcdCP.ext.length = 0))
THEN RETURN;
baseName ← bcdExpanded.Substr[start: 0, len: bcdCP.base.start + bcdCP.base.length];
shortName ← bcdExpanded.Substr[start: bcdCP.base.start, len: bcdCP.base.length];
bcdName ← baseName.Cat[".BCD"];
switches ← GetSwitches[bcdName];
makes ← LIST[sought ← bcdNode ← GetNode[bcdName]];
bcdCreateTime ← Latest[bcdNode, FileTime];
from ← NIL;
sourceType ← Mesa;
cmd ← Rope.Cat["RCompile ", switches, " ", sourceName ← baseName.Concat[".Mesa"]];
IF
(
NOT EnumerateDependancies[
(sourceNode ← GetNode[sourceName]),
sourceType,
NoteDep])
AND EnumerateDependancies[
(configNode ← GetNode[configName ← baseName.Concat[".Config"]]),
Config,
NoteDep]
THEN {
sourceName ← configName;
sourceNode ← configNode;
sourceType ← Config;
cmd ← Rope.Cat["Bind ", switches, " ", shortName]};
why ← LIST[sourceNode];
foundData ← md ← NEW [SourceDataRep ← [bcdName: bcdName, sourceType: sourceType, supports: NIL, sourceNode: sourceNode, bcdNode: bcdNode, bcdCreateTime: bcdCreateTime, cmd: cmd]];
IF bcdCreateTime # BasicTime.nullGMT THEN MakeSupports[md];
from ← CONS[sourceNode, from];
class ← configAndMesaClass;
END;
GetSwitches:
PROC [bcdName:
ROPE]
RETURNS [switches:
ROPE] =
BEGIN
ss: IO.STREAM ← NIL;
ss ← FS.StreamOpen[bcdName.Cat[".Switches"] !FS.Error => CONTINUE];
IF ss = NIL THEN RETURN [NIL];
[] ← ss.SkipWhitespace[];
IF ss.EndOf[] THEN RETURN [NIL];
switches ← ss.GetTokenRope[IO.IDProc].token;
ss.Close[];
END;
ExplainSource: ExplainProc
--PROC [c: Command, to: IO.STREAM]-- =
BEGIN
NoteStamps:
PROC [n: Node, cur, used: Stamp, dateworthy:
BOOLEAN ←
FALSE] =
BEGIN
to.PutF["\t%g%g\t", IO.rope[IF cur # used THEN "*" ELSE ""], IO.rope[n.PublicPartsOfNode[].name]];
TRUSTED {ListerUtils.PrintVersion[used, to, dateworthy]};
to.PutRope["\t"];
TRUSTED {ListerUtils.PrintVersion[cur, to, dateworthy]};
to.PutRope["\n"];
END;
md: SourceData ← NARROW[c.PublicPartsOfCommand[].foundData];
IF md.bcdCreateTime = BasicTime.nullGMT THEN {to.PutF["\t\tBCD didn't exist\n"]; RETURN};
to.PutF["\t%g of %g\n\treferences:\tused version:\tcurrent version\n", IO.rope[md.bcdName], IO.time[md.bcdCreateTime]];
NoteStamps[md.sourceNode, CurSourceStamp[md.sourceNode], md.sourceStamp, TRUE];
FOR sl: SupportList ← md.supports, sl.rest
WHILE sl #
NIL
DO
NoteStamps[sl.first.node, CurBCDStamp[sl.first.node], sl.first.version];
ENDLOOP;
END;
SourceNotCurrent:
PROC [c: Command]
RETURNS [notCurrent:
BOOLEAN]
--NotCurrentProc-- =
BEGIN
md: SourceData ← NARROW[c.PublicPartsOfCommand[].foundData];
bcdCreateTime: BasicTime.GMT;
IF NOT Needed[md.bcdNode] THEN RETURN;
bcdCreateTime ← Latest[md.bcdNode];
IF bcdCreateTime = BasicTime.nullGMT THEN RETURN [FALSE];
IF bcdCreateTime # md.bcdCreateTime
THEN {
md.bcdCreateTime ← bcdCreateTime;
MakeSupports[md]};
IF NonCurrent[CurSourceStamp[md.sourceNode], md.sourceStamp] THEN RETURN [TRUE];
FOR sl: SupportList ← md.supports, sl.rest
WHILE sl #
NIL
DO
IF NonCurrent[CurBCDStamp[sl.first.node], sl.first.version] THEN RETURN [TRUE];
ENDLOOP;
notCurrent ← FALSE;
END;
NonCurrent:
PROC [cur, old: Stamp]
RETURNS [nc:
BOOLEAN] =
{nc ← (cur # TimeStamp.Null) AND (cur # old)};
CurSourceStamp:
PROC [node: Node]
RETURNS [stamp: Stamp] =
BEGIN
time: BasicTime.GMT ← Latest[node];
IF time = BasicTime.nullGMT THEN RETURN [TimeStamp.Null];
stamp ← [net: 0, host: 0, time: BasicTime.ToPupTime[time]];
END;
CurBCDStamp:
PROC [node: Node]
RETURNS [stamp: Stamp] =
BEGIN
sr: StampRef ← NARROW[List.Assoc[key: $Stamp, aList: node.PublicPartsOfNode[].props]];
created: BasicTime.GMT;
bcd: ListerUtils.RefBCD;
IF sr =
NIL
THEN node.SetProps[List.PutAssoc[
key: $Stamp,
val: sr ←
NEW [StampRep ← [
created: BasicTime.nullGMT,
stamp: TimeStamp.Null]],
aList: node.PublicPartsOfNode[].props]];
created ← Latest[node, FileTime];
IF created = BasicTime.nullGMT THEN RETURN [TimeStamp.Null];
IF created = sr.created THEN RETURN [sr.stamp];
sr.created ← created;
TRUSTED {bcd ← ListerUtils.ReadBcd[node.PublicPartsOfNode[].name !FS.Error => {bcd ← NIL; CONTINUE}]};
IF bcd = NIL THEN RETURN [TimeStamp.Null];
IF bcd.versionIdent # BcdDefs.VersionID THEN RETURN [TimeStamp.Null];
stamp ← sr.stamp ← bcd.version;
END;
MakeSupports:
PROC [md: SourceData] =
BEGIN
NoteSupport:
PROC [name:
ROPE, version: Stamp] =
BEGIN
extName: ROPE ← FS.ExpandName[name.Cat[".BCD"]].fullFName;
node: Node ← GetNode[extName];
md.supports ← CONS[[node, version], md.supports];
END;
bcd: ListerUtils.RefBCD;
md.supports ← NIL;
TRUSTED {bcd ← ListerUtils.ReadBcd[md.bcdName !FS.Error => {bcd ← NIL; CONTINUE}]};
IF bcd = NIL THEN RETURN;
IF bcd.versionIdent # BcdDefs.VersionID THEN RETURN;
md.sourceStamp ← bcd.sourceVersion;
[] ← BCDery.EnumerateFiles[bcd: bcd, bcdFileName: md.bcdName, to: NoteSupport];
END;
RederiveSource:
PROC [c: Command]
RETURNS [from: NodeList, cmd:
ROPE]
--RederiveProc-- =
BEGIN
md: SourceData ← NARROW[c.PublicPartsOfCommand[].foundData];
NoteDep:
PROC [fileName:
ROPE] =
{from ←
CONS[
GetNode[FS.ExpandName[fileName.Cat[".BCD"]].fullFName],
from]};
from ← NIL;
[] ← EnumerateDependancies[md.sourceNode, md.sourceType, NoteDep];
from ← CONS[md.sourceNode, from];
cmd ← md.cmd;
END;
Break:
IO.BreakProc = {
RETURN [
SELECT char
FROM
IN ['a .. 'z], IN ['A .. 'Z], IN ['0 .. '9] => other,
IO.SP, IO.CR, IO.TAB, IO.LF, IO.FF => sepr,
ENDCASE => break]};
Letter:
PROC [c:
CHAR]
RETURNS [letter:
BOOLEAN] =
INLINE
{letter ← (c IN ['a .. 'z]) OR (c IN ['A .. 'Z])};
EnumerateDependancies:
PROC [sourceNode: Node, sourceType: SourceType, consume:
PROC [fileName:
ROPE]]
RETURNS [exists:
BOOLEAN] =
BEGIN
pd: ParseData ← NARROW[parses.Lookup[sourceNode.PublicPartsOfNode[].name]];
cur: BasicTime.GMT;
IF pd = NIL THEN parses.Insert[pd ← NEW [ParseDataRep ← [source: sourceNode.PublicPartsOfNode[].name, sourceType: sourceType]], pd];
cur ← Latest[sourceNode, FileTime];
IF NOT (exists ← cur # BasicTime.nullGMT) THEN RETURN;
IF cur # pd.stamp
THEN {
NoteDep:
PROC [fileName:
ROPE] = {
pd.refdModules ← CONS[fileName, pd.refdModules];
consume[fileName]};
pd.stamp ← cur; pd.refdModules ← NIL;
SELECT pd.sourceType
FROM
Mesa => [] ← EnumerateMesaDependancies[sourceNode.PublicPartsOfNode[].name, NoteDep];
Config => [] ← EnumerateConfigDependancies[sourceNode.PublicPartsOfNode[].name, NoteDep];
ENDCASE => ERROR;
}
ELSE {
FOR rml: RopeList ← pd.refdModules, rml.rest
WHILE rml #
NIL
DO
consume[rml.first] ENDLOOP};
END;
EnumerateMesaDependancies:
PROC [sourceName:
ROPE, consume:
PROC [fileName:
ROPE]]
RETURNS [exists:
BOOLEAN] =
BEGIN
NextToken:
PROC
RETURNS [
ROPE] =
{RETURN [source.GetTokenRope[Break].token]};
ParseDirectory:
PROC
RETURNS [next:
ROPE] =
BEGIN
firstClause: BOOL ← TRUE;
ParseClause:
PROC
RETURNS [next:
ROPE] =
BEGIN
fileName: ROPE ← "?";
next ← NextToken[];
IF firstClause
THEN
{firstClause ← FALSE; IF next.Equal[";"] THEN RETURN};
IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError;
fileName ← next;
next ← NextToken[];
IF next.Equal[":"]
THEN
BEGIN
next ← NextToken[];
IF next.Equal["TYPE"]
THEN {
next ← NextToken[];
IF Letter[next.Fetch[0]] AND NOT next.Equal["USING"] THEN next ← NextToken[];
}
ELSE
IF next.Equal["FROM"]
THEN {
fileName ← source.GetRopeLiteral[];
next ← NextToken[];
}
ELSE ERROR GotoSyntaxError;
END;
IF next.Equal["USING"]
THEN
BEGIN
IF NOT (next ← NextToken[]).Equal["["] THEN ERROR GotoSyntaxError;
WHILE NOT (next ← NextToken[]).Equal["]"] DO NULL ENDLOOP;
next ← NextToken[];
END;
consume[fileName];
END;
IF (next ← NextToken[]).Equal["DIRECTORY"]
THEN
BEGIN
DO
IF (next ← ParseClause[]).Equal[";"] THEN EXIT;
IF NOT next.Equal[","] THEN ERROR GotoSyntaxError;
ENDLOOP;
next ← NextToken[];
END;
IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError;
END;
source: IO.STREAM ← NIL;
source ← FS.StreamOpen[fileName: sourceName, accessOptions: $read !FS.Error => {source ← NIL; CONTINUE}];
exists ← source # NIL;
IF debugging THEN Log[(IF exists THEN "Parsing %g" ELSE "Missing %g"), IO.rope[sourceName]];
IF NOT exists THEN RETURN;
source ← IOClasses.CreateCommentFilterStream[source];
[] ← source.GetIndex[];
[] ← ParseDirectory[!GotoSyntaxError =>
BEGIN
SIGNAL Warning[IO.PutFR["Syntax error in %g; parse aborted at %g", IO.rope[sourceName], IO.int[source.GetIndex[]]]];
CONTINUE
END];
source.Close[];
END;
IsConfig:
PROC [word:
ROPE]
RETURNS [is:
BOOLEAN] =
{is ← word.Equal["CONFIGURATION"] OR word.Equal["CONFIG"]};
EnumerateConfigDependancies:
PROC [sourceName:
ROPE, Consume:
PROC [fileName:
ROPE]]
RETURNS [exists:
BOOLEAN] =
BEGIN
locals: LIST OF LIST OF ROPE ← NIL;
AddDef: PROC [name: ROPE] = {locals.first ← CONS[name, locals.first]};
MaybeConsume:
PROC [moduleName:
ROPE] = {
FOR l:
LIST
OF
LIST
OF
ROPE ← locals, l.rest
WHILE l #
NIL
DO
FOR m:
LIST
OF
ROPE ← l.first, m.rest
WHILE m #
NIL
DO
IF m.first.Equal[moduleName] THEN RETURN;
ENDLOOP;
ENDLOOP;
Consume[moduleName]};
NextToken: PROC RETURNS [ROPE] = {RETURN [source.GetTokenRope[Break].token]};
ParseConfigDescription:
PROC =
BEGIN
next: ROPE ← ParseCDirectory[];
next ← ParseCPacking[next];
next ← ParseConfiguration[next];
IF NOT next.Equal["."] THEN ERROR GotoSyntaxError;
END;
ParseConfiguration:
PROC [first:
ROPE]
RETURNS [next:
ROPE] =
BEGIN
IF NOT Letter[first.Fetch[0]] THEN ERROR GotoSyntaxError;
IF NOT (next ← NextToken[]).Equal[":"] THEN ERROR GotoSyntaxError;
IF NOT IsConfig[next ← NextToken[]] THEN ERROR GotoSyntaxError;
next ← ParseConfigurationRemains[];
END;
ParseConfigurationRemains:
PROC
RETURNS [next:
ROPE] =
BEGIN
next ← ParseCHeadRemains[];
IF NOT next.Equal["="] THEN ERROR GotoSyntaxError;
ParseCBody[];
next ← NextToken[];
END;
ParseCBody:
PROC =
BEGIN
next: ROPE ← NextToken[];
curly: BOOLEAN;
IF next.Equal["BEGIN"] THEN curly ← FALSE
ELSE IF next.Equal["{"] THEN curly ← TRUE
ELSE ERROR GotoSyntaxError;
locals ← CONS[NIL, locals];
next ← NextToken[];
DO
semiSeen: BOOL ← FALSE;
next ← ParseCStatement[next];
WHILE next.Equal[";"]
DO
next ← NextToken[];
semiSeen ← TRUE;
ENDLOOP;
IF next.Equal[IF curly THEN "}" ELSE "END"] THEN EXIT;
IF NOT semiSeen THEN ERROR GotoSyntaxError;
ENDLOOP;
locals ← locals.rest;
END;
ParseCStatement:
PROC [first:
ROPE]
RETURNS [next:
ROPE] =
BEGIN
next ← first;
IF next.Equal["["]
THEN
BEGIN
next ← ParseItemList[FALSE];
IF NOT next.Equal["]"] THEN ERROR GotoSyntaxError;
next ← NextToken[];
IF NOT next.Equal["←"] THEN ERROR GotoSyntaxError;
next ← ParseCExpression[];
END
ELSE
BEGIN
lhs, rhs: ROPE;
named: BOOL ← FALSE;
lhs ← rhs ← next;
IF named ← (next ← NextToken[]).Equal[":"]
THEN
BEGIN
IF IsConfig[rhs ← NextToken[]]
THEN {
AddDef[lhs];
next ← ParseConfigurationRemains[];
RETURN};
next ← NextToken[];
END;
IF next.Equal["←"]
THEN
BEGIN
AddDef[lhs];
next ← ParseCExpression[];
END
ELSE
BEGIN
IF named THEN AddDef[lhs];
MaybeConsume[rhs];
IF next.Equal["["]
THEN
BEGIN
IF NOT (next ← NextToken[]).Equal["]"] THEN next ← EatIDList[next, FALSE];
IF NOT next.Equal["]"] THEN ERROR GotoSyntaxError;
next ← ParseCLinks[];
END
ELSE
IF next.Equal["LINKS"]
THEN {
SIGNAL Warning[IO.PutFR["[] missing before %g in %g", IO.int[source.GetIndex[]], IO.rope[sourceName]]];
next ← ParseCLinks[next];
};
END;
END;
END;
ParseCExpression:
PROC
RETURNS [next:
ROPE] =
BEGIN
next ← ParseCRightSide[];
WHILE next.Equal["PLUS"]
DO
next ← ParseCRightSide[];
ENDLOOP;
WHILE next.Equal["THEN"]
DO
next ← ParseCRightSide[];
ENDLOOP;
END;
ParseCRightSide:
PROC
RETURNS [next:
ROPE] =
BEGIN
next ← ParseItem[NIL, TRUE];
IF next.Equal["["]
THEN
BEGIN
IF NOT (next ← NextToken[]).Equal["]"] THEN next ← EatIDList[next, FALSE];
IF NOT next.Equal["]"] THEN ERROR GotoSyntaxError;
next ← ParseCLinks[];
END;
END;
ParseCHeadRemains:
PROC
RETURNS [next:
ROPE] =
BEGIN
next ← ParseCLinks[];
next ← ParseImports[next];
next ← ParseCExports[next];
next ← ParseControlClause[next];
END;
ParseCLinks:
PROC [first:
ROPE ←
NIL]
RETURNS [next:
ROPE] =
BEGIN
next ← IF first # NIL THEN first ELSE NextToken[];
IF NOT next.Equal["LINKS"] THEN RETURN;
IF NOT (next ← NextToken[]).Equal[":"] THEN ERROR GotoSyntaxError;
next ← NextToken[];
IF NOT (next.Equal["CODE"] OR next.Equal["FRAME"]) THEN ERROR GotoSyntaxError;
next ← NextToken[];
END;
ParseImports:
PROC [first:
ROPE]
RETURNS [next:
ROPE] =
BEGIN
IF NOT first.Equal["IMPORTS"] THEN RETURN [first];
next ← ParseItemList[FALSE];
END;
ParseCExports:
PROC [first:
ROPE]
RETURNS [next:
ROPE] =
BEGIN
IF NOT first.Equal["EXPORTS"] THEN RETURN [first];
next ← ParseItemList[FALSE];
END;
ParseControlClause:
PROC [first:
ROPE]
RETURNS [next:
ROPE] =
BEGIN
IF NOT first.Equal["CONTROL"] THEN RETURN [first];
next ← EatIDList[NIL, FALSE];
END;
ParseItemList:
PROC [notice:
BOOLEAN]
RETURNS [next:
ROPE] =
BEGIN
DO
next ← ParseItem[NIL, notice];
IF NOT next.Equal[","] THEN EXIT;
ENDLOOP;
END;
ParseItem:
PROC [first:
ROPE, notice:
BOOLEAN]
RETURNS [next:
ROPE] =
BEGIN
lhs, rhs: ROPE;
named: BOOL ← FALSE;
lhs ← rhs ← IF first = NIL THEN NextToken[] ELSE first;
IF named ← (next ← NextToken[]).Equal[":"]
THEN
BEGIN
rhs ← NextToken[];
next ← NextToken[];
END;
IF notice
THEN {
IF named THEN AddDef[lhs];
MaybeConsume[rhs]};
END;
ParseCDirectory:
PROC
RETURNS [next:
ROPE] =
BEGIN
firstClause: BOOL ← TRUE;
ParseClause:
PROC
RETURNS [next:
ROPE] =
BEGIN
moduleName: ROPE ← "?";
fileName: ROPE ← "?";
next ← NextToken[];
IF firstClause
THEN
{firstClause ← FALSE; IF next.Equal[";"] THEN RETURN};
IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError;
moduleName ← fileName ← next;
next ← NextToken[];
IF next.Equal[":"]
THEN
BEGIN
next ← NextToken[];
IF next.Equal["TYPE"]
THEN {
next ← NextToken[];
IF Letter[next.Fetch[0]] THEN next ← NextToken[];
}
ELSE
IF next.Equal["FROM"]
THEN {
fileName ← source.GetRopeLiteral[];
next ← NextToken[];
}
ELSE ERROR GotoSyntaxError;
END;
Consume[fileName];
AddDef[moduleName];
END;
locals ← CONS[NIL, locals];
IF NOT (next ← NextToken[]).Equal["DIRECTORY"] THEN RETURN;
DO
IF (next ← ParseClause[]).Equal[";"] THEN EXIT;
IF NOT next.Equal[","] THEN ERROR GotoSyntaxError;
ENDLOOP;
next ← NextToken[];
END;
ParseCPacking:
PROC [first:
ROPE]
RETURNS [next:
ROPE] =
BEGIN
next ← first;
WHILE next.Equal["PACK"]
DO
next ← EatIDList[NIL, FALSE];
IF NOT next.Equal[";"] THEN ERROR GotoSyntaxError;
next ← NextToken[];
ENDLOOP;
END;
EatIDList:
PROC [first:
ROPE, notice:
BOOLEAN]
RETURNS [next:
ROPE] =
BEGIN
next ← IF first = NIL THEN NextToken[] ELSE first;
DO
IF NOT Letter[next.Fetch[0]] THEN ERROR GotoSyntaxError;
IF notice THEN Consume[next];
next ← NextToken[];
IF NOT next.Equal[","] THEN EXIT;
next ← NextToken[];
ENDLOOP;
END;
source: IO.STREAM ← NIL;
source ← FS.StreamOpen[fileName: sourceName, accessOptions: $read !FS.Error => {source ← NIL; CONTINUE}];
exists ← source # NIL;
IF debugging THEN Log[(IF exists THEN "Parsing %g" ELSE "Missing %g"), IO.rope[sourceName]];
IF NOT exists THEN RETURN;
source ← IOClasses.CreateCommentFilterStream[source];
[] ← source.GetIndex[];
ParseConfigDescription[!GotoSyntaxError =>
BEGIN
SIGNAL Warning[IO.PutFR["Syntax error in %g; parse aborted at %g", IO.rope[sourceName], IO.int[source.GetIndex[]]]];
CONTINUE
END];
source.Close[];
END;
AddFinder[[SourceFind], front];
END.