-- file tCLList.mesa 
-- last edited by Satterthwaite on May 12, 1983 8:52 am

DIRECTORY
  BcdDefs: TYPE USING [Base, MTIndex],
  BcdOps: TYPE USING [BcdBase, MTHandle],
  CatchFormat: TYPE USING [
    CatchEV, CatchEVBody, CatchEVHandle, Codebase, EnableHandle, EnableTableBody],
  CharIO: TYPE USING [PutChar, PutNumber, PutString, PutSubString],
  Environment: TYPE USING [Byte, PageCount],
  ESCAlpha: TYPE USING [alpha],
  ESCAlphaSDDefsNames: TYPE USING [],
  FileSegment: TYPE USING [Pages, nullPages],
  FileStream: TYPE USING [Create, EndOf, SetIndex],
  Format: TYPE USING [NumberFormat],
  Heap: TYPE USING [systemZone],
  Inline: TYPE USING [BITOR],
  ListerOps: TYPE USING [CodeOptions],
  ListerUtil: TYPE USING [
    CreateStream, LoadBcd, LoadModule, MapPages, Message,
    SetFileName, SetRoutineSymbols, PutFileID, UnknownModule],
  Mopcodes: TYPE USING [
    zESC, zESCL, zJ2, zJ4, zJ6, zJ8, zJEBB, zJEP, zJIB, zJIW,
    zJNEBB, zJNEP, zJNZ3, zJNZ4, zJZ3, zJZ4, zKFCB,
    zLI0, zLI10, zLIB, zLID0, zLIW, zPS0F, zPSF, zPSLF,
    zR0F, zRF, zRGILP, zRGIP, zRL0F, zRLDILP, zRLDIP, zRLF,
    zRLILP, zRLILPF, zRLIP, zRLIPF,
    zW0F, zWLDILP, zWLF, zWLILP, zWLIP, zWF, zWS0F],
  OpTableDefs: TYPE USING [InstLength, InstName],
  OSMiscOps: TYPE USING [FileError, FindFile],
  PrincOps: TYPE USING [InstWord],
  Runtime: TYPE USING [GetTableBase],
  Space: TYPE USING [Handle, LongPointer, Delete],
  Stream: TYPE USING [Delete, GetChar, Handle],
  Strings: TYPE USING [String, SubStringDescriptor, EqualSubStrings],
  Symbols: TYPE USING [
    Name, ISEIndex, BodyInfo, BTIndex, BTNull, CBTIndex,
    nullName, SENull],
  SymbolSegment: TYPE USING [FGTEntry],
  SymbolTable: TYPE USING [Base, Acquire, Release, SetCacheSize];

CLList: PROGRAM
  IMPORTS
    CharIO, FileStream, ESCAlphaSDDefsNames, Heap, Inline, ListerUtil, OpTableDefs,
    OSMiscOps, Runtime, Space, Stream, Strings, SymbolTable
  EXPORTS ListerOps = {
  
  CodeOptions: TYPE ~ ListerOps.CodeOptions;
  
  MTIndex: TYPE ~ BcdDefs.MTIndex;
  NumberFormat: TYPE ~ Format.NumberFormat;
  PageCount: TYPE ~ Environment.PageCount;
  BYTE: TYPE ~ Environment.Byte;
  OpCode: TYPE ~ BYTE;
  
  JumpOp: TYPE ~ [Mopcodes.zJ2..Mopcodes.zJIW];

  FineGrainInfo: TYPE ~ RECORD [
    firstSource, lastSource: CARDINAL ← nullSource,
    pc: CARDINAL,
    procEnd: BOOL,
    bti: Symbols.CBTIndex];
  FGT: TYPE ~ RECORD [
    length: NAT,
    info: SEQUENCE maxLength: NAT OF FineGrainInfo];
  FGHandle: TYPE ~ LONG POINTER TO FGT;

  nullSource: CARDINAL ~ CARDINAL.LAST; -- if lastSource, causes to EOF
  
  myFGT: FGHandle;
  
  DigestFGT: PROC ~ {
    OPEN s~~symbols;
    bti, prev: Symbols.BTIndex;
    cspp: CatchFormat.Codebase ~ codebase;
    catchEV: CatchFormat.CatchEV ~ LOOPHOLE[cspp.entry[cspp.header.nEntries]/2];
    catchEntry: CatchFormat.CatchEVHandle ~ @cspp[catchEV];
    
    AddMyEntry: PROC [
       source: CARDINAL←nullSource, object: CARDINAL, procEnd: BOOL←FALSE] ~ {
      IF n = myFGTSize THEN {
	oldFGT: FGHandle ← myFGT;
	myFGTSize ← myFGTSize + 10;
	SetupMyFGT[];
	FOR i: NAT IN [0..oldFGT.maxLength) DO
	  myFGT[i] ← oldFGT[i] ENDLOOP;
	(Heap.systemZone).FREE[@oldFGT]};
      myFGT[n] ←
	[firstSource~source, pc~object, procEnd~procEnd, bti~LOOPHOLE[bti]];
      myFGT.length ← n ← n + 1};
      
    AddBodyFGT: PROC [bti: Symbols.CBTIndex] ~ {
      OPEN s~~symbols;
      procstart: CARDINAL ~ WITH body~~s.bb[bti] SELECT FROM
        Catch => catchEntry[body.index],
        ENDCASE => cspp.entry[body.entryIndex].pc;
      info: Symbols.BodyInfo.External ~ NARROW[s.bb[bti].info, Symbols.BodyInfo.External];
      fgLast: CARDINAL ~ info.startIndex + info.indexLength - 1;
      lastSource: CARDINAL ← s.bb[bti].sourceIndex;
      lastObject: CARDINAL ← procstart;
      FOR i: CARDINAL IN [info.startIndex..fgLast] DO
	f: SymbolSegment.FGTEntry ~ s.fgTable[i];
	WITH f SELECT FROM
	  normal => {
	    lastSource ← lastSource + deltaSource;
	    lastObject ← lastObject + deltaObject;
	    AddMyEntry[source~lastSource, object~lastObject]};
	  step =>
	    IF which = source THEN lastSource ← lastSource + delta
	    ELSE lastObject ← lastObject + delta;
	  ENDCASE;
	ENDLOOP;
      AddMyEntry[object~procstart+info.bytes, procEnd~TRUE]};
      
    SetupMyFGT: PROC ~ INLINE {
      myFGT ← (Heap.systemZone).NEW[FGT[myFGTSize] ← [length~0, info~TRASH]]};
      
    BySource: PROC [r1, r2: LONG POINTER TO FineGrainInfo] RETURNS [BOOL] ~ {
      RETURN [
        IF r1.firstSource > r2.firstSource THEN TRUE
        ELSE IF r1.firstSource = r2.firstSource THEN r1.pc > r2.pc
        ELSE FALSE]};
      
    ByPC: PROC [r1, r2: LONG POINTER TO FineGrainInfo] RETURNS [BOOL] ~ {
      RETURN [
        IF r1.pc > r2.pc THEN TRUE
        ELSE IF r1.pc < r2.pc THEN FALSE
        ELSE IF r1.procEnd THEN FALSE
        ELSE IF r2.procEnd THEN TRUE
        ELSE r1.firstSource > r2.firstSource]};
      
    Sort: PROC [
       n: CARDINAL,
       greater: PROC [r1, r2: LONG POINTER TO FineGrainInfo] RETURNS [BOOL]] ~ {
      i: CARDINAL;
      temp: FineGrainInfo;

      SiftUp: PROC [l, u: CARDINAL] ~ {
	s: CARDINAL;
	key: FineGrainInfo ← myFGT[l-1];
	DO
	  s ← l*2;
	  IF s > u THEN EXIT;
	  IF s < u AND greater[@myFGT[s+1-1], @myFGT[s-1]] THEN s ← s+1;
	  IF greater[@key, @myFGT[s-1]] THEN EXIT;
	  myFGT[l-1] ← myFGT[s-1];
	  l ← s;
	  ENDLOOP;
	myFGT[l-1] ← key};

      FOR i DECREASING IN [2..n/2] DO SiftUp[i, n]; ENDLOOP;
      FOR i DECREASING IN [2..n] DO
	SiftUp[1, i];
	temp ← myFGT[1-1];
	myFGT[1-1] ← myFGT[i-1];
	myFGT[i-1] ← temp;
	ENDLOOP};
      
    n: CARDINAL ← 0;
    myFGTSize: CARDINAL ← (3*s.fgTable.LENGTH)/2;
    SetupMyFGT[];
    bti ← Symbols.BTIndex.FIRST;
    IF s.bb[bti].sourceIndex # 0 THEN 
      AddMyEntry[source~0, object~cspp.entry[0].pc];
    DO
      WITH s.bb[bti] SELECT FROM
	Callable => IF ~inline THEN AddBodyFGT[LOOPHOLE[bti]];
	ENDCASE;
      IF s.bb[bti].firstSon # Symbols.BTNull THEN bti ← s.bb[bti].firstSon
      ELSE
	DO
	  prev ← bti;
	  bti ← s.bb[bti].link.index;
	  IF bti = Symbols.BTNull THEN GO TO Done;
	  IF s.bb[prev].link.which # parent THEN EXIT;
	  ENDLOOP;
      REPEAT Done => NULL;
      ENDLOOP;
    Sort[n, BySource];
    FOR i: CARDINAL DECREASING IN [0 .. n-1) DO
      IF myFGT[i].firstSource = nullSource THEN LOOP;
      IF myFGT[i].firstSource = myFGT[i+1].firstSource THEN {
        myFGT[i].lastSource ← myFGT[i+1].lastSource; 
	myFGT[i+1].firstSource ← myFGT[i+1].lastSource}
      ELSE myFGT[i].lastSource ← myFGT[i + 1].firstSource;
      ENDLOOP;
    Sort[n, ByPC]};
    
  offset: CARDINAL;
  codebase: LONG POINTER;
  codepages: PageCount;
  symbols: SymbolTable.Base;
  Tinst, Tbytes, Pinst, Pbytes: CARDINAL ← 0;
  
  -- number formats (initialized by Octify)
  
  decimal: NumberFormat ~ [base~10, columns~1, zerofill~FALSE, unsigned~TRUE];
  decimal3: NumberFormat ~ [base~10, columns~3, zerofill~FALSE, unsigned~TRUE];

  hoctal0: NumberFormat;
  hoctal1: NumberFormat;
  hoctal3: NumberFormat;
  hoctal3z: NumberFormat;
  hoctal5: NumberFormat;
  hoctal6: NumberFormat;
  
  -- set base for listings
  
  Hexify: PROC ~ {
    hoctal0 ← [base~16, columns~0, zerofill~FALSE, unsigned~TRUE];
    hoctal1 ← [base~16, columns~1, zerofill~FALSE, unsigned~TRUE];
    hoctal3 ← [base~16, columns~3, zerofill~FALSE, unsigned~TRUE];
    hoctal3z ← [base~16, columns~3, zerofill~FALSE, unsigned~TRUE];
    hoctal5 ← [base~16, columns~5, zerofill~FALSE, unsigned~TRUE];
    hoctal6 ← [base~16, columns~6, zerofill~FALSE, unsigned~TRUE]};
    
  Octify: PROC ~ {
    hoctal0 ← [base~8, columns~0, zerofill~FALSE, unsigned~TRUE];
    hoctal1 ← [base~8, columns~1, zerofill~FALSE, unsigned~TRUE];
    hoctal3 ← [base~8, columns~3, zerofill~FALSE, unsigned~TRUE];
    hoctal3z ← [base~8, columns~3, zerofill~TRUE, unsigned~TRUE];
    hoctal5 ← [base~8, columns~5, zerofill~FALSE, unsigned~TRUE];
    hoctal6 ← [base~8, columns~6, zerofill~FALSE, unsigned~TRUE]};
    
  -- source file procedures
  
  source: Stream.Handle;
  sourceAvailable: BOOL;
  
  out: Stream.Handle ← NIL;
  
  OpenOutput: PROC [root: Strings.String] ~ {
    outName: STRING ← [40];
    ListerUtil.SetFileName[outName, root, "cl"L];
    out ← ListerUtil.CreateStream[outName]};
    
  CloseOutput: PROC ~ {
    Stream.Delete[out];  out ← NIL};
    

  OutCheck: PROC [xfirst: CARDINAL, xlast: CARDINAL] ~ {
    nextchar: CHAR;
    lastcr: CARDINAL;
    IF ~sourceAvailable THEN RETURN;
    FOR lastcr ← xfirst, lastcr - 1 UNTIL lastcr = 0 DO
      FileStream.SetIndex[source, lastcr];
      IF source.GetChar = '\n THEN EXIT;
      ENDLOOP;
    THROUGH (lastcr..xfirst) DO CharIO.PutChar[out, ' ] ENDLOOP;
    FileStream.SetIndex[source, xfirst];
    WHILE xfirst # xlast DO
      IF FileStream.EndOf[source] THEN GOTO eof;
      nextchar ← source.GetChar;
      xfirst ← xfirst + 1;
      IF nextchar = '\032 THEN	-- Bravo trailer
	WHILE nextchar # '\n DO
	  IF FileStream.EndOf[source] THEN GOTO eof;
	  nextchar ← source.GetChar;
	  xfirst ← xfirst + 1;
	  ENDLOOP;
      CharIO.PutChar[out, nextchar];
      REPEAT eof => NULL;
      ENDLOOP;
    IF nextchar # '\n THEN CharIO.PutChar[out, '\n]};
    
  SetUpSource: PROC ~ {
    sourceAvailable ← TRUE;
    source ← FileStream.Create[
      OSMiscOps.FindFile[symbols.sourceFile
        ! OSMiscOps.FileError => {sourceAvailable ← FALSE; CONTINUE}]]};
    
  CloseSource: PROC ~ {IF sourceAvailable THEN Stream.Delete[source]};
    

  FilterBody: PROC [bti: Symbols.CBTIndex, key: Strings.String] RETURNS [BOOL←TRUE] ~ {
    IF key # NIL THEN {
      sei: Symbols.ISEIndex ~ symbols.bb[bti].id;
      hti: Symbols.Name;
      d1: Strings.SubStringDescriptor;
      d2: Strings.SubStringDescriptor ← [base~key, offset~0, length~key.length];
      IF sei = Symbols.SENull OR (hti ← symbols.seb[sei].hash) = Symbols.nullName THEN
        RETURN [FALSE];
      symbols.SubStringForName[@d1, hti];
      RETURN [Strings.EqualSubStrings[@d1, @d2]]}};
 
  PrintBodyName: PROC [bti: Symbols.CBTIndex] ~ {
    IF ~sourceAvailable THEN {
      sei: Symbols.ISEIndex ~ symbols.bb[bti].id;
      hti: Symbols.Name;
      IF sei # Symbols.SENull AND (hti ← symbols.seb[sei].hash) # Symbols.nullName THEN {
	ss: Strings.SubStringDescriptor;
	symbols.SubStringForName[@ss, hti];
	CharIO.PutSubString[out, @ss]; CharIO.PutString[out, ":\n"L]}}};
    
  EvenUp: PROC [n: CARDINAL] RETURNS [CARDINAL] ~ INLINE { 
    -- Round up to an even number
    RETURN [n + n MOD 2]};
    
  GetByte: PROC [pc: CARDINAL] RETURNS [BYTE] ~ { 
    -- pc is a byte address
    w: LONG POINTER TO PrincOps.InstWord ~ codebase + pc/2;
    RETURN [IF pc MOD 2 = 0 THEN w.evenbyte ELSE w.oddbyte]};
    
  GetWord: PROC [pc: CARDINAL] RETURNS [WORD] ~ INLINE { 
    -- pc is a word address
    RETURN [(codebase + pc)↑]};
    
  JumpAddress: PROC [jop: OpCode, arg: INTEGER] RETURNS [CARDINAL] ~ {
    -- given a jump operator and its argument, return its target address
    OPEN Mopcodes;
    SELECT OpTableDefs.InstLength[jop] FROM
      1 =>
	SELECT jop FROM
	  IN [zJ2..zJ4] => arg ← jop - zJ2 + 2;
	  zJ6 => arg ← 6;
	  zJ8 => arg ← 8;
	  IN [zJZ3..zJZ4] => arg ← jop - zJZ3 + 3;
	  IN [zJNZ3..zJNZ4] => arg ← jop - zJNZ3 + 3;
	  ENDCASE => ERROR;
      2 => SELECT jop FROM
        zJEP, zJNEP => arg ← arg MOD 16 + 4 - 1;
        ENDCASE => 
	  BEGIN
	  IF arg > 177B THEN arg ← Inline.BITOR[arg, 177400B];
	  arg ← arg - 1;
	  END;
      ENDCASE => {
        SELECT jop FROM
          zJEBB, zJNEBB => IF arg > 177B THEN arg ← Inline.BITOR[arg, 177400B];
          ENDCASE;
	arg ← arg - 2};
    RETURN [INTEGER[offset] + arg]};
    
  OutWJTab: PROC [tabstart, tablength: CARDINAL, options: CodeOptions] ~ {
    Pbytes ← Pbytes + tablength*2;
    FOR pc: CARDINAL IN [tabstart..tabstart + tablength) DO
      w: INTEGER ~ GetWord[pc];
      CharIO.PutString[out, "\n\t\t"L];
      IF options.stripped THEN {CharIO.PutNumber[out, w, hoctal5]; LOOP};
      IF options.full THEN CharIO.PutString[out, "\t\t"L];
      CharIO.PutString[out, " ("L];
      CharIO.PutNumber[out, JumpAddress[Mopcodes.zJIW, w], hoctal5];
      CharIO.PutChar[out, ')];
      ENDLOOP};
    
  OutBJTab: PROC [tabstart, tablength: CARDINAL, options: CodeOptions] ~ {
    Pbytes ← Pbytes + EvenUp[tablength];
    FOR pc: CARDINAL IN [tabstart*2..tabstart*2 + tablength) DO
      b: BYTE ~ GetByte[pc];
      CharIO.PutString[out, "\n\t\t"L];
      IF options.stripped THEN {CharIO.PutNumber[out, b, hoctal5]; LOOP};
      IF options.full THEN CharIO.PutString[out, "\t\t"L];
      CharIO.PutString[out, " ("L];
      CharIO.PutNumber[out, JumpAddress[Mopcodes.zJIB, b], hoctal5];
      CharIO.PutChar[out, ')];
      ENDLOOP};
    
  PutPair: PROC [byte: CARDINAL] ~ {
    a: CARDINAL ~ byte/16;
    b: CARDINAL ~ byte MOD 16;
    IF a < 8 AND b < 8 THEN CharIO.PutChar[out, ' ];
    CharIO.PutChar[out, '[];
    CharIO.PutNumber[out, a, hoctal1];
    CharIO.PutChar[out, ',];
    CharIO.PutNumber[out, b, hoctal1];
    CharIO.PutChar[out, ']]};
    
  PrintCode: PROC [
     startCode, endCode: CARDINAL, wideCatch: BOOL, options: CodeOptions] ~ {
    -- list opcodes for indicated segment of code
    OPEN Mopcodes;
    lastConstant: INTEGER;
    FOR offset IN [startCode..endCode) DO
      inst: BYTE ~ GetByte[offset];
      il: [0..3] ~ OpTableDefs.InstLength[inst];
      -- loginst[inst];
      Pinst ← Pinst + 1;
      CharIO.PutChar[out, '\t];
      IF ~options.stripped THEN {
	IF options.full THEN {
	  CharIO.PutNumber[out, offset/2, hoctal5];
	  CharIO.PutString[out, (IF offset MOD 2 = 0 THEN ",E "L ELSE ",O "L)]};
	CharIO.PutNumber[out, offset, hoctal5];
	CharIO.PutChar[out, ':]};
      IF options.full THEN {
	CharIO.PutString[out, "\t["L]; CharIO.PutNumber[out, inst, hoctal3z]; CharIO.PutChar[out, ']]};
      CharIO.PutChar[out, '\t];
      IF wideCatch AND offset = startCode+1 THEN {
        CharIO.PutNumber[out, inst, hoctal1];
	CharIO.PutChar[out, '\t];
	LOOP};
      CharIO.PutString[out, OpTableDefs.InstName[inst]];
      SELECT il FROM
	0, 1 => {
	  Pbytes ← Pbytes + 1;
	  IF inst IN [zLI0..zLI10] THEN lastConstant ← inst - zLI0
	  ELSE IF inst = zLID0 THEN lastConstant ← 0
	  ELSE IF inst IN JumpOp AND ~options.stripped THEN {
	    CharIO.PutString[out, "\t       ("L];
	    CharIO.PutNumber[out, JumpAddress[inst, 0], hoctal1];
	    CharIO.PutChar[out, ')]}};
	2 => {
	  byte: BYTE ~ GetByte[(offset ← offset + 1)];
	  Pbytes ← Pbytes + 2;
	  CharIO.PutChar[out, '\t];
	  SELECT inst FROM
	    zRLIP, zRLILP, zRLDIP, zRLDILP, zRGIP, zRGILP, 
	    zWLIP, zWLILP, zWLDILP, zR0F, zRL0F, zW0F, 
	    zWS0F, zPS0F, zJEP, zJNEP =>
	      PutPair[byte];
	    zESC => {
	      IF options.full THEN CharIO.PutNumber[out, byte, hoctal6];
	      EscName[byte]};
	    zKFCB => {
	      IF options.full THEN CharIO.PutNumber[out, byte, hoctal6];
	      SddName[byte]};
	    ENDCASE => CharIO.PutNumber[out, byte, hoctal6];
	  IF inst = zLIB THEN lastConstant ← byte
	  ELSE IF inst IN JumpOp AND ~options.stripped THEN {
	    CharIO.PutString[out, " ("L];
	    CharIO.PutNumber[out, JumpAddress[inst, byte], hoctal1];
	    CharIO.PutChar[out, ')]}};
	3 => {
	  ab: RECORD [first, second: BYTE];
	  Pbytes ← Pbytes + 3;
	  ab.first ← GetByte[(offset ← offset + 1)];
	  ab.second ← GetByte[(offset ← offset + 1)];
	  CharIO.PutChar[out, '\t];
	  SELECT inst FROM
	    zRF, zWF, zRLF, zWLF, zPSF, zPSLF => {
	      CharIO.PutNumber[out, ab.first, hoctal6];
	      CharIO.PutString[out, ", "L];
	      PutPair[ab.second]};
	    ENDCASE => {
	      v: INTEGER;
	      SELECT inst FROM
	        zRLIPF, zRLILPF => {
		  PutPair[ab.first];
		  CharIO.PutString[out, ", "L];
		  PutPair[ab.second]};
	        zJEBB, zJNEBB => {
		  CharIO.PutNumber[out, ab.first, hoctal6];
		  CharIO.PutString[out, ", "L];
		  CharIO.PutNumber[out, ab.second, hoctal6];
		  v ← ab.second};
	        zESCL => {
		  IF options.full THEN CharIO.PutNumber[out, ab.first, hoctal3];
		  EscName[ab.first];
		  CharIO.PutNumber[out, ab.second, hoctal6]};
	        ENDCASE => CharIO.PutNumber[out, (v ← ab.first*256 + ab.second), hoctal6];
	      SELECT inst FROM
		zJIB => OutBJTab[v, lastConstant, options];
		zJIW => OutWJTab[v, lastConstant, options];
		zLIW => lastConstant ← v;
		IN JumpOp =>
		  IF ~options.stripped THEN {
		    CharIO.PutString[out, " ("L];
		    CharIO.PutNumber[out, JumpAddress[inst, v], hoctal1];
		    CharIO.PutChar[out, ')]};
		ENDCASE}};
	ENDCASE;
      CharIO.PutChar[out, '\n];
      ENDLOOP};
    
 
  CompStrDesc: TYPE ~ RECORD [offset, length: CARDINAL];
  CompStrRecord: TYPE ~ RECORD [
    stringOffset: CSRptr RELATIVE POINTER TO StringBody,
    ESCAlphaNames: ARRAY ESCAlpha.alpha OF CompStrDesc,
    SDDefsNames: ARRAY Environment.Byte OF CompStrDesc];
  CSRptr: TYPE ~ LONG BASE POINTER TO CompStrRecord;

  csrP: CSRptr ~ Runtime.GetTableBase[LOOPHOLE[ESCAlphaSDDefsNames]];
 
  EscName: PROC [alpha: BYTE] ~ {
    ss: Strings.SubStringDescriptor;
    ss.base ← @csrP[csrP.stringOffset];
    ss.offset ← csrP.ESCAlphaNames[alpha].offset;
    ss.length ← csrP.ESCAlphaNames[alpha].length;
    IF ss.length < 8 THEN PutBlanks[8-ss.length] ELSE PutBlanks[1];
    CharIO.PutSubString[out, @ss]};

  SddName: PROC [op: BYTE] ~ {
    ss: Strings.SubStringDescriptor;
    ss.base ← @csrP[csrP.stringOffset];
    ss.offset ← csrP.SDDefsNames[op].offset;
    ss.length ← csrP.SDDefsNames[op].length;
    IF ss.length < 8 THEN PutBlanks[8-ss.length] ELSE PutBlanks[1];
    CharIO.PutSubString[out, @ss]};
 
   PutBlanks: PROC [n: CARDINAL] ~ {
     THROUGH [1..n] DO CharIO.PutChar[out, ' ] ENDLOOP};
     
     
   ListModule: PROC [
      file, module, proc: Strings.String, output: Strings.String, options: CodeOptions] ~ {
    bcdFile: Strings.String ← [100];
    bcdSeg, cSeg, sSeg: FileSegment.Pages;
    mti: BcdDefs.MTIndex;
    ListerUtil.SetFileName[bcdFile, file, "bcd"L];
    bcdSeg ← ListerUtil.LoadBcd[bcdFile];
    IF bcdSeg = FileSegment.nullPages THEN GO TO NoFile;
    [mti, cSeg, sSeg] ← ListerUtil.LoadModule[bcdSeg, module
      ! ListerUtil.UnknownModule => {GO TO NoModule}];
    DoCodeListing[cSeg, sSeg, bcdSeg, mti, proc, output, options]
    EXITS
      NoFile => ListerUtil.Message["File cannot be opened"L];
      NoModule => {
	ListerUtil.Message["File does not contain module "L];
	ListerUtil.Message[module]}};
    
  ShowTotals: PROC ~ {
    CharIO.PutString[out, "Instructions: "L];
    CharIO.PutNumber[out, Pinst, decimal];
    CharIO.PutString[out, ", Bytes: "L];
    CharIO.PutNumber[out, Pbytes ← EvenUp[Pbytes], decimal];
    CharIO.PutString[out, "\n\n"L];
    Tinst ← Tinst + Pinst;
    Pinst ← 0;
    Tbytes ← Tbytes + Pbytes;
    Pbytes ← 0};
    
  DoCodeListing: PROC [
      cseg, sseg, bcdseg: FileSegment.Pages,
      mti: MTIndex, proc: Strings.String,
      output: Strings.String, options: CodeOptions] ~ {
    OPEN BcdDefs, Symbols;
    codeSpace: Space.Handle;
    crossJumped: BOOL;
    codeOffset, frameSize: CARDINAL;
    prevBti: BTIndex ← BTNull;
      BEGIN
      bcdSpace: Space.Handle ← ListerUtil.MapPages[bcdseg];
      bcd: BcdOps.BcdBase ← bcdSpace.LongPointer;
      mth: BcdOps.MTHandle ← @LOOPHOLE[bcd + bcd.mtOffset, Base][mti];
      codeOffset ← mth.code.offset;
      frameSize ← mth.framesize;
      crossJumped ← mth.crossJumped;
      Space.Delete[bcdSpace];
      END;
    IF cseg = FileSegment.nullPages THEN
      ListerUtil.Message["Code not available"L]
    ELSE IF sseg = FileSegment.nullPages THEN
      ListerUtil.Message["Symbols not available"L]
    ELSE {
      print: BOOL ← FALSE;
      procFirst: CARDINAL ← 0;
      codeSpace ← ListerUtil.MapPages[cseg];
      codebase ← codeSpace.LongPointer + codeOffset;
      codepages ← cseg.span.pages;
      SymbolTable.SetCacheSize[0];	-- clear cache
      symbols ← SymbolTable.Acquire[sseg];
      IF symbols.fgTable = NIL THEN {
	ListerUtil.Message["Bad bcd format"L]; GO TO Fail};
      ListerUtil.SetRoutineSymbols[symbols];
      SetUpSource[];
      OpenOutput[output];
      ListerUtil.PutFileID[out];
      IF crossJumped THEN CharIO.PutString[out, "Cross jumped\n"L];
      CharIO.PutString[out, "Global frame size:  "L];
      CharIO.PutNumber[out, frameSize, decimal];
      CharIO.PutString[out, "\n\n"L];
      IF options.radix = $hex THEN Hexify[] ELSE Octify[];

      IF proc = NIL THEN ShowEntryVectors[];
      IF proc = NIL THEN ShowEnableTable[];
    
      Tbytes ← Tinst ← 0;
      DigestFGT[];
      FOR i: CARDINAL IN [0..myFGT.length) DO
	ff: FineGrainInfo ~ myFGT[i];
	wideCatch: BOOL ← FALSE;
	IF ff.bti # prevBti THEN {
	  IF prevBti # BTNull AND print THEN ShowTotals[];
	  print ← FilterBody[ff.bti, proc]};
	IF ff.firstSource # nullSource AND print THEN
	  IF ff.lastSource = ff.firstSource THEN CharIO.PutChar[out, '\n]
	  ELSE OutCheck[ff.firstSource, ff.lastSource];
	IF ff.bti # prevBti THEN {
	  WITH brc~~symbols.bb[ff.bti] SELECT FROM
	    Catch => {
	      fsi: CARDINAL ← 1;
	      IF GetByte[ff.pc] = Mopcodes.zJ2 THEN {
	        fsi ← GetByte[ff.pc+1];  
	        wideCatch ← TRUE};  -- display second byte in octal (as fsi)
	      IF print THEN {
		IF ~sourceAvailable THEN CharIO.PutChar[out, '\n];
		CharIO.PutString[out, "   Catch entry point: "L];
		CharIO.PutNumber[out, brc.index, decimal];
		CharIO.PutString[out, ",  frame size index: "L];
		CharIO.PutNumber[out, fsi, decimal];
		CharIO.PutChar[out, '\n]}};
	    ENDCASE => {
	      ep: CARDINAL ~ symbols.bb[ff.bti].entryIndex;
	      IF print THEN {
		PrintBodyName[ff.bti];
		IF options.full THEN CharIO.PutChar[out, '\t];
		CharIO.PutString[out, "   Entry point: "L];
		CharIO.PutNumber[out, ep, decimal];
		CharIO.PutString[out, ",   Frame size index:  "L];
		CharIO.PutNumber[out, GetByte[ff.pc], decimal];
		CharIO.PutChar[out, '\n]};
	      procFirst ← ff.pc}};
	IF print THEN {
	  IF ~ff.procEnd THEN {
	    first: CARDINAL ← ff.pc;
	    IF first = procFirst THEN first ← first + 1;
	    PrintCode[first, myFGT[i + 1].pc, wideCatch, options]};
	  CharIO.PutChar[out, '\n]};
	prevBti ← ff.bti;
	ENDLOOP;
      IF prevBti # Symbols.BTNull AND print THEN ShowTotals[];
      (Heap.systemZone).FREE[@myFGT];
      SymbolTable.Release[symbols];
      Space.Delete[codeSpace];
      CloseSource[];
      CharIO.PutChar[out, '\n];
      IF proc = NIL THEN {
	IF options.full THEN CharIO.PutChar[out, '\t];
	CharIO.PutString[out, "Total instructions: "L];
	CharIO.PutNumber[out, Tinst, decimal];
	CharIO.PutString[out, ", Bytes: "L];
	CharIO.PutNumber[out, Tbytes, decimal];
	CharIO.PutChar[out, '\n]};
      CloseOutput[]
      EXITS
	Fail => {SymbolTable.Release[symbols]; Space.Delete[codeSpace]}}};
    

  ShowEntryVectors: PROC ~ {
    cspp: CatchFormat.Codebase ~ codebase;
    -- first word after EV is rel. byte ptr to catch ev
    catchEV: CatchFormat.CatchEV ~ LOOPHOLE[cspp.entry[cspp.header.nEntries]/2];
    catchEntry: CatchFormat.CatchEVHandle ~ @cspp[catchEV];
    CharIO.PutString[out, "Entry Vector:  evi [bytePC]"L];
    CharIO.PutChar[out, '\n];
    FOR evi: CARDINAL IN [0..cspp.header.nEntries) DO
      CharIO.PutString[out, "  "L];
      CharIO.PutNumber[out, evi, decimal];
      CharIO.PutString[out, " ["L];
      CharIO.PutNumber[out, cspp.entry[evi], hoctal0];
      CharIO.PutString[out, "]\n"L];
      ENDLOOP;
    CharIO.PutString[out, "\nCatch Entry Vector:  cevi [bytePC]\n"L];
    IF catchEV = LOOPHOLE[0] THEN CharIO.PutString[out, "  None"L]
    ELSE FOR cevi: CARDINAL IN [0..catchEntry.count) DO
      CharIO.PutString[out, "  "L];
      CharIO.PutNumber[out, cevi, decimal];
      CharIO.PutString[out, " ["L];
      CharIO.PutNumber[out, catchEntry[cevi], hoctal0];
      CharIO.PutString[out, "]\n"L];
      ENDLOOP;
    CharIO.PutString[out, "\n\n"L]};
    
  ShowEnableTable: PROC ~ {
    cspp: CatchFormat.Codebase ~ codebase;
    -- first word after EV is rel. byte ptr to catch ev
    catchEV: CatchFormat.CatchEV ~ LOOPHOLE[cspp.entry[cspp.header.nEntries]/2];
    catchEntry: CatchFormat.CatchEVHandle ~ @cspp[catchEV];
    -- the (outermost, level 0) enable table follows the catch entry vector
    outerET: CatchFormat.EnableHandle ~ 
      LOOPHOLE[catchEntry + CatchFormat.CatchEVBody[catchEntry.count].SIZE];
      
    PrintEnableEntries: PROC [firstPC, lastPC, level: CARDINAL] ~ {
      et: CatchFormat.EnableHandle ← outerET;
      i: CARDINAL;
      FOR i IN [0..level) DO
        et ← et + CatchFormat.EnableTableBody[et.count].SIZE;
	ENDLOOP;
      FOR i IN [0..et.count) DO
        start: CARDINAL ~ et[i].start;
	end: CARDINAL ~ (et[i].start + et[i].length - 1);
        IF firstPC <= start AND end <= lastPC THEN {
	  FOR j: CARDINAL IN [0..level] DO
	    CharIO.PutString[out, "   "L];
	    ENDLOOP;
	  CharIO.PutChar[out, '[];
	  CharIO.PutNumber[out, start, hoctal0];
	  CharIO.PutString[out, ".."L];
	  CharIO.PutNumber[out, end, hoctal0];
	  CharIO.PutString[out, "]  "L];
	  CharIO.PutNumber[out, et[i].index, decimal];
          CharIO.PutChar[out, '\n];
	  IF et[i].alsoNested THEN
	    PrintEnableEntries[firstPC~start, lastPC~end, level~(level+1)]};
	ENDLOOP};
      
    IF catchEV = LOOPHOLE[0] THEN RETURN;
    CharIO.PutString[out, "Enable Items:  [firstPC..lastPC]  catchIndex\n"L];
    PrintEnableEntries[firstPC~0, lastPC~NAT.LAST, level~0];
    CharIO.PutChar[out, '\n]};


  ListProc: PUBLIC PROC [
      input, proc: Strings.String, output: Strings.String, options: CodeOptions] ~ {
    ListModule[input, input, proc, output, options]};
      
  ListCode: PUBLIC PROC [root: Strings.String, options: CodeOptions] ~ {
    ListModule[root, root, NIL, root, options]};
    
  ListCodeInConfig: PUBLIC PROC [config, name: Strings.String, options: CodeOptions] ~ {
    ListModule[config, name, NIL, name, options]};
    
 -- initialization
  Octify[];

  }.