{$R+}    {Range checking off}                                         {.CP14}
{$B-}    {Boolean complete evaluation on}
{$S+}    {Stack checking on}
{$I+}    {I/O checking on}
{$N-}    {No numeric coprocessor}

Unit PXLLIST;

Interface

Uses
  Crt,
  Dos,
  PXLINIT;

procedure ListIt;

{===========================================================================}

Implementation

procedure ListIt;                                                     {.CP10}
const
   TableSize               = 2521;
   Digits                  = 5;
   ProcName                = #158;
   TabChr                  = #0;
   AtStart:    set of char = ['A'..'Z'];
   MiddleSet:  set of char = ['A'..'Z','0'..'9','_'];
   HexNumbers: set of char = ['A'..'F','0'..'9'];
   NumZ9:      set of char = ['0'..'9'];
   Num19:      set of char = ['1'..'9'];
   MaxHeader = 5;
type                                                                  {.CP20}
   Ref          =   ^Item;
   WPt          =   ^WordType;
   TableNum     =   0..TableSize;
   WordType     =   record
                       Key:    Str20;
                       Name:   Str20;
                       First:  Ref;
                    end;
   Item         =   record
                       LinNum: 0..MaxInt;
                       Next:   Ref;
                    end;
   Incs        =    (CantFind,TooDeep,Started,Ended,OK);
   HdSegType   =    (Left,Center,Right);
   HdPgType    =    (First,Other);
   HdLineType  =    array[Left..Right] of LineType;
   HdType      =    array[1..MaxHeader] of HdLineType;
   HeaderType  =    array[First..Other] of HdType;
var                                                                   {.CP22}
   Header:         HeaderType;
   NumOfWords:     TableNum;
   T:              array[TableNum] of WPt;
   Tp:             WPt;
   MaxLess,
   Max,Longest,
   ScanCount,K,
   Occur,PCount,
   Pager,Depth:    integer;
   Cut,Uncut:      Str2;
   Cuts,Uncuts:    array[1..3] of Str2;
   OpLen,ClLen,
   B,Inrec:        byte;
   RecDepth,
   CaseDepth:      array[1..20] of byte;
   IncLine,
   LineEnd,UC:     str255;
   IncMark:        string[8];
   Elite,Condensed,
   LongOne,NoLine: boolean;
   IncState:       Incs;

   procedure BlankHeaderLines;                                        {.CP10}
   var
      LNo:    integer;
      HS:     HdSegType;
   begin
      for LNo := 1 to MaxHeader do
         for HS := Left to Right do
            Header[First][LNo,HS] := '';
      Header[Other] := Header[First]
   end; {BlankHeaderLines}

   function IsBlank(HL: HdLineType): boolean;                          {.CP8}
   var
      Sg: HdSegType;
   begin
      IsBlank := True;
      for Sg := Left to Right do
         if HL[Sg]<>'' then IsBlank := False
   end; {IsBlank}

   function HeaderLineNo(var H: HdType):integer;                       {.CP8}
   var
      Nr: integer;
   begin
      Nr := MaxHeader;
      while (Nr>0) and IsBlank(H[Nr]) do dec(Nr);
      HeaderLineNo := Nr
   end; {HeaderLineNo}

   procedure GetHeaderInstruction(Line: string);                      {.CP24}
      (*
      What this is supposed to do:
         "{" + ".H" triggers header function.  Possibilities are
         .HN  = no header at all
         .HnL = Left side of Header line #n
         .HnC = Center of Header line #n
         .HnR = Right side of Header line #n
         .HnN = No Header line #n  (Has no effect in PXL.HDR or Top Lines)
         .HPLnn = nn lines per page (default is 66 - BottomMargin)
      Text for header line segment begins 1 col AFTER end of symbol
      Within header line text:
         .Fn = file name
         .Fd = file date (long date)
         .Ft = file time (12 hr am/pm)
         .Pd = present (or printout) date (numeral)
         .Pd = present (or printout) time (24 hr)
         .Id = ID (from PXL.ID)
          #  = page number
       *)
   var
      IStrg: LineType;
      Cue:   Str3;
      Col:   integer;

      procedure ResetMaxLin(S: LineType);                             {.CP24}
      {This is activated by an .HPLnn command in the text or in PXL.HDR.}
      {Be careful.  It sets the number of lines printed, not the length }
      {of the paper.  It will override the BottomMargin set in PXL.PAS. }
      {If your printer is set up to put fewer than the number set here, }
      {you get a mess.  Ordinarily, strange paper sizes can be set with }
      {PXLINST, provided you can forego FF's.                           }
      var
         NumStr: Str20;
         K,E:    integer;
      begin
         if S[1]='L' then begin
            K := 2;
            NumStr := '';
            while (S[K] in NumZ9) and (K<=ord(S[0])) do begin
               NumStr := NumStr + S[K];
               inc(K)
            end; {while 0..9}
            if NumStr[0]>#0 then val(NumStr,K,E);
            if (K>0) and (E=0) then MaxLin := K        {if error, do nothing}
         end {if L}
      end; {ResetMaxLin}

      function FixedUpHeaderLine(L: LineType): string;                {.CP10}
      begin
         while pos('.Fn',L)>0 do Replace('.Fn',FileName,L);
         while pos('.Fd',L)>0 do Replace('.Fd',FileDate,L);
         while pos('.Pd',L)>0 do Replace('.Pd',PrintDate,L);
         while pos('.Ft',L)>0 do Replace('.Ft',FileTime,L);
         while pos('.Pt',L)>0 do Replace('.Pt',PrintTime,L);
         while pos('.Id',L)>0 do Replace('.Id',UserID,L);
         FixedUpHeaderLine := L
      end; {FixedUpHeaderLine}

      procedure InterpretInstruction(Strg: LineType);                 {.CP17}
      const
         Symbols:  set of char = ['C','L','N','R'];
      var
         HNo:  byte;
         HSg:  HdSegType;
         C:    char;
         Pg:   HdPgType;

      begin {InterpretInstruction}
        C := Strg[1];
        delete(Strg,1,1);
        if C='N' then
           BlankHeaderLines
        else if C='P' then
           ResetMaxLin(Strg)
        else if C in Num19 then begin                            {.CP28}
           HNo := ord(C) - $30;
           if HNo<1 then HNo := 1;
           if HNo>MaxHeader then HNo := MaxHeader;
           C := Strg[1];
           delete(Strg,1,2);        {eat both this char and delimiting space}
           if C in Symbols then begin
              if C='N' then begin
                 if (Page<2) and IsBlank(Header[Other][HNo])
                    then Pg := First
                    else Pg := Other;
                 for HSg := Left to Right do Header[Pg][HNo,HSg] := ''
              end {if N}
              else begin
                 case C of
                    'L':  HSg := Left;
                    'C':  HSg := Center;
                    'R':  HSg := Right;
                 end; {case}
                 Strg := FixedUpHeaderLine(Strg);
                 if (Page>1) or (Header[First][HNo,HSg]<>'')
		    then Pg := Other
		    else Pg := First;
		 for Pg := Pg to Other do Header[Pg][HNo,HSg] := Strg;
              end {else not N}
           end {if Symbol}
        end {else if 1..9}
      end; {InterpretInstruction}

   begin {GetHeaderInstruction}                                     {.CP13}
      Cue := '{' + '.H';
      while pos(Cue,Line)>0 do begin
         Col := pos(Cue,Line) + 3;
         IStrg := '';
         while (Line[Col]<>'}') and (Col<=ord(Line[0])) do begin
            IStrg := IStrg + Line[Col];
            inc(Col)
         end; {while}
         Line := Copy(Line,succ(Col),255);
         InterpretInstruction(IStrg)
      end {while}
   end; {GetHeaderInstruction}

   function HeaderLine(H: HdLineType): LineType;                     {.CP21}
   var
      Spaces,K: integer;
      Temp:     LineType;
      Pg:       HdPgType;
      Sg:       HdSegType;   C: char;
   begin
      Temp := '';
      if Page<2
         then Pg := First
         else Pg := Other;
      for Sg := Left to Right do         {Must update page number every page}
         while pos('#',H[Sg])>0  do
            Replace('#',StrgI(Page,1),H[Sg]);
      repeat               {Splice left & right segs --chopping if necessary}
         Spaces := ord(H[Left,0]) + ord(H[Right,0]);
         if Spaces>79 then begin
             if H[Right,0]>#0 then delete(H[Right],1,1)
             else if H[Left,0]>#0 then dec(H[Left,0])
         end {if Spaces}
      until Spaces<=79;
      Temp := H[Left];           {Overprint line with Center segment} {.CP10}
      for K := 1 to (79 - Spaces) do Temp := Temp + #32;
      Temp := Temp + H[Right];
      if H[Center]<>'' then begin
         Spaces :=  39 - (ord(H[Center,0]) div 2);
         for K := 1 to ord(H[Center,0]) do
            Temp[K+Spaces] := H[Center,K]
      end; {if Center}
      HeaderLine := Temp;
   end; {HeaderLine}

   procedure MakeFirstHeader(var Fil: text);                          {.CP25}
   var
      Lin:    Str255;

      function GotDefaultHeaderFromFile: boolean;
      var
         FName:  LineType;
         F:      text;
      begin
         FName := 'PXL.HDR';
         if FindFile(FName) then begin
            assign(F,FName);
            reset(F);
            while not Eof(F) do begin
               readln(F,Lin);
               if pos('{' + '.H',Lin)<>0 then begin
                  GetHeaderInstruction(Lin)
               end {if Cue}
            end; {while not Eof}
            close(F);
            GotDefaultHeaderFromFile := True
         end {if FindFile}
         else
            GotDefaultHeaderFromFile := False
      end; {GotDefaultHeaderFromFile}

      procedure MakeStandardDefaultHeader;                            {.CP14}
      begin
         Header[First][1,Right] := FileTime + ', ' + FileDate;
         if XRefOnly
            then Header[First][1,Left] :='Cross-Reference of: '
            else Header[First][1,Left] := 'File: ';
         Header[First][1,Left] := Header[First][1,Left] + FileName;
         if UserID[0]>#0 then
            Header[First][1,Left] := Header[First][1,Left]
                                     + '  [' + UserID + ']';
         Header[Other][1] := Header[First][1];
         Header[Other][1,Right] := 'Page #' (* + StrgI(Page,1); *)
      end; {MakeStandardDefaultHeader}

      procedure LoadFirstHeader(var F: text);                         {.CP16}
      var
         L:      string;
         B,Col:  byte;
      begin
         reset(Fil);
         repeat
            readln(Fil,L);
            B := pos('{'+'.H',L);
            if B>0 then begin
               GetHeaderInstruction(L);
               delete(L,1,B);
               while (L[1]<>'}') and (L[0]<>#0)  do delete(L,1,1)
            end {if >0}
         until B=0;
      end; {LoadFirstHeader}

   begin {MakeFirstHeader}                                             {.CP9}
      BlankHeaderLines;
      if not GotDefaultHeaderFromFile then MakeStandardDefaultHeader;
      reset(Fil);
      readln(Fil,Lin);
      if pos('{'+'.H',Lin)<>0 then LoadFirstHeader(F);    {Check top of file}
      reset(Fil);                                {Return file open but reset}
      PageLineNumber := HeaderLineNo(Header[First]) + 2;
   end; {MakeFirstHeader}

   procedure PrintHeader(var PLine: integer); {Print header line(s)}  {.CP21}
   var
      Line:   String;
      K,Nr:   integer;
      Pg:     HdPgType;
   begin
      {$I-}
      writeln(Lst);
      {$I+}
      if not (IOresult=0) then
         CantCont('','Printer''s out');
      Line := '';
      if GotPrnData then
         if Wide then                                  {Set normal Pica}
            write(Lst,Istring[CondE])
         else if Numberlines then
            write(Lst,Istring[EliteE]);
      if Page<2
         then Pg := First
         else Pg := Other;
      Nr := HeaderLineNo(Header[Pg]);
      for K := 1 to Nr do                                             {.CP13}
         writeln(Lst,HeaderLine(Header[Pg][K]));
      if GotPrnData then
         if Wide then                                 {Set Condensed type}
            write(Lst,Istring[CondB])
         else if NumberLines then                     {or Elite}
            write(Lst,Istring[EliteB]);
      writeln(Lst);
      inc(Page);
      PLine := 2 + Nr;
   end; {PrintHeader}

   procedure PrintControl(var PageLineNumber: integer);               {.CP21}
   var
      Sym: string[8];
      I, J, Err: integer;
   begin
      if pos(concat('{.','PA}'),Line)<>0 then
         PageLineNumber := succ(MaxLin)
      else if pos(concat('{.','CP'),Line) <>0 then begin
         I := pos(concat('{.','CP'),Line) + 4;
         Sym := '';
         while Line[I] in NumZ9 do begin
            Sym := concat(Sym,Line[I]);
            I := succ(I);
         end {while};
         val(Sym,I,Err);
         if Err<>0 then I := 0;  {in case print control symbol is bungled}
         if PageLineNumber > (MaxLin-I) then PageLineNumber := succ(MaxLin);
      end {if}
   end; {PrintControl}

   procedure ReadingMatterI;                                          {.CP12}
   begin
      Blank(9,12);
      if not Xref then
         CenterCRT('Sending ' + FileName + ' to ' + OutputDevice,
               10,Bright,0)
      else if not XRefOnly then
         CenterCRT('Scanning ' + FileName + ' and sending to '
               + OutputDevice + '.', 10,Bright,0)
      else
         CenterCRT('Scanning ' +  FileName,10,Bright,0)
   end; {ReadingMatterI}

   procedure ReadingMatterII;                                          {.CP5}
   begin
      CenterCRT('Sending cross-reference to ' + OutputDevice,
                10,Bright,Inside)
   end; {ReadingMatterII}

   procedure NewPage(Pager: integer);                                 {.CP15}
   var
      I:           integer;
   begin
(*      {$I-}
      writeln(Lst);
      {$I-}
      if IOresult<>0 then CantCont('','Printer''s out.'); *)
      if Inst[FF,1]=12 then begin
         {$I-}
         write(Lst,#12);
         {$I-}
         if IOresult<>0 then CantCont('','Printer''s out.');
      end {if FF}
      else begin
         {$I-}
         writeln(Lst);
         {$I-}
         if IOresult<>0 then CantCont('','Printer''s out.');
         for I := succ(Pager) to Inst[FF,1] do writeln(Lst);
      end {no FF}
   end; {NewPage}

   procedure PrintTable;                                              {.CP17}
   type
      ProcPtr   =  ^ProcWord;
      ProcWord  =  record
                      Name:   Str20;
                      LinNum: 0..MaxInt;
                      Next:   ProcPtr;
                   end;
   var
      I:           TableNum;
      Lin:         integer;
      NumPerLine:  byte;
      PL:          record
                      First: ProcPtr;
                      Last:  ProcPtr;
                   end;
      PLptr:       ProcPtr;

      procedure Compress(var N: TableNum);                            {.CP11}
      var
         I: TableNum;
      begin
         N := 0;
         for I := 0 to TableSize do
            if T[I] <> Nil then begin
               T[N] := T[I];
               inc(N)
            end; {if T[I]}
      end; {Compress}

      procedure Sort(Lo, Hi: integer); {Quicksort}                    {.CP31}
      var
         Low,High: TableNum;
         Mid,Temp: WPt;
      begin
         repeat                                 {Pick split points}
            Mid := T[(Lo+Hi) div 2];
            Low := Lo;
            High := Hi;
            repeat                                 {partitions}
               while T[Low]^.Key<Mid^.Key do Inc(Low);
               while T[High]^.Key>Mid^.Key do dec(High);
               if Low<=High then begin
                  Temp := T[Low];
                  T[Low] := T[High];
                  T[High] := Temp;
                  if Low<TableSize then inc(Low);
                  if High>0 then dec(High)
               end {if Low<=}
            until Low > High;
            {recursively sort shorter sub-segment}
            if (High-lo) < (Hi-Low) then begin
               if Lo < High then Sort(Lo,High);
               Lo := Low
            end {if (High}
            else begin
               if Low < Hi then Sort(Low,Hi);
               Hi := High;
            end {else}
         until Hi <= Lo
      end; {Sort}

      procedure PageOut;                                               {.CP7}
      begin
         NewPage(Lin);
         PrintHeader(Lin);
         writeln(Lst);
         inc(Lin)
      end; {PageOut}

      procedure PrintWord(W: WordType);                               {.CP20}
      var
         X,Y,Z:      Ref;
         Num:        integer;
         B:          byte;

      procedure ProcProc; {Add new proc/func name to list}
      begin
         Delete(W.Name,1,1);                          {remove tell-tale mark}
         New(PLptr);
         PLptr^.Name := W.Name;
         PLptr^.LinNum := X^.LinNum;
         PLptr^.Next := Nil;
         if PL.First = Nil then begin
            PL.First := PLptr;
            PL.Last := PLptr
         end; {if first procedure}
         PL.Last^.Next := PLptr;
         PL.Last := PLptr
      end; {ProcProc}

      begin {PrintWord}                                               {.CP10}
         if Lin>MaxLin then PageOut;
         X := W.First; Y := X^.Next; X^.Next := Nil;
         while Y<>Nil do begin         {inky pinky pider, reversing pointers}
            Z := Y^.Next; Y^.Next := X; X := Y; Y := Z;
         end; {while Y<>Nil}
         Num := 0;
         if W.Name[1]=ProcName then ProcProc;         {add to proc/func list}
         Write(Lst,#32,W.Name);
         for B := 1 to Longest-ord(W.Name[0]) do write(Lst,#32);
         repeat                                  {write line numbers} {.CP21}
            if Num=NumPerLine then begin              {new line if necessary}
               Num := 0;
               writeln(Lst);
               inc(Lin);
               if Lin>MaxLin then begin
                  PageOut;
                  Write(Lst,#32,W.Name);
                  for B := 1 to Longest-ord(W.Name[0]) do
                     write(Lst,#32)
               end {if Lin}
               else
                  Write(Lst,#32:(succ(Longest)))
            end; {if Num}
            inc(Num);
            write(Lst,X^.LinNum:Digits);
            X := X^.Next
         until X=Nil;
         writeln(Lst);
         inc(Lin)
      end; {PrintWord}

      procedure PrintPL;  {Print list of procedures & functions}      {.CP15}
      var
         B:     byte;

         procedure PrintAProc;             {print one line in proc/func list}
         var
            B: byte;
         begin
            write(Lst,#32,PL.First^.Name);
            for B := 1 to Longest-ord(PL.First^.Name[0]) do write(Lst,#32);
            writeln(Lst,PL.First^.LinNum:Digits);
            inc(I);
            GotoXY(30,16);
            Write(I:5);
            PL.First := PL.First^.Next;
         end; {PrintAProc}

      begin {PrintPL}                                                 {.CP19}
         if (Lin+PCount+5) > MaxLin then
            PageOut
         else begin
            writeln(Lst);
            inc(Lin)
         end; {else}
         writeln(Lst,'Procedures and Functions:');
         writeln(Lst);
         if PL.First=PL.Last then              {Just one proc/func in list}
            PrintAProc
         else
            while (PL.First<>Nil) and not enough do begin
               inc(Lin);
               if Lin > MaxLin then PageOut;
               PrintAProc;
               Enough := Escape
            end {while}
      end; {PrintPL}

   begin {PrintTable}                                                 {.CP15}
      if NumberLines then
         if Mrk then Max := Max+10 {take account of space for beg/end count}
         else Max := Max + 6;
      NumPerLine := (Max-Longest) div Digits;
      PL.First := Nil; PL.Last := Nil;
      Compress(NumOfWords);
      Sort(0,pred(NumOfWords));
      PrintHeader(Lin);
      writeln(Lst);
      writeln(Lst,'Crosslisting of Identifiers:');
      writeln(Lst);
      WriteCRT('X-Ref Lines:   ',16,15,Bright);
      Lin := Lin + 3;
      I := 0;
      while (I<NumOfWords) and not Enough do begin {print XRef lines} {.CP15}
         PrintWord(T[I]^);
         inc(I);
         GotoXY(30,16); write(I:5);                   {keep user entertained}
         Enough := Escape
      end; {while}
      if (PCount>0) and not Enough then PrintPL;
      writeln(Lst);
      write(Lst,'Lines: ',LineNumber,'    Identifiers: ',ScanCount,
         '    Occurrences: ',Occur);
      if PCount>0 then
         writeln(Lst,'    Procedures: ',PCount)
      else
         writeln(Lst)
   end; {PrintTable}

   procedure ScanAndHash(var UC,Line: Str255; LinNo: integer);        {.CP18}
   var
      Ident:      WordType;
      Len,I:      byte;
      Col:        integer;
      ProcOrFunc: boolean;

      procedure Calamity;
      begin
         ClrScr;
         PXLRectangle;
         CenterCRT('CALAMITY',11,Bright,0);
         WriteCRT('Too many @$#%'+#237+'@! identifiers',13,25,Bright);
         WriteCRT('    I can''t handle that.',14,25,Bright);
         CloseCarefully(F);
         RestoreScreen;
         Halt
      end; {Calamity}

      procedure Hash(Ident: WordType);                                 {.CP17}
      var
         Found:     boolean;
         ID:        record
                       case byte of
                          1: (Key: str20);
                          2: (O:   integer);
                          3: (Arr: array[0..20] of byte);
                    end;
         X:         Ref;
         H,D,Start: TableNum;
      begin
         ID.Key := Ident.Key;
         inc(Occur);
         H := abs(ID.O) mod TableSize;        {hash using 1st 2 bytes of key}
         Start := H;
         new(X); X^.LinNum := LinNo; Start := H; D := 1;
         repeat                                                       {.CP26}
            if T[H]^.Key = ID.Key then begin          {found the Key        }
               Found := True;
               X^.Next := T[H]^.First;                   {add line # to list}
               T[H]^.First := X
            end {if found key}
            else if T[H] = Nil then begin             {empty place --new key}
               Found := True;
               inc(ScanCount);                            {count it         }
               if ord(ID.Key[0])>Longest then             {update Longest   }
                   Longest := ord(ID.Key[0]);
               New(Tp);
               Tp^.Key := ID.Key;                         {set up new key   }
               Tp^.Name := Ident.Name;                    {and name         }
               Tp^.First := X;                            {and first line # }
               T[H] := Tp;                                {& put in hash tbl}
               X^.Next := Nil
            end {else if new}
            else begin                                {place occupied       }
               Found := False;
               H := H + ID.Arr[ID.Arr[0]];   {re-hash using last byte of key}
               if H>=TableSize then H := H - TableSize;
               if H=Start then Calamity
            end {else --place otherwise occupied}
         until Found
      end; {Hash}

   begin  {ScanAndHash}                                               {.CP16}
      GotoXY(30,14); write(LinNo:5);                  {keep user entertained}
      Col := 1; ProcOrFunc := False;
      Len := ord(UC[0]);
      while Col<=Len do begin                                {creep along UC}
         if UC[Col]<>#32 then begin                  {looking for non-blanks}
            if UC[Col] <> ProcName then begin   {if a normal character      }
               Ident.Key := ''; Ident.Name := '';
               I := Col + 20;                    {20 chars is max key length}
               while (UC[Col]<>#32) and (Col<=Len) do begin {read non-blanks}
                  if Col<I then begin
                     Ident.Key := Ident.Key + UC[Col];
                     Ident.Name := Ident.Name + Line[Col]
                  end; {if Col}
                  inc(Col);
               end; {while}
               if ProcOrFunc then begin     {.CP15} {if it's a new procedure}
                  insert(ProcName,Ident.Name,1);       {mark the Name       }
                  ProcOrFunc := False
               end; {if ProcOrFunc}
               Hash(Ident)                          {put into the hash table}
            end {if not ProcName}
            else begin                           {if it's the Procedure sign}
               ProcOrFunc := True;
               inc(Col)
            end {else --ProcName}
         end {if not blank}
         else
            inc(Col);
      end {while}
   end; {ScanAndHash}

   procedure Underline (var Line: Str255);                            {.CP19}
   var
      K,J:         integer;
      B:           byte;
      InMiddle,
      InHex:    Boolean;

      procedure Ins (var Line,UC :Str255; Op,Cl:Str3);
      var
         Z,Len,B:     byte;
         K,Col:       integer;
         ShdBeMarked: boolean;
         Obj:         Str10;
      begin {Ins}
         for K := 1 to NRes do begin            {Check against Key word list}
            if Pos(Reserv[K],UC)<>0 then begin     {if Key word is in line  }
               Obj := Reserv[K];
               Col := pos(Obj,UC);
               Len := ord(Obj[0]);
               repeat                                                 {.CP15}
                  if (UC[pred(Col)]=#32) and               {if surroundings OK  }
                     (UC[Col+Len]=#32) then begin
                     insert(Cl,Line,Col+Len);              {Insert Closing  }
                     insert(Op,Line,Col);                  {Insert Opening  }
                     for B := Col to Col+pred(Len) do      {blank Obj in UC }
                        UC[B] := #32;
                     if Xref and (Obj='PROCEDURE')
                        or (Obj='FUNCTION') then begin     {Mark Proc & Func}
                        inc(PCount);
                        UC[Col+OpLen] := ProcName
                     end; {if XRef &}
                     for B := 1 to OpLen+ClLen do     {Blanks to match up UC}
                        insert(#32,UC,Col);
                     Col := Col + Len + OpLen + ClLen;   {move to end of Obj}
                     if NumberLines then begin                        {.CP23}
                        if (Obj='BEGIN') or
                        (Obj='REPEAT') or (Obj='CASE') then {count begin/end}
                           inc(Depth)
                        else if (Obj='END')  then begin {Style Critics: Yes,}
                           if InRec=0 then              {this should be a   }
                              dec(Depth)                {procedure in itself}
                           else begin                   {but, in so busy a  }
                              Depth := RecDepth[InRec]; {loop, we must avoid}
                              dec(InRec)                {overhead.          }
                           end {else if InRec}
                        end {else if END}
                        else if (Obj='UNTIL') then
                           dec(Depth)
                        else if Obj='RECORD' then begin
                           inc(InRec);
                           RecDepth[InRec] := Depth;
                           inc(Depth)
                        end {else if RECORD}
                     end; {if NumberLines}
                  end {if surroundings Okay}
                  else
                     Col := Col + Len;                    {move Col past obj}
                  if Col>(ord(Line[0])-succ(Len)) then                {.CP13}
                     ShdBeMarked := False
                  else begin                                       {Another?}
                     B := pos(Obj,copy(UC,succ(Col),ord(UC[0])-Col));  {.CP9}
                     if B=0 then                         {No, so         }
                        ShdBeMarked := False             {   Exit        }
                     else begin                          {Yes, so        }
                        Col := Col + B;                  {   Move up Col }
                        ShdBeMarked := True              {   Go again    }
                     end {else}
                  end {if Col}
               until not ShdBeMarked
            end {if Col<>0}
         end {for K --once for each word in Key word list}
      end; {procedure Ins}

   procedure BlankBrackets(var UC: Str255);                           {.CP18}
   var
      I,J,PosCut,
      PosUnCut:       byte;
   begin
      if Cut <> '' then begin        {already in a bracket --check for close}
         PosUnCut := pos(UnCut,UC);
         if PosUnCut=0 then                  {no close}
            for I := 1 to ord(UC[0]) do      {blank all of UC}
               UC[I] := #32
         else begin                          {has closer}
            if UnCut = '*)' then
               inc(PosUnCut);
            for I := 1 to PosUnCut do        {blank UC to closer}
               UC[I] := #32;
            Cut := ''; UnCut := ''
         end {else}
      end; {if Cut}
      while (pos(Cuts[1],UC)<>0) or                                   {.CP29}
            (pos(Cuts[2],UC)<>0) or
            (pos(Cuts[3],UC)<>0) do begin   {UC contains openers}
         J := ord(UC[0]);
         for I := 1 to 3 do begin               {find first opener}
            PosCut := pos(Cuts[I],UC);
            if (PosCut>0) and
               (PosCut<J) then begin
                  Cut := Cuts[I];
                  UnCut := UnCuts[I];
                  J := PosCut
            end {if}
         end; {for I}
         PosCut := J;
         PosUncut := pos(UnCut,copy(UC,succ(pos(Cut,UC)),255));
         if PosUnCut<>0 then begin     {If there's a closer, find its posit}
            PosUnCut := PosUnCut + PosCut;
            if UnCut = '*)' then
               inc(PosUnCut);
            for I := PosCut to PosUnCut do            {blank UC in brackets}
               UC[I] := #32;
            Cut := '';                                {reset Cut & UnCut}
            UnCut := ''
         end {there's a closer}
         else                                   {if no closer}
            for I := PosCut to ord(UC[0]) do          {blank rest of UC}
               UC[I] := #32;
      end {while openers in UC}
   end; {BlankBrackets}

   procedure ClearIdentifiers (var UC: Str255);                       {.CP29}
   var
      I:           byte;
   begin
      InMiddle := False; InHex := False;
      for I := 1 to ord(UC[0]) do
         if UC[I] = #32 then begin                                  {a blank}
            InMiddle := False;
            InHex := False
         end {if blank}
         else if UC[I] = '$' then begin                 {start of hex number}
            InHex := True;
            InMiddle := False;
            UC[I] := #32
         end {else $}
         else
            if InMiddle then begin                         {in an identifier}
               if not (UC[I] in MiddleSet) then begin
                  UC[I] := #32;
                  InMiddle := False
               end {if not UC}
            end {if InMiddle}
            else if InHex then begin                        {in a hex number}
               if not (UC[I] in HexNumbers) then InHex := False;
               if InHex or not (UC[I] in AtStart) then UC[I] := #32
            end {else Hex number}
            else if (UC[I] in AtStart) then InMiddle := True {start an ident}
            else UC[I] := #32
   end; {ClearIdentifiers}

   begin {Underline}                                                   {.CP9}
      UC := Line;                                    {Prepare guide template}
      for B := 1 to ord(UC[0]) do UC[B] := UpCase(UC[B]);      {All capitals}
      BlankBrackets(UC);                   {Remove all comments & quotations}
      ClearIdentifiers(UC);             {Remove everything not an identifier}
      Ins(Line,UC,Opening,Closing)    {Insert printer chars around Key words}
   end; {Underline}

   procedure PrintLine;               {Print one line}                {.CP26}
   var
      B,
      RealLength:  byte;
      Opener:      LineType;
   begin
      RealLength := ord(Line[0]) - 2;       {Length w/o pad or print symbols}
      Opener := '';
      if Mrk or XRef then Underline(Line);
      if (NumberLines) then begin            {write line number or spaces}
         if NoLine or (RealLength=0) then begin    {if a continuation    }
            Opener := Opener + '     ';
            if Mrk then
               Opener := Opener + '       '            {spaces only      }
            else
               Opener := Opener + '  '
         end {if NoLine}
         else begin                                {if beginning new line}
            Opener := Opener + StrgI(LineNumber,5);      {write line numb}
            if Mrk then
               Opener := Opener+ ' ' +StrgB(Depth,2) + '    ' {& depth}
            else
               Opener := Opener + '  ';                       {no depth}
            NoLine := False
         end {else --not NoLine}
      end; {if Numberlines}
      if XRef then                                                {.CP22}
         ScanAndHash(UC,Line,LineNumber)                 {Scan for X-ref}
      else begin
         GotoXY(46,16);                           {Keep user entertained}
         write(LineNumber:5)
      end; {else not XRef}
      Line := copy(Line,2,ord(Line[0])-2);                {remove padding}
      if (IncMark[0]>#0) or (IncLine[0]>#0) then begin
         for B := RealLength to pred(MaxLess) do
             Line := Line + #32;
         Line := Line + IncLine + IncMark;
         IncLine := '';
         IncState := OK;
      end; {if IncMark}
      if not XRefOnly then writeln(Lst,Opener,Line);        {Enfin! WRITE}
      if LongOne then
         NoLine := True
      else begin
         NoLine := False;
         inc(LineNumber)
      end {else if not NoLine}
   end; {PrintLine}

   procedure TabSpace;      {make room for tabs (every 8 chars)}      {.CP15}
   var
      B,Col,Nchrs: byte;

      procedure StartLineEnd;
      begin
         LineEnd := '';
         LongOne := True
      end; {StartLineEnd}

   begin
      if Line[1]=TabChr then begin    {turn ldg TabChr to Tab & strip others}
         Line[1] := #9;
         while Line[2]=TabChr do delete(Line,2,1)
      end; {if Line[1]}
      Col := 1;                                                       {.CP26}
      while Col<= ord(Line[0]) do begin
         if Line[Col]=#9 then begin                   {if Tab in that column}
            Delete(Line,Col,1);                             {remove Tab char}
            Nchrs := Col mod 8;
            if Nchrs=0 then Nchrs := 8;
            Nchrs := 9 - Nchrs;                  {number of blanks to insert}
            for B := 1 to Nchrs do begin
               insert(TabChr,Line,Col);                      {insert TabChrs}
               if not LongOne then                      {Check if overlength}
                  if ord(Line[0])>Max then StartLineEnd;
            end; {for B}
            Col := Col + pred(Nchrs);                {move Col to end of Tab}
            if LongOne then begin                   {re-cut Line and LineEnd}
               B := ord(Line[0]) - Nchrs;
               while not (Line[B] in [#32,TabChr]) do dec(B);    {find blank}
               Nchrs := ord(Line[0]) - B;
               for B := 1 to Nchrs do begin                     {shift chars}
                  LineEnd := Line[ord(line[0])] + LineEnd;
                  delete(Line,ord(line[0]),1)
               end {for B}
            end {if LongOne}
         end; {if Line[Col] is Tab}
         inc(Col)                                             {increment Col}
      end {while Col}
   end; {TabSpace}

   procedure FixRemainder;                                            {.CP17}
   var
      B:           byte;
   begin
      while (LineEnd[1]=#32) and (ord(LineEnd[0])>0) do       {Strip leading}
         delete(LineEnd,1,1);                           {blanks from LineEnd}
      B := 1;
      while (LineEnd[B]=TabChr) and (B<=ord(LineEnd[0])) do        {get past}
         inc(B);                                                    {TabChrs}
      while (LineEnd[B]=#32) and (ord(LineEnd[0])>=B) do      {strip further}
         delete(LineEnd,B,1);                                        {blanks}
      B := 1;
      while (B<ord(Line[0])) and (Line[B]=' ') do begin      {Pad LineEnd to}
         inc(B);                                                 {line it up}
         LineEnd := ' ' + LineEnd
      end {while (B<}
   end; {FixRemainder}

   procedure DeTab; {turn initial Tab chars into blanks}              {.CP10}
   var
      B:           byte;
   begin
      for B := 1 to ord(Line[0])do
         if Line[B]=TabChr then Line[B] := #32;
   end; {DeTab}

   procedure CutIt(Mx: integer); {Cut line at last}                   {.CP16}
   var                            {possible blank}
      B,Col:       byte;
      Temp:        Str255;
   begin
      B := Mx;
      while (B>0) and (Line[B]<>' ') do dec(B); {Find last blank space}
      Col := 1;
      while (Col<=B) and (Line[Col]=' ') do inc(Col);       {find 1st non-sp}
      if (Col>=B) then B := Mx;
      Temp := copy(Line,1,pred(B));
      delete(Line,1,pred(B));                                     {Chop line}
      LineEnd := Line + LineEnd;                     {Remainder into LineEnd}
      Line := Temp;
      LongOne := True;                                             {Set flag}
   end; {CutIt}

   procedure SetMax;                                                  {.CP13}

      procedure UseEliteForCondensed;
      var
         I:   integer;
      begin
         Istring[CondB] := Istring[EliteB];
         Istring[CondE] := Istring[EliteE];
         for I := 1 to 3 do begin
            Inst[CondB,I] := Inst[EliteB,I];
            Inst[CondE,I] := Inst[EliteE,I]
         end; {for I}
      end; {UseEliteForCondensed}

      function CondensedElite: boolean; {T iff CondB = EliteB}         {.CP7}
      var
         I:   integer;
      begin
         CondensedElite := True;
         for I := 1 to 3 do
            if (Inst[EliteB,I]<>Inst[CondB,I]) then
               CondensedElite := False
      end; {CondensedElite}

   begin {SetMax}                                                     {.CP32}
      if not GotPrnData then
         if NumberLines
            then Max := 68
            else Max := 79
      else begin
         if Wide
            then Max := 131
            else Max := 79;
         if NumberLines and Condensed then begin
            if CondensedElite then
               Max := 120             {if so then presume both are condensed}
            else if Elite then begin  {if we have both and they're different}
               if Wide
                  then Max := 120
                  else Max := 84
            end {else if E & C}
            else                         {if we have Condensed but not Elite}
               Max := Max - 11
         end; {if NumberLines and Condensed}
         if Elite and (not Condensed) then begin
            UseEliteForCondensed;
            if Wide then Max := 95;
            if NumberLines then Max := 84
         end {if Elite & not Condensed}
         else if not (Elite or Condensed) then begin
            if wide then Max := 79;
            if NumberLines then Max := 68
         end; {if neither}
         if NumberLines and not Mrk then Max := Max + 4
      end; {else GotPrnData}
   end; {SetMax}

   procedure XRBillboard;                                              {.CP9}
   begin
      if XRef then
         WriteCRT('Program lines:',14,15,Bright)
      else begin
         WriteCRT('--- Not Cross-Referencing ---',14,26,Bright);
         WriteCRT('    Printing Line: ',16,26,Bright)
      end {else}
   end; {XRBillboard}

   procedure TotItUp;                                                  {.CP6}
   begin
      GotoXY(49,14); write('Identifiers: ',ScanCount:5);
      GotoXY(49,15); write('Procedures:  ',Pcount:5);
      GotoXY(49,16); write('Occurrences: ',Occur:5)
   end; {TotItUp}

   procedure MarkInc;  {insert INC marker in Line}                    {.CP15}
   var
      B,Indent:    byte;
   begin
      IncMark := '';
      for B := 2 to IFN do IncMark := IncMark + '*';
      case IncState of
         Started:  IncLine := '<=== Including '
                              + IFileName[IFN] + ' ';
         Ended:    IncLine := '<=== Finished '
                              + IFileName[succ(IFN)] + ' *';
         TooDeep:  IncLine := '<=== Too many includes.  Can''t include it.';
         CantFind: Incline := '<=== Couldn''t find it.';
      end; {case}
   end; {MarkInc}

   procedure Include;                                                 {.CP10}
   var
      B,E:         byte;
      ComString:   CMD;
      IncFile:     boolean;

      function DepthOK: boolean;
      begin
         DepthOK := IFN < NoIncFiles
      end; {DepthOK}

      procedure TryToOpen(FName: LineType; var F: text);                 {.CP10}
      begin
         assign(F,FName);
         {$I-}
         reset(F);
         {$I+}
         if IOresult=0
            then IncState := Started
            else IncState := CantFind
      end; {TryToOpen}

   begin  {Include}                                                   {.CP13}
      B := Pos('{$'+'I',Line) + 3;
      E := Pos('}',Line);
      if (E<>0) and (E>B) then begin
         ComString := Copy(Line,B,E-B);              {Peel out string}
         if (pos('-',ComString)<>0) or (pos('+',ComString)<>0)
            then IncFile := False         {Check whether include instruction}
            else IncFile := True
      end {if E...}
      else begin
         ComString := '';
         IncFile := False
      end; {else}
      if IncFile then begin                        {if an INCLUDE}     {.CP7}
         while (ComString[1]=#32) and (ComString[0]>#0) do
            delete(ComString,1,1);                    {strip leading blanks }
         while ComString[ord(ComString[0])]=#32 do    {strip trailing blanks}
            dec(ComString[0]);
         inc(IFN);                                     {move a level down   }
         IFileName[IFN] := ComString;
         if DepthOK then begin                         {if depth left}{.CP10}
            FixUpFileName(IFileName[IFN]);
            TryToOpen(IFileName[IFN],IFil[IFN]);          {try name as found}
            if IncState=CantFind then begin
               while (pos(':',IFileName[IFN])<>0)         {if no go as found}
                     or (pos('\',IFileName[IFN])<>0) do
                        delete(IFileName[IFN],1,1);   {try same path as main}
               IFileName[IFN] := PathSign + IFileName[IFN];
               TryToOpen(IFileName[IFN],IFil[IFN]);
            end; {if couldn't find}
            if IncState=CantFind then    {if still no go, search path}{.CP11}
               if FindFile(IFileName[IFN]) then begin    {if found}
                  Assign(IFil[IFN],IFileName[IFN]);         {set up new file}
                  Reset(IFil[IFN]);
                  IncState := Started
               end; {if file found}
            if IncState=Started then             {if file found (somewhere)}
               CenterCRT('Including ' + IFileName[IFN],
                         12,Bright,Inside)    {showing where found}
            else begin                         {If file not found     {.CP11}
               Blank(12,12);                                 {report failure}
               FixUpFileName(IFileName[IFN]);
               CenterCRT('Can''t find '+IFileName[IFN],
                          12,Bright,Inside);
               dec(IFN);
            end; {if can't find it}
            while (pos(':',IFileName[IFN])<>0)           {strip pathmarks}
               or (pos('\',IFileName[IFN])<>0) do        {for printout}
                  delete(IFileName[IFN],1,1);
         end {if depth left}
         else begin                             {report no depth left} {.CP8}
            CenterCRT('Too many Include files',12,Bright,Inside);
            dec(IFN);
            IncState := TooDeep
         end; {else --no depth left}
         MarkInc;
      end {if IncFile}
   end; {Include}

   procedure CutAndPrint;                                             {.CP24}
   begin
      if LongOne then begin
         Line := LineEnd;
         LongOne := False
      end {if LongOne}
      else begin
         readln(IFil[IFN],Line);
         if EOF(IFil[IFN]) and (IFN>1) then begin
            CloseCarefully(IFil[IFN]);
            dec(IFN);
            IncState := Ended;
            MarkInc
         end; {if Eof}
         if pos('{',Line)<>0 then begin
            if pos('{.',Line)<>0 then begin
               if pos('{'+'.H',Line)<>0 then GetHeaderInstruction(Line);
               if (pos('{'+'.C',Line)<>0) or (pos('{'+'.P',Line)<>0) then
                  PrintControl(PageLineNumber);
            end; {if '{.'}
            if Pos('{'+'$I',Line)=1 then Include
         end; {if '{'}
         if PageLineNumber=-1 then PrintHeader(PageLineNumber);
      end; {else --read next line}
      LineEnd := '';                                                  {.CP15}
      MaxLess := Max - ord(IncMark[0]) - ord(IncLine[0]);
      if ord(Line[0])>MaxLess then CutIt(MaxLess);{CutIt sets LongOne = True}
      if pos(#9,Line)<>0 then TabSpace;
      if ord(LineEnd[0])>0 then FixRemainder; {pad LineEnd w matching blanks}
      if Pos(TabChr,Line)<>0 then DeTab;
      Line := ' ' + Line + ' ';                   {Pad line w blanks at ends}
      inc(PageLineNumber);
      Pager := PageLineNumber;
      if (PageLineNumber>MaxLin) and not XRefOnly then begin
         NewPage(Pager);
         PrintHeader(PageLineNumber);
      end; {if (PageLine.. }
      PrintLine;
   end; {CutAndPrint}

   procedure Initialize;                                              {.CP10}
   var
      HS: HdSegType;
      K: integer;
   begin
      for K := 1 to NoIncFiles do IFileName[K] := '';
      for K := 1 to 20 do begin
         RecDepth[K] := 0;
         CaseDepth[K] := 0
      end; {for K}
      Occur := 0; ScanCount := 0; PCount := 0;                        {.CP14}
      for K := 0 to TableSize do T[K] := Nil; Longest := 0;
      OpLen := ord(Opening[0]); ClLen := ord(Closing[0]);
      Cut := ''; UnCut := ''; Depth := 0; InRec := 0;
      LongOne := False; NoLine := False; Enough := False;
      Cuts[1] := '(*'; Cuts[2] := '{'; Cuts[3] := #39;
      UnCuts[1] := '*)'; UnCuts[2] := '}'; UnCuts[3] := #39;
      LineNumber := 1; Page := 1; IncState := OK;
      IFN := 1; assign(IFil[1],FileName); FileName := Shortened(FileName);
      MakeFirstHeader(IFil[1]);
      IncMark := '';IncLine := '';
      if Inst[EliteB,1]=255 then Elite := False else Elite := True;
      if Inst[CondB,1]=255  then Condensed := False else Condensed := True
   end; {Initialize}

begin {ListIt}                                                        {.CP30}
   ReadingMatterI;
   Enough := Escape;
   if not Enough then begin
      assign(Lst,OutputDevice); rewrite(Lst);
      CursorOff;
      Initialize;
      SetMax;
      if FFeed then NewPage(1);
      if not XRefOnly then PageLineNumber := -1;
      XRBillboard;
      while (LongOne or not EOF(IFil[IFN])) and not Enough do begin
         CutAndPrint;
         Enough := Escape
      end; {while}
      for B := IFN to 1 do CloseCarefully(IFil[IFN]);    {Close source files}
      if not XRefOnly then NewPage(Pager);
      if XRef and not Enough then begin
         XRefOnly := True;          {used as a flag  --over clever, no doubt}
         ReadingMatterII;
         PrintTable;
         TotItUp;
         NewPage(Pager)
      end; {if XRef and not Enough}
      if Wide then write(Lst,Istring[CondE]);    {Put printer back to normal}
      if Numberlines then write(Lst,Istring[EliteE])
   end {if not Enough}
end; {ListIt}

End. {Unit PXLLIST}
