-- ListImpl.Mesa  
--   Edited by Bruce on  13-Jan-81 11:01:26
--   Edited by Sweet on  20-Mar-81 14:10:24
--   Edited by Lewis on  14-Jan-81 15:59:40
--   Edited by Satterthwaite on May 10, 1983 12:56 pm

DIRECTORY
  Ascii: TYPE USING [CR],
  BcdDefs: TYPE,
  BcdOps: TYPE USING [BcdBase, NameString],
  CommanderOps: TYPE USING [AddCommand, CommandBlockHandle],
  FileStream: TYPE USING [Create, EndOf],
  FileSegment: TYPE USING [Pages, nullPages],
  GSort: TYPE USING [
    CompareProcType, Port, PutProcType, SortItemPort, SortStarter, SortStopper, Sort],
  ListerDefs: TYPE,
  LongString: TYPE,
  OSMiscOps: TYPE USING [FileError, FindFile],
  OutputDefs: TYPE,
  Space: TYPE USING [Handle, Delete, LongPointer],
  Stream: TYPE USING [Delete, GetChar, Handle],
  Symbols: TYPE,
  SymbolTable: TYPE USING [Acquire, Base, Release];

ListImpl: PROGRAM
  IMPORTS
    CommanderOps, FileStream, GSort, ListerDefs, LongString, OSMiscOps,
    OutputDefs, Space, Stream, SymbolTable =
  BEGIN OPEN OutputDefs, BcdDefs;
  
  bcd: BcdOps.BcdBase;

  tb: Base;
  ssb: BcdOps.NameString;
  evb: Base;
  spb: Base;
  ctb: Base;
  mtb: Base;
  itb: Base;
  etb: Base;
  sgb: Base;
  ftb: Base;
  ntb: Base;
  typb: Base;
  
  InstallBcd: PROCEDURE [seg: FileSegment.Pages] RETURNS [space: Space.Handle] =
    BEGIN
    DO
      size: CARDINAL;
      space ← ListerDefs.MapPages[seg];
      bcd ← space.LongPointer;
      IF (size ← bcd.nPages) = seg.span.pages THEN EXIT;
      seg.span.pages ← size;
      Space.Delete[space];
      ENDLOOP;
    tb ← LOOPHOLE[bcd];
    ssb ← LOOPHOLE[bcd + bcd.ssOffset];
    ctb ← tb + bcd.ctOffset;
    mtb ← tb + bcd.mtOffset;
    itb ← tb + bcd.impOffset;
    etb ← tb + bcd.expOffset;
    sgb ← tb + bcd.sgOffset;
    ftb ← tb + bcd.ftOffset;
    ntb ← tb + bcd.ntOffset;
    evb ← tb + bcd.evOffset;
    spb ← tb + bcd.spOffset;
    typb ← tb + bcd.typOffset;
    RETURN
    END;
    
  UnstallBcd: PROCEDURE [space: Space.Handle] =
    BEGIN
    Space.Delete[space];
    RETURN
    END;
    
  ScanExports: PROCEDURE [action: PROC[EXPIndex]] =
    BEGIN
    eti: EXPIndex ← FIRST[EXPIndex];
    UNTIL eti = bcd.expLimit DO
      action[eti !
	ListerDefs.NoFile =>
	  BEGIN OPEN etb[eti];
	  ListerDefs.WriteString["Can't find "L];
	  WriteName[ftb[file].name];
	  ListerDefs.WriteChar[Ascii.CR];
	  CONTINUE
	  END;
	ListerDefs.MultipleModules =>
	  BEGIN OPEN etb[eti];
	  ListerDefs.WriteString["Bad format for "L];
	  WriteName[ftb[file].name];
	  ListerDefs.WriteChar[Ascii.CR];
	  CONTINUE
	  END;
	ListerDefs.NoSymbols =>
	  BEGIN OPEN etb[eti];
	  ListerDefs.WriteString["No symbols for "L];
	  WriteName[ftb[file].name];
	  ListerDefs.WriteChar[Ascii.CR];
	  CONTINUE
	  END;
	ListerDefs.IncorrectVersion =>
	  BEGIN OPEN etb[eti];
	  ListerDefs.WriteString["Wrong version: "L];
	  WriteName[ftb[file].name];
	  ListerDefs.WriteChar[Ascii.CR];
	  CONTINUE
	  END];
      UnloadSymbols[];
      eti ← eti + etb[eti].size + SIZE[EXPRecord];
      IF LOOPHOLE[eti, CARDINAL] > LOOPHOLE[bcd.expLimit, CARDINAL] THEN
	GO TO Bogus;
      REPEAT Bogus => PrintGarbage[];
      ENDLOOP;
    RETURN
    END;
    
  ExportsToSort: PROC [eti: EXPIndex] =
    BEGIN OPEN etb[eti];
    i: CARDINAL;
    n: CARDINAL ← 0;
    link: BcdDefs.Link;
    sei: Symbols.ISEIndex;
    interfaceName.length ← 0;
    AppendName[interfaceName, name];
    FOR i IN [0..size) DO
      link ← links[i];
      IF link # BcdDefs.NullLink THEN
	BEGIN
	IF ~loaded THEN LoadSymbols[file];
	sei ← SeiForItem[i];
	IF sei = Symbols.ISENull THEN LOOP;
	RecordExport[sei, TRUE];
	END;
      ENDLOOP;
    RETURN
    END;
    
  SeiForItem: PROCEDURE [item: CARDINAL] RETURNS [sei: Symbols.ISEIndex] =
    BEGIN OPEN symbols;
    FOR sei ← FirstCtxSe[stHandle.outerCtx], NextSe[sei] UNTIL sei =
      Symbols.ISENull DO
      IF seb[sei].idValue = item THEN
	SELECT LinkMode[
	  sei] FROM
	  val => IF seb[sei].extended THEN RETURN[Symbols.ISENull] ELSE RETURN;
	  ref => RETURN[sei];
	  manifest => LOOP; -- constant
	  
	  ENDCASE => RETURN[Symbols.ISENull];
      ENDLOOP;
    ERROR;
    END;

  AddInterface: PROC =
    BEGIN OPEN Symbols, symbols;
    interfaceName.length ← 0;
    LongString.AppendString[interfaceName, moduleName];
    LoadSymbolsName[moduleName !
      ListerDefs.NoFile =>
	BEGIN
	ListerDefs.WriteString["Can't find "L];
	ListerDefs.WriteString[moduleName];
	ListerDefs.WriteChar[Ascii.CR];
        GO TO cant;
	END];
    FOR sei: ISEIndex ← FirstCtxSe[stHandle.outerCtx],
	NextSe[sei] UNTIL sei = ISENull DO
      SELECT LinkMode[sei] FROM
	val, ref => RecordExport[sei, FALSE];
	ENDCASE;
      ENDLOOP;
    UnloadSymbols[];
    EXITS
      cant => NULL;
    END;
    
    
  symbols: SymbolTable.Base ← NIL;
  sseg: FileSegment.Pages ← FileSegment.nullPages;
  
  loaded: BOOLEAN ← FALSE;
  
  LoadSymbols: PROCEDURE [file: FTIndex] =
    BEGIN OPEN ListerDefs;
    s: STRING ← [60];
    IF file = FTNull OR file = FTSelf
     OR symbols # NIL OR sseg # FileSegment.nullPages THEN ERROR;
    GetBcdName[s, ftb[file].name];
    LoadSymbolsName[s];
    IF ftb[file].version # symbols.stHandle.version THEN
      SIGNAL ListerDefs.IncorrectVersion;
    END;
    
  LoadSymbolsName: PROCEDURE [s: STRING] =
    BEGIN OPEN ListerDefs;
    cseg: FileSegment.Pages;
    bs: STRING ← [50];
    LongString.AppendString[bs, s];
    FOR i: CARDINAL IN [0..bs.length) DO
      IF bs[i] = '. THEN EXIT;
      REPEAT
        FINISHED => LongString.AppendString[bs, ".bcd"L];
      ENDLOOP;
    [code: cseg, symbols: sseg] ← Load[bs ! NoCode, NoFGT => RESUME ];
    symbols ← SymbolTable.Acquire[sseg];
    SetRoutineSymbols[symbols];
    loaded ← TRUE;
    END;
    
  UnloadSymbols: PROCEDURE =
    BEGIN
    IF symbols # NIL THEN SymbolTable.Release[symbols];
    symbols ← NIL;
    sseg ← FileSegment.nullPages;
    loaded ← FALSE;
    END;
    
  -- Utility Prints
  
  
  PrintGarbage: PROCEDURE =
    BEGIN
    PutString["? looks like garbage to me ..."L];
    PutCR[];
    RETURN
    END;
    
  GetBcdName: PUBLIC PROCEDURE [s: STRING, n: NameRecord] =
    BEGIN
    i: CARDINAL;
    ssd: LongString.SubStringDescriptor ←
      [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
    s.length ← 0;
    LongString.AppendSubString[s, @ssd];
    FOR i IN [0..s.length) DO IF s[i] = '. THEN RETURN ENDLOOP;
    LongString.AppendString[s, ".bcd"L];
    RETURN
    END;
    
  -- Utility Puts
  
  
  PutName: PUBLIC PROCEDURE [n: NameRecord] =
    BEGIN
    ssd: LongString.SubStringDescriptor ←
      [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
    PutLongSubString[@ssd];
    RETURN
    END;
    
  AppendName: PUBLIC PROCEDURE [s: STRING, n: NameRecord] =
    BEGIN
    ssd: LongString.SubStringDescriptor ←
      [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
    LongString.AppendSubString[s, @ssd];
    RETURN
    END;
    
  WriteName: PUBLIC PROCEDURE [n: NameRecord] =
    BEGIN
    s: LONG STRING = @ssb.string;
    last: CARDINAL = MIN[ssb.size[n], 100];
    FOR i: CARDINAL ← n, i + 1 UNTIL i >= n + last DO
      ListerDefs.WriteChar[s[i]];
      ENDLOOP;
    RETURN
    END;
    
  RecordExport: PROC [item: Symbols.ISEIndex, present: BOOLEAN] =
    BEGIN OPEN LongString;
    desc: SubStringDescriptor;
    sortLength: CARDINAL;
    buffer↑ ← [
      interface: NULL, 
      module: NULL, 
      item: [length: 0, maxlength: 100, text:]];
    symbols.SubStringForName[LOOPHOLE[@desc], symbols.NameForSe[item]];
    AppendSubString[@buffer.item, @desc];
    buffer.interface ←  
	LOOPHOLE[sortLength ← WordsForString[buffer.item.length] + 2];
    buffer[buffer.interface] ← [length: 0, maxlength: 100, text:];
    AppendString[@buffer[buffer.interface], interfaceName];
    sortLength ← sortLength + WordsForString[interfaceName.length];
    IF ~present THEN buffer.module ← NullString
    ELSE
      BEGIN
      buffer.module ← LOOPHOLE[sortLength];
      buffer[buffer.module] ← [length: 0, maxlength: 100, text:];
      AppendString[@buffer[buffer.module], moduleName];
      sortLength ← sortLength + WordsForString[moduleName.length];
      END;
    buffer ← OutToSort[sortLength];
    END;

  Compare: PROCEDURE [p1, p2: LONG POINTER] RETURNS [i: INTEGER] =
    BEGIN OPEN LongString;
    k1: KeyBase = p1;
    k2: KeyBase = p2;
    i ← CompareStrings[@k1[k1.interface], @k2[k2.interface]];
    IF i = 0 THEN i ← CompareStrings[@k1.item, @k2.item];
    IF i = 0 THEN i ← CompareStrings[@k1[k1.module], @k2[k2.module]];
    END;
    
  lastInterface: STRING ← [60];
  lastItem: STRING ← [60];
  first: BOOLEAN ← TRUE;

  Put: PROC [p: LONG POINTER, len: CARDINAL] =
    BEGIN
    key: KeyBase = p;
    NextItem[
      interface: @key[key.interface],
      item: @key.item,
      module: IF key.module = NullString THEN NIL ELSE @key[key.module]];
    END;
  
  NextItem: PROCEDURE [interface, item, module: LONG STRING] =
    BEGIN OPEN OutputDefs;
    IF ~LongString.EqualString[interface, lastInterface] THEN
      BEGIN
      IF ~first THEN PutChar[')];
      first ← TRUE;
      PutCR[];
      PutCR[];
      PutLongString[interface];
      lastInterface.length ← 0;
      LongString.AppendString[lastInterface, interface];
      lastItem.length ← 0;
      END;
    IF ~LongString.EqualString[item, lastItem] THEN
      {IF ~first THEN PutChar[')];
      PutCR[]; PutString["    "L]; PutLongString[item]; 
      lastItem.length ← 0; LongString.AppendString[lastItem, item];
      first ← TRUE};
    IF module # NIL THEN
      BEGIN
      IF first THEN {PutString["  ("L]; first ← FALSE}
      ELSE PutString[", "L];
      PutLongString[module];
      END;
    END;
    
  KeyBase: TYPE = LONG BASE POINTER TO SortKey;
  SortKey: TYPE = RECORD [
    interface, module: KeyBase RELATIVE POINTER TO StringBody,
    item: StringBody];
  NullString: KeyBase RELATIVE POINTER TO StringBody = LOOPHOLE[0];
  buffer: KeyBase;
  interfaceName: STRING ← [60];
  moduleName: STRING ← [40];
  
  
  OutToSort: GSort.SortItemPort;
    onLine: CARDINAL;
    firstFile: BOOLEAN ← TRUE;
  
  Implementors: PROCEDURE [fileList: STRING] =
    BEGIN OPEN OutputDefs;
    s: STRING ← [50];
    ch: CHARACTER;
    -- open list of names
    cs: Stream.Handle ← FileStream.Create[
      OSMiscOps.FindFile[fileList ! OSMiscOps.FileError => GO TO notFound]];
    -- crank up the sort package
    LOOPHOLE[OutToSort, GSort.Port].out ← GSort.Sort;
    buffer ← LOOPHOLE[OutToSort, GSort.SortStarter][
      nextItem: @OutToSort, put: Put,
      compare: Compare, expectedItemSize: 40, maxItemSize: 70];
    OutputDefs.OpenOutput[fileList, ".iml"L];
    PutString["Interface items implemented by:"L];
    PutCR[]; onLine ← 0;
    -- go through list of names, calling OutToSort
    UNTIL FileStream.EndOf[cs] DO
      s.length ← 0;
      WHILE ~FileStream.EndOf[cs] AND (ch ← cs.GetChar[]) # '  DO
	LongString.AppendChar[s, ch] ENDLOOP;
      IF s.length > 0 THEN
	BEGIN OPEN ListerDefs;
	WriteChar[Ascii.CR];
	WriteString["    "L];
	WriteString[s];
	ProcessFile[s];
	END;
      ENDLOOP;
    ListerDefs.WriteChar[Ascii.CR];
    Stream.Delete[cs];
    PutCR[];
    -- get ready to write output
    lastInterface.length ← 0;
    lastItem.length ← 0;
    -- shut down the sort package (and call Put many times)
    LOOPHOLE[OutToSort, GSort.SortStopper][];
    IF ~first THEN PutChar[')];
    OutputDefs.PutCR[];
    OutputDefs.CloseOutput[];
    EXITS notFound => ListerDefs.WriteLine["  Command file not found"L];
    END;

  ProcessFile: PROCEDURE [root: STRING] =
    BEGIN
    i: CARDINAL;
    bcdfile: STRING ← [40];
    file: STRING ← [40];
    seg: FileSegment.Pages;
    BEGIN
    FOR i IN [0..root.length) DO
      IF root[i] = '. THEN EXIT;
      LongString.AppendChar[bcdfile, root[i]];
      ENDLOOP;
    LongString.AppendString[file, bcdfile];
    LongString.AppendString[bcdfile, ".bcd"];
    END;
    BEGIN
    bcdSpace: Space.Handle;
    seg ← [
      file: OSMiscOps.FindFile[bcdfile, ! OSMiscOps.FileError => GO TO NoFile],
      span: [1, 1]];
    bcdSpace ← InstallBcd[seg];
    BEGIN
      name: BcdDefs.NameRecord = mtb[FIRST[BcdDefs.MTIndex]].name;
      moduleName.length ← 0; AppendName[moduleName, name];
      IF firstFile THEN firstFile ← FALSE ELSE PutChar[',];
      IF onLine + file.length > 70 THEN {PutCR[]; onLine ← 0}
	ELSE {PutChar[' ]; onLine ← onLine + 2}; -- 1 for the comma
	PutString[file]; onLine ← onLine + file.length;
      IF bcd.definitions THEN AddInterface[]
      ELSE ScanExports[ExportsToSort];
      END;
    UnstallBcd[bcdSpace];
    EXITS NoFile => ListerDefs.WriteString[" File not found"L];
    END;
    RETURN
    END;
    
  Init: PROCEDURE =
    BEGIN
    command: CommanderOps.CommandBlockHandle;
    command ← CommanderOps.AddCommand[
      "Implementors", LOOPHOLE[Implementors], 1];
    command.params[0] ← [type: string, prompt: "fileList"];
    END;
    
  Init[];
  
  END....