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

DIRECTORY
  AltoDefs USING [PageSize],
  AltoFileDefs USING [FP],
  BcdDefs USING [
    FTIndex, FTNull, FTSelf, MTIndex, SGIndex, VersionStamp, NullVersion, Base],
  BcdOps USING [
    BcdBase, FTHandle, MTHandle, NameString, ProcessModules, ProcessSegs,
    SGHandle],
  ControlDefs USING [FrameCodeBase, GFT, GFTItem, GlobalFrame, NullGlobalFrame],
  DirectoryDefs USING [EnumerateDirectory],
  ImageFileInfoDefs: FROM "imagefileinfodefs",
  ImageFormat USING [ImageHeader],
  LoadStateFormat USING [
    ModuleTable, ConfigIndex, NullConfig, LoadState, AltoVersionID, BcdObject],
  LoadStateOps USING [EnumerationDirection, Map],
  MiscDefs USING [Zero],
  SDDefs USING [SD, sGFTLength],
  SegmentDefs USING [
    AddressFromPage, DeleteFileSegment, FileHandle, FileSegmentAddress,
    FileSegmentHandle, FileSegmentObject, InsertFile, NewFile, NewFileSegment,
    OldFileOnly, Read, SwapIn, SwapOut, Unlock, VMtoFileSegment],
  String USING [
    AppendSubString, EquivalentSubStrings, SubString, SubStringDescriptor],
  Storage USING [Node, Pages, Free, FreePages];

ImageInfo: PROGRAM
  IMPORTS
    DirectoryDefs, ImageFileInfoDefs, BcdOps, MiscDefs, SegmentDefs, String,
    Storage
  EXPORTS ImageFileInfoDefs, LoadStateOps
  SHARES ImageFormat =
  BEGIN OPEN ImageFileInfoDefs, LoadStateFormat;
  
  SetImage: PUBLIC PROCEDURE [name: STRING] =
    BEGIN OPEN SegmentDefs;
    ClearFileObjectTable[];
    ClearFrameObjectTable[];
    IF headerSeg # NIL THEN
      BEGIN Unlock[headerSeg]; DeleteFileSegment[headerSeg] END;
    headerSeg ← NewFileSegment[NewFile[name, Read, OldFileOnly], 1, 1, Read];
    SwapIn[headerSeg];
    InitImageLoadState[headerSeg];
    InitializeImageCache[headerSeg];
    Unlock[headerSeg];
    RETURN
    END;
    
  headerSeg: FileSegmentHandle ← NIL;
  
  Version: PUBLIC PROCEDURE RETURNS [v: BcdDefs.VersionStamp] =
    BEGIN OPEN SegmentDefs;
    image: POINTER TO ImageFormat.ImageHeader;
    IF headerSeg = NIL THEN RETURN[BcdDefs.NullVersion];
    SwapIn[headerSeg];
    image ← FileSegmentAddress[headerSeg];
    v ← image.prefix.version;
    Unlock[headerSeg];
    RETURN
    END;
    
  HeaderSegment: PUBLIC PROCEDURE RETURNS [FileSegmentHandle] =
    BEGIN RETURN[headerSeg]; END;
    
  -- Global Frame Table management
  
  
  EnumerateGlobalFrames: PUBLIC PROCEDURE [
    proc: PROCEDURE [GlobalFrameHandle] RETURNS [BOOLEAN]]
    RETURNS [GlobalFrameHandle] =
    BEGIN
    i: CARDINAL;
    frame: GlobalFrameHandle;
    gft: POINTER TO ARRAY [0..0) OF ControlDefs.GFTItem ← ControlDefs.GFT;
    FOR i IN [0..SDDefs.SD[SDDefs.sGFTLength]) DO
      frame ← READ[@gft[i].frame];
      IF frame # ControlDefs.NullGlobalFrame AND READ[@gft[i].epbase] = 0 AND
	proc[frame] THEN RETURN[frame];
      ENDLOOP;
    RETURN[ControlDefs.NullGlobalFrame]
    END;
    
  GlobalFrame: TYPE = ControlDefs.GlobalFrame;
  globalFrame: GlobalFrame;
  
  VirtualGlobalFrame: PUBLIC PROCEDURE [frame: GlobalFrameHandle]
    RETURNS [GlobalFrameHandle] =
    BEGIN
    CopyRead[to: @globalFrame, from: frame, nwords: SIZE[GlobalFrame]];
    RETURN[@globalFrame]
    END;
    
  BcdBase: TYPE = BcdOps.BcdBase;
  EnumerationDirection: TYPE = LoadStateOps.EnumerationDirection;
  ConfigIndex: TYPE = LoadStateFormat.ConfigIndex;
  
  loadstate: PUBLIC LoadStateFormat.LoadState;
  gft: PUBLIC LoadStateFormat.ModuleTable;
  nbcds: CARDINAL;
  
  InputLoadState: PUBLIC PROCEDURE RETURNS [ConfigIndex] =
    BEGIN OPEN LoadStateFormat, SegmentDefs, SDDefs;
    IF state = NIL THEN ERROR;
    SwapIn[state];
    loadstate ← FileSegmentAddress[state];
    BEGIN
    ENABLE UNWIND => Unlock[state];
    IF loadstate.versionident # AltoVersionID THEN ERROR;
    END;
    gft ← DESCRIPTOR[@loadstate.gft, READ[SD + sGFTLength]];
    nbcds ← loadstate.nBcds;
    RETURN[nbcds]
    END;
    
  ReleaseLoadState: PUBLIC PROCEDURE =
    BEGIN OPEN SegmentDefs;
    IF ~state.swappedin THEN RETURN;
    Unlock[state];
    IF state.lock = 0 THEN BEGIN SwapOut[state]; loadstate ← NIL; nbcds ← 0; END;
    END;
    
  MapConfigToReal: PUBLIC PROCEDURE [cgfi: GFTIndex, config: ConfigIndex]
    RETURNS [rgfi: GFTIndex] =
    BEGIN
    IF cgfi = 0 THEN RETURN[0];
    FOR rgfi IN [0..LENGTH[gft]) DO
      IF gft[rgfi].config = config AND gft[rgfi].gfi = cgfi THEN RETURN[rgfi];
      ENDLOOP;
    RETURN[0];
    END;
    
  MapRealToConfig: PUBLIC PROCEDURE [rgfi: GFTIndex]
    RETURNS [cgfi: GFTIndex, config: ConfigIndex] =
    BEGIN RETURN[gft[rgfi].gfi, gft[rgfi].config]; END;
    
  GetMap: PUBLIC PROCEDURE [config: ConfigIndex] RETURNS [map: LoadStateOps.Map] =
    BEGIN
    max: CARDINAL ← 0;
    i: GFTIndex;
    FOR i IN [0..LENGTH[gft]) DO
      IF gft[i].config = config THEN max ← MAX[max, gft[i].gfi]; ENDLOOP;
    max ← max + 1;
    map ← DESCRIPTOR[Storage.Node[max], max];
    MiscDefs.Zero[BASE[map], max];
    FOR i IN [0..LENGTH[gft]) DO
      IF gft[i].config = config THEN map[gft[i].gfi] ← i; ENDLOOP;
    END;
    
  ReleaseMap: PUBLIC PROCEDURE [map: LoadStateOps.Map] =
    BEGIN Storage.Free[BASE[map]]; END;
    
  BcdSegFromLoadState: PUBLIC PROCEDURE [bcd: ConfigIndex]
    RETURNS [seg: FileSegmentHandle] =
    BEGIN OPEN SegmentDefs, LoadStateFormat;
    b: alto BcdObject ← LOOPHOLE[loadstate.bcds[bcd]];
    seg ← NewFileSegment[state.file, b.base, b.pages, Read];
    RETURN
    END;
    
  EnumerateLoadStateGFT: PUBLIC PROCEDURE [
    proc: PROCEDURE [GFTIndex, GFTIndex, ConfigIndex] RETURNS [BOOLEAN]]
    RETURNS [GFTIndex] =
    BEGIN
    i: GFTIndex;
    FOR i IN [0..LENGTH[gft]) DO
      IF proc[i, gft[i].gfi, gft[i].config] THEN RETURN[i]; ENDLOOP;
    RETURN[0]
    END;
    
  EnumerateBcds: PUBLIC PROCEDURE [
    dir: EnumerationDirection, proc: PROCEDURE [ConfigIndex] RETURNS [BOOLEAN]]
    RETURNS [config: ConfigIndex] =
    BEGIN
    SELECT dir FROM
      recentfirst =>
	FOR config DECREASING IN [0..loadstate.nBcds) DO
	  IF proc[config] THEN RETURN[config]; ENDLOOP;
      recentlast =>
	FOR config IN [0..loadstate.nBcds) DO
	  IF proc[config] THEN RETURN[config]; ENDLOOP;
      ENDCASE;
    RETURN[NullConfig]
    END;
    
  InitImageLoadState: PUBLIC PROCEDURE [seg: FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    image: POINTER TO ImageFormat.ImageHeader;
    SwapIn[seg];
    image ← FileSegmentAddress[seg];
    state ← NewFileSegment[
      seg.file, image.prefix.initialLoadStateBase, image.prefix.loadStatePages,
      Read];
    END;
    
  state: PUBLIC FileSegmentHandle;
  
  -- 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;
  
  AddSymbolFileName: PUBLIC PROCEDURE [
    bcd: BcdBase, sgi: SGIndex, sgh: SGHandle] =
    BEGIN
    
    ExistingFile: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.fti = sgh.file]; END;
      
    f: FileItem;
    IF sgh.class = code THEN RETURN;
    IF EnumFileObjects[ExistingFile] = NIL THEN
      BEGIN f ← GetFileObject[]; f↑ ← [fileHandle: NIL, fti: sgh.file]; END;
    IF sgh.file = BcdDefs.FTSelf THEN
      f.fileHandle ← SegmentDefs.VMtoFileSegment[bcd].file;
    RETURN;
    END;
    
  AddModuleSymbols: PUBLIC PROCEDURE [
    bcd: BcdBase, config: ConfigIndex, mti: MTIndex, mth: MTHandle] =
    BEGIN OPEN SegmentDefs;
    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: FileHandle;
    frame: GlobalFrameHandle;
    symfile ←
      IF (fi ← EnumFileObjects[SymbolFile]) = NIL THEN NIL ELSE fi.fileHandle;
    frame ← READ[@ControlDefs.GFT[MapConfigToReal[mth.gfi, config]].frame];
    fr ← GetFrameItem[];
    fr.symbols ←
      IF symfile = NIL OR sgh.pages = 0 THEN NIL
      ELSE NewFileSegment[symfile, sgh.base, sgh.pages + sgh.extraPages, Read];
    fr.frame ← frame;
    sgh ← @LOOPHOLE[bcd + bcd.sgOffset, BcdDefs.Base][mth.code.sgi];
    fr.code ← NewFileSegment[headerSeg.file, sgh.base, sgh.pages, Read];
    fr.offset ← mth.code.offset;
    RETURN;
    END;
    
  bcd: BcdBase;
  
  FindAllSymbols: PUBLIC PROCEDURE =
    BEGIN
    
    LookupEachBcd: PROCEDURE [config: ConfigIndex] RETURNS [BOOLEAN] =
      BEGIN
      
      EnterFile: PROCEDURE [sgh: SGHandle, sgi: SGIndex] RETURNS [BOOLEAN] =
	BEGIN AddSymbolFileName[bcd, sgi, sgh]; RETURN[FALSE]; END;
        
      DoModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
	BEGIN AddModuleSymbols[bcd, config, mti, mth]; RETURN[FALSE]; END;
        
      bcdseg: FileSegmentHandle ← BcdSegFromLoadState[config];
      SegmentDefs.SwapIn[bcdseg];
      bcd ← SegmentDefs.FileSegmentAddress[bcdseg];
      [] ← BcdOps.ProcessSegs[bcd, EnterFile];
      LookupFiles[];
      [] ← BcdOps.ProcessModules[bcd, DoModule];
      ClearFileObjectTable[];
      RETURN[FALSE]
      END;
      
    [] ← InputLoadState[];
    [] ← EnumerateBcds[recentfirst, LookupEachBcd];
    ReleaseLoadState[];
    RETURN
    END;
    
  SymbolSegForFrame: PUBLIC PROCEDURE [frame: GlobalFrameHandle]
    RETURNS [seg: FileSegmentHandle] =
    BEGIN
    
    SymbolFile: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.frame = frame]; END;
      
    FindModule: PROCEDURE [mth: MTHandle, mti: BcdDefs.MTIndex]
      RETURNS [BOOLEAN] = BEGIN RETURN[mth.gfi = cgfi]; END;
      
    f: FrameItem;
    bcdseg: FileSegmentHandle;
    config: ConfigIndex;
    cgfi: GFTIndex;
    mth: MTHandle;
    mti: MTIndex;
    sgb: BcdDefs.Base;
    IF frame = ControlDefs.NullGlobalFrame THEN RETURN[NIL];
    IF (f ← EnumFrameObjects[SymbolFile]) # NIL THEN RETURN[f.symbols];
    IF VirtualGlobalFrame[frame].copied THEN
      BEGIN
      original: GlobalFrameHandle ← FindOriginal[frame];
      seg ← SymbolSegForFrame[original];
      AddSymbolsForCopy[frame, original];
      RETURN
      END;
    [] ← InputLoadState[];
    [config: config, cgfi: cgfi] ← MapRealToConfig[VirtualGlobalFrame[frame].gfi];
    bcdseg ← BcdSegFromLoadState[config];
    SegmentDefs.SwapIn[bcdseg];
    bcd ← SegmentDefs.FileSegmentAddress[bcdseg];
    [mth: mth, mti: mti] ← BcdOps.ProcessModules[bcd, FindModule];
    sgb ← LOOPHOLE[bcd + bcd.sgOffset];
    AddSymbolFileName[bcd, mth.sseg, @sgb[mth.sseg]];
    LookupFiles[];
    AddModuleSymbols[bcd, config, mti, mth];
    ClearFileObjectTable[];
    ReleaseLoadState[];
    SegmentDefs.Unlock[bcdseg];
    SegmentDefs.DeleteFileSegment[bcdseg];
    RETURN[EnumFrameObjects[SymbolFile].symbols]
    END;
    
  CodeSegForFrame: PUBLIC PROCEDURE [frame: GlobalFrameHandle]
    RETURNS [seg: FileSegmentHandle, offset: CARDINAL] =
    BEGIN
    
    CodeFile: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.frame = frame]; END;
      
    f: FrameItem;
    IF frame = ControlDefs.NullGlobalFrame THEN RETURN[NIL, 0];
    IF (f ← EnumFrameObjects[CodeFile]) # NIL THEN RETURN[f.code, f.offset];
    [] ← SymbolSegForFrame[frame];
    f ← EnumFrameObjects[CodeFile];
    RETURN[f.code, f.offset];
    END;
    
  FindOriginal: PUBLIC PROCEDURE [copy: GlobalFrameHandle]
    RETURNS [GlobalFrameHandle] =
    BEGIN
    
    Original: PROCEDURE [f: GlobalFrameHandle] RETURNS [BOOLEAN] =
      BEGIN
      RETURN[
	f
	  #
	  copy
	  AND
	  ~VirtualGlobalFrame[
	  f].copied
	  AND
	  SameCode[
	  copy, f]
	  =
	  identical];
      END;
      
    RETURN[EnumerateGlobalFrames[Original]]
    END;
    
  CodeMatch: TYPE = {identical, same, different};
  
  SameCode: PROCEDURE [f1, f2: GlobalFrameHandle] RETURNS [CodeMatch] =
    BEGIN
    Size: CARDINAL = SIZE[ControlDefs.FrameCodeBase];
    fcb1, fcb2: ControlDefs.FrameCodeBase;
    CopyRead[to: @fcb1, from: @f1.code, nwords: Size];
    CopyRead[to: @fcb2, from: @f2.code, nwords: Size];
    IF fcb1.highByte = 0 OR fcb2.highByte = 0 THEN
      BEGIN
      fcb1.out ← fcb2.out ← FALSE;
      RETURN[IF fcb1 = fcb2 THEN identical ELSE different];
      END;
    IF fcb1.handle # fcb2.handle THEN RETURN[different];
    RETURN[IF GetOffset[fcb1] = GetOffset[fcb2] THEN identical ELSE same];
    END;
    
  GetOffset: PROCEDURE [fcb: ControlDefs.FrameCodeBase]
    RETURNS [offset: CARDINAL] =
    BEGIN OPEN SegmentDefs;
    seg: SegmentDefs.FileSegmentHandle ← VirtualFileSegment[fcb.handle];
    IF ~seg.swappedin THEN BEGIN fcb.out ← FALSE; RETURN[fcb.offset]; END;
    RETURN[fcb.shortbase - SegmentDefs.AddressFromPage[seg.VMpage]]
    END;
    
  FrameToModuleName: PUBLIC PROCEDURE [frame: GlobalFrameHandle, name: STRING] =
    BEGIN
    cgfi: GFTIndex;
    config: ConfigIndex;
    ns: BcdOps.NameString;
    bcdseg: FileSegmentHandle;
    bcd: BcdBase;
    
    FindModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN
      ss: String.SubStringDescriptor;
      IF cgfi IN [mth.gfi..mth.gfi + mth.ngfi) THEN
	BEGIN
	ss ← [base: @ns.string, offset: mth.name, length: ns.size[mth.name]];
	String.AppendSubString[name, @ss];
	RETURN[TRUE];
	END;
      RETURN[FALSE];
      END;
      
    IF frame = ControlDefs.NullGlobalFrame THEN RETURN;
    IF VirtualGlobalFrame[frame].copied THEN
      BEGIN
      original: GlobalFrameHandle ← FindOriginal[frame];
      FrameToModuleName[original, name];
      RETURN
      END;
    [] ← InputLoadState[];
    [config: config, cgfi: cgfi] ← MapRealToConfig[VirtualGlobalFrame[frame].gfi];
    bcdseg ← BcdSegFromLoadState[config];
    SegmentDefs.SwapIn[bcdseg];
    bcd ← SegmentDefs.FileSegmentAddress[bcdseg];
    ns ← LOOPHOLE[bcd + bcd.ssOffset];
    [] ← BcdOps.ProcessModules[bcd, FindModule];
    ReleaseLoadState[];
    SegmentDefs.Unlock[bcdseg];
    SegmentDefs.DeleteFileSegment[bcdseg];
    RETURN
    END;
    
  segment: SegmentDefs.FileSegmentObject;
  
  VirtualFileSegment: PUBLIC PROCEDURE [seg: FileSegmentHandle]
    RETURNS [FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    CopyRead[to: @segment, from: seg, nwords: SIZE[FileSegmentObject]];
    RETURN[@segment]
    END;
    
  AddSymbolsForCopy: PROCEDURE [copy, original: GlobalFrameHandle] =
    BEGIN
    copyFI, originalFI: FrameItem;
    
    FindMatch: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] =
      BEGIN RETURN[f.frame = original]; END;
      
    originalFI ← EnumFrameObjects[FindMatch];
    copyFI ← GetFrameItem[];
    copyFI↑ ← originalFI↑;
    copyFI.frame ← copy;
    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;
    
  -- Frame Symbol Table Management
  
  FrameObject: TYPE = RECORD [
    frame: GlobalFrameHandle, symbols, code: FileSegmentHandle, offset: CARDINAL];
  
  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 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;
      RETURN[@frameTable.subTable[0]];
      END;
    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...