{[b+]}
{ NOTICE OF COPYRIGHT AND OWNERSHIP OF SOFTWARE:

  Copyright 1980, 1981, 1982, 1983 by Oregon Software, Inc.
  All Rights Reserved.

  Whether this program is copied in whole or in part and whether this
  program is copied in original or in modified form, ALL COPIES OF THIS
  PROGRAM MUST DISPLAY THIS NOTICE OF COPYRIGHT AND OWNERSHIP IN FULL.

  Pascal symbol cross-reference utility
  Release version: 2.1A  Level: 4  Date: 19-Apr-1983 11:27:46
  Processor: ALL
  System: ALL
}

{  Cross reference generator for Pascal
                                       N. Wirth   7 May 74
                                       J. Zaun    9 Nov 78
                                       M. Ball    9 Feb 81


   Originally written by N. Wirth for the CDC 6000 Implementation.

   Converted by J. Zaun to "paslist" to cater to the idiosyncracies
   of the Univac 1100 operating system and provide a way to get page
   ejects, etc into a listing.

   Further modified to cater to the idiosyncracies of the PDP-11
   operating systems and reduce space requirements.  This includes
   some blatant hacks to make things fit into a single word of a
   packed record.
}

label
  99;

const

  panic_space = 200; {bytes at which to quit}

  hash_max = 733; {size of hash table}

  blank12 = '            '; {12 blanks}
  id_length = 12; {12 char identifiers}
  ref_length = 6; {length of each reference}

  case_shift = - 32; {ASCII case shift offset}

  {[s=4] key word indexes, defined to allow binary search }
  and_key = 1;       arr_key = 2;       beg_key = 3;       cas_key = 4;
  con_key = 5;       div_key = 6;       do_key = 7;        dwn_key = 8;
  els_key = 9;       end_key = 10;      ext_key = 11;      fil_key = 12;
  for_key = 13;      fun_key = 14;      got_key = 15;      if_key = 16;
  in_key = 17;       lab_key = 18;      mod_key = 19;      nil_key = 20;
  non_key = 21;      not_key = 22;      of_key = 23;       or_key = 24;
  org_key = 25;      oth_key = 26;      pac_key = 27;      prc_key = 28;
  prg_key = 29;      rec_key = 30;      rep_key = 31;      set_key = 32;
  thn_key = 33;      to_key = 34;       typ_key = 35;      unt_key = 36;
  var_key = 37;      whl_key = 38;      wth_key = 39;

  {[s=1] end key definitions}

type

  index = 0..hash_max; {hash table size}
  alpha = packed array [1..id_length] of char;

  kinds = (no_kind, decl_kind, asg_kind); {ref kinds}

  item_ptr = ^item;
  item =
    packed record
      line: 0..16383; {line no of ref, limits set to allow 2 word item}
      kind: kinds; {reference kind}
      next: item_ptr; {next reference for this entry}
    end;

  entries =
    packed record
      entri: alpha; {item name}
      last: item_ptr; {last reference to this item}
      next_indx: index; {next link in hash chain}
    end;

var

  {input variables}

  id: alpha; {Input identifier}
  id_nmbr, total: integer; {id counters}
  char_cnt, blank_cnt: integer; {character pos with id}
  line_nmbr: integer; {input line number}
  error_line: integer; {Line number of error}
  line_end: boolean; {TRUE at line_end}

  listing_wanted: boolean; {listing of input desired}

  {structured constants}

  r: array [and_key..wth_key] of alpha; {key word table}
  alpha_numerics, numerics, numbers, upper_cases: set of char;
  lower_cases: set of char;
  decls: set of and_key..wth_key; {start of decl}

  decl, def: boolean; {Tells when to mark refs as decl}

  {hash table variables}

  hash_table: array [index] of entries; {the hash table}
  indx, last_indx: index; {hash indexes}
  asg_ref: item_ptr; {line ref of last identitier}
  asg_ok: boolean; {determines which ref is asg ref}

  {output variables}

  done: boolean; {TRUE when done}
  error: boolean; {TRUE when table is full}
  flag_char: char; {char to print along left column}
  current_letter: char; {current xref letter}
  line_length: integer;

function space: integer;

{ Returns the amount of stack space remaining
}

  external;
{*---------------------------------*
 | Read and Process Command String |
 *---------------------------------*}

  const
    %include csicon;
    InputExt = 'pas';
    OutputExt = 'crf';
    CSIprompt = 'XRF>'; {to use if prompting}
    min_line_length = 20; {shortest allowable line width}

  type
    ArgType = (UnknownArg, 
               InputFileArg, OutputFileArg,
               WidthArg, ListArg, NoListArg,
               MalformedArg, MissingArg);
    SubArgType = 0..0;
    %include csityp;


  procedure exitst(i: Integer);
    external;

  %include csipro;
  %include getcs;  
  %include fixarg;
  %include cnvnum;

  procedure csi;

    const
      {[f-]}
      ArgDefs = ArgDefTable (
        (('                ',  1,  0), 0, OptionalArg, NullArg),
        (('Input_File      ', 11, 10), 1, RequiredArg, FileArg),
        (('Output_File     ',  2, 11), 2, OptionalArg, FileArg),
        (('Width           ',  1,  5), 0, OptionalArg, StringArg),
        (('List            ',  1,  4), 0, OptionalArg, NullArg),
        (('Nolist          ',  3,  6), 0, OptionalArg, NullArg),
        (('                ',  1,  0), 0, OptionalArg, NullArg),
        (('                ',  1,  0), 0, OptionalArg, NullArg));
      {[f+]}

    type
      ErrorMsg = (UnknownArgMsg, MalformedArgMsg, MissingArgMsg,
                  ExtraOutputMsg, ExtraInputMsg, BadWidthMsg);

    var
      InputFlg, OutputFlg: (No, Yes, Unknown);
      InputArg, OutputArg: ArgValue;
      error: Boolean;


    procedure SetupError(msg: ErrorMsg;
                         arg: ArgValue);


      begin
        case msg of
          UnknownArgMsg: write('Unexpected argument');
          MalformedArgMsg: write('Bad argument syntax');
          MissingArgMsg: write('Required argument missing');
          ExtraOutputMsg: write('Extra output file');
          ExtraInputMsg: write('Extra input file');
          BadWidthMsg: write('Width value out of range');
          end;
        if arg.Len > 0 then write(' (', arg.txt: arg.Len, ')');
        writeln;
        error := true;
      end;


    procedure ProcessArg(arg: ArgValue; typ: ArgType);

      var
        numerror: boolean; {numeric error flag}


      begin
        case typ of
          UnknownArg: SetupError(UnknownArgMsg, arg);
          OutputFileArg:
            begin
            if OutputFlg <> Unknown then SetupError(ExtraOutputMsg, arg);
            OutputArg := arg;
            OutputFlg := Yes;
            end;
          InputFileArg:
            begin
            if InputFlg <> Unknown then SetupError(ExtraInputMsg, arg);
            InputArg := arg;
            InputFlg := Yes;
            end;
          ListArg: listing_wanted := true;
          NoListArg: listing_wanted := false;
          WidthArg:
            begin
            CnvNumericArg(arg, line_length, numerror);
            if numerror or (line_length < min_line_length) then
              SetupError(BadWidthMsg, arg);
            end;
          MalformedArg: SetupError(MalformedArgMsg, arg);
          MissingArg: SetupError(MissingArgMsg, arg);
          end;
      end;


    begin  {csi}
      InputFlg := Unknown;
      OutputFlg := Unknown;

      error := false;
      GetCS(ArgDefs, ProcessArg);
      if error then exitst(4);

      if OutputFlg = Yes then
        FixFileArg(OutputArg, ActualFile, OutputExt, OutputArg)
      else FixFileArg(InputArg, DefaultFile, OutputExt, OutputArg);
      FixFileArg(InputArg, ActualFile, InputExt, InputArg);

      reset(Input, InputArg.txt);
      rewrite(Output, OutputArg.txt);
    end; {csi}

procedure initialize;

  var
    indx: index;

  begin
    {hash table variables}
    for indx := 0 to hash_max - 1 do hash_table[indx].entri := blank12;
    total := 0;
    id_nmbr := 0;
    line_nmbr := 0;
    last_indx := hash_max;

    {I/O related variables}
    done := false;
    error := false;
    current_letter := ' ';
    blank_cnt := id_length;

    {character sets}
    numbers := ['0'..'9'];
    upper_cases := ['A'..'Z', '$'];
    lower_cases := ['a'..'z'];
    alpha_numerics := lower_cases + ['_'] + upper_cases + numbers;
    numerics := ['b', 'B', 'E', 'e'] + numbers;

    {syntax variables}
    decl := true;
    def := false;
    asg_ok := true;
    flag_char := ' ';
    decls := [con_key, typ_key, var_key, prc_key, prg_key, fun_key];

    {[s=2] key word table -- these entries MUST be in alphabetical order}

    r[and_key] := 'AND         ';        r[arr_key] := 'ARRAY       ';
    r[beg_key] := 'BEGIN       ';        r[cas_key] := 'CASE        ';
    r[con_key] := 'CONST       ';        r[div_key] := 'DIV         ';
    r[do_key] := 'DO          ';         r[dwn_key] := 'DOWNTO      ';
    r[els_key] := 'ELSE        ';        r[end_key] := 'END         ';
    r[ext_key] := 'EXTERNAL    ';        r[fil_key] := 'FILE        ';
    r[for_key] := 'FOR         ';        r[fun_key] := 'FUNCTION    ';
    r[got_key] := 'GOTO        ';        r[if_key] := 'IF          ';
    r[in_key] := 'IN          ';         r[lab_key] := 'LABEL       ';
    r[mod_key] := 'MOD         ';        r[nil_key] := 'NIL         ';
    r[non_key] := 'NONPASCAL   ';        r[not_key] := 'NOT         ';
    r[of_key] := 'OF          ';         r[or_key] := 'OR          ';
    r[org_key] := 'ORIGIN      ';        r[oth_key] := 'OTHERWISE   ';
    r[pac_key] := 'PACKED      ';        r[prc_key] := 'PROCEDURE   ';
    r[prg_key] := 'PROGRAM     ';        r[rec_key] := 'RECORD      ';
    r[rep_key] := 'REPEAT      ';        r[set_key] := 'SET         ';
    r[thn_key] := 'THEN        ';        r[to_key] := 'TO          ';
    r[typ_key] := 'TYPE        ';        r[unt_key] := 'UNTIL       ';
    r[var_key] := 'VAR         ';        r[whl_key] := 'WHILE       ';
    r[wth_key] := 'WITH        ';

    {[s=1]}

    listing_wanted := false;
    line_length := 80;
    csi;

  end {initialize} ;

{*---------------------*
 |  I/O procedures     |
 *---------------------*   }


procedure new_line(ch: char);


  begin { Process a new input line, generating a line number if a listing is
         being generated. NOTE: The linenumber, etc is calculated to take
         exactly 8 characters, so tabs look right }
    line_nmbr := line_nmbr + 1;
    if listing_wanted then write(output, ch, line_nmbr: 6, ' ');
  end;


procedure sget;


  begin
    if eof(input) then goto 99
    else if eoln(input) then
      begin
      if listing_wanted then writeln;
      get(input);
      if not eof(input) then new_line(flag_char);
      line_end := true;
      end
    else
      begin
      line_end := false;
      get(input)
      end;
    if eof(input) then goto 99;
  end;


procedure copy_char;


  begin
    if listing_wanted then write(input^);
    sget;
  end;

 {*-----------------------------*
  | Lexical and Syntax Analysis |
  *-----------------------------* }


function reserved_wrd(word: alpha): boolean;

  var
    low, high, key: integer;
    key_ok: boolean;


  begin
    low := and_key;
    high := wth_key;
    repeat { binary search }
      key := (low + high) div 2;
      if r[key] <= word then low := key + 1;
      if r[key] >= word then high := key - 1;
    until low > high;
    key_ok := (r[key] = word);
    if key_ok then
      begin
      if key in decls then decl := true
      else if key = beg_key then decl := false
      else if (key = rec_key) or (key = cas_key) then def := false;
      end;
    reserved_wrd := key_ok;
  end { function } ;


procedure cross_ref;

  var
    indx, dst, i: index;
    ref: item_ptr;
    found: boolean;


  begin
    indx := 1;
    found := false;
    dst := 1;
    for i := 1 to 8 do indx := abs((indx * ord(id[i])) mod hash_max);
    total := total + 1;
    if space >= panic_space then
      begin
      new(ref);
      if asg_ok then asg_ref := ref;
      with ref^ do
        begin
        line := line_nmbr;
        next := nil;
        if decl and not def then kind := decl_kind
        else kind := no_kind;
        end;
      repeat
        with hash_table[indx] do
          begin
          if entri = id then
            begin {found}
            found := true;
            ref^.next := last;
            last := ref;
            end
          else if entri = blank12 then
            begin {new entri}
            found := true;
            id_nmbr := id_nmbr + 1;
            entri := id;
            last := ref;
            next_indx := last_indx;
            last_indx := indx;
            end
          else
            begin {collision}
            indx := (indx + dst) mod hash_max;
            dst := dst + 2;
            if dst >= hash_max then
              begin
              writeln;
              writeln('**** Too many unique identifiers.');
              error := true;
              error_line := line_nmbr;
              found := true
              end
            end
          end {with}
      until found;
      end
    else
      begin
      writeln;
      writeln('**** Too many references.');
      error := true;
      error_line := line_nmbr;
      end;
  end {cross_ref} ;


procedure identifier;


  begin
    if not error then
      begin
      char_cnt := 0;
      repeat
        if (char_cnt < id_length) then
          begin
          char_cnt := char_cnt + 1;
          if input^ in lower_cases then
            id[char_cnt] := chr(ord(input^) + case_shift)
          else id[char_cnt] := input^;
          end;
        copy_char
      until not (input^ in alpha_numerics);
      if char_cnt >= blank_cnt then blank_cnt := char_cnt
      else
        repeat
          id[blank_cnt] := ' ';
          blank_cnt := blank_cnt - 1
        until blank_cnt = char_cnt;
      if not reserved_wrd(id) then cross_ref;
      end
    else
      repeat
        copy_char
      until not (input^ in alpha_numerics);
  end {identifier} ;


procedure number;


  begin
    repeat
      copy_char;
    until not (input^ in numerics)
  end {number} ;


procedure string;


  begin
    flag_char := 's';
    repeat
      copy_char;
    until (input^ = '''') or (line_end);
    flag_char := ' ';
    copy_char;
  end {string} ;


procedure comment;


  begin
    flag_char := 'c';
    copy_char;
    while input^ <> '}' do copy_char;
    flag_char := ' ';
    copy_char;
  end {comment} ;


procedure comment1(termchar: char);


  begin
    copy_char;
    if input^ = '*' then
      begin
      flag_char := 'c';
      copy_char;
      repeat
        while input^ <> '*' do copy_char;
        copy_char;
      until input^ = termchar;
      flag_char := ' ';
      copy_char;
      end
  end {comment1} ;


procedure special_char;

  var
    got: boolean;


  begin
    got := false;
    if input^ = '[' then asg_ok := false
    else if input^ = ']' then asg_ok := true
    else if input^ = '=' then def := true
    else if input^ = ';' then def := false
    else if input^ = '%' then
      while not eoln do copy_char
    else if input^ = ':' then
      begin
      copy_char;
      got := true;
      if input^ = '=' then
        begin
        if not error then asg_ref^.kind := asg_kind;
        copy_char;
        end
      else def := true;
      end;
    if not got then
      begin
      copy_char
      end;
  end;


procedure scan_input;


  begin
    new_line(' ');
    while not (done or eof(input)) do
      begin
      if input^ in upper_cases + lower_cases then identifier
      else if input^ in numbers then number
      else if input^ = '''' then string
      else if input^ = '{' then comment
      else if input^ = '(' then comment1(')')
      else if input^ = '/' then comment1('/')
      else special_char;
      end;
  end {print_listing} ;

{*----------------------*
 | hash table printing  |
 *----------------------* }


procedure print_entri(hash: entries);

  var
    out_count: integer;
    ch: char;
    i: integer;
    ref, last_ref, next_ref: item_ptr; {used to track entry list}


  begin
    with hash do
      begin
      if entri[1] <> current_letter then
        begin
        current_letter := entri[1];
        writeln;
        writeln('-', current_letter, '-');
        end;
      write(entri, ' ');
      out_count := id_length + 1;
      end;
    last_ref := nil;
    ref := nil;
    next_ref := hash.last;

    while next_ref <> nil do
      begin
      ref := next_ref;
      next_ref := ref^.next;
      ref^.next := last_ref;
      last_ref := ref;
      end;

    repeat
      if out_count > line_length - ref_length - 1 then
        begin {continue on next line}
        writeln;
        write(' ': id_length + 1);
        out_count := id_length + 1;
        end;
      out_count := out_count + ref_length + 1;
      with ref^ do
        begin
        if kind = decl_kind then ch := '*'
        else if kind = asg_kind then ch := '='
        else ch := ' ';
        write(line: ref_length, ch);
        ref := next
        end;
    until ref = nil;
    writeln;
  end {print_entri} ;


procedure print_cross_ref;

  var
    i, j, min: index;


  begin
    if listing_wanted then page(output);
    writeln('Cross reference:  * indicates definition, = indicates assignment'
            );
    i := last_indx;
    while i <> hash_max do
      begin
      min := i;
      j := hash_table[i].next_indx;
      while j <> hash_max do
        begin
        if hash_table[j].entri < hash_table[min].entri then min := j;
        j := hash_table[j].next_indx;
        end;
      print_entri(hash_table[min]);
      if min <> i then
        begin
        hash_table[min].entri := hash_table[i].entri;
        hash_table[min].last := hash_table[i].last;
        end;
      i := hash_table[i].next_indx;
      end; {while loop}
    writeln;
    writeln;
    if error then
      begin
      writeln('Memory capacity exceeded at line:', error_line: 5);
      writeln('Program too large for xref.');
      end;
    writeln('end xref', id_nmbr: 4, ' identifiers', total: 6,
            ' total references')
  end {print_cross_ref} ;

{ *-------------------*
  |   main program    |
  *-------------------* }


begin
  initialize;
  scan_input;
99:
  print_cross_ref;
end.
                                                                                                                                                                                                     