-- BcdInfo.mesa; edited by Sandman, July 8, 1980  8:52 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [PageSize],
  AltoFileDefs USING [FP],
  BcdDefs USING [FTIndex, FTNull, FTSelf, MTIndex, SGIndex, Base, VersionID],
  BcdInfoDefs USING [],
  BcdOps USING [
    BcdBase, FTHandle, MTHandle, NameString, ProcessModules, ProcessSegs,
    SGHandle],
  DirectoryDefs USING [EnumerateDirectory],
  SegmentDefs USING [
    DeleteFileSegment, FileHandle, FileSegmentAddress, FileSegmentHandle,
    InsertFile, MoveFileSegment, NewFile, NewFileSegment, OldFileOnly, Read,
    SwapIn, Unlock, VMtoFileSegment],
  String USING [EquivalentSubStrings, SubString, SubStringDescriptor],
  Storage USING [Pages, FreePages];

BcdInfo: PROGRAM
  IMPORTS DirectoryDefs, BcdOps, SegmentDefs, String, Storage EXPORTS BcdInfoDefs
  =
  BEGIN OPEN SegmentDefs;
  
  bcd: BcdOps.BcdBase ← NIL;
  bcdseg: FileSegmentHandle ← NIL;
  
  SetBcd: PUBLIC PROCEDURE [name: STRING] RETURNS [BcdOps.BcdBase] =
    BEGIN
    pages: CARDINAL;
    ClearFileObjectTable[];
    ClearFrameObjectTable[];
    IF bcdseg # NIL THEN BEGIN Unlock[bcdseg]; DeleteFileSegment[bcdseg] END;
    bcdseg ← NewFileSegment[NewFile[name, Read, OldFileOnly], 1, 1, Read];
    SwapIn[bcdseg];
    bcd ← FileSegmentAddress[bcdseg];
    IF (pages ← bcd.nPages) # 1 THEN
      BEGIN
      Unlock[bcdseg];
      MoveFileSegment[bcdseg, 1, pages];
      SwapIn[bcdseg];
      bcd ← FileSegmentAddress[bcdseg];
      END;
    IF bcd.versionIdent # BcdDefs.VersionID THEN
      BEGIN Unlock[bcdseg]; DeleteFileSegment[bcdseg]; RETURN[NIL]; END;
    RETURN[bcd]
    END;
    
  -- SymbolTable Lookup
  
  FTIndex: TYPE = BcdDefs.FTIndex;
  FTHandle: TYPE = BcdOps.FTHandle;
  MTIndex: TYPE = BcdDefs.MTIndex;
  MTHandle: TYPE = BcdOps.MTHandle;
  SGIndex: TYPE = BcdDefs.SGIndex;
  SGHandle: TYPE = BcdOps.SGHandle;
  
  CodeForModule: PUBLIC PROCEDURE [mti: MTIndex] RETURNS [FileSegmentHandle] =
    BEGIN
    f: FrameItem;
    sgb, mtb: BcdDefs.Base;
    
    CodeFile: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.mti = mti]; END;
      
    IF (f ← EnumFrameObjects[CodeFile]) # NIL THEN RETURN[f.code];
    sgb ← LOOPHOLE[bcd + bcd.sgOffset];
    mtb ← LOOPHOLE[bcd + bcd.mtOffset];
    AddFileName[bcd, mtb[mti].code.sgi, @sgb[mtb[mti].code.sgi]];
    LookupFiles[];
    AddModuleCode[bcd, mti, @mtb[mti]];
    ClearFileObjectTable[];
    RETURN[EnumFrameObjects[CodeFile].code]
    END;
    
  SymbolsForModule: PUBLIC PROCEDURE [mti: MTIndex] RETURNS [FileSegmentHandle] =
    BEGIN
    f: FrameItem;
    sgb, mtb: BcdDefs.Base;
    
    SymbolFile: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.mti = mti]; END;
      
    IF (f ← EnumFrameObjects[SymbolFile]) # NIL THEN RETURN[f.symbols];
    sgb ← LOOPHOLE[bcd + bcd.sgOffset];
    mtb ← LOOPHOLE[bcd + bcd.mtOffset];
    AddFileName[bcd, mtb[mti].sseg, @sgb[mtb[mti].sseg]];
    LookupFiles[];
    AddModuleSymbols[bcd, mti, @mtb[mti]];
    ClearFileObjectTable[];
    RETURN[EnumFrameObjects[SymbolFile].symbols]
    END;
    
  AddFileName: PUBLIC PROCEDURE [
    bcd: BcdOps.BcdBase, sgi: SGIndex, sgh: SGHandle] =
    BEGIN
    
    ExistingFile: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.fti = sgh.file]; END;
      
    f: FileItem;
    IF EnumFileObjects[ExistingFile] # NIL THEN RETURN;
    f ← GetFileObject[];
    f↑ ← [fileHandle: NIL, fti: sgh.file];
    IF sgh.file = BcdDefs.FTSelf THEN
      f.fileHandle ← SegmentDefs.VMtoFileSegment[bcd].file;
    RETURN;
    END;
    
  AddModuleSymbols: PROCEDURE [bcd: BcdOps.BcdBase, mti: MTIndex, mth: MTHandle] =
    BEGIN
    sgh: SGHandle = @LOOPHOLE[bcd + bcd.sgOffset, BcdDefs.Base][mth.sseg];
    
    SymbolFile: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.fti = sgh.file]; END;
      
    fi: FileItem;
    fr: FrameItem;
    symfile: SegmentDefs.FileHandle;
    symfile ←
      IF (fi ← EnumFileObjects[SymbolFile]) = NIL THEN NIL ELSE fi.fileHandle;
    fr ← GetFrameItem[mti];
    fr.symbols ←
      IF symfile = NIL OR sgh.pages = 0 THEN NIL
      ELSE SegmentDefs.NewFileSegment[
	symfile, sgh.base, sgh.pages + sgh.extraPages, SegmentDefs.Read];
    RETURN;
    END;
    
  AddModuleCode: PROCEDURE [bcd: BcdOps.BcdBase, mti: MTIndex, mth: MTHandle] =
    BEGIN OPEN SegmentDefs;
    sgh: SGHandle = @LOOPHOLE[bcd + bcd.sgOffset, BcdDefs.Base][mth.code.sgi];
    
    CodeFile: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.fti = sgh.file]; END;
      
    fi: FileItem;
    fr: FrameItem;
    codeFile: FileHandle;
    codeFile ←
      IF (fi ← EnumFileObjects[CodeFile]) = NIL THEN NIL ELSE fi.fileHandle;
    fr ← GetFrameItem[mti];
    fr.code ←
      IF codeFile = NIL OR sgh.pages = 0 THEN NIL
      ELSE NewFileSegment[codeFile, sgh.base, sgh.pages, Read];
    RETURN;
    END;
    
  FindAllFiles: PUBLIC PROCEDURE =
    BEGIN
    
    EnterFile: PROCEDURE [sgh: SGHandle, sgi: SGIndex] RETURNS [BOOLEAN] =
      BEGIN AddFileName[bcd, sgi, sgh]; RETURN[FALSE]; END;
      
    DoModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN
      AddModuleSymbols[bcd, mti, mth];
      AddModuleCode[bcd, mti, mth];
      RETURN[FALSE];
      END;
      
    [] ← BcdOps.ProcessSegs[bcd, EnterFile];
    LookupFiles[];
    [] ← BcdOps.ProcessModules[bcd, DoModule];
    ClearFileObjectTable[];
    RETURN
    END;
    
  -- File Lookup Object Management
  
  FileObject: TYPE = RECORD [
    fileHandle: SegmentDefs.FileHandle, fti: BcdDefs.FTIndex];
  
  FileItem: TYPE = POINTER TO FileObject;
  
  FileObjectTable: TYPE = RECORD [
    link: POINTER TO FileObjectTable, subTable: ARRAY [0..0) OF FileObject];
  
  fileTable: POINTER TO FileObjectTable ← NIL;
  nFiles: CARDINAL ← MaxNFileObjects;
  MaxNFileObjects: CARDINAL = (AltoDefs.PageSize - 1)/SIZE[FileObject];
  
  GetFileObject: PROCEDURE RETURNS [f: FileItem] =
    BEGIN
    newTable: POINTER TO FileObjectTable;
    IF nFiles < MaxNFileObjects THEN
      BEGIN f ← @fileTable.subTable[nFiles]; nFiles ← nFiles + 1; END
    ELSE
      BEGIN
      newTable ← Storage.Pages[1];
      newTable.link ← fileTable;
      fileTable ← newTable;
      nFiles ← 1;
      RETURN[@fileTable.subTable[0]];
      END;
    END;
    
  EnumFileObjects: PROCEDURE [proc: PROCEDURE [f: FileItem] RETURNS [BOOLEAN]]
    RETURNS [f: FileItem] =
    BEGIN
    i: CARDINAL;
    table: POINTER TO FileObjectTable;
    IF fileTable = NIL THEN RETURN[NIL];
    IF nFiles < MaxNFileObjects THEN
      FOR i IN [0..nFiles) DO
	IF proc[f ← @fileTable.subTable[i]] THEN RETURN; ENDLOOP;
    FOR table ← fileTable.link, table.link UNTIL table = NIL DO
      FOR i IN [0..MaxNFileObjects) DO
	IF proc[f ← @table.subTable[i]] THEN RETURN; ENDLOOP;
      ENDLOOP;
    RETURN[NIL];
    END;
    
  ClearFileObjectTable: PROCEDURE =
    BEGIN
    table: POINTER TO FileObjectTable;
    UNTIL fileTable = NIL DO
      table ← fileTable;
      fileTable ← fileTable.link;
      Storage.FreePages[table];
      ENDLOOP;
    nFiles ← MaxNFileObjects;
    RETURN
    END;
    
  -- Symbol Table Management
  
  FrameObject: TYPE = RECORD [mti: MTIndex, symbols, code: FileSegmentHandle];
  
  FrameItem: TYPE = POINTER TO FrameObject;
  
  FrameObjectTable: TYPE = RECORD [
    link: POINTER TO FrameObjectTable, subTable: ARRAY [0..0) OF FrameObject];
  
  frameTable: POINTER TO FrameObjectTable ← NIL;
  nFrames: CARDINAL ← MaxNFrameObjects;
  MaxNFrameObjects: CARDINAL = (AltoDefs.PageSize - 1)/SIZE[FrameObject];
  
  GetFrameItem: PROCEDURE [mti: MTIndex] RETURNS [f: FrameItem] =
    BEGIN
    
    FindMatch: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.mti = mti]; END;
      
    IF (f ← EnumFrameObjects[FindMatch]) = NIL THEN f ← NewFrameItem[mti];
    END;
    
  NewFrameItem: PROCEDURE [mti: MTIndex] RETURNS [f: FrameItem] =
    BEGIN
    newTable: POINTER TO FrameObjectTable;
    IF nFrames < MaxNFrameObjects THEN
      BEGIN f ← @frameTable.subTable[nFrames]; nFrames ← nFrames + 1; END
    ELSE
      BEGIN
      newTable ← Storage.Pages[1];
      newTable.link ← frameTable;
      frameTable ← newTable;
      nFrames ← 1;
      f ← @frameTable.subTable[0];
      END;
    f↑ ← [mti: mti, code: NIL, symbols: NIL];
    END;
    
  EnumFrameObjects: PROCEDURE [proc: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN]]
    RETURNS [f: FrameItem] =
    BEGIN
    i: CARDINAL;
    table: POINTER TO FrameObjectTable;
    IF frameTable = NIL THEN RETURN[NIL];
    IF nFrames < MaxNFrameObjects THEN
      FOR i IN [0..nFrames) DO
	IF proc[f ← @frameTable.subTable[i]] THEN RETURN; ENDLOOP;
    FOR table ← frameTable.link, table.link UNTIL table = NIL DO
      FOR i IN [0..MaxNFrameObjects) DO
	IF proc[f ← @table.subTable[i]] THEN RETURN; ENDLOOP;
      ENDLOOP;
    RETURN[NIL];
    END;
    
  ClearFrameObjectTable: PROCEDURE =
    BEGIN
    table: POINTER TO FrameObjectTable;
    UNTIL frameTable = NIL DO
      table ← frameTable;
      frameTable ← frameTable.link;
      Storage.FreePages[table];
      ENDLOOP;
    nFrames ← MaxNFrameObjects;
    RETURN
    END;
    
  -- Directory Lookup of File Table
  
  nFilesToFind: CARDINAL;
  
  LookupFiles: PUBLIC PROCEDURE =
    BEGIN
    name: STRING ← [40];
    
    CountFilesToLookup: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] =
      BEGIN nFilesToFind ← nFilesToFind + 1; RETURN[FALSE]; END;
      
    nFilesToFind ← 0;
    [] ← EnumFileObjects[CountFilesToLookup];
    DirectoryDefs.EnumerateDirectory[CheckOne];
    RETURN
    END;
    
  CheckOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, name: STRING]
    RETURNS [found: BOOLEAN] =
    BEGIN OPEN String;
    i: CARDINAL;
    dirName: SubStringDescriptor;
    bcd: SubStringDescriptor ← [base: "bcd"L, offset: 0, length: 3];
    file: FileItem;
    FOR i IN [0..name.length) DO
      IF name[i] = '. THEN
	BEGIN
	IF name.length - i # 5 THEN GOTO UseWholeName;
	dirName ← [base: name, offset: i + 1, length: 3];
	IF ~EquivalentSubStrings[@dirName, @bcd] THEN GOTO UseWholeName;
	dirName.offset ← 0;
	dirName.length ← i;
	GOTO HasBCDExtension;
	END;
      REPEAT
	UseWholeName => NULL;
	HasBCDExtension =>
	  BEGIN
	  [found, file] ← FindFileName[@dirName];
	  IF found THEN RETURN[ThisIsTheOne[fp, file]];
	  END;
      ENDLOOP;
    dirName ← [base: name, offset: 0, length: name.length - 1];
    [found, file] ← FindFileName[@dirName];
    RETURN[IF found THEN ThisIsTheOne[fp, file] ELSE FALSE];
    END;
    
  ThisIsTheOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, file: FileItem]
    RETURNS [BOOLEAN] =
    BEGIN
    file.fileHandle ← SegmentDefs.InsertFile[fp, SegmentDefs.Read];
    RETURN[(nFilesToFind ← nFilesToFind - 1) = 0];
    END;
    
  FindFileName: PUBLIC PROCEDURE [name: String.SubString]
    RETURNS [found: BOOLEAN, file: FileItem] =
    BEGIN OPEN String;
    ns: BcdOps.NameString ← LOOPHOLE[bcd + bcd.ssOffset];
    ftb: BcdDefs.Base ← LOOPHOLE[bcd + bcd.ftOffset];
    filename: SubStringDescriptor ← [base: @ns.string, offset:, length:];
    
    MatchName: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] =
      BEGIN
      IF f.fti = BcdDefs.FTNull THEN RETURN[FALSE];
      IF f.fti = BcdDefs.FTSelf THEN RETURN[FALSE];
      filename.offset ← ftb[f.fti].name;
      filename.length ← ns.size[ftb[f.fti].name];
      IF LastCharIsDot[@filename] THEN name.length ← name.length + 1;
      RETURN[EquivalentSubStrings[@filename, name]];
      END;
      
    f: FileItem;
    f ← EnumFileObjects[MatchName];
    RETURN[f # NIL, f];
    END;
    
  LastCharIsDot: PUBLIC PROCEDURE [name: String.SubString] RETURNS [BOOLEAN] =
    BEGIN RETURN[name.base[name.offset + name.length - 1] = '.]; END;
    
  
  END...