-- ListImpl.Mesa  
--   Edited by Sweet on  3-Sep-80 12:36:48

DIRECTORY
  AltoDefs: FROM "altodefs",
  BcdDefs,
  BcdOps USING [NameString],
  CommanderDefs USING [AddCommand, CommandBlockHandle],
  GPsortDefs USING [CompareProcType, GetProcType, PutProcType, Sort],
  IODefs,
  ListerDefs,
  OutputDefs,
  SegmentDefs,
  StreamDefs,
  String,
  Symbols,
  SymbolTable USING [Acquire, Base, Release, TableForSegment];

ListImpl: PROGRAM
  IMPORTS
    CommanderDefs, GPsortDefs, IODefs, ListerDefs, OutputDefs, SegmentDefs, 
    StreamDefs, String, SymbolTable =
  BEGIN OPEN OutputDefs, BcdDefs;
  
  bcd: POINTER TO BCD;
  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: SegmentDefs.FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    size: CARDINAL;
    SwapIn[seg];
    bcd ← FileSegmentAddress[seg];
    IF (size ← bcd.nPages) # seg.pages THEN
      BEGIN
      Unlock[seg];
      MoveFileSegment[seg, seg.base, size];
      SwapIn[seg];
      bcd ← FileSegmentAddress[seg];
      END;
    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 [seg: SegmentDefs.FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    IF seg.swappedin THEN Unlock[seg];
    SwapOut[seg];
    RETURN
    END;
    
  ScanExports: PROCEDURE [action: PROC[EXPIndex]] =
    BEGIN
    eti: EXPIndex ← FIRST[EXPIndex];
    UNTIL eti = bcd.expLimit DO
      action[eti !
	SegmentDefs.FileNameError =>
	  BEGIN OPEN IODefs, etb[eti];
	  WriteString["Can't find "L];
	  WriteName[ftb[file].name];
	  WriteChar[CR];
	  CONTINUE
	  END;
	ListerDefs.MultipleModules =>
	  BEGIN OPEN IODefs, etb[eti];
	  WriteString["Bad format for "L];
	  WriteName[ftb[file].name];
	  WriteChar[CR];
	  CONTINUE
	  END;
	ListerDefs.NoSymbols =>
	  BEGIN OPEN IODefs, etb[eti];
	  WriteString["No symbols for "L];
	  WriteName[ftb[file].name];
	  WriteChar[CR];
	  CONTINUE
	  END;
	ListerDefs.IncorrectVersion =>
	  BEGIN OPEN IODefs, etb[eti];
	  WriteString["Wrong version: "L];
	  WriteName[ftb[file].name];
	  WriteChar[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;
    String.AppendString[interfaceName, moduleName];
    LoadSymbolsName[moduleName !
      SegmentDefs.FileNameError =>
	BEGIN OPEN IODefs;
	WriteString["Can't find "L];
	WriteString[moduleName];
	WriteChar[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: SegmentDefs.FileSegmentHandle ← NIL;
  
  loaded: BOOLEAN ← FALSE;
  
  LoadSymbols: PROCEDURE [file: FTIndex] =
    BEGIN OPEN ListerDefs;
    s: STRING ← [60];
    IF file = FTNull OR file = FTSelf OR symbols # NIL OR sseg # NIL 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: SegmentDefs.FileSegmentHandle;
    bs: STRING ← [50];
    String.AppendString[bs, s];
    FOR i: CARDINAL IN [0..bs.length) DO
      IF bs[i] = '. THEN EXIT;
      REPEAT
        FINISHED => String.AppendString[bs, ".bcd"L];
      ENDLOOP;
    [code: cseg, symbols: sseg] ← Load[bs ! NoCode, NoFGT => RESUME ];
    IF cseg # NIL THEN SegmentDefs.DeleteFileSegment[cseg];
    symbols ← SymbolTable.Acquire[SymbolTable.TableForSegment[sseg]];
    SetRoutineSymbols[symbols];
    loaded ← TRUE;
    END;
    
  UnloadSymbols: PROCEDURE =
    BEGIN OPEN SegmentDefs;
    IF symbols # NIL THEN SymbolTable.Release[symbols];
    IF sseg # NIL THEN DeleteFileSegment[sseg ! SwapError => CONTINUE];
    symbols ← NIL;
    sseg ← NIL;
    loaded ← FALSE;
    END;
    
  -- Utility Prints
  
  
  PrintGarbage: PROCEDURE =
    BEGIN
    PutString["? looks like garbage to me ..."];
    PutCR[];
    RETURN
    END;
    
  GetBcdName: PUBLIC PROCEDURE [s: STRING, n: NameRecord] =
    BEGIN
    i: CARDINAL;
    ssd: String.SubStringDescriptor ←
      [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
    s.length ← 0;
    String.AppendSubString[s, @ssd];
    FOR i IN [0..s.length) DO IF s[i] = '. THEN RETURN ENDLOOP;
    String.AppendString[s, ".bcd"L];
    RETURN
    END;
    
  -- Utility Puts
  
  
  PutName: PUBLIC PROCEDURE [n: NameRecord] =
    BEGIN
    ssd: String.SubStringDescriptor ←
      [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
    PutSubString[@ssd];
    RETURN
    END;
    
  AppendName: PUBLIC PROCEDURE [s: STRING, n: NameRecord] =
    BEGIN
    ssd: String.SubStringDescriptor ←
      [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
    String.AppendSubString[s, @ssd];
    RETURN
    END;
    
  WriteName: PUBLIC PROCEDURE [n: NameRecord] =
    BEGIN
    ssd: String.SubStringDescriptor ←
      [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
    IODefs.WriteSubString[@ssd];
    RETURN
    END;
    
  RecordExport: PROC [item: Symbols.ISEIndex, present: BOOLEAN] =
    BEGIN OPEN String;
    desc: String.SubStringDescriptor;
    sortLength: CARDINAL;
    buffer↑ ← [
      interface: NULL, 
      module: NULL, 
      item: [length: 0, maxlength: 100, text:]];
    symbols.SubStringForHash[@desc, symbols.HashForSe[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: POINTER] RETURNS [i: INTEGER] =
    BEGIN
    k1:KeyBase = p1;
    k2: KeyBase = p2;
    i ← String.CompareStrings[@k1[k1.interface], @k2[k2.interface]];
    IF i = 0 THEN i ← String.CompareStrings[@k1.item, @k2.item];
    IF i = 0 THEN i ← String.CompareStrings[@k1[k1.module], @k2[k2.module]];
    END;
    
  lastInterface: STRING ← [60];
  lastItem: STRING ← [60];
  first: BOOLEAN ← TRUE;

  Put: PROC [p: 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: STRING] =
    BEGIN OPEN OutputDefs;
    IF ~String.EqualString[interface, lastInterface] THEN
      BEGIN
      IF ~first THEN PutChar[')];
      first ← TRUE;
      PutCR[];
      PutCR[];
      PutString[interface];
      lastInterface.length ← 0;
      String.AppendString[lastInterface, interface];
      lastItem.length ← 0;
      END;
    IF ~String.EqualString[item, lastItem] THEN
      {IF ~first THEN PutChar[')];
      PutCR[]; PutString["    "L]; PutString[item]; 
      lastItem.length ← 0; String.AppendString[lastItem, item];
      first ← TRUE};
    IF module # NIL THEN
      BEGIN
      IF first THEN {PutString["  ("L]; first ← FALSE}
      ELSE PutString[", "L];
      PutString[module];
      END;
    END;
    
  KeyBase: TYPE = 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];
  
  port: TYPE = MACHINE DEPENDENT RECORD [in, out: UNSPECIFIED];
  
  OutToSort: PORT [len: CARDINAL] RETURNS [POINTER];
  SortStarter: TYPE = PORT [
    get: GPsortDefs.GetProcType, put: GPsortDefs.PutProcType,
    compare: GPsortDefs.CompareProcType, expectedItemSize: CARDINAL,
    maxItemSize: CARDINAL, reservedPages: CARDINAL] RETURNS [POINTER];
  SortStopper: TYPE = PORT [len: CARDINAL ← 0];
    onLine: CARDINAL;
    firstFile: BOOLEAN ← TRUE;
  
  Implementors: PROCEDURE [fileList: STRING] =
    BEGIN OPEN String, StreamDefs, OutputDefs;
    s: STRING ← [50];
    ch: CHARACTER;
    -- open list of names
    cs: StreamHandle ← NewByteStream[
      fileList, Read ! SegmentDefs.FileNameError => GO TO notFound];
    -- crank up the sort package
    LOOPHOLE[OutToSort, port].out ← GPsortDefs.Sort;
    buffer ← LOOPHOLE[OutToSort, SortStarter][
      get: LOOPHOLE[@OutToSort, GPsortDefs.GetProcType], put: Put,
      compare: Compare, expectedItemSize: 40, maxItemSize: 70, reservedPages: 90];
    OutputDefs.OpenOutput[fileList, ".iml"];
    PutString["Interface items implemented by:"];
    PutCR[]; onLine ← 0;
    -- go through list of names, calling OutToSort
    UNTIL cs.endof[cs] DO
      s.length ← 0;
      WHILE ~cs.endof[cs] AND (ch ← cs.get[cs]) # '  DO
	AppendChar[s, ch]; ENDLOOP;
      IF s.length > 0 THEN
	BEGIN OPEN IODefs;
	WriteChar[CR];
	WriteString["    "L];
	WriteString[s];
	ProcessFile[s];
	END;
      ENDLOOP;
    IODefs.WriteChar[IODefs.CR];
    cs.destroy[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, SortStopper][];
    IF ~first THEN PutChar[')];
    OutputDefs.PutCR[];
    OutputDefs.CloseOutput[];
    EXITS notFound => IODefs.WriteLine["  Command file not found"L];
    END;

  ProcessFile: PROCEDURE [root: STRING] =
    BEGIN
    i: CARDINAL;
    bcdfile: STRING ← [40];
    file: STRING ← [40];
    seg: SegmentDefs.FileSegmentHandle;
    BEGIN OPEN String;
    FOR i IN [0..root.length) DO
      IF root[i] = '. THEN EXIT;
      AppendChar[bcdfile, root[i]];
      ENDLOOP;
    AppendString[file, bcdfile];
    AppendString[bcdfile, ".bcd"];
    END;
    BEGIN OPEN SegmentDefs;
    seg ← NewFileSegment[
      NewFile[bcdfile, Read, DefaultVersion ! FileNameError => GO TO NoFile], 1,
      1, Read];
    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[seg];
    EXITS NoFile => IODefs.WriteString[" File not found"];
    END;
    RETURN
    END;
    
  Init: PROCEDURE =
    BEGIN
    command: CommanderDefs.CommandBlockHandle;
    command ← CommanderDefs.AddCommand[
      "Implementors", LOOPHOLE[Implementors], 1];
    command.params[0] ← [type: string, prompt: "fileList"];
    END;
    
  Init[];
  
  END....