program PLIST;
(*
  Written by: Rick Schaeffer
              E. 13611 26th Av.
              Spokane, Wa.  99216

  modifications (7/8/84  by Len Whitten, CIS: [73545,1006])
     1) added error handling if file not found
     2) added default extension of .PAS to main & include files
     3) added "WhenCreated" procedure to extract file
        creation date & time from TURBO FIB
     4) added demarcation of where include file ends
     5) added upper char. conversion to include file
     6) increased left margin to 5 spaces (80 char. line just fits @ 10cpi)
     7) added listing control: {.L-} turns it off, {.L+} turns it back on,
        must be in column 1
     
  further modifications (7/12/84 by Rick Schaeffer)
     1) cleaned up the command line parsing routines and put them in
        separate procedures.  Now permits any number of command line
        arguments, each argument separated with at least one space.
     2) added support for an optional second command line parameter
        which specifies whether include files will be listed or not.
        The command is invoked by placing "/i" on the command line
        at least one space after the file name to be listed.  For
        instance, to list MYPROG.PAS as well as any "included" files,
        the command line would be: PLIST MYPROG /I

   further modification (8/28/84) by Jay Kadashaw)
      1) Restored filedate and filetime after listing an included
         file.
      2) Added comment counter and begin/end counter.
      3) Output can be routed to either the printer or console.
      4) After listing first file the user is prompted for next
         file if any.
*)

(* Supported pseudo operations:
    1) Listing control: {.L-} turns it off, {.L+} turns it back on,
       must be in column 1
    2. Page ejection: {.PAGE}, must be in column 1.
    *)

 { When program is first run will check for a file
   name passed by DOS, and will try to open that file.  If no name is
   passed, will ask operator for a file name to open.  Proc will tell
   operator if file doesn't exist and will allow multiple retrys.

   Included files will be expanded only if the program is invoked as
   follows:
     pretty filename /i
   The default is not to expand included files.

   On 2nd and later executions, proc will not check for DOS passed file
   name.  In all cases, proc will assume a file type of .PAS if file
   type is not specified.
   PROGRAM EXIT from this proc when a null string is encountered in
   response to a file name request. }

const monthmask = $000F;
  daymask = $001F;
  minutemask = $003F;
  secondmask = $001F;
  First   : boolean = true;    {true when prog is run}

{ to customize code for your printer - adjust the next item }

  maxline = 58;

  cr = #13;
  lf = #10;
  ff = #12;

type
   two_letters = string[2];
   dtstr = string[8];
   fnmtype = string[14];
   instring = string[135];
   regpack = record
      ax,bx,cx,dx,bp,si,di,ds,es,flags : integer;
   end;

Var
  Buff1     : instring;          {input line buffer}
  listfil   : text;              {FIB for LST: or CON: output}
  infile    : text;              {FIB for input file}
  fnam      : fnmtype;           {in file name}
  bcount    : integer;           {begin/end counter}
  kcount    : integer;           {comment counter}
  linect    : integer;           {output file line counter}
  pageno    : integer;
  offset    : integer;
  print     : boolean;           (* {.L-} don't print *)
                                 (* {.L+} print       *)
  print_head : boolean;
  c         : char;
  month, day, year,
  hour, minute, second : two_letters;
  sysdate, systime,
  filedate, filetime : dtstr;
  expand_includes    : boolean;
  holdarg            : instring;
  allregs : regpack;
{.page}
procedure getchar(var char_value : char);
   begin
     allregs.ax := $0000;
     intr($16, allregs);
     char_value := chr(ord(lo(allregs.ax)));
   end; {getchar}

procedure fill_blanks (var line: dtstr);
  var
    i : integer;
begin
  for i:= 1 to 8 do if line[i] = ' ' then line[i]:= '0';
end;  {fill_blanks}

procedure getdate(var date : dtstr);

begin
   allregs.ax := $2A * 256;
   MsDos(allregs);
   str((allregs.dx div 256):2,month);
   str((allregs.dx mod 256):2,day);
   str((allregs.cx - 1900):2,year);
   date := month + '/' + day + '/' + year;
   fill_blanks (date);
end;  {getdate}

procedure gettime(var time : dtstr);

begin
   allregs.ax := $2C * 256;
   MsDos(allregs);
   str((allregs.cx div 256):2,hour);
   str((allregs.cx mod 256):2,minute);
   str((allregs.dx div 256):2,second);
   time := hour + ':' + minute + ':' + second;
   fill_blanks (time);
end;  {gettime}

procedure WhenCreated (var date, time: dtstr; var infile: text);

var fulltime,fulldate: integer;

begin

{fulldate gets the area of the FIB which corresponds to bytes 20-21
 of the FCB. Format is: bits 0 - 4: day of month
                             5 - 8: month of year
                             9 -15: year - 1980                     }

    fulldate:= memw [seg(infile):ofs(infile)+31];
    str(((fulldate shr 9) + 80):2,year);
    str(((fulldate shr 5) and monthmask):2,month);
    str((fulldate and daymask):2,day);
    date:= month + '/' + day + '/' + year;
    fill_blanks(date);

{fulltime gets the area of the FIB which corresponds to bytes 22-23
 of the FCB. Format is: bits 0 - 4: seconds/2
                             5 -10: minutes
                             11-15: hours                          }

    fulltime:= memw [seg(infile):ofs(infile)+33];
    str((fulltime shr 11):2,hour);
    str(((fulltime shr 5) and minutemask):2,minute);
    str(((fulltime and secondmask) * 2):2,second);
    time:= hour + ':' + minute + ':' + second;
    fill_blanks (time);
end;  {WhenCreated}

procedure print_heading(filename : fnmtype);

var offset_inc: integer;

begin
   if print then
     begin
       pageno := pageno + 1;
       write(listfil, ff);  {top of form}
       writeln(listfil);
       write(listfil,'     TURBO Pascal Program Lister');
       writeln(listfil,' ':8,'Printed: ',sysdate,'  ',
               systime,'   Page ',pageno:4);
       if filename <> fnam then begin
          offset_inc:= 14 - length (filename);
          write(listfil,'     Include File: ',filename,' ':offset_inc,
             'Created: ',filedate,'  ',filetime);
       end
       else write(listfil,'     Main File: ',fnam,' ':offset,
             'Created: ',filedate,'  ',filetime);
       writeln(listfil); writeln(listfil);
       writeln(listfil, ' C  B');
       writeln(listfil);
       linect := 6;
     end; {check for print}
end;  {print_heading}

procedure printline(iptline : instring; filename : fnmtype);
begin
   if print then
     begin
       if linect < 56 then
         begin
          writeln(listfil,'     ',iptline);
          linect := linect + 1;
         end
          else
           begin
             print_heading(filename);
           end;
     end; {check for print}
end;  {printline}
{.page}
function chkinc(var iptline : instring; var incflname : fnmtype) : boolean;
var
   done : boolean;
   i, j : integer;
begin
   i := 4; j := 1; incflname := '';
   if copy(iptline, 1, 3) = '{$I' then begin
      i := 4; j := 1; incflname := '';
      while (iptline[i] = ' ') and (i <= length(iptline)) do i := i + 1;
      done := false;
      while not done do begin
         if i <= length(iptline) then begin
            if not (iptline[i] in [' ','}','+','-']) then begin
               incflname[j] := iptline[i];
               i := i + 1; j := j + 1;
            end else done := true;
         end else done := true;
         if j > 14 then done := true;
      end;
      incflname[0] := chr(j - 1);
   end;
   if incflname <> '' then chkinc := true else chkinc := false;
end;  {chkinc}

function parse_cmd(argno : integer) : instring;
var
   i,j : integer;
   wkstr : instring;
   done : boolean;
   cmdline : ^instring;
begin
   cmdline := ptr(CSEG,$0080);
   wkstr := '';
   done := false; i := 1; j := 0;
   if length(cmdline^) < i then done := true;
   repeat
      while ((cmdline^[i] = ' ') and (not done)) do begin
         i := i + 1;
         if i > length(cmdline^) then done := true;
      end;
      if not done then j := j + 1;
      while ((cmdline^[i] <> ' ') and (not done)) do begin
         wkstr := wkstr + cmdline^[i];
         i := i + 1;
         if i > length(cmdline^) then done := true;
      end;
      if (j <> argno) then wkstr := '';
   until (done or (j = argno));
   for i := 1 to length(wkstr) do
      wkstr[i] := upcase(wkstr[i]); {all arguments forced to upper case}
   parse_cmd := wkstr;
end;

 PROCEDURE GET_IN_FILE;     {GETS INPUT FILE NAME }
   var
    existing : boolean;
  begin
    repeat             {until file exists}
      holdarg := parse_cmd(1); {get command line argument # 1}
      if (length(holdarg) in [1..14]) and first then
        fnam := holdarg  {move possible file name to fnam}
      else
        begin
          writeln;
          write(' ENTER FILE NAME TO LIST or <cr> to EXIT  ');
          readln(fnam);
        end;

     if fnam = '' then HALT;         {***** EXIT *****}
     if pos('.',fnam) = 0 then       {file type given?}
       fnam := concat(fnam,'.PAS');  {file default to .PAS type}

     {get optional command line argument # 2}
     if (length(holdarg) in [1..14]) and first then
       begin
         holdarg := parse_cmd(2);
         if holdarg = '/I' then expand_includes := true
            else expand_includes := false;
       end;

     first := false;                 {get passed file name only once}
     assign( infile, fnam);
       {$I-}
     reset( infile );                {check for existence of file}
       {$I+}
     existing := (ioresult = 0);     {true if file found}
     if not existing then
       begin
        writeln;
        writeln(' FILE DOESN''T EXIST'); {tell operator the sad news}
       end;
    until existing;                     {until file exists}
 end; {GET_IN_FILE}

{ GET_OUT_FILE procedure asks operator to select output to console
  device or list device, and then assigns and resets a file control
  block to the appropriate device.  'C' or 'P' is only correct
  response, and multiple retrys are allowed. }

Procedure Get_Out_File;
  var
    c : char;
  begin
    repeat    {until good selection}
      writeln; write(' OUTPUT LISTING TO (C)ONSOLE OR (P)RINTER ?  ');
      getchar(c);
      c := upcase(c); write(c);
   until c in ['C', 'P'];

   writeln;
   if c = 'C' then
      assign (listfil, 'CON:')
   else
      assign (listfil, 'LST:');

   reset(listfil);
 end;  {GET_OUT_FILE}

Procedure ListIt(filename : fnmtype); forward;
{.page}
{ SCAN_LINE procedure scans one line of Turbo Pascal source code
  looking for BEGIN/END pairs, CASE/END pairs, LITERAL fields
  and COMMENT fields.  BCOUNT is begin/end and case/end counter.
  KCOUNT is comment counter.  Begin/case/ends are only valid
  outside of comment fields and literal constant fields (KCOUNT = 0
  and NOT LITERAL).
  Some of the code in the SCAN_LINE procedure appears at first glance
  to be repitive and/or redundant, but was added to speed up the
  process of scanning each line of source code.}

Procedure SCAN_LINE;
  var
    literal : boolean;          { true if in literal field}
    tmp     : string[7];        { tmp work area }
    i       : integer;          {loop variable index}
    buff2   : instring;         {working line buffer}
    incflname : fnmtype;        {in file name}
    filedate_save : dtstr;
    filetime_save : dtstr;
  begin
    literal := false;

    buff2[0] := buff1[0];  {copy input buffer to working buffer}
    for i := 1 to length(buff1) do
     buff2[i] := upcase(buff1[i]);  {and translate to upper case}

    if chkinc(buff2, incflname) and expand_includes then
       begin
       for i := 1 to length(incflname) do
           incflname[i] := upcase(incflname[i]);
          if pos('.',incflname) = 0 then incflname := incflname + '.PAS';
          printline('*************************************',incflname);
          printline('    Including "'+incflname+'"', incflname);
          printline('*************************************',incflname);
          filedate_save := filedate;  {save filedate & filetime for}
          filetime_save := filetime;  {main file                   }
          listit(incflname);
          filedate := filedate_save;  {restore}
          filetime := filetime_save;
          printline('*************************************',incflname);
          printline('    End of    "'+incflname+'"', incflname);
          printline('*************************************',incflname);
         end;  {include file check}

    if copy(buff2,1,5) = '{.L-}' then print := false;
    if copy(buff2,1,5) = '{.L+}' then print := true;

    if copy(buff2,1,7) = '{.PAGE}' then print_head := true;

    buff2 := concat('  ', buff2, '      ');  {add on some working space}
    for i := 1 to length(buff2) - 6 do
      begin
        tmp := copy(buff2, i, 7);
        if not literal then   {possible to find comment delim}
          begin
           {determine if comment area delim}
           if tmp[1] in ['{', '}', '(', '*'] then
             begin
               if (tmp[1] = '{') or (copy(tmp,1,2)='(*') then
                 kcount := succ(kcount);  {count comment opens}
               if (tmp[1] = '}') or (copy(tmp,1,2)='*)') then
                 kcount := pred(kcount);  {un-count comment closes}
             end;
          end;

         if kcount = 0 then  {we aren't in a comment area}
           begin
            if tmp[1] = chr(39) then
              literal := not literal;   {toggle literal flag}

           if not literal and (tmp[2] in ['B','C','E']) then
             begin
               if (tmp = ' BEGIN ') or (copy(tmp,1,6) = ' CASE ') then
                begin
                 bcount := succ(bcount);  {count BEGIN}
                 i := i + 5;              {skip rest of begin}
                end;
               if (copy(tmp,1,4) = ' END') and
                  (tmp[5] in ['.', ' ', ';']) and
                   (bcount > 0) then
                begin
                 bcount := pred(bcount);   {un-count for END}
                 i := i + 4;
                end;
              end;  {if not literal}
           end;  { if kcount = 0 }
        end;  { for i := }
    end;  {SCAN_LINE}
{.page}
Procedure ListIt;
  var
    infile : text;
  begin
     assign(infile, filename);
   {$I-} reset(infile) {$I+} ;
   if IOresult <> 0 then begin
      writeln ('File ',filename,' not found.');
      halt;
   end;
     WhenCreated (filedate,filetime,infile);
         print_heading(filename);
         while not eof(infile) do
           begin
            readln(infile, buff1);
            scan_line;
            if print_head then
                print_heading(filename);
            if print and (not print_head) then
              begin
                writeln(listfil,kcount : 2, bcount : 3, '  ', buff1);
                linect := succ(linect);
                if linect > maxline then
                  begin
                    print_heading(filename);
                  end;
              end;
            print_head := false;
         end;     {while not eof}
  end; {ListIt}

{.page}
  begin {main procedure}
     getdate(sysdate);
     gettime(systime);
     expand_includes := false;       {default settings}
     print := true;

   repeat {forever}
     ClrScr;
     GotoXY(2, 2);
     writeln('TURBO Pascal Formatted Listing');
     GotoXY(2, 4);
     get_in_file;      {file to list}
     offset := 24 - length(fnam);
     get_out_file;     {where to list it}
     pageno := 0;
     linect := 1;      {output line counter}
     kcount := 0;
     bcount := 0;
     print_head := false;
     listit(fnam);
    write(cr, lf, 'HIT ANY KEY TO CONTINUE ');  {allow op to see end
                                                 of listing}
    getchar(c);
    until false {repeat forever - exit is in GET_IN_FILE PROCEDURE}
 end.  {main procedure}
                                                                                                                     cedure}
                                                                                                                     