-- MDScanImpl.Mesa
-- last edit 17-Dec-81 12:59:49
-- last edit May 22, 1983 4:48 pm, Russ Atkinson
-- 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 STRINGNIL,
 val: LONG STRINGNIL
 ];

-- MDS USAGE !!!
peektok: PUBLIC MDSubr.Token;
peekvalue: PUBLIC LONG UNSPECIFIED;
nextchar: CHAR;
tokstring: LONG POINTER TO ARRAY MDSubr.Token OF LONG STRINGNIL;
aliasseq: LONG POINTER TO AliasSeqRecord ← NIL;
savestr: LONG STRINGNIL;
toksave: LONG STRINGNIL;
-- 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: LONG STRING] = {
tok: MDSubr.Token ← tokBAD;
tokvalue: LONG UNSPECIFIED;
str: LONG STRING;
child: CARDINAL;
stemp: STRING ← [100];
imp, isconfig, isdefns: BOOLFALSE;
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: LONG 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: LONG 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: INTEGERLOOPHOLE[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: LONG 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: LONG 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: LONG STRING] RETURNS[BOOL]] = {
fcount: CARDINAL ← 0;

AddDirName: PROC[cap: File.Capability, sfn: LONG 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: BOOLFALSE]={

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: BOOLFALSE]={

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;
};





}.