-- ListPub.mesa; modified by Sweet, August 28, 1980  9:50 AM

DIRECTORY
  AltoDefs USING [PageNumber, BytesPerPage],
  AltoFileDefs USING [FP],
  CommanderDefs USING [AddCommand, CommandBlockHandle],
  DirectoryDefs USING [DirectoryLookup],
  DisplayDefs USING [DisplayOn, DisplayOff],
  GPsortDefs USING [PutProcType, GetProcType, LT, EQ, GT, Sort],
  InlineDefs USING [BITXOR],
  IODefs USING [CR, WriteString],
  ListerDefs USING [
    IncorrectVersion, Load, MultipleModules, NoCode, NoFGT, NoSymbols, PrintSei,
    SetRoutineSymbols],
  OutputDefs USING [
    outStream, CloseOutput, OpenOutput, PutChar, PutCR, PutDecimal, PutNumber,
    PutOctal, PutString],
  SegmentDefs USING [
    DeleteFileSegment, DestroyFile, FileNameError, FileSegmentHandle, LockFile,
    UnlockFile, Read, SwapError],
  StreamDefs USING [
    CreateByteStream, DiskHandle, NormalizeIndex, GetIndex, GrIndex,
    NewByteStream, StreamIndex],
  String USING [
    AppendChar, AppendString, AppendSubString, SubStringDescriptor,
    WordsForString],
  SymbolTable USING [Acquire, Release, Base, SetCacheSize, TableForSegment],
  Symbols USING [
    BodyRecord, BTIndex, codeANY, SERecord, codeCHAR, codeINT, CTXIndex, HTNull,
    ISEIndex, ISENull, lZ, RecordSEIndex, RecordSENull, SEIndex, SENull,
    TransferMode, typeTYPE, CSEIndex],
  Table USING [Base, Limit];

ListPub: PROGRAM
  IMPORTS
    CommanderDefs, DirectoryDefs, DisplayDefs, GPsortDefs, InlineDefs, IODefs,
    ListerDefs, OutputDefs, SegmentDefs, StreamDefs, String, SymbolTable
  EXPORTS ListerDefs =
  BEGIN OPEN Symbols;
  
  ProcType: TYPE = PROCEDURE [root: STRING];
  
  cz: CHARACTER = 32C;
  FileTooBig: SIGNAL = CODE;
  largestItem: CARDINAL;
  lastItem: StreamDefs.StreamIndex;
  moduleList: STRING ← [40];
  inSh, outSh, sortSh: StreamDefs.DiskHandle;
  symbols: SymbolTable.Base;
  
  Cap: PROCEDURE [ch: CHARACTER] RETURNS [cap: CHARACTER] =
    BEGIN RETURN[IF ch IN ['a..'z] THEN ch - ('a - 'A) ELSE ch] END;
    
  CompareStrings: PROCEDURE [p1, p2: POINTER] RETURNS [INTEGER] =
    BEGIN OPEN GPsortDefs;
    s1: STRING ← p1;
    s2: STRING ← p2;
    idx: CARDINAL;
    c1, c2: CHARACTER;
    FOR idx IN [0..MIN[s1.length, s2.length]) DO
      c1 ← Cap[s1[idx]];
      c2 ← Cap[s2[idx]];
      SELECT c1 FROM < c2 => RETURN[LT]; > c2 => RETURN[GT]; ENDCASE;
      ENDLOOP;
    SELECT s1.length FROM
      < s2.length => RETURN[LT];
      = s2.length => RETURN[EQ];
      ENDCASE => RETURN[GT];
    END;
    
  GetItem: GPsortDefs.GetProcType =
    BEGIN
    char: CHARACTER ← 0C;
    s: STRING ← p;
    s↑ ← [length: 0, maxlength: largestItem - 2, text:];
    UNTIL sortSh.endof[sortSh] DO
      char ← sortSh.get[sortSh];
      IF char = IODefs.CR THEN EXIT ELSE String.AppendChar[s, char];
      REPEAT FINISHED => RETURN[0];
      ENDLOOP;
    RETURN[String.WordsForString[s.length]]
    END;
    
  PutItem: GPsortDefs.PutProcType =
    BEGIN OPEN StreamDefs, OutputDefs;
    maxSi: StreamIndex ← NormalizeIndex[[0, 50000]];
    trailer: STRING = "l3398d2998\b"L;
    namelength: CARDINAL ← 0;
    itemString: STRING ← p;
    PutString[itemString];
    PutChar[cz];
    PutString[trailer];
    UNTIL itemString[namelength] = ': DO
      namelength ← namelength + 1;
      IF namelength > itemString.length THEN ERROR;
      ENDLOOP;
    PutDecimal[namelength];
    PutChar['B];
    PutCR[];
    IF GrIndex[GetIndex[outSh], maxSi] THEN SIGNAL FileTooBig;
    END;
    
  doPriv, xferOnly: BOOLEAN;
  
  PrintSymbols: PROCEDURE =
    BEGIN OPEN symbols, String;
    modname: STRING ← [50]; -- :SP[name]SP
    ss: SubStringDescriptor;
    mySei, sei: ISEIndex;
    thisItem: StreamDefs.StreamIndex;
    AppendString[modname, ": ["L]; -- set up modname
    FOR sei ← FirstCtxSe[stHandle.directoryCtx], NextSe[sei] UNTIL sei = ISENull
      DO mySei ← sei; ENDLOOP;
    SubStringForHash[@ss, seb[mySei].hash];
    AppendSubString[modname, @ss];
    AppendString[modname, "] "L];
    AppendSubString[moduleList, @ss];
    BlinkCursor[];
    AppendChar[moduleList, ' ];
    FOR sei ← FirstCtxSe[stHandle.outerCtx], NextSe[sei] UNTIL sei = ISENull DO
      IF (doPriv OR seb[sei].public) AND ( ~xferOnly OR XferMode[seb[sei].idType]
	# none) THEN
	BEGIN
	defaultPublic ← TRUE;
	PrintSym[sei, modname];
	OutputDefs.PutCR[];
	thisItem ← StreamDefs.GetIndex[outSh];
	largestItem ← MAX[largestItem, SiSub[thisItem, lastItem]];
	lastItem ← thisItem;
	END;
      ENDLOOP;
    END;
    
  SiSub: PROCEDURE [si1, si2: StreamDefs.StreamIndex] RETURNS [CARDINAL] =
    BEGIN OPEN AltoDefs;
    pages: PageNumber ← si1.page - si2.page;
    bytes: CARDINAL ← si1.byte - si2.byte;
    RETURN[pages*BytesPerPage + bytes]
    END;
    
  defaultPublic: BOOLEAN;
  
  PrintSym: PROCEDURE [sei: ISEIndex, colonstring: STRING] =
    BEGIN OPEN symbols;
    savePublic: BOOLEAN ← defaultPublic;
    typeSei: SEIndex;
    IF seb[sei].hash # HTNull THEN
      BEGIN ListerDefs.PrintSei[sei]; OutputDefs.PutString[colonstring]; END;
    IF seb[sei].public # defaultPublic THEN
      BEGIN
      defaultPublic ← seb[sei].public;
      OutputDefs.PutString[IF defaultPublic THEN "PUBLIC "L ELSE "PRIVATE "L];
      END;
    IF seb[sei].idType = typeTYPE THEN
      BEGIN
      typeSei ← seb[sei].idInfo;
      OutputDefs.PutString["TYPE = "L];
      [] ← PrintType[typeSei, NoSub];
      END
    ELSE
      BEGIN
      vf: ValFormat;
      typeSei ← seb[sei].idType;
      vf ← PrintType[typeSei, NoSub];
      IF seb[sei].constant AND vf.tag # none THEN
	BEGIN
	OutputDefs.PutString[" = "L];
	PrintTypedVal[seb[sei].idValue, vf];
	END;
      END;
    defaultPublic ← savePublic;
    END;
    
  PrintTypedVal: PROCEDURE [val: UNSPECIFIED, vf: ValFormat] =
    BEGIN OPEN OutputDefs;
    WITH vf SELECT FROM
      num => PrintValue[val];
      char => BEGIN PutNumber[val, [8, FALSE, TRUE, 0]]; PutChar['C] END;
      enum => PutEnum[val, esei];
      ENDCASE;
    END;
    
  PrintFieldCtx: PROCEDURE [ctx: CTXIndex] =
    BEGIN OPEN symbols, OutputDefs;
    isei: ISEIndex ← FirstCtxSe[ctx];
    first: BOOLEAN ← TRUE;
    IF isei # ISENull AND seb[isei].idCtx # ctx THEN isei ← NextSe[isei];
    IF isei = ISENull THEN BEGIN PutString["NULL"L]; RETURN END;
    PutChar['[];
    FOR isei ← isei, NextSe[isei] UNTIL isei = ISENull DO
      IF first THEN first ← FALSE ELSE PutString[", "L];
      PrintSym[isei, ": "L];
      ENDLOOP;
    PutChar[']];
    END;
    
  PrintValue: PROCEDURE [value: UNSPECIFIED] =
    BEGIN
    IF LOOPHOLE[value, CARDINAL] < 1000 THEN OutputDefs.PutDecimal[value]
    ELSE OutputDefs.PutOctal[value];
    END;
    
  NoSub: PROCEDURE [vf: ValFormat] = BEGIN RETURN END;
    
  arraySub: BOOLEAN ← FALSE;
  
  EnumeratedSEIndex: TYPE = Table.Base RELATIVE POINTER [0..Table.Limit) TO
    enumerated cons
  SERecord;
  ValFormat: TYPE = RECORD [
    SELECT tag: * FROM
    none => NULL,
      num => NULL,
      char => NULL,
      enum => [esei: EnumeratedSEIndex],
      ENDCASE];
  
  PutEnum: PROCEDURE [val: UNSPECIFIED, esei: EnumeratedSEIndex] =
    BEGIN OPEN Symbols, OutputDefs, symbols;
    sei: ISEIndex;
    FOR sei ← FirstCtxSe[seb[esei].valueCtx], NextSe[sei] WHILE sei # ISENull DO
      IF seb[sei].idValue = val THEN BEGIN ListerDefs.PrintSei[sei]; RETURN; END;
      ENDLOOP;
    PutString["LOOPHOLE ["L];
    PrintValue[val];
    PutChar[']];
    END;
    
  PrintType: PROCEDURE [tsei: SEIndex, dosub: PROCEDURE [vf: ValFormat]]
    RETURNS [vf: ValFormat] =
    BEGIN OPEN Symbols, OutputDefs, ListerDefs, symbols;
    vf ← [none[]];
    WITH t: seb[tsei] SELECT FROM
      id =>
	BEGIN OPEN Symbols;
	printBase: BOOLEAN ← TRUE;
	ifInteger: BOOLEAN ← FALSE;
	bsei: SEIndex ← tsei;
	csei: CSEIndex;
	DO
	  csei ← UnderType[bsei];
	  WITH seb[csei] SELECT FROM
	    basic =>
	      BEGIN
	      SELECT code FROM
		codeINT => BEGIN printBase ← ifInteger; vf ← [num[]] END;
		codeCHAR => vf ← [char[]];
		ENDCASE;
	      EXIT;
	      END;
	    subrange => BEGIN bsei ← rangeType; ifInteger ← TRUE END;
	    enumerated =>
	      BEGIN printBase ← TRUE; vf ← [enum[LOOPHOLE[csei]]]; EXIT END;
	    ENDCASE => EXIT;
	  ENDLOOP;
	IF printBase OR dosub = NoSub THEN
	  BEGIN
	  PrintSei[LOOPHOLE[tsei]];
	  UNTIL (tsei ← TypeLink[tsei]) = SENull DO
	    WITH seb[tsei] SELECT FROM
	      id => BEGIN PutChar[' ]; PrintSei[LOOPHOLE[tsei]] END;
	      ENDCASE;
	    ENDLOOP;
	  END;
	dosub[vf];
	END;
      cons =>
	WITH t SELECT FROM
	  --basic =>  won't see one, see the id first.
	  
	  enumerated =>
	    BEGIN
	    isei: ISEIndex;
	    first: BOOLEAN ← TRUE;
	    PutChar['{];
	    FOR isei ← FirstCtxSe[valueCtx], NextSe[isei] UNTIL isei = ISENull DO
	      IF first THEN first ← FALSE ELSE PutString[", "L];
	      PrintSei[isei];
	      ENDLOOP;
	    PutChar['}];
	    END;
	  record =>
	    BEGIN
	    IF ctxb[fieldCtx].level # lZ THEN
	      BEGIN
	      fctx: CTXIndex = fieldCtx;
	      bti: BTIndex ← FIRST[BTIndex];
	      btlimit: BTIndex = bti + stHandle.bodyBlock.size;
	      PutString["FRAME ["];
	      UNTIL bti = btlimit DO
		WITH entry: bb[bti] SELECT FROM
		  Callable =>
		    BEGIN
		    IF entry.localCtx = fctx THEN
		      BEGIN PrintSei[entry.id]; PutChar[']]; EXIT END;
		    bti ←
		      bti +
			(WITH entry SELECT FROM
			   Inner => SIZE[Inner Callable BodyRecord],
			   ENDCASE => SIZE[Outer Callable BodyRecord]);
		    END;
		  ENDCASE => bti ← bti + SIZE[Other BodyRecord];
		ENDLOOP;
	      END
	    ELSE
	      BEGIN
	      IF monitored THEN PutString["MONITORED "L];
	      IF machineDep THEN PutString["MACHINE DEPENDENT "L];
	      PutString["RECORD"L];
	      PrintFieldCtx[fieldCtx];
	      END;
	    END;
	  ref =>
	    BEGIN
	    IF readOnly THEN PutString["READ ONLY "L];
	    IF ordered THEN PutString["ORDERED "L];
	    IF basing THEN PutString["BASE "L];
	    PutString["POINTER"L];
	    IF dosub # NoSub THEN BEGIN PutChar[' ]; dosub[[num[]]]; END;
	    WITH seb[UnderType[refType]] SELECT FROM
	      basic => IF code = Symbols.codeANY THEN GO TO noprint;
	      ENDCASE;
	    PutString[" TO "L];
	    [] ← PrintType[refType, NoSub];
	    EXITS noprint => NULL;
	    END;
	  array =>
	    BEGIN
	    IF packed THEN PutString["PACKED "L];
	    PutString["ARRAY "L];
	    arraySub ← TRUE;
	    [] ← PrintType[indexType, NoSub];
	    arraySub ← FALSE;
	    PutString[" OF "L];
	    [] ← PrintType[componentType, NoSub];
	    END;
	  arraydesc =>
	    BEGIN
	    PutString["DESCRIPTOR FOR "L];
	    [] ← PrintType[describedType, NoSub];
	    END;
	  transfer =>
	    BEGIN
	    PutModeName[mode];
	    IF inRecord # RecordSENull THEN
	      BEGIN PutChar[' ]; PrintFieldCtx[seb[inRecord].fieldCtx]; END;
	    IF outRecord # RecordSENull THEN
	      BEGIN
	      PutString[" RETURNS "L];
	      PrintFieldCtx[seb[outRecord].fieldCtx];
	      END;
	    END;
	  union =>
	    BEGIN
	    tagType: SEIndex;
	    PutString["SELECT "L];
	    IF ~controlled THEN
	      IF overlaid THEN PutString["OVERLAID "L]
	      ELSE PutString["COMPUTED "L]
	    ELSE BEGIN PrintSei[tagSei]; PutString[": "L] END;
	    tagType ← seb[tagSei].idType;
	    IF seb[tagSei].public # defaultPublic THEN
	      OutputDefs.PutString[
		IF defaultPublic THEN "PRIVATE "L ELSE "PUBLIC "L];
	    WITH seb[tagType] SELECT FROM
	      id => [] ← PrintType[tagType, NoSub];
	      cons => PutChar['*];
	      ENDCASE;
	    PutString[" FROM "L];
	    BEGIN
	    isei: ISEIndex;
	    first: BOOLEAN ← TRUE;
	    varRec: RecordSEIndex;
	    FOR isei ← FirstCtxSe[caseCtx], NextSe[isei] UNTIL isei = ISENull DO
	      IF first THEN first ← FALSE ELSE PutString[", "L];
	      PrintSei[isei];
	      PutString[" => "L];
	      varRec ← seb[isei].idInfo;
	      PrintFieldCtx[seb[varRec].fieldCtx];
	      ENDLOOP;
	    PutString[" ENDCASE"L];
	    END;
	    END;
	  relative =>
	    BEGIN
	    IF baseType # SENull THEN [] ← PrintType[baseType, NoSub];
	    PutString["RELATIVE "L];
	    [] ← PrintType[offsetType, dosub];
	    END;
	  subrange =>
	    BEGIN
	    org: INTEGER ← origin;
	    size: CARDINAL ← range;
	    
	    doit: PROCEDURE [pvf: ValFormat] =
	      BEGIN
	      PutChar['[];
	      PrintTypedVal[org, pvf];
	      PutString[".."L];
	      IF arraySub AND size = 177777B THEN
		BEGIN PrintTypedVal[org, pvf]; PutChar[')] END
	      ELSE BEGIN PrintTypedVal[org + size, pvf]; PutChar[']] END;
	      END;
	      
	    vf ← PrintType[rangeType, doit];
	    END;
	  long =>
	    BEGIN PutString["LONG "L]; [] ← PrintType[rangeType, NoSub]; END;
	  real => PutString["REAL"L];
	  ENDCASE => PutString["Send message to SDSUPPORT"L];
      ENDCASE;
    END;
    
  PutModeName: PROCEDURE [n: TransferMode] =
    BEGIN
    ModePrintName: ARRAY TransferMode OF STRING =
      ["PROCEDURE"L, "PORT"L, "SIGNAL"L, "ERROR"L, "PROCESS"L, "PROGRAM"L,
	"NONE"L];
    OutputDefs.PutString[ModePrintName[n]]
    END;
    
  DoSymbols: PROCEDURE [bcdFile: STRING] =
    BEGIN OPEN ListerDefs;
    defs: BOOLEAN ← FALSE;
    cseg, sseg: SegmentDefs.FileSegmentHandle;
    BEGIN
    [code: cseg, symbols: sseg] ← Load[
      bcdFile ! NoFGT => RESUME ; NoCode => RESUME ; -- language feature
       NoSymbols, IncorrectVersion, MultipleModules => GOTO badformat;
      SegmentDefs.FileNameError => GOTO badname];
    IF cseg # NIL THEN SegmentDefs.DeleteFileSegment[cseg];
    DisplayDefs.DisplayOff[black];
    symbols ← SymbolTable.Acquire[SymbolTable.TableForSegment[sseg]];
    SetRoutineSymbols[symbols];
    PrintSymbols[];
    SymbolTable.Release[symbols];
    SymbolTable.SetCacheSize[0];
    SegmentDefs.DeleteFileSegment[sseg ! SegmentDefs.SwapError => CONTINUE];
    EXITS
      badformat =>
	BEGIN OPEN IODefs;
	DisplayDefs.DisplayOn[];
	WriteString[bcdFile];
	WriteString[" Has A Bad Format!"L];
	END;
      badname =>
	BEGIN OPEN IODefs;
	DisplayDefs.DisplayOn[];
	WriteString[bcdFile];
	WriteString[" Not Found!"L];
	END;
    END;
    END;
    
   -- Of DoSymbols
  
  
  AppendBcd: PROCEDURE [s: STRING] =
    BEGIN
    i: CARDINAL;
    FOR i IN [0..s.length) DO
      IF s[i] = '. THEN BEGIN s.length ← i; EXIT END ENDLOOP;
    String.AppendString[s, ".bcd"L];
    END;
    
  globalRoot: STRING;
  
  DoIt: PROCEDURE [root: STRING, myDoPriv, myXferOnly: BOOLEAN] =
    BEGIN OPEN SegmentDefs, OutputDefs;
    list: BOOLEAN;
    bcdFile: STRING ← [40];
    sortFile: STRING ← "2.xref";
    fp: AltoFileDefs.FP;
    globalRoot ← root;
    doPriv ← myDoPriv;
    xferOnly ← myXferOnly;
    String.AppendString[bcdFile, root];
    AppendBcd[bcdFile];
    list ← NOT DirectoryDefs.DirectoryLookup[@fp, bcdFile, FALSE];
    largestItem ← 0;
    lastItem ← [0, 0];
    OutputDefs.OpenOutput[root, ".scratch$"L];
    outSh ← LOOPHOLE[outStream];
    IF list THEN
      BEGIN OPEN StreamDefs;
      inSh ← NewByteStream[root, Read ! FileNameError => GOTO badname];
      GPsortDefs.Sort[GetName, PutName, CompareStrings, 22, 22, 140];
      PutChar[cz];
      PutChar['j];
      PutCR[]; -- trailer for module list
      inSh.destroy[inSh];
      EXITS badname => BEGIN IODefs.WriteString["File Not Found!"L]; RETURN END;
      END
    ELSE
      BEGIN
      DoSymbols[bcdFile];
      ChangeOutput[];
      PutString[moduleList];
      moduleList.length ← 0;
      PutChar[cz];
      PutChar['c];
      PutCR[]; -- trailer for heading
      
      END;
    PutChar[cz];
    PutCR[]; -- skip a line
    largestItem ← largestItem + 20; -- a little slop
    BlinkCursor[];
    GPsortDefs.Sort[
      GetItem, PutItem, CompareStrings, 100, largestItem/2, 15 !
      FileTooBig =>
	BEGIN
	CloseOutput[];
	OpenOutput[root, sortFile];
	outSh ← LOOPHOLE[outStream];
	sortFile[0] ← sortFile[0] + 1;
	RESUME
	END];
    DisplayDefs.DisplayOn[];
    sortSh.destroy[sortSh];
    UnlockFile[sortSh.file];
    DestroyFile[sortSh.file];
    CloseOutput[];
    END;
    
  BlinkCursor: PROCEDURE =
    BEGIN
    map: POINTER TO WORD = LOOPHOLE[431B];
    i: CARDINAL;
    FOR i IN [0..16) DO
      (map + i)↑ ← InlineDefs.BITXOR[(map + i)↑, 177777B]; ENDLOOP;
    FOR i IN [0..1000) DO NULL ENDLOOP; -- wait a little while
    FOR i IN [0..16) DO
      (map + i)↑ ← InlineDefs.BITXOR[(map + i)↑, 177777B]; ENDLOOP;
    END;
    
  ChangeOutput: PROCEDURE =
    BEGIN OPEN SegmentDefs, OutputDefs;
    LockFile[outSh.file];
    CloseOutput[];
    sortSh ← StreamDefs.CreateByteStream[outSh.file, Read];
    OpenOutput[globalRoot, ".xref"L];
    outSh ← LOOPHOLE[outStream];
    PutString["PUBLIC SYMBOLS FOR "L];
    END;
    
  GetName: GPsortDefs.GetProcType =
    BEGIN OPEN String;
    char: CHARACTER ← 0C;
    file: STRING ← [40];
    s: STRING ← p;
    s↑ ← [length: 0, maxlength: 40, text:];
    UNTIL inSh.endof[inSh] DO
      char ← inSh.get[inSh];
      SELECT char FROM
	'-, '., '$ => AppendChar[file, char];
	IN ['0..'9] => AppendChar[file, char];
	IN ['A..'Z] => AppendChar[file, char];
	IN ['a..'z] => AppendChar[file, char];
	ENDCASE => IF file.length # 0 THEN EXIT;
      REPEAT
	FINISHED =>
	  BEGIN OPEN OutputDefs;
	  ChangeOutput[];
	  PutChar[cz];
	  PutChar['c];
	  PutCR[]; -- trailer for heading
	  RETURN[0];
	  END;
      ENDLOOP;
    AppendBcd[file];
    DoSymbols[file];
    AppendString[s, moduleList];
    moduleList.length ← 0;
    RETURN[WordsForString[s.length]]
    END;
    
  PutName: GPsortDefs.PutProcType =
    BEGIN s: STRING ← LOOPHOLE[p]; OutputDefs.PutString[s]; END;
    
  -- mainline
  
  command: CommanderDefs.CommandBlockHandle;
  
  command ← CommanderDefs.AddCommand["Xref", LOOPHOLE[DoIt], 3];
  command.params[0] ← [type: string, prompt: "Filename"];
  command.params[1] ← [type: boolean, prompt: "Include Private Symbols?"];
  command.params[2] ← [type: boolean, prompt: "Procedures Only?"];
  
  END...