-- ListXref.mesa
-- last edited by Sweet; 20-Mar-81 11:56:48
-- last edited by Satterthwaite; September 20, 1982 1:43 pm

DIRECTORY
  Ascii USING [CR],
  BcdDefs,
  BcdOps,
  CommanderOps USING [AddCommand, CommandBlockHandle],
  FileSegment: TYPE USING [Pages],
  FileStream: TYPE USING [Create, EndOf],
  Format,
  GSort USING [CompareProcType, Port, PutProcType, Sort, SortItemPort, SortStarter, SortStopper],
  Heap: TYPE USING [systemZone],
  ListerDefs USING [
    IncorrectVersion, Load, MapPages, MultipleModules, NoCode, NoFGT, NoFile,
    NoSymbols, SetRoutineSymbols, WriteChar, WriteLine, WriteString],
  LongString USING [
    AppendChar, AppendString, AppendSubString, CompareStrings, EquivalentString,
    SubString, SubStringDescriptor, WordsForString],
  Mopcodes USING [
    zEFC0, zEFC15, zEFCB, zJ2, zJIW, zLADRB, zLFC1, zLFC16, zLFCB, zLLB, zNOOP, zSFC],
  OpTableDefs USING [InstAligned, InstLength],
  OSMiscOps: TYPE USING [FileError, FindFile],
  OutputDefs USING [CloseOutput, OpenOutput, PutCR, PutLongString, PutString],
  PrincOps USING [CSegPrefix, FrameHandle],
  Space: TYPE USING [Handle, Delete, LongPointer],
  Stream: TYPE USING [Delete, GetChar, Handle],
  Symbols USING [
    BitAddress, BTIndex, BTNull, CTXIndex, HTIndex, ISEIndex, ISENull, SENull],
  SymbolTable USING [Acquire, Base, Release];

ListXref: PROGRAM
  IMPORTS
    CommanderOps, FileStream, GSort, Heap, ListerDefs, Strings: LongString,
    OpTableDefs, OSMiscOps, OutputDefs, Space, Stream, SymbolTable =
  BEGIN OPEN ListerDefs, OutputDefs;
  
  FrameHandle: TYPE = PrincOps.FrameHandle;
  NumberFormat: TYPE = Format.NumberFormat;
  BYTE: TYPE = [0..256);
  opcode: TYPE = BYTE;
  
  JumpOp: TYPE = [Mopcodes.zJ2..Mopcodes.zJIW];
  InstWord: TYPE = MACHINE DEPENDENT RECORD [
    SELECT COMPUTED BOOLEAN FROM
    FALSE => [oddbyte, evenbyte: BYTE],
      TRUE => [evenbyte, oddbyte: BYTE],
      ENDCASE];
  
  offset: CARDINAL;
  codebase: LONG POINTER;
  codepages: CARDINAL;
  symbols: SymbolTable.Base;
  Tinst, Tbytes, Pinst, Pbytes: CARDINAL ← 0;
  dStar: BOOLEAN ← FALSE;
  
  KeyBase: TYPE = LONG BASE POINTER TO SortKey;
  SortKey: TYPE = RECORD [
    callee: KeyBase RELATIVE POINTER TO StringBody, caller: StringBody];
  buffer: KeyBase;
  callerName: STRING ← [80];
  moduleName: STRING ← [40];
  
  RecordLocal: PROCEDURE [ep: CARDINAL] =
    BEGIN OPEN Strings;
    desc: SubStringDescriptor;
    buffer↑ ← [callee: NULL, caller: [length: 0, maxlength: 100, text:]];
    AppendString[@buffer.caller, callerName];
    buffer.callee ← LOOPHOLE[WordsForString[buffer.caller.length] + 1];
    buffer[buffer.callee] ← [length: 0, maxlength: 100, text:];
    IF epMap = NIL THEN ERROR;
    symbols.SubStringForHash[@desc, epMap[ep]];
    AppendSubString[@buffer[buffer.callee], @desc];
    AppendString[@buffer[buffer.callee], moduleName];
    buffer ← OutToSort[
      WordsForString[buffer.caller.length] + WordsForString[
	buffer[buffer.callee].length] + 1];
    END;
    
  RecordExternal: PROCEDURE [link: CARDINAL] =
    BEGIN OPEN Strings;
    desc: SubStringDescriptor;
    offset: CARDINAL;
    buffer↑ ← [callee: NULL, caller: [length: 0, maxlength: 100, text:]];
    AppendString[@buffer.caller, callerName];
    buffer.callee ← LOOPHOLE[WordsForString[buffer.caller.length] + 1];
    buffer[buffer.callee] ← [length: 0, maxlength: 100, text:];
    IF linkMap = NIL THEN ERROR;
    symbols.SubStringForHash[@desc, linkMap[link].hti];
    AppendSubString[@buffer[buffer.callee], @desc];
    AppendChar[@buffer[buffer.callee], '[];
    offset ← linkMap[link].ssd.offset;
    FOR i: CARDINAL IN [0 .. linkMap[link].ssd.length) DO
      AppendChar[@buffer[buffer.callee], linkMap[link].ssd.base[offset+i]]
      ENDLOOP;
    AppendChar[@buffer[buffer.callee], ']];
    buffer ← OutToSort[
      WordsForString[buffer.caller.length] + WordsForString[
	buffer[buffer.callee].length] + 1];
    END;
    
   RecordUnknown: PROCEDURE =
    BEGIN OPEN Strings;
    buffer↑ ← [callee: NULL, caller: [length: 0, maxlength: 100, text:]];
    AppendString[@buffer.caller, callerName];
    buffer.callee ← LOOPHOLE[WordsForString[buffer.caller.length] + 1];
    buffer[buffer.callee] ← [length: 1, maxlength: 100, text:];
    buffer[buffer.callee].text[0] ← '*;
    buffer ← OutToSort[WordsForString[buffer.caller.length] + 3 + 1];
    END;
    
  CompareCallers: PROCEDURE [p1, p2: LONG POINTER] RETURNS [i: INTEGER] =
    BEGIN
    k1: KeyBase = p1;
    k2: KeyBase = p2;
    i ← Strings.CompareStrings[@k1.caller, @k2.caller];
    IF i = 0 THEN i ← Strings.CompareStrings[@k1[k1.callee], @k2[k2.callee]];
    END;
    
  CompareCallees: PROCEDURE [p1, p2: LONG POINTER] RETURNS [i: INTEGER] =
    BEGIN
    k1: KeyBase = p1;
    k2: KeyBase = p2;
    i ← Strings.CompareStrings[@k1[k1.callee], @k2[k2.callee]];
    IF i = 0 THEN i ← Strings.CompareStrings[@k1.caller, @k2.caller];
    END;
    
  lastMajor: STRING ← [80];
  lastMinor: STRING ← [80];
  onThisLine: CARDINAL ← 0;
  MaxOnLine: CARDINAL ← 80;
  first: BOOLEAN ← TRUE;
  
  NextItem: PROCEDURE [major, minor: LONG STRING] =
    BEGIN OPEN OutputDefs;
    IF ~Strings.EquivalentString[major, lastMajor] THEN
      BEGIN
      PutCR[];
      PutCR[];
      PutLongString[major];
      PutCR[];
      PutString["    "L];
      onThisLine ← 4;
      first ← TRUE;
      lastMajor.length ← 0;
      Strings.AppendString[lastMajor, major];
      END;
    IF ~first THEN
      BEGIN
      IF Strings.EquivalentString[minor, lastMinor] THEN RETURN;
      PutString[", "L];
      onThisLine ← onThisLine + 2;
      IF onThisLine + minor.length > MaxOnLine THEN
	{PutCR[]; PutString["    "L]; onThisLine ← 4};
      END;
    PutLongString[minor];
    onThisLine ← onThisLine + minor.length;
    lastMinor.length ← 0;
    Strings.AppendString[lastMinor, minor];
    first ← FALSE;
    END;
    
  PutByCaller: PROCEDURE [p: LONG POINTER, len: CARDINAL] =
    BEGIN
    key: KeyBase = p;
    NextItem[major: @key.caller, minor: @key[key.callee]];
    END;
    
  PutByCallee: PROCEDURE [p: LONG POINTER, len: CARDINAL] =
    BEGIN
    key: KeyBase = p;
    NextItem[major: @key[key.callee], minor: @key.caller];
    END;
    
  EPList: TYPE = RECORD [SEQUENCE length: NAT OF Symbols.HTIndex];
  epMap: LONG POINTER TO EPList ← NIL;
  
  CreateEpMap: PROCEDURE =
    BEGIN
    n: CARDINAL ← 0;
    
    Count: PROCEDURE [bti: Symbols.BTIndex] RETURNS [stop: BOOLEAN] =
      BEGIN
      stop ← FALSE;
      WITH b: symbols.bb[bti] SELECT FROM
	Callable => IF ~b.inline THEN n ← MAX[b.entryIndex, n];
	ENDCASE;
      END;
      
    Enter: PROCEDURE [bti: Symbols.BTIndex] RETURNS [stop: BOOLEAN] =
      BEGIN
      stop ← FALSE;
      WITH b: symbols.bb[bti] SELECT FROM
	Callable =>
	  IF ~b.inline THEN epMap[b.entryIndex] ← symbols.HashForSe[b.id];
	ENDCASE;
      END;
      
    [] ← symbols.EnumerateBodies[FIRST[Symbols.BTIndex], Count];
    epMap ← (Heap.systemZone).NEW[EPList[n+1]];
    [] ← symbols.EnumerateBodies[FIRST[Symbols.BTIndex], Enter];
    END;
    
  LinkMapItem: TYPE = RECORD [
    hti: Symbols.HTIndex,
    ssd: Strings.SubStringDescriptor];
  LinkList: TYPE = RECORD [SEQUENCE length: NAT OF LinkMapItem];
  linkMap: LONG POINTER TO LinkList ← NIL;
  
  CreateLinkMap: PROCEDURE =
    BEGIN
    m: CARDINAL ← 0;
    
    FindMax: PROCEDURE [sei: Symbols.ISEIndex, mname: Strings.SubString] =
      BEGIN OPEN symbols;
      IF seb[sei].linkSpace AND ~seb[sei].constant AND ~seb[sei].extended THEN
	BEGIN a: Symbols.BitAddress = seb[sei].idValue; m ← MAX[m, a.wd]; END;
      END;
      
    Insert: PROCEDURE [sei: Symbols.ISEIndex, mname: Strings.SubString] =
      BEGIN OPEN symbols;
      IF seb[sei].linkSpace AND ~seb[sei].constant AND ~seb[sei].extended THEN
	BEGIN
	a: Symbols.BitAddress = seb[sei].idValue;
	linkMap[a.wd] ← [symbols.HashForSe[sei], mname↑];
	END;
      END;
      
    GenImports[FindMax];
    linkMap ← (Heap.systemZone).NEW[LinkList[m + 1]];
    GenImports[Insert];
    END;
    
  GenCtx: PROCEDURE [ctx: Symbols.CTXIndex, p: PROCEDURE [Symbols.ISEIndex]] =
    BEGIN OPEN Symbols, symbols;
    sei: ISEIndex;
    FOR sei ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = SENull DO p[sei]; ENDLOOP;
    END;
    
  GenImports: PROCEDURE [
    action: PROC [sei: Symbols.ISEIndex, mname: Strings.SubString]] =
    BEGIN OPEN Symbols, symbols;
    sei: ISEIndex;
    ctx: CTXIndex;
    modnameSS: Strings.SubStringDescriptor;
    
    DoAction: PROCEDURE [sei: ISEIndex] = BEGIN action[sei, @modnameSS]; END;
      
    FOR sei ← FirstCtxSe[stHandle.importCtx], NextSe[sei] UNTIL sei = ISENull
      DO
      SubStringForHash[@modnameSS, seb[sei].hash];
      WITH seb[UnderType[seb[sei].idType]] SELECT FROM
	definition =>
	  BEGIN
	  isei: ISEIndex;
	  ctx ← defCtx;
	  FOR isei ← FirstCtxSe[stHandle.importCtx], NextSe[isei] UNTIL isei =
	    ISENull DO
	    WITH seb[UnderType[seb[isei].idType]] SELECT FROM
	      definition =>
		WITH ctxb[defCtx] SELECT FROM
		  imported =>
		    IF includeLink = ctx THEN BEGIN ctx ← defCtx; EXIT END;
		  ENDCASE;
	      ENDCASE;
	    ENDLOOP;
	  END;
	ENDCASE;
      GenCtx[ctx, DoAction];
      WITH ctxb[ctx] SELECT FROM
	included => NULL;
	imported => GenCtx[includeLink, DoAction];
	ENDCASE => LOOP; -- main body
      
      ENDLOOP;
    END;
    
  EvenUp: PROCEDURE [n: CARDINAL] RETURNS [CARDINAL] = 
    -- Round up to an even number
    BEGIN RETURN[n + n MOD 2]; END;
    
  getbyte: PROCEDURE [pc: CARDINAL] RETURNS [b: BYTE] = 
    -- pc is a byte address
    BEGIN
    w: LONG POINTER TO InstWord;
    w ← codebase + pc/2;
    b ←
      IF pc MOD 2 = 0 THEN
      (WITH w↑ SELECT dStar FROM
	 FALSE => evenbyte,
	 TRUE => evenbyte,
	 ENDCASE => 0)
      ELSE
	(WITH w↑ SELECT dStar FROM
	   FALSE => oddbyte,
	   TRUE => oddbyte,
	   ENDCASE => 0);
    END;
    
  getword: PROCEDURE [pc: CARDINAL] RETURNS [WORD] = 
    -- pc is a word address
    BEGIN RETURN[(codebase + pc)↑]; END;
    
  ExamineCode: PROCEDURE [startcode, endcode: CARDINAL] =
    BEGIN -- list opcodes for indicated segment of code
    OPEN Mopcodes;
    inst, byte, lastInst: BYTE;
    il: [0..3];
    lastInst ← zNOOP;
    FOR offset IN [startcode..endcode) DO
      lastInst ← inst;
      inst ← getbyte[offset];
      il ← OpTableDefs.InstLength[inst];
      IF ~dStar AND OpTableDefs.InstAligned[inst] AND (offset + il) MOD 2 # 0 THEN
	[] ← getbyte[offset ← offset + 1];
      SELECT il FROM
	0, 1 =>
	  SELECT inst FROM
	    IN [zLFC1..zLFC16] => RecordLocal[inst - zLFC1 + 1];
	    IN [zEFC0..zEFC15] => RecordExternal[inst - zEFC0];
	    zSFC => IF ~(lastInst = zLADRB OR (lastInst = zLLB AND byte = 2)) THEN RecordUnknown[];
	    ENDCASE;
	2 =>
	  BEGIN
	  byte ← getbyte[(offset ← offset + 1)];
	  SELECT inst FROM
	    zLFCB => RecordLocal[byte];
	    zEFCB => RecordExternal[byte];
	    ENDCASE;
	  END;
	3 =>
	  BEGIN
	  [] ← getbyte[(offset ← offset + 1)];
	  [] ← getbyte[(offset ← offset + 1)];
	  END;
	ENDCASE;
      ENDLOOP;
    END;
    
  ProcessFile: PROCEDURE [root: STRING] =
    BEGIN OPEN Strings, symbols, Symbols;
    i: CARDINAL;
    cseg, sseg, bcdseg: FileSegment.Pages;
    codeSpace, bcdSpace: Space.Handle;
    bcd: BcdOps.BcdBase;
    mth: BcdOps.MTHandle;
    bcdFile: STRING ← [40];
    cspp: LONG POINTER TO PrincOps.CSegPrefix;
    prevBti: BTIndex ← BTNull;
    desc: SubStringDescriptor;
    
    SearchBody: PROCEDURE [bti: BTIndex] RETURNS [stop: BOOLEAN] =
      BEGIN
      ipc: CARDINAL;
      WITH b: symbols.bb[bti] SELECT FROM
	Callable =>
	  IF ~b.inline THEN
	    BEGIN
	    desc: SubStringDescriptor;
	    hti: HTIndex = symbols.HashForSe[b.id];
	    symbols.SubStringForHash[@desc, hti];
	    callerName.length ← 0;
	    AppendSubString[callerName, @desc];
	    AppendString[callerName, moduleName];
	    ipc ← cspp.entry[b.entryIndex].initialpc*2;
	    WITH bi: b.info SELECT FROM
	      External => IF bi.bytes # 0 THEN ExamineCode[ipc, ipc + bi.bytes];
	      ENDCASE => ERROR;
	    END;
	ENDCASE;
      RETURN[FALSE]
      END;
      
    AppendString[bcdFile, root];
    FOR i IN [0..root.length) DO
      IF root[i] = '. THEN {bcdFile.length ← i; EXIT}; ENDLOOP;
    AppendString[bcdFile, ".bcd"L];
    BEGIN OPEN ListerDefs;
    [cseg, sseg, bcdseg] ← Load[bcdFile !
      NoFGT => RESUME ; NoCode => GO TO badformat;
      NoSymbols, IncorrectVersion, MultipleModules => GOTO badformat;
      NoFile => GOTO badname];
    bcdSpace ← MapPages[bcdseg];
    bcd ← bcdSpace.LongPointer;
    mth ← @LOOPHOLE[bcd + bcd.mtOffset, BcdDefs.Base][FIRST[BcdDefs.MTIndex]];
    codeSpace ← MapPages[cseg];
    codebase ← codeSpace.LongPointer + mth.code.offset;
    codepages ← cseg.span.pages;
    cspp ← codebase;
    dStar ← ~cspp.header.info.altoCode;
    symbols ← SymbolTable.Acquire[sseg];
    Space.Delete[bcdSpace];
    ListerDefs.SetRoutineSymbols[symbols];
    BEGIN OPEN s: symbols;
    main: BTIndex = FIRST[BTIndex];
    hti: HTIndex;
    CreateEpMap[];
    CreateLinkMap[];
    WITH b: s.bb[main] SELECT FROM
      Callable => hti ← s.HashForSe[b.id];
      ENDCASE => ERROR;
    moduleName.length ← 0;
    AppendChar[moduleName, '[];
    s.SubStringForHash[@desc, hti];
    AppendSubString[moduleName, @desc];
    AppendChar[moduleName, ']];
    [] ← s.EnumerateBodies[FIRST[BTIndex], SearchBody];
    END;
    SymbolTable.Release[symbols];
    Space.Delete[codeSpace];
    IF epMap # NIL THEN (Heap.systemZone).FREE[@epMap];
    IF linkMap # NIL THEN (Heap.systemZone).FREE[@linkMap];
    EXITS
      badformat => WriteString["--ignored (defs or config?)"L];
      badname => WriteString["--not found"L];
    END;
    END;
    
  OutToSort: GSort.SortItemPort;
  
  DoXref: PROCEDURE [
    fileList: STRING, Compare: GSort.CompareProcType,
    Put: GSort.PutProcType, ext: STRING] =
    BEGIN OPEN Strings;
    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, pagesInHeap: 90];
    -- go through list of names, calling OutToSort
    UNTIL FileStream.EndOf[cs] DO
      s.length ← 0;
      WHILE ~FileStream.EndOf[cs] AND (ch ← cs.GetChar[]) # '  DO
	AppendChar[s, ch]; ENDLOOP;
      IF s.length > 0 THEN
	BEGIN
	WriteString["    "L];
	WriteString[s];
	ProcessFile[s];
	WriteChar[Ascii.CR];
	END;
      ENDLOOP;
    Stream.Delete[cs];
    -- get ready to write output
    OutputDefs.OpenOutput[fileList, ext];
    lastMajor.length ← 0;
    lastMinor.length ← 0;
    -- shut down the sort package (and call Put many times)
    LOOPHOLE[OutToSort, GSort.SortStopper][];
    OutputDefs.PutCR[];
    OutputDefs.CloseOutput[];
    EXITS notFound => WriteLine["  Command file not found"L];
    END;
    
  XrefByCaller: PROCEDURE [fileList: STRING] =
    BEGIN DoXref[fileList, CompareCallers, PutByCaller, ".xlr"L]; END;
    
  XrefByCallee: PROCEDURE [fileList: STRING] =
    BEGIN DoXref[fileList, CompareCallees, PutByCallee, ".xle"L]; END;
    
  Init: PROCEDURE =
    BEGIN
    command: CommanderOps.CommandBlockHandle;
    command ← CommanderOps.AddCommand["XrefByCaller", LOOPHOLE[XrefByCaller], 1];
    command.params[0] ← [type: string, prompt: "Filename"];
    command ← CommanderOps.AddCommand["XrefByCallee", LOOPHOLE[XrefByCallee], 1];
    command.params[0] ← [type: string, prompt: "Filename"];
    END;
    
  Init[];
  END.