-- FileListsImpl.Mesa  
--   Last edited by Sandman on July 8, 1980  9:14 AM
--   Last edited by Sweet on August 28, 1980  10:01 AM
--   Last edited by Lewis on 14-Dec-80 19:27:35
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  BcdDefs USING [VersionStamp],
  Environment USING [PageCount, wordsPerPage],
  Inline USING [BITAND, BITXOR],
  FileLists,
  Segments USING [PageCount],
  Space USING [
    Create, Delete, GetHandle, Handle, LongPointer, Map, 
    PageFromLongPointer, virtualMemory],
  LongString USING [AppendChar, AppendString, EquivalentString],
  IncludeCheckerTable USING [
    Allocate, AddNotify, Base, Create, Destroy, DropNotify, Notifier,
    Region, Selector];

FileListsImpl: PROGRAM
    IMPORTS Inline, Space, LongString, IncludeCheckerTable
    EXPORTS FileLists =
  BEGIN  OPEN FileLists;
  
  FilenameChars: CARDINAL = 39;
  
  
 -- DYNAMICALLY ALLOCATED TABLES
  
  userListType:    IncludeCheckerTable.Selector = 0;
  fileEntryType:   IncludeCheckerTable.Selector = 1;
  includeFileType: IncludeCheckerTable.Selector = 2;
  incFileDescType: IncludeCheckerTable.Selector = 3;
  
    
  ulb, feb, ifb, ifdb: PUBLIC IncludeCheckerTable.Base;
  
  UpdateTableBases: IncludeCheckerTable.Notifier =
    BEGIN
    ulb ← base[userListType];
    feb ← base[fileEntryType];
    ifb ← base[includeFileType];
    ifdb ← base[incFileDescType];
    END;
    

    
 -- HASHING  AND STRING OPERATIONS
  
  HVSize: CARDINAL = 71;
  ulHashVec: ARRAY [0..HVSize) OF UserListPtr;
  feHashVec: ARRAY [0..HVSize) OF FE;
  ifdHashVec: ARRAY [0..HVSize) OF IncFileDesc;
  
  HashFn: PROC [s: LONG STRING] RETURNS [[0..HVSize)] =
    BEGIN
    
    CharBits: PROC [CHARACTER, WORD] RETURNS [WORD] =
      LOOPHOLE[Inline.BITAND];

    Mask: WORD = 337B;
    n: CARDINAL = s.length;
    v: WORD;
    v ← CharBits[s[0], Mask]*177B + CharBits[s[n - 1], Mask];
    RETURN[Inline.BITXOR[v, n*17B] MOD HVSize]
    END;
    
  CompareString: PROC [a, b: LONG STRING] RETURNS [{less, equal, greater}] =
    BEGIN
    l: CARDINAL = MIN[a.length, b.length];
    i: CARDINAL;
    ca, cb: CHARACTER;
    
    CharAnd: PROC [CHARACTER, WORD] RETURNS [CHARACTER] = Inline.BITAND;
    
    FOR i IN [0..l) DO
      ca ← a[i];  cb ← b[i];
      IF ca IN ['a..'z] THEN ca ← CharAnd[ca, 137B]; -- ignore case shifts
      IF cb IN ['a..'z] THEN cb ← CharAnd[cb, 137B];
      IF ca < cb THEN RETURN[less];
      IF ca > cb THEN RETURN[greater];
      ENDLOOP;
    RETURN[
      SELECT a.length FROM
	< b.length => less,
	= b.length => equal,
	ENDCASE => greater]
    END;

    
 -- INITIALIZATION AND FINALIZATION
  
  TablePages: Segments.PageCount = 150;
  tableSpace: LONG POINTER ← NIL;
  
  Initialize: PUBLIC PROC =
    BEGIN
    tableRegion: IncludeCheckerTable.Region;
    TableWeights: ARRAY [0..3] OF CARDINAL ← [2, 4, 11, 3]; -- empiracal
    tableSpace ← GetTableStorage[pages: TablePages];
    tableRegion ← [
      origin: LOOPHOLE[tableSpace, IncludeCheckerTable.Base],
      size: (TablePages * Environment.wordsPerPage)];
    IncludeCheckerTable.Create[
      region: tableRegion, 
      weights: DESCRIPTOR[TableWeights]];
    IncludeCheckerTable.AddNotify[UpdateTableBases];
    FOR i: CARDINAL IN [0..HVSize) DO 
      ulHashVec[i] ← ULnil;
      feHashVec[i] ← FEnil;
      ifdHashVec[i] ← IFDnil;
      ENDLOOP;
    userList ← userListEnd ← ULnil;
    fileList ← FEnil;
    END;
    
  Finalize: PUBLIC PROC =
    BEGIN
    IncludeCheckerTable.DropNotify[UpdateTableBases];
    IncludeCheckerTable.Destroy[];
    FreeTableStorage[tableSpace];
    END;

  GetTableStorage: PROC [
      pages: Environment.PageCount] RETURNS [base: LONG POINTER] =
    BEGIN
    new: Space.Handle ← Space.Create[
      size: pages, parent: Space.virtualMemory];
    Space.Map[new];
    base ← Space.LongPointer[new];
    END;

  FreeTableStorage: PROC [base: LONG POINTER] = 
     {Space.Delete[Space.GetHandle[Space.PageFromLongPointer[base]]]};

    
 -- USER-SPECIFIED FILE NAME LIST
  
  userList: UserListPtr ← ULnil;
  userListEnd: UserListPtr ← ULnil;
  mainPart: STRING ← [FilenameChars + 1];
  
  InsertInUserList: PUBLIC PROC [fileName: LONG STRING] =
    BEGIN
    hash: [0..HVSize);
    newItem: UserListPtr;
    mainPart.length ← 0;
    FOR i: CARDINAL IN [0..fileName.length) DO 
      IF fileName[i] = '. THEN EXIT;
      LongString.AppendChar[mainPart, fileName[i]];
      ENDLOOP;
    hash ← HashFn[mainPart];
    FOR p: UserListPtr ← ulHashVec[hash], ulb[p].next WHILE p # ULnil DO
      IF LongString.EquivalentString[mainPart, @ulb[p].name] THEN RETURN;
      ENDLOOP;
    newItem ← ulHashVec[hash] ← NewUserListItem[mainPart, ulHashVec[hash]];
    IF userList = ULnil THEN userList ← userListEnd ← newItem
    ELSE 
      BEGIN
      ulb[userListEnd].link ← newItem;
      userListEnd ← newItem;
      END;
    END;
    
  NewUserListItem: PROC [
      name: LONG STRING, next: UserListPtr] RETURNS [p: UserListPtr] =
    BEGIN
    p ← IncludeCheckerTable.Allocate[userListType, (SIZE[UserListItem] + (name.length + 1)/2)];
    ulb[p] ← UserListItem[
      next: next, 
      link: ULnil, 
      name: [length: 0, maxlength: name.length, text: ]];
    LongString.AppendString[@ulb[p].name, name];
    RETURN[p];
    END;
    
  IsInUserList: PUBLIC PROC [fileName: LONG STRING] RETURNS [BOOLEAN] =
    BEGIN
    IF userList = ULnil THEN RETURN[FALSE]
    ELSE RETURN[ScanUserList[fileName].found];
    END;
    
  ScanUserList: PROC [
      fileName: LONG STRING]
      RETURNS [found: BOOLEAN, userListName: LONG STRING] =
    BEGIN
    hash: [0..HVSize);
    IF userList # ULnil THEN
      BEGIN
      hash ← HashFn[fileName];
      FOR p: UserListPtr ← ulHashVec[hash], ulb[p].next WHILE p # ULnil DO
        IF LongString.EquivalentString[fileName, @ulb[p].name] THEN
	  RETURN[TRUE, @ulb[p].name];
        ENDLOOP;
      END;
    RETURN[FALSE, NIL];
    END;
    
  EnumerateUserList: PUBLIC PROC [
      userProc: PROC[LONG STRING] RETURNS [BOOLEAN]] =
    BEGIN
    FOR p: UserListPtr ← userList, ulb[p].link WHILE p # ULnil DO
      IF userProc[@ulb[p].name] THEN RETURN;
      ENDLOOP;
    END;
    
  UserListLength: PUBLIC PROC RETURNS [count: CARDINAL] =
    BEGIN
     
    AddOne: PROC[LONG STRING] RETURNS [stop: BOOLEAN] = 
      {count ← count+1;  RETURN[FALSE]};
    
    count ← 0;
    IF userList # ULnil THEN EnumerateUserList[AddOne];
    RETURN[count];
    END;

    
 -- FILE LIST
  
  fileList: PUBLIC FE ← FEnil;
  userNameCopy: STRING ← [FilenameChars + 1];
  
  InsertInFileList: PUBLIC PROC [name: LONG STRING] RETURNS [fe: FE] =
    BEGIN
    found: BOOLEAN;
    userListName: LONG STRING;
    hash: [0..HVSize) ← HashFn[name];
    userNameCopy.length ← 0;
    FOR fe ← feHashVec[hash], feb[fe].next WHILE fe # FEnil DO
      IF LongString.EquivalentString[name, @feb[fe].name] THEN RETURN[fe];
      ENDLOOP;
    [found, userListName] ← ScanUserList[name]; -- use user's name if possible
    IF found THEN 
      BEGIN
      LongString.AppendString[to: userNameCopy, from: userListName];
      fe ← feHashVec[hash] ← NewFE[userNameCopy, feHashVec[hash]];
      END
    ELSE fe ← feHashVec[hash] ← NewFE[name, feHashVec[hash]];
    RETURN[fe];
    END;
    
  NewFE: PROC [name: LONG STRING, next: FE] RETURNS [fe: FE] =
    BEGIN
    last: FE ← FEnil; -- follows fe
    FOR fe ← fileList, feb[fe].link UNTIL fe = FEnil DO
      SELECT CompareString[@feb[fe].name, name] FROM
	less => last ← fe;
	equal => ERROR;
	ENDCASE => EXIT;
      ENDLOOP;
    fe ← IncludeCheckerTable.Allocate[fileEntryType, (SIZE[FileEntry] + (name.length + 1)/2)];
    IF last = FEnil THEN
      BEGIN
      feb[fe] ← FileEntry[
	next: next, link: fileList,
	name: StringBody[length: 0, maxlength: name.length, text:]];
      fileList ← fe;
      END
    ELSE
      BEGIN
      feb[fe] ← FileEntry[
	next: next, link: feb[last].link,
	name: [length: 0, maxlength: name.length, text:]];
      feb[last].link ← fe;
      END;
    LongString.AppendString[@feb[fe].name, name];
    RETURN[fe];
    END;

    
 -- INCLUDES/INCLUDED BY LISTS
  
  InsertIncludeFileItem: PUBLIC PROC [
        incList: IncFile, 
        fe: FE, feName: LONG STRING, stamp: BcdDefs.VersionStamp,
        fileOpenedByCompiler: BOOLEAN] 
      RETURNS [IncFile] =
    BEGIN
    p: IncFile;
    incListLast: IncFile ← IFnil;
    incListFile: FE;
    FOR p ← incList, ifb[p].link UNTIL p = IFnil DO
      incListFile ← ifdb[ifb[p].includeFileDesc].file;
      SELECT CompareString[@feb[incListFile].name, feName] FROM
	less => incListLast ← p;
	equal => RETURN[incList];
	ENDCASE => EXIT;
      ENDLOOP;
    p ← IncludeCheckerTable.Allocate[includeFileType, SIZE[IncludeFileItem]];
    IF incListLast = IFnil THEN
      BEGIN
      ifb[p] ← IncludeFileItem[
	link: incList, includeFileDesc: AddIFD[fe, feName, stamp],
	fileOpenedByCompiler: fileOpenedByCompiler];
      incList ← p;
      END
    ELSE
      BEGIN
      ifb[p] ← IncludeFileItem[
	link: ifb[incListLast].link, 
        includeFileDesc: AddIFD[fe, feName, stamp],
	fileOpenedByCompiler: fileOpenedByCompiler];
      ifb[incListLast].link ← p;
      END;
    RETURN[incList];
    END;

    
 -- INCLUDED FILE DESCRIPTORS (avoids duplicating data in includes/included by lists)
  
  AddIFD: PROC [
        fe: FE, feName: LONG STRING, stamp: BcdDefs.VersionStamp]
      RETURNS [d: IncFileDesc] =
    BEGIN
    i: [0..HVSize) ← HashFn[feName];
    incFile: FE;
    FOR d ← ifdHashVec[i], ifdb[d].next UNTIL d = IFDnil DO
      incFile ← ifdb[d].file;
      IF LongString.EquivalentString[feName, @feb[incFile].name] 
        AND stamp = ifdb[d].stamp THEN RETURN[d];
      ENDLOOP;
    d ← ifdHashVec[i] ← NewIFD[fe, stamp, ifdHashVec[i]]; -- get new ifd
    RETURN[d];
    END;
    
  NewIFD: PROC [
        fe: FE, stamp: BcdDefs.VersionStamp, next: IncFileDesc]
      RETURNS [d: IncFileDesc] =
    BEGIN
    d ← IncludeCheckerTable.Allocate[incFileDescType, SIZE[IncFileDescItem]];
    ifdb[d] ← IncFileDescItem[next: next, file: fe, stamp: stamp];
    RETURN[d];
    END;
    
  END.