-- LoaderUtilities.mesa
-- Last Modified by Levin,  August 29, 1978  8:38 AM 

DIRECTORY
  AltoFileDefs: FROM "altofiledefs" USING [FP],
  BcdDefs: FROM "bcddefs" USING [
    CTHandle, CTIndex, CTNull, FTIndex, FTNull, FTSelf, MTHandle, MTIndex,
    MTNull, NameString, SGHandle, SGIndex],
  CodeDefs: FROM "codedefs" USING [CodeHandle],
  ControlDefs: FROM "controldefs" USING [
    Alloc, GFT, GFTIndex, GFTNull, GlobalFrameHandle, MaxAllocSlot,
    NullGlobalFrame],
  DirectoryDefs: FROM "directorydefs" USING [EnumerateDirectory],
  FrameDefs: FROM "framedefs" USING [FrameSize],
  InlineDefs: FROM "inlinedefs" USING [BITAND],
  LoaderBcdUtilDefs: FROM "loaderbcdutildefs" USING [
    EnumerateConfigTable, EnumerateModuleTable, EnumerateSegTable],
  LoaderDefs: FROM "loaderdefs",
  LoaderUtilityDefs: FROM "loaderutilitydefs" USING [
    BcdBase, Binding, FileHandle, FileSegmentHandle, FileTable,
    FileTableObject, GlobalFrameHandle, ImportBindingLink, Relocation],
  SegmentDefs: FROM "segmentdefs" USING [
    EnumerateFileSegments, FileHandle, FileSegmentHandle, InsertFile,
    NewFileSegment, Read, ReleaseFile, VMtoFileSegment],
  StringDefs: FROM "stringdefs" USING [
    AppendString, AppendSubString, EquivalentSubStrings, SubString,
    SubStringDescriptor],
  SystemDefs: FROM "systemdefs" USING [AllocateHeapNode, FreeHeapNode];

DEFINITIONS FROM BcdDefs, LoaderUtilityDefs;

LoaderUtilities: PROGRAM
  IMPORTS DirectoryDefs, FrameDefs, LoaderBcdUtilDefs,
    CodeDefs, SegmentDefs, StringDefs, SystemDefs
  EXPORTS LoaderUtilityDefs = PUBLIC

  BEGIN

  files: FileTable ← NIL;
  loadee: BcdBase;
  ssb: BcdDefs.NameString;
  ftb: CARDINAL;
  nfilestofind: CARDINAL ← 0;
  tableopen: BOOLEAN ← FALSE;

  SubStringDescriptor: TYPE = StringDefs.SubStringDescriptor;

  AddFileName: PUBLIC PROCEDURE [file: BcdDefs.FTIndex] =
    BEGIN
    p: FileTable;
    i, offset, length: CARDINAL;
    FOR p ← files, p.link UNTIL p = NIL DO
      IF file = p.file THEN RETURN;
      ENDLOOP;
    p ← SystemDefs.AllocateHeapNode[SIZE[FileTableObject]];
    p↑ ← [file: file, filehandle: NIL, ext: FALSE, link: files];
    files ← p;
    IF file = BcdDefs.FTSelf THEN
      BEGIN
      p.filehandle ← SegmentDefs.VMtoFileSegment[loadee].file;
      RETURN
      END;
    IF file = BcdDefs.FTNull THEN BEGIN  p.filehandle ← NIL;  RETURN  END;
    offset ← (ftb+file).name;
    length ← ssb.size[(ftb+file).name];
    FOR i IN [offset..offset+length) DO
      IF ssb.string.text[i] = '. THEN
        BEGIN  p.ext ← TRUE;  EXIT  END;
      ENDLOOP;
    nfilestofind ← nfilestofind + 1;
    RETURN;
    END;

  FindFileName: PUBLIC PROCEDURE [name: StringDefs.SubString, ext: BOOLEAN]
   RETURNS [found: BOOLEAN, file: FileTable] =
    BEGIN OPEN StringDefs;
    filename: SubStringDescriptor ← [base: @ssb.string, offset:, length:];
    FOR file ← files, file.link UNTIL file = NIL DO
      filename.offset ← (ftb+file.file).name;
      filename.length ← ssb.size[(ftb+file.file).name];
      IF LastCharIsDot[@filename] THEN name.length ← name.length + 1;
      IF ext = file.ext AND EquivalentSubStrings[@filename, name] THEN
        RETURN[TRUE, file];
      ENDLOOP;
    RETURN[FALSE, NIL];
    END;

  LastCharIsDot: PUBLIC PROCEDURE [name: StringDefs.SubString] RETURNS [BOOLEAN] =
    BEGIN
    RETURN[name.base[name.offset+name.length-1] = '.];
    END;

  FileNotFound: PUBLIC SIGNAL [name: STRING] RETURNS [file: FileHandle] = CODE;

  LookupFileTable: PUBLIC PROCEDURE =
    BEGIN
    p: FileTable;
    ssd: StringDefs.SubStringDescriptor;
    name: STRING ← [40];
    IF nfilestofind # 0 THEN DirectoryDefs.EnumerateDirectory[CheckOne];
    FOR p ← files, p.link UNTIL p = NIL DO
      IF p.filehandle = NIL AND p.file # BcdDefs.FTNull THEN
        BEGIN
        ssd ← [base: @ssb.string, offset: (ftb+p.file).name,
          length: ssb.size[(ftb+p.file).name]];
        name.length ← 0;
        StringDefs.AppendSubString[name, @ssd];
        IF p.ext THEN StringDefs.AppendString[name, ".bcd"L];
        p.filehandle ← SIGNAL FileNotFound[name];
        END;
      ENDLOOP;
    END;

  CheckOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, name: STRING]
   RETURNS [found: BOOLEAN] =
    BEGIN OPEN StringDefs;
    i: CARDINAL;
    dirName: SubStringDescriptor;
    bcd: SubStringDescriptor ← [base: "bcd"L, offset: 0, length: 3];
    file: LoaderUtilityDefs.FileTable;
    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, FALSE];
          IF found THEN RETURN [ThisIsTheOne[fp, file]];
          END;
      ENDLOOP;
    dirName ← [base: name, offset: 0, length: name.length-1]; -- ignore dot on end
    [found, file] ← FindFileName[@dirName, TRUE];
    RETURN [IF found THEN ThisIsTheOne[fp, file] ELSE FALSE];
    END;

  ThisIsTheOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, file: FileTable]
   RETURNS [BOOLEAN] =
    BEGIN
    file.filehandle ← SegmentDefs.InsertFile[fp, SegmentDefs.Read];
    nfilestofind ← nfilestofind - 1;
    RETURN [nfilestofind=0];
    END;

  FileHandleFromTable: PUBLIC PROCEDURE [filename: BcdDefs.FTIndex]
   RETURNS [file: SegmentDefs.FileHandle] =
    BEGIN
    p: FileTable;
    FOR p ← files, p.link UNTIL p = NIL DO
      IF p.file = filename THEN
        RETURN[p.filehandle];
      ENDLOOP;
    RETURN[NIL];
    END;

  FinalizeUtilities: PUBLIC PROCEDURE =
    BEGIN
    p: FileTable;
    FOR files ← files, p UNTIL files = NIL DO
      p ← files.link;
      IF files.file # NIL AND files.filehandle.segcount = 0 THEN
        SegmentDefs.ReleaseFile[files.filehandle];
      SystemDefs.FreeHeapNode[files];
      ENDLOOP;
    tableopen ← FALSE;
    SystemDefs.FreeHeapNode[BASE[ModuleTable]];
    END;

  InitializeUtilities: PUBLIC PROCEDURE [bcd: BcdBase] =
    BEGIN OPEN SystemDefs;
    loadee ← bcd;
    ssb ← LOOPHOLE[loadee+loadee.ssOffset];
    ftb ← LOOPHOLE[loadee+loadee.ftOffset];
    IF tableopen THEN FinalizeUtilities[];
    tableopen ← TRUE;
    ModuleTable ← DESCRIPTOR[
      AllocateHeapNode[bcd.nModules*SIZE[ModuleInfo]], bcd.nModules];
    nModulesEntered ← 0;
    END;

-- Utility Routines

  AssignControlModules: PUBLIC PROCEDURE [loadee: BcdBase, Reloc: Relocation] =
    BEGIN OPEN ControlDefs;
    ctb: CARDINAL ← LOOPHOLE[loadee+loadee.ctOffset];
    mtb: CARDINAL ← LOOPHOLE[loadee+loadee.mtOffset];
    ModuleSearch: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN OPEN ControlDefs;
      frame: GlobalFrameHandle ← GFT[Reloc[mth.gfi]].frame;
      cti: CTIndex;
      gfi: GFTIndex;
      ControlGfi: PROCEDURE [cti: CTIndex] RETURNS [GFTIndex] =
        BEGIN
        RETURN[IF cti = CTNull OR (ctb+cti).control = MTNull THEN GFTNull
          ELSE (mtb+(ctb+cti).control).gfi];
        END;
      gfi ← ControlGfi[cti ← mth.config];
      WHILE gfi = mth.gfi DO gfi ← ControlGfi[cti ← (ctb+cti).config] ENDLOOP;
      frame.global[0] ←
        (IF gfi = GFTNull THEN NullGlobalFrame ELSE GFT[Reloc[gfi]].frame);
      RETURN [FALSE];
      END;

    [] ← LoaderBcdUtilDefs.EnumerateModuleTable[loadee, ModuleSearch];
    END;

  EnterCodeFileNames: PUBLIC PROCEDURE [loadee: BcdBase] =
    BEGIN
    SegSearch: PROCEDURE [sgh: SGHandle, sgi: SGIndex] RETURNS [BOOLEAN] =
      BEGIN
      IF sgh.class = code THEN AddFileName[sgh.file];
      RETURN[FALSE];
      END;
    [] ← LoaderBcdUtilDefs.EnumerateSegTable[loadee, SegSearch];
    RETURN;
    END;

  AllocateSingleModule: PUBLIC PROCEDURE [
    loadee: BcdBase, framelinks: BOOLEAN] RETURNS [frame: POINTER] =
    BEGIN
    fsi: CARDINAL ← 0;
    i: CARDINAL;
    mth: MTHandle ← LOOPHOLE[loadee+loadee.mtOffset,CARDINAL]+FIRST[MTIndex];
    framelinks ← framelinks OR mth.links = frame OR ~mth.code.linkspace;
    IF framelinks THEN fsi ← mth.frame.length;
    fsi ← NextMultipleOfFour[fsi] + mth.framesize;
    FOR i IN [0..ControlDefs.MaxAllocSlot) DO
      IF FrameDefs.FrameSize[i] >= fsi THEN
        BEGIN fsi ← i; EXIT END;
      ENDLOOP;
    frame ← ControlDefs.Alloc[fsi];
    IF framelinks THEN
      frame ← NextMultipleOfFour[frame + mth.frame.length];
    RETURN[frame];
    END;

  NextMultipleOfFour: PROCEDURE [x: UNSPECIFIED] RETURNS [UNSPECIFIED] =
    BEGIN
    RETURN[x + InlineDefs.BITAND[-x, 3B]];
    END;

  RequiredFrameSpace: PUBLIC PROCEDURE [
    loadee: BcdBase, alloc, framelinks: BOOLEAN] RETURNS [space: CARDINAL] =
    BEGIN
    FrameSize: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN
      IF alloc THEN space ← NextMultipleOfFour[space+1];
      IF framelinks OR mth.links = frame OR ~mth.code.linkspace THEN
        space ← space + mth.frame.length;
      space ← NextMultipleOfFour[space] + mth.framesize;
      IF alloc AND ~framelinks AND mth.links = code AND mth.code.linkspace AND
        mth.framesize <= 4 THEN space ← space+3;  -- this tries
        -- to catch the case where a frame is alloced and framesize <= 4 so
        -- it makes it so that enough space is counted so that a small frame
        -- will fit.
      RETURN[FALSE];
      END;
    space ← 0;
    [] ← LoaderBcdUtilDefs.EnumerateModuleTable[loadee, FrameSize];
    RETURN;
    END;

  ControlModuleFrame: PUBLIC PROCEDURE [loadee: BcdBase, Reloc: Relocation]
    RETURNS [ControlDefs.GlobalFrameHandle] =
    BEGIN OPEN ControlDefs;
    mtb: CARDINAL ← LOOPHOLE[loadee + loadee.mtOffset];
    control: MTIndex ← MTNull;
    ConfigSearch: PROCEDURE [cth: CTHandle, cti: CTIndex] RETURNS [BOOLEAN] =
      BEGIN
      IF cth.config = CTNull THEN
        BEGIN
        control ← cth.control;
        RETURN [TRUE];
        END;
      RETURN [FALSE];
      END;
    [] ← LoaderBcdUtilDefs.EnumerateConfigTable[loadee, ConfigSearch];
    RETURN[IF control = MTNull THEN ControlDefs.NullGlobalFrame
      ELSE GFT[Reloc[(mtb+control).gfi]].frame];
    END;

  InitImportBinding: PUBLIC PROCEDURE [size: CARDINAL]
    RETURNS [binding: Binding] =
    BEGIN OPEN SystemDefs;
    i: CARDINAL;
    binding ←
      DESCRIPTOR[AllocateHeapNode[size*SIZE[ImportBindingLink]], size];
    FOR i IN [0..size) DO
      binding[i] ← [whichgfi: 0, body: notbound[]];
      ENDLOOP;
    END;

  FindFrameIndex: PUBLIC PROCEDURE [
    mth: MTHandle, framelinks: BOOLEAN] RETURNS [fsi: CARDINAL] =
    BEGIN
    space: CARDINAL ← 0;
    IF framelinks THEN space ← mth.frame.length;
    space ← NextMultipleOfFour[space] + mth.framesize;
    FOR fsi DECREASING IN [0..ControlDefs.MaxAllocSlot) DO
      IF space >= FrameDefs.FrameSize[fsi] THEN RETURN[fsi];
      ENDLOOP;
    RETURN[0]; -- see RequiredFrameSpace for alloced modules w/ framesize<7
    END;

  ModuleInfo: TYPE = RECORD [
    mth: MTHandle,
    frame: GlobalFrameHandle,
    bound: BOOLEAN,
    sgi: SGIndex];

  ModuleTable: DESCRIPTOR FOR ARRAY OF ModuleInfo;
  nModulesEntered: CARDINAL;

  FindCodeSegment: PUBLIC PROCEDURE [
    loadee: BcdBase, mth: MTHandle, frame: GlobalFrameHandle]
    RETURNS [seg: FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    sgh: SGHandle ← mth.code.sgi+LOOPHOLE[loadee+loadee.sgOffset, CARDINAL];
    file: FileHandle;
    i: CARDINAL;
    pages: CARDINAL;
    FindSegment: PROCEDURE [s: FileSegmentHandle] RETURNS [BOOLEAN] =
      BEGIN
      RETURN[s.file = file AND s.base = sgh.base AND s.pages = pages];
      END;
    FOR i IN [0..nModulesEntered) DO
      IF ModuleTable[i].mth.code.sgi = mth.code.sgi THEN
        RETURN[CodeDefs.CodeHandle[ModuleTable[i].frame]];
      ENDLOOP;
    file ← FileHandleFromTable[sgh.file];
    pages ← sgh.pages+sgh.extraPages;
    seg ← EnumerateFileSegments[FindSegment];
    IF seg = NIL THEN seg ← NewFileSegment[file, sgh.base, pages, Read];
    ModuleTable[nModulesEntered] ←
      ModuleInfo[mth, frame, FALSE, mth.code.sgi];
    nModulesEntered ← nModulesEntered + 1;
    RETURN
    END;

  ModuleIsBound: PUBLIC PROCEDURE [mth: MTHandle] =
    BEGIN
    i: CARDINAL;
    FOR i IN [0..nModulesEntered) DO
      IF ModuleTable[i].mth = mth THEN ModuleTable[i].bound ← TRUE;
      ENDLOOP;
    RETURN
    END;

  IsModuleBound: PUBLIC PROCEDURE [mth: MTHandle] RETURNS [BOOLEAN] =
    BEGIN
    i: CARDINAL;
    FOR i IN [0..nModulesEntered) DO
      IF ModuleTable[i].mth = mth AND ModuleTable[i].bound THEN RETURN[TRUE];
      ENDLOOP;
    RETURN[FALSE];
    END;

  END....