-- MDScanImpl.Mesa, last edit 17-Dec-81 12:59:49
-- Pilot 6.0/ Mesa 7.0
-- scanning and parsing subroutines for the system modeller

DIRECTORY
  Ascii: TYPE USING [CR, FF, TAB],
  CWF: TYPE USING [SWF1, WF0, WF1, WF2, WF3],
  File: TYPE USING [Capability],
  LongString: TYPE USING [EqualString, EquivalentString],
  MDSubr: TYPE USING [AddToDep, AddToDepends, AddToMod, ADepRecord, DepSeq, EntrySeq, 
  	GetAnEntry, InsertOtherSym, IsAlpha, IsDigit, Look, lookNil, LookRecord, 
	LookSeq, ModInfo, ModType, StringSeq, Token, versionUnknown],
  Space: TYPE USING [Create, Delete, Handle, Map, virtualMemory],
  Stream: TYPE USING [Handle],
  String: TYPE USING [CompareStrings, StringToDecimal],
  Subr: TYPE USING [AllocateString, Any, CopyString, EndsIn, EnumerateDirectory, FreeString, 
  	GetChar, GetCreateDateWithSpace, GetLine, GetString, LongZone, PackedTime, 
	strcpy, StripLeadingBlanks, SubStrCopy],
  SystemInternal: TYPE USING [UniversalID],
  Time: TYPE USING [Current];

MDScanImpl: PROGRAM 
IMPORTS CWF, LongString, MDSubr, Space, String, Subr, Time
EXPORTS MDSubr = {

AliasSeqRecord: TYPE = RECORD[
	size: CARDINAL ← 0,
	body: SEQUENCE maxsize: CARDINAL OF AliasRecord
	];
AliasRecord: TYPE =  RECORD[
	str: LONG STRING ← NIL,
	val: LONG STRING ← NIL
	];

-- MDS USAGE !!!
peektok: PUBLIC MDSubr.Token;
peekvalue: PUBLIC LONG UNSPECIFIED;
nextchar: CHAR;
tokstring: LONG POINTER TO ARRAY MDSubr.Token OF LONG STRING ← NIL;
aliasseq: LONG POINTER TO AliasSeqRecord ← NIL;
savestr: LONG STRING ← NIL;
toksave: LONG STRING ← NIL;
-- endof MDS USAGE

NALIASES: CARDINAL = 50;
MaxLineLength: CARDINAL = 600;

-- parse the stream handle, add that parent depends 
-- on everything in the streamhandle
-- it turns out we ignore IMPORTS and EXPORTS since those entries must appear
-- in the DIRECTORY clause
-- if pmod = NIL then use depseq
ParseObject: PUBLIC PROC[sh: Stream.Handle, parent: CARDINAL,
	entryseq: MDSubr.EntrySeq, pmod: MDSubr.ModInfo, 
	depseq: MDSubr.DepSeq, stringseq: MDSubr.StringSeq, sfn: STRING] = {
tok: MDSubr.Token ← tokBAD;
tokvalue: LONG UNSPECIFIED;
str: LONG STRING;
child: CARDINAL;
stemp: STRING ← [100];
imp, isconfig, isdefns: BOOL ← FALSE;
savename: STRING ← [100];

ScanInit[sh];
IF pmod ~= NIL THEN {
	pmod.isconfig ← pmod.isdefn ← FALSE;
	pmod.impinx ← pmod.expinx ← pmod.dirinx ← 0;
	};
WHILE tok ~= tokBEGIN AND tok ~= tokEOF DO
	SELECT tok FROM 
	tokDIR => tok ← Direct[sh, parent, entryseq, pmod, depseq, sfn];
	tokCONFIG => {
		IF savename.length > 0 AND depseq ~= NIL THEN 
			depseq.modulename ← Subr.CopyString[savename, entryseq.entryzone];
		isconfig ← TRUE;
		[tok,tokvalue] ← NextTok[sh];
		};
	tokPROGRAM => {
		IF savename.length > 0 AND depseq ~= NIL THEN 
			depseq.modulename ← Subr.CopyString[savename, entryseq.entryzone];
		isconfig ← FALSE;
		[tok,tokvalue] ← NextTok[sh];
		};
	tokMONITOR => {
		IF savename.length > 0 AND depseq ~= NIL THEN 
			depseq.modulename ← Subr.CopyString[savename, entryseq.entryzone];
		isconfig ← FALSE;
		[tok,tokvalue] ← NextTok[sh];
		};
	tokDEFINITIONS => {
		IF savename.length > 0 AND depseq ~= NIL THEN 
			depseq.modulename ← Subr.CopyString[savename, entryseq.entryzone];
		isdefns ← TRUE;
		[tok,tokvalue] ← NextTok[sh];
		};
	tokIMPORTS, tokEXPORTS => {
		imp ← tok = tokIMPORTS;
		[tok, tokvalue] ← NextTok[sh];
		WHILE tok = tokID OR tok = tokCOLON OR tok = tokCOMMA DO
			IF tok = tokID AND peektok ~= tokCOLON THEN {
				str ← tokvalue;
				str ← GetAlias[str];
				CWF.SWF1[stemp,"%s.bcd"L, str];
				IF pmod ~= NIL THEN {
					[child,] ← MDSubr.GetAnEntry[stemp,
						entryseq];
					MDSubr.AddToMod[pmod, child, IF imp THEN
						imports ELSE exports];
					}
				ELSE {
					adeprecord: MDSubr.ADepRecord ← [];
					adeprecord.bcdfilename
						← Subr.CopyString[stemp, entryseq.entryzone];
					adeprecord.relation ← IF imp THEN
						imports ELSE exports;
					adeprecord.modulename
						← Subr.CopyString[tokvalue, entryseq.entryzone];
					AddToDep[depseq, @adeprecord];
					};
				};
			[tok, tokvalue] ← NextTok[sh];
			ENDLOOP;
		};
	ENDCASE => {
		-- this may be the module name
		IF tok = tokID THEN Subr.strcpy[savename, tokvalue];
		[tok,tokvalue] ← NextTok[sh];
		};
	ENDLOOP;
IF pmod ~= NIL THEN {
	pmod.isconfig ← isconfig;
	pmod.isdefn ← isdefns;
	}
ELSE{
	depseq.isconfig ← isconfig;
	depseq.isdefns ← isdefns;
	};
IF NOT isconfig THEN RETURN;
tok ← ConfigBody[sh, parent, entryseq, sfn, depseq];
IF tok = tokEND AND stringseq ~= NIL THEN
	ParseStrings[sh, stringseq];
RETURN;
};

-- parses the configuration AFTER the {
ConfigBody: PROC[sh: Stream.Handle, parent: CARDINAL,
	entryseq: MDSubr.EntrySeq, sfn: STRING, depseq: MDSubr.DepSeq] 
	RETURNS[tok: MDSubr.Token] = {
tokvalue: LONG UNSPECIFIED;
str: LONG STRING;
child: CARDINAL;
stemp: STRING ← [40];
save: STRING ← [40];

-- [itemlist] ← id : id [idlist] LINKS : {CODE, FRAME} {PLUS, THEN} ...;
-- id : id ← ...;
DO
 	{
	[tok,tokvalue] ← NextTok[sh];
	save.length ← 0;
	IF tok = tokID THEN Subr.strcpy[save, tokvalue]; -- used if ← or :
	IF tok = tokEND THEN EXIT;
	IF tok = tokLB THEN {
 		WHILE tok ~= tokRB AND tok ~= tokSEMI AND tok ~= tokEND
 		AND tok ~= tokEOF DO
 			[tok, tokvalue] ← NextTok[sh];
			IF tok = tokID THEN AddAlias[tokvalue, NIL];
 			ENDLOOP;
 		CheckTok[tok,tokRB,sfn];
 		}
	-- ELSE IF peektok = tokCOLON THEN {
 		-- [tok,] ← NextTok[sh];
		-- IF peektok = tokID AND save.length > 0 THEN
			-- AddAlias[save, NIL];
 		-- [tok,tokvalue] ← NextTok[sh];
 		-- }
		;
 	IF peektok = tokARROW THEN {
 		[tok,] ← NextTok[sh];
		IF peektok = tokID AND save.length > 0 THEN	
			AddAlias[save, NIL];
 		[tok,tokvalue] ← NextTok[sh];
 		};
 	DO
 		CheckTok[tok, tokID,sfn];
 		IF peektok = tokCOLON THEN {
			-- id1: id2 
			-- id1: CONFIGURATION
			str ← tokvalue;
			Subr.strcpy[save, str];
 			[] ← NextTok[sh];
 			[tok, tokvalue] ← NextTok[sh];
			IF tok = tokCONFIG THEN {
				WHILE tok ~= tokEOF AND tok ~= tokBEGIN DO
					[tok, tokvalue] ← NextTok[sh];
					ENDLOOP;
				IF tok = tokEOF THEN RETURN[tok];
				AddAlias[save, NIL];
				tok ← ConfigBody[sh, parent, entryseq, sfn,
					depseq];
				CheckTok[tok, tokEND,sfn];
				[tok,] ← NextTok[sh];
				CheckTok[tok, tokSEMI,sfn];
				GOTO outer;
				};
 			CheckTok[tok, tokID,sfn];
			AddAlias[save, NIL];	-- ignore id1
 			};
 		str ← tokvalue;
		str ← GetAlias[str];
		IF str ~= NIL THEN {
	 		CWF.SWF1[stemp, "%s.bcd"L, str];
	 		IF entryseq ~= NIL THEN {
				[child,] ← MDSubr.GetAnEntry[stemp, entryseq];
	 			MDSubr.AddToDepends[parent, child, entryseq];
				}
			ELSE {
				adeprecord: MDSubr.ADepRecord ← [];
				adeprecord.bcdfilename
					← Subr.CopyString[stemp, entryseq.entryzone];
				MDSubr.AddToDep[depseq, @adeprecord];
				};
			};
 		[tok,] ← NextTok[sh];
		WHILE tok ~= tokSEMI AND tok ~= tokPLUS AND tok ~= tokTHEN
		AND tok ~= tokEND AND tok ~= tokEOF DO
 			[tok, tokvalue] ← NextTok[sh];
 			ENDLOOP;
 		IF tok = tokPLUS OR tok = tokTHEN THEN {
 			[tok, tokvalue] ← NextTok[sh];
 			LOOP;
 			};
 		EXIT;
 		ENDLOOP;
 	IF tok = tokSEMI THEN LOOP
	ELSE {
		CheckTok[tok, tokEND, sfn];
		RETURN[tok];
		};
	EXITS
	outer => NULL;
 	};
	ENDLOOP;
};

-- Interface: FROM "FileName": TYPE USING [a,b,c],;
-- Interface: TYPE USING [a,b,c],;
-- Interface ,;
-- FileName: TYPE Interface
-- FileName: TYPE
Direct: PROC[sh: Stream.Handle, parent: CARDINAL, entryseq: MDSubr.EntrySeq, 
	pmod: MDSubr.ModInfo, depseq: MDSubr.DepSeq, sfn: STRING]
	RETURNS[MDSubr.Token] = {
tok: MDSubr.Token;
tokvalue: LONG UNSPECIFIED;
filename: LONG STRING;
stemp: STRING ← [40];
child: CARDINAL;
interface: STRING ← [100];
i: CARDINAL;
isfrom: BOOL;

[tok,tokvalue] ← NextTok[sh];
WHILE tok ~= tokPROGRAM AND tok ~= tokEOF AND tok ~= tokDEFINITIONS
AND tok ~= tokSEMI AND tok~= tokCONFIG DO
	IF tok = tokID THEN {
		filename ← tokvalue;
		Subr.strcpy[interface, filename];
		[tok, tokvalue] ← NextTok[sh];
		IF tok = tokCOLON THEN {
			[tok, tokvalue] ← NextTok[sh];
			IF tok = tokFROM THEN isfrom ← TRUE
			ELSE IF tok = tokTYPE THEN isfrom ← FALSE
			ELSE CheckTok[tok, tokFROM, sfn];
			IF peektok = tokSTRLIT OR peektok = tokID THEN {
				[tok, tokvalue] ← NextTok[sh];
				filename ← tokvalue;
				IF NOT isfrom THEN {
					Subr.strcpy[stemp, filename];
					Subr.strcpy[filename, interface];
					Subr.strcpy[interface, stemp];
					};
				IF Subr.EndsIn[filename, ".bcd"L] THEN
					filename.length ← filename.length - 4;
				IF NOT LongString.EquivalentString[interface,
				 filename] THEN {
					IF Subr.Any[filename, '>] THEN {
						i ← filename.length - 1;
						DO
						   IF filename[i] = '> THEN {
						       Subr.SubStrCopy[filename,
							filename, i+1];
						   	EXIT;
						   	};
						   IF i = 0 THEN EXIT;
						   i ← i - 1;
						   ENDLOOP;
						};
					AddAlias[interface, filename];
					Subr.strcpy[interface, filename];
					};
				};
			};
		CWF.SWF1[stemp,"%s.bcd"L,interface];
		IF pmod ~= NIL THEN {
			[child,] ← MDSubr.GetAnEntry[stemp, entryseq];
			MDSubr.AddToDepends[parent, child, entryseq];
			MDSubr.AddToMod[pmod, child, directory];
			}
		ELSE {
			adeprecord: MDSubr.ADepRecord  ← [];
			adeprecord.bcdfilename ← Subr.CopyString[stemp, entryseq.entryzone];
			adeprecord.relation ← directory;
			adeprecord.modulename ← Subr.CopyString[interface, entryseq.entryzone];
			AddToDep[depseq, @adeprecord];
			};
		WHILE tok ~= tokCOMMA AND tok ~= tokSEMI AND tok ~= tokEOF
		AND tok ~= tokPROGRAM AND tok ~= tokDEFINITIONS AND
		tok ~= tokCONFIG DO
			IF tok = tokLB THEN {
				WHILE tok ~= tokEOF AND tok ~= tokRB DO
					[tok,tokvalue] ← NextTok[sh];
					ENDLOOP;
				CheckTok[tok, tokRB, sfn];
				};
			[tok,tokvalue] ← NextTok[sh];
			ENDLOOP;
		}
		ELSE [tok,tokvalue] ← NextTok[sh];
	ENDLOOP;
RETURN[tok];
};


ParseStrings: PROC[sh: Stream.Handle, stringseq: MDSubr.StringSeq] = {
line: STRING ← [MaxLineLength];
linx: CARDINAL;
str: STRING ← [MaxLineLength];
val: STRING ← [MaxLineLength];
WHILE Subr.GetLine[sh,line] DO
	linx ← 0;
	linx ← Subr.GetString[line, str, linx];
	IF str.length = 0 OR NOT MDSubr.IsAlpha[str[0]] THEN LOOP;
	linx ← Subr.GetString[line, val, linx];
	IF val.length ~= 1 OR val[0] ~= '= THEN LOOP;
	Subr.SubStrCopy[val, line, linx];
	Subr.StripLeadingBlanks[str];
	Subr.StripLeadingBlanks[val];
	MDSubr.InsertOtherSym[str, val, stringseq];
	ENDLOOP;
};


-- initiallizes the various data structures
ScanInit: PUBLIC PROC [st: Stream.Handle]  = {
IF aliasseq = NIL THEN Init[];
FreeAliases[];	-- clear out any aliases here previously
peektok ← tokBAD;
peekvalue ← 0;
nextchar ← Ascii.CR;
[] ← NextTok[st];
};

-- to free this memory, simply call StopScanner
Init: PROC = {
longzone: UNCOUNTED ZONE ← Subr.LongZone[];
tokstring ← longzone.NEW[ARRAY MDSubr.Token OF LONG STRING];
InitTokString[];
aliasseq ← longzone.NEW[AliasSeqRecord[NALIASES]];
savestr ← Subr.AllocateString[200];
toksave ← Subr.AllocateString[200];
};

-- frees memory as needed, call only once
StopScanner: PUBLIC PROC = {
longzone: UNCOUNTED ZONE ← Subr.LongZone[];
IF tokstring = NIL THEN RETURN;	-- Init never called
FOR t: MDSubr.Token IN MDSubr.Token DO
	Subr.FreeString[tokstring[t]];
	ENDLOOP;
longzone.FREE[@tokstring];
FreeAliases[];
longzone.FREE[@aliasseq];
Subr.FreeString[toksave];
Subr.FreeString[savestr];
savestr ← toksave ← NIL;
};

-- frees memory for aliases, call many times
FreeAliases: PROC = {
FOR i: CARDINAL IN [0 .. aliasseq.size) DO
	Subr.FreeString[aliasseq[i].str];
	Subr.FreeString[aliasseq[i].val];
	ENDLOOP;
aliasseq.size ← 0;
};

-- NOTE: this checks the type of MDSubr.Token in case the string literal must 
-- be saved
NextTok: PUBLIC PROC [st: Stream.Handle] RETURNS[MDSubr.Token,LONG UNSPECIFIED] ={
tok: MDSubr.Token;
tokvalue: LONG UNSPECIFIED;

tok ← peektok;
IF tok = tokID OR tok = tokSTRLIT THEN {
	Subr.strcpy[toksave,peekvalue];
	tokvalue ← toksave;
	}
ELSE tokvalue ← peekvalue;
[peektok,peekvalue] ← ReadTok[st];
-- IF Subr.debugflg THEN {
	-- CWF.WF1["tok %s"L,GetTokString[tok]];
	-- IF tok = tokID THEN CWF.WF1[" = '%s'\n"L,tokvalue];
	-- CWF.WF0["\n"L];
	-- };
RETURN[tok,tokvalue];
};

-- if tok = tokID, tokvalue is a STRING
ReadTok: PROC [st: Stream.Handle]
	RETURNS[toktype: MDSubr.Token,tokval: LONG UNSPECIFIED] ={
i: CARDINAL;
lastchar: CHAR;

DO
	IF nextchar = 0C THEN RETURN[tokEOF,0];
	WHILE nextchar = '  OR nextchar = Ascii.CR OR nextchar = Ascii.TAB DO
		nextchar ← Subr.GetChar[st]
		ENDLOOP;
	IF MDSubr.IsAlpha[nextchar] THEN {
		i ← 0;
		WHILE (MDSubr.IsAlpha[nextchar]  OR MDSubr.IsDigit[nextchar])
		AND i <= savestr.maxlength DO
			savestr[i] ← nextchar;
			i ← i + 1;
			nextchar ← Subr.GetChar[st];
			ENDLOOP;
		savestr.length ← i;
		toktype ← KeywordLookup[savestr];
		IF toktype ~= tokBAD THEN RETURN[toktype,0];
		-- not keyword, must be identifier
		RETURN[tokID,savestr]
		}
	ELSE IF MDSubr.IsDigit[nextchar] THEN {
		stemp: STRING ← [40];
		i ← 0;
		WHILE MDSubr.IsDigit[nextchar] AND i <= stemp.maxlength DO
			stemp[i] ← nextchar;
			i ← i+1;
			nextchar ← Subr.GetChar[st];
			ENDLOOP;
		stemp.length ← i;
		RETURN[tokNUM,String.StringToDecimal[stemp]]
		}
	ELSE {
		lastchar ← nextchar;
		nextchar ← Subr.GetChar[st];
		SELECT lastchar FROM
		'( => 	toktype ← tokLP;
		') => 	toktype ← tokRP;
		'[ => 	toktype ← tokLB;
		'] => 	toktype ← tokRB;
		'{ => 	toktype ← tokBEGIN;
		'} => 	toktype ← tokEND;
		'. => 	toktype ← tokDOT;
		': => 	toktype ← tokCOLON;
		', => 	toktype ← tokCOMMA;
		'@ => 	toktype ← tokAT;
		'! => 	toktype ← tokBANG;
		'; => 	toktype ← tokSEMI;
		'> => 	toktype ← tokGT;
		'< => 	toktype ← tokLT;
		'↑ => 	toktype ← tokUP;
		'← => 	toktype ← tokARROW;
		Ascii.FF => LOOP;
		0C => 	toktype ← tokEOF;
		'= => 	{
			toktype ← tokEQ;
			IF nextchar = '> THEN {
				nextchar ← Subr.GetChar[st];
				toktype ← tokIMPLIES;
				};
			};
		'- => 	{
			{
			IF nextchar ~= '- THEN CWF.WF0["bad comment\n"L];
			nextchar ← Subr.GetChar[st];
			WHILE nextchar ~= Ascii.CR AND nextchar ~= 0C DO
				IF nextchar = '- THEN {
					nextchar ← Subr.GetChar[st];
					IF nextchar = '- THEN {
						nextchar ← Subr.GetChar[st];
						GOTO goon;
						};
					};
				nextchar ← Subr.GetChar[st];
				ENDLOOP;
			nextchar ← Subr.GetChar[st];
			EXITS
			goon => NULL;
			};
			LOOP;
			};
		'" =>	{
			i ← 0;
			WHILE i < savestr.maxlength DO
				savestr[i] ← nextchar;
				nextchar ← Subr.GetChar[st];
				i ← i + 1;
				IF nextchar = '" THEN {
					nextchar ← Subr.GetChar[st];
					IF nextchar = '" THEN LOOP;
					EXIT;
					};
				REPEAT
				FINISHED =>
					CWF.WF0["String literal too long\n"L];
				ENDLOOP;
			savestr.length ← i;
			RETURN[tokSTRLIT, savestr];
			};
		ENDCASE => {
			i: INTEGER ← LOOPHOLE[lastchar];
			CWF.WF1["unknown char %c\n"L,@i];
			toktype ← tokEOF;
			};
		RETURN[toktype,0];
		};
	ENDLOOP;
};

-- return the tok if found, return tokBAD if error
KeywordLookup: PROC[str: LONG STRING] RETURNS[tok: MDSubr.Token] = {
OPEN LongString;
IF EquivalentString[str,"BEGIN"L] THEN RETURN[tokBEGIN];
-- deviation due to use of Defs modules "Code"
IF EqualString[str,"CODE"L] THEN RETURN[tokCODE];
IF EquivalentString[str,"CONFIGURATION"L] THEN RETURN[tokCONFIG];
IF EquivalentString[str,"CONTROL"L] THEN RETURN[tokCONTROL];
IF EquivalentString[str,"DEFINITIONS"L] THEN RETURN[tokDEFINITIONS];
-- deviation due to use of Defs modules "Directory"
IF EqualString[str,"DIRECTORY"L] THEN RETURN[tokDIR];
IF EquivalentString[str,"END"L] THEN RETURN[tokEND];
IF EquivalentString[str,"EXPORTS"L] THEN RETURN[tokEXPORTS];
IF EquivalentString[str,"FRAME"L] THEN RETURN[tokFRAME];
IF EquivalentString[str,"FROM"L] THEN RETURN[tokFROM];
IF EquivalentString[str,"IMPORTS"L] THEN RETURN[tokIMPORTS];
IF EquivalentString[str,"LET"L] THEN RETURN[tokLINKS];
IF EquivalentString[str,"LINKS"L] THEN RETURN[tokLINKS];
IF EquivalentString[str,"MONITOR"L] THEN RETURN[tokMONITOR];
IF EquivalentString[str,"OPEN"L] THEN RETURN[tokLINKS];
IF EquivalentString[str,"OTHERS"L] THEN RETURN[tokOTHERS];
IF EquivalentString[str,"PACK"L] THEN RETURN[tokPACK];
IF EquivalentString[str,"PLUS"L] THEN RETURN[tokPLUS];
IF EquivalentString[str,"PROC"L] THEN RETURN[tokPROC];
IF EquivalentString[str,"PROGRAM"L] THEN RETURN[tokPROGRAM];
IF EquivalentString[str,"RETURNS"L] THEN RETURN[tokRETURNS];
IF EquivalentString[str,"SELECT"L] THEN RETURN[tokSELECT];
IF EquivalentString[str,"SHARES"L] THEN RETURN[tokSHARES];
-- deviation due to use of Defs modules "String"
IF EqualString[str,"STRING"L] THEN RETURN[tokSTRING];
IF EquivalentString[str,"THEN"L] THEN RETURN[tokTHEN];
IF EquivalentString[str,"TYPE"L] THEN RETURN[tokTYPE];
IF EquivalentString[str,"USING"L] THEN RETURN[tokUSING];
RETURN[tokBAD];
};

GetTokString: PUBLIC PROC[tok: MDSubr.Token] RETURNS[LONG STRING] = {
RETURN[tokstring[tok]];
};

CheckTok: PUBLIC PROC[tokis, tokshouldbe: MDSubr.Token, sfn: STRING] = {
IF tokis ~= tokshouldbe THEN
	CWF.WF3["MDSubr.Token is %s, should be %s, in file %s\n"L,
		GetTokString[tokis],GetTokString[tokshouldbe],sfn];
};

AddAlias: PROC[str, val: LONG STRING] = {
IF aliasseq.size >= aliasseq.maxsize THEN CWF.WF0["Error - too many aliases\n"L]
ELSE {
	aliasseq[aliasseq.size].str ← Subr.CopyString[str];
	aliasseq[aliasseq.size].val ← IF val ~= NIL THEN Subr.CopyString[val] ELSE NIL;
	aliasseq.size ← aliasseq.size + 1;
	};
};

-- aliases must be correctly capitalized
GetAlias: PROC[str: LONG STRING] RETURNS[LONG STRING] = {
FOR i: CARDINAL IN [0 .. aliasseq.size) DO
	IF LongString.EqualString[str, aliasseq[i].str] THEN
		RETURN[aliasseq[i].val];
	ENDLOOP;
RETURN[str];
};


AddToMod: PUBLIC PROC[pmod: MDSubr.ModInfo, child: CARDINAL, type: MDSubr.ModType] =
{
SELECT type FROM
imports => {
	IF pmod.impinx >= LENGTH[pmod.imports] THEN
		CWF.WF0["pmod - too many imports\n"L]
	ELSE {
		pmod.imports[pmod.impinx] ← child;
		pmod.impinx ← pmod.impinx + 1;
		};
	};
exports => {
	IF pmod.expinx >= LENGTH[pmod.exports] THEN
		CWF.WF0["pmod - too many exports\n"L]
	ELSE {
		pmod.exports[pmod.expinx] ← child;
		pmod.expinx ← pmod.expinx + 1;
		};
	};
directory => {
	IF pmod.dirinx >= LENGTH[pmod.dirname] THEN
		CWF.WF0["pmod - too many directory entries\n"L]
	ELSE {
		pmod.dirname[pmod.dirinx] ← child;
		pmod.dirinx ← pmod.dirinx + 1;
		};
	};
ENDCASE;
};

AddToDep: PUBLIC PROC[depseq: MDSubr.DepSeq, 
	padeprecord: POINTER TO MDSubr.ADepRecord] = {
IF padeprecord↑.relation = errortype THEN ERROR;
IF depseq.size + 1 >= depseq.maxsize THEN 
	CWF.WF0["Error - too many dependencies.\n"L]
ELSE {
	depseq[depseq.size] ← padeprecord↑;
	depseq.size ← depseq.size + 1;
	};
};

-- fills in FP if file in in lookseq
ReadLocalDirectory: PUBLIC PROC[lookseq: MDSubr.LookSeq] ={
fcount: CARDINAL ← 0;

	MyDirProc: PROC[cap: File.Capability, sfn: STRING] RETURNS [BOOL] = {
	look: MDSubr.Look;
	fcount ← fcount + 1;
	[look,] ← LookLook[sfn, lookseq];
	IF look ~= NIL THEN {
		look.cap ← cap;
		look.presentonlocaldisk ← TRUE;
		RETURN[FALSE];
		};
	RETURN[FALSE];
	};

-- CWF.WF0["Reading local directory.\n"L];
Subr.EnumerateDirectory[MyDirProc];
CWF.WF1["%u files in local directory\n"L, @fcount];
};

-- fills in lookseq with all files in directory
-- except if ThrowAwayThisFile[]
ReadInLocalDirectoryAll: PUBLIC PROC[lookseq: MDSubr.LookSeq,
	ThrowAwayThisFile: PROC[name: STRING] RETURNS[BOOL]] = {
fcount: CARDINAL ← 0;

	AddDirName: PROC[cap: File.Capability, sfn: STRING] RETURNS[BOOL] = {
	look: MDSubr.Look;
	fcount ← fcount + 1;
	IF ThrowAwayThisFile[sfn] THEN RETURN[FALSE];
	look ← AddToLook[sfn,lookseq];
	look.cap ← cap;
	look.presentonlocaldisk ← TRUE;
	IF lookseq.size >= lookseq.maxsize THEN {
		m: CARDINAL ← lookseq.maxsize;
		CWF.WF1[
		"Error - More than %d files, will do only that many files.\n"L,
			@m];
		RETURN[TRUE];
		};
	RETURN[FALSE];
	};

-- CWF.WF0["Begin reading local directory.\n"L];
Subr.EnumerateDirectory[AddDirName];
CWF.WF1["%u files in local directory\n"L, @fcount];
};

AnalyzeLocalFiles: PUBLIC PROC[oktosort: BOOL, lookseq: MDSubr.LookSeq, 
	getcreatedate: BOOL] = {
-- fill in mod time for all local files we need
time: Subr.PackedTime;
-- mtc, mtr: LONG CARDINAL;
count: CARDINAL ← 0;
look: MDSubr.Look;
filename: STRING ← [100];
space: Space.Handle;

IF oktosort THEN SortByFileCap[lookseq];
space ← Space.Create[size: 1, parent: Space.virtualMemory];
Space.Map[space];	-- map in for CopyIn
-- CWF.WF0["Begin reading local file times.\n"];
time ← Time.Current[];
FOR i: CARDINAL IN [0 .. lookseq.size) DO
	look ← @lookseq[i];
	IF look.name = NIL OR NOT look.need
		OR NOT look.presentonlocaldisk
		OR look.localversion ~= MDSubr.versionUnknown THEN LOOP;
	-- [create: mtc, read: mtr, length: look.locallength] ←
		-- Subr.GetCreateDate[look.cap];
	-- [createDate: mtc, readDate: mtr, byteLength: look.locallength] ←
		-- Directory.GetProps[look.cap, filename
			-- ! Directory.Error => {
				-- CWF.WF0["Directory.Error.\n"L];
				-- mtc ← mtr ← 0;
				-- CONTINUE
			-- }];
	-- look.localversion ← IF getcreatedate THEN mtc ELSE mtr;
	look.localversion ← Subr.GetCreateDateWithSpace[look.cap, space];
	count ← count + 1;
	ENDLOOP;
time ← Time.Current[] - time;
Space.Delete[space: space];
IF count > 0 THEN 
	CWF.WF2["Took %lu secs to read properties of %u files.\n"L,
		@time, @count];
};

PrintLook: PUBLIC PROC[lookseq: MDSubr.LookSeq] = {
look: MDSubr.Look;
len: LONG CARDINAL;
CWF.WF0["Look Summary:\n"L];
FOR i: CARDINAL IN [0 .. lookseq.size) DO
	look ← @lookseq[i];
	IF look.name ~= NIL AND look.need THEN {
		CWF.WF1["File %s:\n"L,look.name];
		IF look.presentonlocaldisk THEN {
			CWF.WF0["   Local Disk"L];
			IF look.localversion ~= MDSubr.versionUnknown THEN {
				len ← look.localversion; 
				CWF.WF1[", Time %lt"L, @len];
				};
			CWF.WF0["\n"L];
			};
		};
	ENDLOOP;
};


SortByFileCap: PROC[lookseq: MDSubr.LookSeq]={

	FileTimeLessThan: PROC[left, right:MDSubr.Look]RETURNS[BOOL] = {
	l,r: LONG POINTER TO SystemInternal.UniversalID;
	l ← LOOPHOLE[@left.cap];
	r ← LOOPHOLE[@right.cap];
	RETURN[l.sequence < r.sequence];
	};

TreeSort[lookseq, FileTimeLessThan];
};

SortByFileTime: PUBLIC PROC[lookseq: MDSubr.LookSeq,
	descending: BOOL ← FALSE]={

	FileTimeLessThan: PROC[left, right:MDSubr.Look]RETURNS[BOOL] = {
	RETURN[IF descending
		THEN(left.localversion > right.localversion)
		ELSE(left.localversion < right.localversion)];
	};

TreeSort[lookseq, FileTimeLessThan];
};

SortByFileName: PUBLIC PROC[lookseq: MDSubr.LookSeq, 
	descending: BOOL ← FALSE]={

	FileNameLessThan: PROC[left, right: MDSubr.Look] RETURNS [BOOL] = {
	s1: STRING ← [100];
	s2: STRING ← [100];
	IF left.name = NIL OR right.name = NIL THEN RETURN[FALSE];
	Subr.strcpy[s1, left.name];
	Subr.strcpy[s2, right.name];
	-- IF String.CompareStrings[s1, s2, TRUE] > 0 THEN
		-- CWF.WF2["%s is greater than than %s\n"L, s1,s2];
	RETURN[IF descending
		THEN(String.CompareStrings[s1, s2, TRUE] > 0)
		ELSE(String.CompareStrings[s1, s2, TRUE] < 0)];
	};

TreeSort[lookseq, FileNameLessThan];
};

TreeSort: PROC[lookseq: MDSubr.LookSeq,LessThan: PROC[MDSubr.Look,
	  MDSubr.Look] RETURNS[BOOL]] = {
left, right: MDSubr.Look;

	siftUp: PROC[low, high: INTEGER] ={
	k, son: INTEGER;
	left, right: MDSubr.Look;
	k ← low;
	DO
		IF 2*k>high THEN EXIT;
		left ← @lookseq[2*k+1-1];
		right ← @lookseq[2*k-1];
		IF 2*k+1>high OR LessThan[left, right] THEN
			son ← 2*k
		ELSE
			son ← 2*k+1;
		left ← @lookseq[son-1];
		right ← @lookseq[k-1];
		IF LessThan[left, right] THEN {
			EXIT;
			};
		LookExch[left, right];
		k ← son;
		ENDLOOP;
	};

FOR i:CARDINAL DECREASING IN [1..lookseq.size/2] DO 
	siftUp[i,lookseq.size] 
	ENDLOOP;
FOR i:CARDINAL DECREASING IN [1..lookseq.size) DO
	left ← @lookseq[0];
	right ← @lookseq[i];
	LookExch[left, right];
	siftUp[1,i]
	ENDLOOP;
};

LookExch: PROC[left, right: MDSubr.Look] = {
t: MDSubr.LookRecord;
t ← left↑;
left↑ ← right↑;
right↑ ← t;
};

-- returns NIL if not found
LookLook: PUBLIC PROC[name: LONG STRING, lookseq: MDSubr.LookSeq] 
	RETURNS[look: MDSubr.Look, lookinx: CARDINAL] = {
FOR i: CARDINAL IN [0..lookseq.size) DO
	look ← @lookseq[i];
	IF  look.need
	AND look.name ~= NIL
	AND look.name.length = name.length
	AND LongString.EquivalentString[name, look.name]
		THEN RETURN[look, i];
	ENDLOOP;
RETURN[NIL, MDSubr.lookNil];
};

FreeLookSeq: PUBLIC PROC[plookseq: LONG POINTER TO MDSubr.LookSeq] = {
IF plookseq↑ = NIL THEN RETURN;
IF FALSE THEN
	-- no frees allowed
	FOR i: CARDINAL IN [0.. plookseq↑.size) DO
	Subr.FreeString[plookseq↑[i].name];
	ENDLOOP;
-- longzone.FREE[plookseq];
plookseq↑ ← NIL;
};

AddToLook: PUBLIC PROC[name: LONG STRING, lookseq: MDSubr.LookSeq] 
	RETURNS[look: MDSubr.Look] = {
IF lookseq.size >= lookseq.maxsize THEN {
	m: CARDINAL ← lookseq.maxsize;
	CWF.WF1["Error - more than %d files to look for."L, @m];
	RETURN[NIL];
	};
look ← @lookseq[lookseq.size];
-- other fields are defaulted
look↑ ← [need: TRUE, name: Subr.CopyString[name, lookseq.lookzone]];
lookseq.size ← lookseq.size + 1;
RETURN[look];
};

InitTokString: PROC = {
TokString: ARRAY MDSubr.Token OF STRING =
["ErrorMDSubr.Token"L, "EndOfFile"L, "("L, ")"L, "["L, "]"L, "."L,
":"L, "L,"L, "="L, "@"L, "!"L, ";"L, ">"L,"<"L,
"↑"L, "=>"L, "←"L, "Identifier"L, "Number"L, "TYPE"L,
"PLUS"L, "THEN"L, "PROC"L, "RETURNS"L, "StringLiteral"L, 
"STRING"L, "SELECT"L, "FROM"L, "DIRECTORY"L,"IMPORTS"L,
"EXPORTS"L,"PROGRAM"L,"{"L, "DEFINITIONS"L,
"OTHERS"L, "CONTROL"L, "CONFIGURATION"L, "LINKS"L, "CODE"L,
"FRAME"L, "PACK"L, "END"L, "USING"L, "SHARES"L, "LET"L, "OPEN"L, "MONITOR"L];
FOR t: MDSubr.Token IN MDSubr.Token DO
	tokstring[t] ← Subr.CopyString[TokString[t]];
	ENDLOOP;
};





}.