ConfigAndMesaDeps.Mesa
Last Edited by: Spreitzer, April 8, 1985 4:47:33 pm PST
DIRECTORY BasicTime, BcdDefs, BCDery, FS, IO, IOClasses, List, ListerUtils, MakeDo, OrderedSymbolTableRef, Rope, TimeStamp;
ConfigAndMesaDeps: CEDAR PROGRAM
IMPORTS BasicTime, BCDery, FS, IO, IOClasses, List, ListerUtils, MakeDo, OrderedSymbolTableRef, 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: ROPENIL
];
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: OrderedSymbolTableRef.Table ← OrderedSymbolTableRef.CreateTable[CompareParses];
configAndMesaClass: CommandClass ← NEW [CommandClassRep ← [
NotCurrent: SourceNotCurrent,
Rederive: RederiveSource,
UpdateTime: FileTime,
Explain: ExplainSource]];
debugging: BOOLEANFALSE;
CompareParses: OrderedSymbolTableRef.CompareProc --PROC [r1, r2: Item] RETURNS [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[r1].Compare[s2: GetKey[r2], 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["Compile ", 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.STREAMNIL;
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: BOOLEANFALSE] =
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: ROPEFS.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]]];
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: BOOLTRUE;
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.STREAMNIL;
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 ROPENIL;
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: BOOLFALSE;
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: BOOLFALSE;
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: ROPENIL] 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: BOOLFALSE;
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: BOOLTRUE;
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.STREAMNIL;
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.