File PFMAP.PS

Directory of image this file is from
This file as a plain text file

{$X+}
program Referencer(input:'dsk:input.ps',output:'IO:.ls');{$T-,P-,N-,L-}
{-----------------------------------------------------------------------|
|									|
|		   PASCAL PROCEDURAL CROSS-REFERENCER			|
|									|
|	 (c) Copyright 1979 A.H.J.Sale, Southampton, England.		|
|									|
|   DEVELOPMENT 							|
|   This program is a software tool developed  from  a	prototype  by	|
|   A.J.Currie at the University of Southampton, England.  The proto-	|
|   type of 231 lines of source text was used firstly as a basis  for	|
|   extensions,   and	then   rewritten  to  assure  correctness  by	|
|   A.H.J.Sale, on leave from the University  of  Tasmania  and  then	|
|   also  at  the University of Southampton.  The current version was	|
|   stabilized at 1979 December 4; the	development  time  being  es-	|
|   timated at 4 man-days from prototype to production. 		|
|									|
|   PURPOSE								|
|   The program reads Pascal source programs and produces two  tables	|
|   as	output.  These tables are procedural documentation and cross-	|
|   references.  One documents all procedure or function headings  in	|
|   a  format  that  illustrates  lexical  nesting.  The other tables	|
|   gives the locations of heading, block, and	body  for  each  pro-	|
|   cedure and function, and what procedures and functions it immedi-	|
|   ately calls.							|
|									|
|   There is a User Manual for this program; if it has not been  pro-	|
|   vided with your installation write to:				|
|	      Department of Information Science 			|
|	      University of Tasmania					|
|	      P.O.Box 252C, G.P.O. Hobart				|
|	      Tasmania	7001						|
|   and ask for the Technical Report on Referencer, if	it  is	still	|
|   available.	The program is written to be portable and is believed	|
|   to be in Standard Pascal.						|
|									|
|   Permission is granted to copy this program, store it in a comput-	|
|   er	system,  and distribute it, provided that this header comment	|
|   is retained in all copies.						|
|									|
|-----------------------------------------------------------------------}

{-----------------------------------------------------------------------|
|									|
|			   PROGRAM ASSERTIONS				|
|									|
|   Pre-Assertion P1:							|
|	  "The file input contains  a  representation  of  a  correct	|
|	  Standard Pascal program, in the ISO Reference form."		|
|									|
|   Post-assertion P2:							|
|	  P1 and "the file output contains a  representation  of  the	|
|	  two  tables described above, which correctly describe facts	|
|	  about the program."						|
|									|
|-----------------------------------------------------------------------}

const
    { This constant is the number of significant characters kept in
    the identifier entries.  It can readily be changed.  It is not
    advised that it be reduced below 10 (reserved words get to 9). }
    SigCharLimit = 16;

    { This must always be (SigCharLimit - 1).  It is used simply to
    reduce the set range to have a lower bound of 0, not 1. }
    SetLimit = 15;

    { This constant is used to convert upper-case letters to lower-case
    and vice-versa.  It should be equal to ord('a') - ord('A'). }
    UCLCdisplacement = 32;

    { This constant determines the size of the input line buffer.
    The maximum acceptable input line is one smaller because a sentinel
    space is appended to every line. }
    LineLimit = 200;

    { This constant determines the maximum width of the printing of the
    second cross-reference table.  The program deduces how many names
    will fit on a line. }
    LineWidth =  80;

    { This determines the indentation of the lex-levels. }
    Indentation = 4;

    { These constants are used for the sketchy syntax analysis.
    They are collected here so that their lengths may be altered if
    SigCharLimit is altered. }
    Sprogram	  = 'program	     ';
    Sprocedure	  = 'procedure	     ';
    Sfunction	  = 'function	     ';
    Slabel	  = 'label	     ';
    Sconst	  = 'const	     ';
    Stype	  = 'type	     ';
    Svar	  = 'var	     ';
    Sbegin	  = 'begin	     ';
    Scase	  = 'case	     ';
    Send	  = 'end	     ';
    Sforward	  = 'forward	     ';
    Spaces	  = '		     ';

type
    Natural	   = 0..maxint;
    Positive	   = 1..maxint;

    SixChars	   = packed array[1..6] of char;

    SigCharRange   = 1..SigCharLimit;
    SetRange	   = 0..SetLimit;

    PseudoString   = packed array [SigCharRange] of char;
    StringCases    = set of SetRange;

    LineSize	   = 1..LineLimit;
    LineIndex	   = 0..LineLimit;

    SetOfChar	   = set of char;

    ProcKind	   = (FwdHalf,AllFwd,Shortform,Formal,Outside,NotProc);

    PtrToEntry	   = ^ Entry;

    ListOfUsages   = ^ UsageCell;

    PtrToStackCell = ^ StackCell;

    TokenType	   = (OtherSy,NameSy,LParenSy,RParenSy,ColonSy,
		     SemiColSy,PeriodSy,AssignSy,SubRangeSy);

    { This type represents a procedure or function identifier found
    during processing of a program.  The fields are used as follows:
      - procname & caseset = representation of name
      - linenumber	   = where heading starts
      - startofbody	   = where begin of statement-part starts
      - forwardblock	   = where forward-declared block starts
      - status		   = kind or status of name
      - left,right	   = subtrees of the scope-level tree
      - before, after	   = subtrees of the supertree
      - calls		   = a list of the procedures this calls
      - localtree	   = the scope tree for the interior
    }
    Entry =
	 record
	     procname	  : PseudoString;
	     caseset	  : StringCases;
	     linenumber   : Natural;
	     startofbody  : Natural;
	     left,right   : PtrToEntry;
	     before,after : PtrToEntry;
	     calls	  : ListOfUsages;
	     localtree	  : PtrToEntry;
	     case status : ProcKind of
		 FwdHalf,Shortform,Formal,Outside,NotProc:
		     ();
		 AllFwd:
		     ( forwardblock: Natural )
	 end;

    { This type records an instance of an activation of a procedure or
    function.  The next pointers maintain an alphabetically ordered
    list; the what pointer points to the name of the activated code. }
    UsageCell =
	 record
	     what : PtrToEntry;
	     next : ListOfUsages
	 end;

    { This type is used to construct a stack which holds the current
    lexical level information. }
    StackCell =
	 record
	     current   : PtrToEntry;
	     scopetree : PtrToEntry;
	     substack  : PtrToStackCell
	 end;

var
    lineno	 : Natural;
    chno	 : LineIndex;
    total	 : LineIndex;
    depth	 : Natural;
    level	 : -1..maxint;
    pretty	 : Natural;

    { These are used to align the lines of a heading. }
    adjustment	 : (First,Other);
    movement	 : integer;

    { These are true, respectively, if line-buffers need to be
    printed before disposal, and if any errors have occurred. }
    printflag	 : boolean;
    errorflag	 : boolean;

    ch		 : char;

    token	 : tokentype;

    symbol	 : PseudoString;
    symbolcase	 : StringCases;

    savesymbol	 : PseudoString;

    line	 : array[LineSize] of char;

    superroot	 : PtrToEntry;

    stack	 : PtrToStackCell;

    { The remaining variables are pseudo-constants. }
    alphabet	 : SetOfChar;
    alphanums	 : SetOfChar;
    uppercase	 : SetOfChar;
    digits	 : SetOfChar;
    usefulchars  : SetOfChar;

    namesperline : Positive;

    procedure PrintLine;
    var
	i : LineSize;
    begin
	write(output, lineno:5, '    ');
	i := 1;
	{ Is this the first time in a run or not? }
	if adjustment = First then begin
	    { Ignore any leading spaces there happen to be. }
	    while (i < total) and (line[i] = ' ') do
		i := succ(i);
	    { Compute the adjustment needed for other lines. }
	    movement := (level * Indentation) - (i - 1);
	    adjustment := Other;
	    { Insert any necessary indentation }
	    if level > 0 then
		write(output, ' ': (level*Indentation))
	end else begin
	    { It wasn't the first time, so try to adjust this
	    line to align with its mother. }
	    if movement > 0 then begin
		write(output, ' ':movement)
	    end else if movement < 0 then begin
		while (i < total) and (line[i] = ' ') and
		    (i <= - movement) do begin
		    i := succ(i)
		end
	    end
	end;
	{ Write out the line. }
	while i < total do begin
	    write(output, line[i]);
	    i := succ(i)
	end;
	writeln(output)
    end; { PrintLine }

    procedure Error(e: Positive);
    { This procedure is the error message repository. }
    begin
	errorflag := true;
	write(output, 'FATAL ERROR - ');
	case e of
	 1: write(output, 'No "program" word');
	 2: write(output, 'No identifier after prog/proc/func');
	 3: write(output, 'Token after heading expected');
	 4: write(output, 'Lost ".", check begin/case/ends');
	 5: write(output, 'Same name, but not forward-declared')
	end;
	{ We shall print the offending line too. }
	writeln(output, ' - AT FOLLOWING LINE');
	adjustment := first;
	PrintLine
    end; { Error }

    procedure NextCh;
    begin
	if chno = total then begin
	    if printflag then
		PrintLine;
	    total := 0;
	    while not eoln(input) do begin
		total := succ(total);
		 read(input, line[total])
	    end;
	    total := succ(total);
	    line[total] := ' ';
	    readln(input);
	    lineno := lineno + 1;
	    chno := 1;
	    ch := line[1]
	end else begin
	    chno := succ(chno);
	    ch := line[chno]
	end
    end; { NextCh }

    procedure Push(newscope: PtrToEntry);
    var
	newlevel: PtrToStackCell;
    begin
	new(newlevel);
	newlevel^.current := newscope;
	newlevel^.scopetree := nil;
	newlevel^.substack := stack;
	stack := newlevel;
	level := level + 1
    end; { Push }

    procedure Pop;
    var
	oldcell: PtrToStackCell;
    begin
	stack^.current^.localtree := stack^.scopetree;
	oldcell := stack;
	stack := oldcell^.substack;
	{ *** dispose(oldcell); *** }
	level := level - 1
    end; { Pop }

    procedure FindNode(var match   : Boolean;
		       var follow  : PtrToEntry;
			   thisnode: PtrToEntry);
    begin
	match := false;
	while (thisnode <> nil) and not match do begin
	    follow := thisnode;
	    if savesymbol < thisnode^.procname then
		thisnode := thisnode^.left
	    else if savesymbol > thisnode^.procname then
		thisnode := thisnode^.right
	    else
		match := true
	end
    end; { FindNode }

    function MakeEntry (mainprog: Boolean;
			proc	: Boolean): PtrToEntry;
    { The first parameter is true if the name in symbol is the
    program identifier, which has no scope.  The second parameter
    is true if the name in symbol is that of a procedure or function.
    The result returned is the identification of the relevant record. }
    var
	newentry, node: PtrToEntry;
	located: Boolean;

	procedure PutToSuperTree(newnode: PtrToEntry);
	{ This procedure takes the entry that has been created by
	MakeEntry and inserted into the local tree, and also links
	it into the supertree. }
	var
	    place: PtrToEntry;

	    procedure FindLeaf;
	    { FindLeaf searches the supertree to find where this
	    node should be placed.  It will be appended to a leaf
	    of course, and placed after entries with the same
	    name. }
	    var
		subroot : PtrToEntry;
	    begin
		subroot := superroot;
		while subroot <> nil do begin
		    place := subroot;
		    if savesymbol < subroot^.procname then
			subroot := subroot^.before
		    else
			subroot := subroot^.after
		end
	    end; { FindLeaf }

	begin { PutToSuperTree }
	    if superroot = nil then begin
		{ Nothing in the supertree yet. }
		superroot := newnode
	    end else begin
		{ Seek the right place }
		FindLeaf;
		with place^ do begin
		    if savesymbol < procname then
			before := newnode
		    else
			after := newnode
		end
	    end
	end; { PutToSuperTree }

    begin { MakeEntry }
	located := false;
	savesymbol := symbol;
	if mainprog then begin
	    new(newentry);
	end else if stack^.scopetree = nil then begin
	    { Nothing here yet. }
	    new(newentry);
	    stack^.scopetree := newentry
	end else begin
	    { Seek the identifier in the tree. }
	    FindNode(located, node, stack^.scopetree);
	    if not located then begin
		{ Normal case, make an entry. }
		new(newentry);
		with node^ do
		    if symbol < procname then
			left := newentry
		    else
			right := newentry
	    end
	end;
	if not located then begin
	    { Here we initialize all the fields }
	    with newentry^ do begin
		procname := symbol;
		caseset := symbolcase;
		linenumber := lineno;
		startofbody := 0;
		if proc then
		    status := Shortform
		else
		    status := NotProc;
		left := nil;
		right := nil;
		before := nil;
		after := nil;
		calls := nil;
		localtree := nil
	    end;
	    MakeEntry := newentry;
	    if proc then begin
		PutToSuperTree(newentry);
		Push(newentry)
	    end
	end else begin
	    { Well, It'd better be forward or else. }
	    MakeEntry := node;
	    Push(node);
	    if node^.status = FwdHalf then begin
		stack^.scopetree := node^.localtree;
		node^.status := AllFwd;
		node^.forwardblock := lineno
	    end else begin
		Error(5)
	    end
	end
    end; { MakeEntry }

    procedure PrintTree(root: PtrToEntry);
    var
	thiscell: ListOfUsages;
	count: Natural;

	procedure ConditionalWrite(n: Natural;
			  substitute: SixChars);
	begin
	    { Write either the substitute string or a number. }
	    if n = 0 then
		write(output, substitute)
	    else
		write(output, n:6)
	end; { ConditionalWrite }

	procedure NameWrite(p : PtrToEntry);
	var
	    s : SetRange;
	begin
	    for s := 0 to SetLimit do begin
		if s in p^.caseset then
		    write(output,
			chr(ord(p^.procname[s+1])-UCLCdisplacement))
		else
		    write(output, p^.procname[s+1])
	    end
	end; { NameWrite }

    begin { PrintTree }
	if root <> nil then
	    with root^ do begin
		PrintTree(before);

		writeln(output);
		write(output, linenumber: 5);
		ConditionalWrite(startofbody, '      ');
		case status of
		    FwdHalf,NotProc:
			write(output, '   eh?');
		    Formal:
			write(output, '   fml');
		    Outside:
			write(output, '   ext');
		    Shortform:
			write(output, '      ');
		    AllFwd:
			write(output, forwardblock:6)
		end;
		write(output, '  ');
		NameWrite(root);
		write(output, ' :');
		thiscell := calls;
		count := 0;
		while thiscell <> nil do begin
		    if ((count mod namesperline) = 0) and (count <> 0)
			then begin
			writeln(output);
			write(output, ' ':35, ' :')
		    end;
		    write(output, ' ');
		    NameWrite(thiscell^.what);
		    thiscell := thiscell^.next;
		    count := count + 1
		end;
		writeln(output);

		PrintTree(after)
	    end
    end; { PrintTree }

    procedure NextToken;
    { This procedure produces the next "token" in a small set of
    recognized tokens.	Most of these serve an incidental purpose;
    the prime purpose is to recognize names (res'd words or identifiers).
    It serves also to skip dangerous characters in comments, strings,
    and numbers. }

	procedure IgnoreComment;
	{ This procedure skips over comments according to the definition
	in the Draft Pascal Standard. }
	begin
	    NextCh;
	    repeat
		while (ch <> '*') and (ch <> '}') do
		    NextCh;
		if ch = '*' then
		    NextCh;
	    until (ch = ')') or (ch = '}');
	    NextCh
	end; { IgnoreComment }

	procedure IgnoreNumbers;
	{ This procedure skips numbers because the exponent part
	just might get recognized as a name!  Care must be taken
	not to comsume half of a ".." occurring in a construct like
	"1..Name", or worse to consume it and treat the name as a
	possible exponent as in "1..E02". Ugh. }
	begin
	    while ch in digits do
		NextCh;
	    { The construction of NextCh, chno, & line ensure that
	    the following tests are always defined.  It is to get
	    rid of tokens which begin with a period like .. & .)  }
	    if (ch = '.') then begin
		if (line[chno+1] in digits) then begin
		    NextCh;
		    while ch in digits do
			NextCh
		end
	    end;
	    if (ch = 'E') or (ch = 'e') then begin
		NextCh;
		if (ch = '+') or (ch = '-') then
		    NextCh;
		while ch in digits do
		    NextCh
	    end
	end; { IgnoreNumbers }

	procedure ReadIdent;
	{ This procedure reads in an identifier }
	var
	    j : Positive;
	begin
	    token := NameSy;
	    symbol := Spaces;
	    symbolcase := [];
	    j := 1;
	    while (j <= SigCharLimit) and (ch in alphanums) do begin
		if ch in uppercase then begin
		    symbol[j] := chr(ord(ch) + UCLCdisplacement);
		    symbolcase := symbolcase + [j-1]
		end else begin
		    symbol[j] := ch
		end;
		j := j+1;
		NextCh
	    end;
	    { In case there is a tail, skip it. }
	    while ch in alphanums do
		NextCh
	end; { ReadIdent }

    begin { NextToken }
	token := OtherSy;
	repeat
	    if ch in usefulchars then begin
		case ch of

		 ')': begin
			  NextCh;
			  token := RParenSy
		      end;

		 '(': begin
			  NextCh;
			  if ch = '*' then begin
			      IgnoreComment
			  end else begin
			      token := LParenSy
			  end
		      end;

		 '{': begin
			  IgnoreComment
		      end;

		'''': begin
			  NextCh;
			  while ch <> '''' do
			      NextCh;
			  NextCh
		      end;

		 '0','1','2','3','4','5','6','7','8','9':
		      begin
			  IgnoreNumbers
		      end;

		 ':': begin
			  NextCh;
			  if ch = '=' then begin
			      token := AssignSy;
			      NextCh
			  end else begin
			      token := ColonSy
			  end
		      end;

		 '.': begin
			  NextCh;
			  if ch <> '.' then
			      token := PeriodSy
			  else begin
			      token := SubRangeSy;
			      NextCh
			  end
		      end;

		 ';': begin
			  NextCh;
			  token := SemiColSy
		      end;

		 'A','B','C','D','E','F','G','H','I','J','K','L','M',
		 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
		 'a','b','c','d','e','f','g','h','i','j','k','l','m',
		 'n','o','p','q','r','s','t','u','v','w','x','y','z':
		      begin
			  ReadIdent
		      end

		end
	    end else begin
		{ Uninteresting character }
		NextCh
	    end
	until token <> OtherSy
    end; { NextToken }

    procedure ProcessUnit(programid: Boolean);
    { This procedure processes a program unit.	It is called on
    recognition of its leading token = program/procedure/function.
    The parameter records whether we currently have the main program
    identifier in the token, or not.  It doesn't have scope. }
    var
	at : PtrToEntry;

	function NameIsInScope: Boolean;
	{ This function is called during the declaration phase
	of a block, and has to find any procedure which gets
	renamed by the scope rules. }
	var
	    llevel	  : PtrToStackCell;
	    discovered	  : Boolean;
	    where	  : PtrToEntry;
	begin
	    llevel := stack;
	    discovered := false;
	    savesymbol := symbol;
	    while (llevel <> nil) and not discovered do begin
		FindNode(discovered, where, llevel^.scopetree);
		if not discovered then
		    llevel := llevel^.substack
	    end;
	    if discovered then
		NameIsInScope := (where^.status <> NotProc)
	    else
		NameIsInScope := false
	end; { NameIsInScope }

	procedure ProcessBlock;
	{ This procedure is called by ProcessUnit when it has recognized
	the start of a block.  It handles the processing of the block. }
	var
	    address: PtrToEntry;

	    procedure CrossReferencer;
	    { CrossReferencer is called whenever we have a name which
	    might be a call to a procedure or function.  The only way
	    we tell is by looking in the table to see.	If it is, then
	    the list of usages of the procedure we are in is scanned and
	    possibly extended. }
	    var
		newcell : ListOfUsages;
		ptr	: ListOfUsages;
		home	: PtrToEntry;
		slevel	: PtrToStackCell;
		found	: Boolean;

		procedure FindCell;
		{ FindCell is used to scan a List Of Usages to determine
		whether the name already appears there.  If not, it
		leaves ptr pointing to the tail of the list so that an
		addition can be made. }
		var
		    nextptr  : ListOfUsages;
		begin
		    found := false;
		    nextptr := stack^.current^.calls;
		    if nextptr <> nil then
			repeat
			    ptr := nextptr;
			    found := (ptr^.what^.procname = savesymbol);
			    nextptr := ptr^.next
			until found or (nextptr = nil)
		    else
			ptr := nil
		end; { FindCell }

	    begin { CrossReferencer }
		slevel := stack;
		found := false;
		while (slevel <> nil) and not found do begin
		    FindNode(found, home, slevel^.scopetree);
		    if not found then
			slevel := slevel^.substack
		end;
		if found then begin
		    if home^.status <> NotProc then begin
			FindCell;
			if not found then begin
			    new(newcell);
			    if ptr <> nil then
				ptr^.next := newcell
			    else
				stack^.current^.calls := newcell;
			    newcell^.what := home;
			    newcell^.next := nil
			end
		    end
		end
	    end; { CrossReferencer }

	    procedure ScanForName;
	    { This procedure is required to go forward until the
	    current token is a name (reserved word or identifier). }
	    begin
		NextToken;
		while token <> NameSy do
		    NextToken
	    end; { ScanForName }

	begin { ProcessBlock }
	    while (symbol <> Sbegin) do begin
		while (symbol <> Sbegin) and (symbol <> Sprocedure) and
		    (symbol <> Sfunction) do begin
		    ScanForName;
		    if NameIsInScope then begin
			address := MakeEntry(false, false);
			{ MakeEntry made its status NotProc }
		    end
		end;
		if symbol <> Sbegin then begin
		    ProcessUnit(false);
		    ScanForName
		end
	    end;
	    { We have now arrived at the body }
	    depth := 1;
	    stack^.current^.startofbody := lineno;
	    NextToken;
	    while depth <> 0 do begin
		if token <> NameSy then begin
		    NextToken
		end else begin
		    if (symbol = Sbegin) or (symbol = Scase) then begin
			depth := depth + 1;
			NextToken
		    end else if (symbol = Send) then begin
			depth := depth - 1;
			NextToken
		    end else begin
			{ This name is a candidate call.  But first we
			must eliminate assignments to function values. }
			savesymbol := symbol;
			NextToken;
			if token <> AssignSy then begin
			    CrossReferencer
			end else begin
			    NextToken
			end
		    end
		end
	    end
	end; { ProcessBlock }

	procedure ScanParameters;
	{ This procedure scans the parameter list because at the outer
	level there may be a formal procedure we ought to know about. }
	var
	    which : PtrToEntry;

	    procedure ScanTillClose;
	    { This procedure is called when a left parenthesis is
	    detected, and its task is to find the matching right
	    parenthesis.  It does this recursively. }
	    begin
		NextToken;
		while token <> RParenSy do begin
		    if token = LParenSy then
			ScanTillClose;
		    NextToken
		end
	    end; { ScanTillClose }

	begin { ScanParameters }
	    NextToken;
	    while token <> RParenSy do begin
		if (token = NameSy) then begin
		    if (symbol = Sprocedure) or
			(symbol = Sfunction) then begin
			{ A formal procedural/functional parameter. }
			NextToken;
			if token = NameSy then begin
			    which := MakeEntry(false, true);
			    which^.status := Formal;
			    Pop;
			    NextToken;
			    if token = LParenSy then begin
				{ Skip interior lists. }
				ScanTillClose
			    end
			end else begin
			    Error(2);
			    NextToken
			end
		    end else begin
			if NameIsInScope then
			    which := MakeEntry(false, false);
			NextToken
		    end
		end else begin
		    NextToken
		end
	    end;
	    NextToken
	end; { ScanParameters }

    begin { ProcessUnit }
	printflag := true;
	adjustment := First;
	NextToken;
	if token <> NameSy then
	    Error(2)
	else begin
	    { We now have the name to store away. }
	    at := MakeEntry(programid, true);
	    while not (token in [LParenSy,SemiColSy,ColonSy]) do
		NextToken;
	    if token = LParenSy then
		ScanParameters;
	    while token <> SemiColSy do
		NextToken;
	    PrintLine;
	    { We have now printed the procedure heading. }
	    printflag := false;
	    writeln(output);
	    { Our next task is to see if there is an attached block. }
	    NextToken;
	    if token <> NameSy then
		Error(3)
	    else begin
		if (symbol <> Slabel) and (symbol <> Sconst) and
		    (symbol <> Stype) and (symbol <> Sprocedure) and
		    (symbol <> Sfunction) and (symbol <> Svar) and
		    (symbol <> Sbegin) then begin
		    { Bloody directive, mate. }
		    if symbol = Sforward then
			at^.status := FwdHalf
		    else
			at^.status := Outside;
		    Pop
		end else begin
		    ProcessBlock;
		    Pop
		end
	    end
	end
    end; { ProcessUnit }

{ *** --------------------------------------------------------|
|							      |
|   This procedure outlines what is needed  to insert the     |
|   predefined names into Referencer's tables.	De-box it     |
|   and extend it as needed.				      |
|							      |
|   procedure BuildPreDefined;				      |
|   const						      |
|	NoOfNames = 2;					      |
|   type						      |
|	NamesIndex   = 1..NoOfNames;			      |
|   var 						      |
|	kk   : NamesIndex;				      |
|	tt   : array[NamesIndex] of PseudoString;	      |
|	hohum: PtrToEntry;				      |
|   begin						      |
|	tt[01] := 'new		   ';			      |
|	tt[02] := 'writeln	   ';			      |
|	caseset := [];					      |
|	for kk := 1 to NoOfNames do begin		      |
|	    symbol := tt[kk];				      |
|	    hohum := MakeEntry(false,false);		      |
|	    hohum^.status := Outside;			      |
|	end;						      |
|   end;						      |
|							      |
|-------------------------------------------------------- *** }

    procedure PrintHeading;
    begin
	writeln(output, 'Procedural Cross-Referencer - Version S-02.02');
	writeln(output, '=============================================');
	writeln(output)
    end; { PrintHeading }

begin { Referencer }
    superroot := nil;
    { Here we construct an outer-scope stack entry.  This is needed
    to hold any pre-defined names.  The distributed version does not
    include any of these, but they are easily provided.  See the
    outlines in the code marked with *** if you want this feature. }
    new(stack);
    with stack^ do begin
	current := nil;
	scopetree := nil;
	substack := nil
    end;

    printflag := false;

    uppercase := ['A','B','C','D','E','F','G','H','I','J','K','L','M',
		  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    alphabet  := uppercase +
		 ['a','b','c','d','e','f','g','h','i','j','k','l','m',
		  'n','o','p','q','r','s','t','u','v','w','x','y','z'];
    digits    := ['0','1','2','3','4','5','6','7','8','9'];
    alphanums := alphabet + digits {***} + ['_'] {***} ;
    usefulchars := alphabet + digits +
		 ['(', ')', '{', '.', ':', ';', ''''];

    namesperline := (LineWidth - (SigCharLimit + 21)) div
	(SigCharLimit + 1);

    { *** If you want to introduce some options, this is the place
    to insert the call to your OptionAnalyser.	None is provided
    with the standard tool because the requirements vary widely
    across user environments.  The probable options that might be
    provided are (a) whether pre-declared names should appear in
    the call lists, (b) how many columns are to be printed in them
    (namesperline), (c) whether underscore is permitted in identifiers,
    and perhaps whether output should be completely in upper-case
    letters.  The first option (a) requires a call to BuildPreDefined
    just below this point, after analysing options... }

    total := 0;
    chno := 0;
    lineno := 0;
    level := -1;
    errorflag := false;
    { *** BuildPreDefined; *** }

    page(output);
    PrintHeading;
    writeln(output, ' Line   Program/procedure/function heading');
    for pretty := 1 to 43 do
	write(output, '-');
    writeln(output);
    writeln(output);
    { Now we need to get the first token, which should be program. }
    ch := ' ' { *bug fix*  John Easton	SSRFC  U of M  1981-03-17 };
    NextToken;
    if token <> NameSy then
	Error(1)
    else if symbol <> Sprogram then
	Error(1)
    else begin
	ProcessUnit(true);
	{ Having returned, there ought to be a period here. }
	if not errorflag then begin
	    { We check all tokens that begin with a period because
	    what occurs after the closing period is nothing to do
	    with us. }
	    if (token <> PeriodSy) and (token <> SubRangeSy) then
		Error(4)
	    else begin
		adjustment := First;
		PrintLine
	    end
	end
    end;
    { Completed Phase One - now for the next. }
    if not errorflag then begin
	page(output);
	PrintHeading;
	writeln(output,
	    ' Head  Body  Notes ',
	    ' ':SigCharLimit,
	    '	Calls made to');
	for pretty := 1 to (SigCharLimit+37) do
	    write(output, '-');
	writeln(output);
	PrintTree(superroot);
	writeln(output)
    end
end { Referencer }.



Feel free to contact me, David Gesswein djg@pdp8online.com with any questions, comments on the web site, or if you have related equipment, documentation, software etc. you are willing to part with.  I am interested in anything PDP-8 related, computers, peripherals used with them, DEC or third party, or documentation. 

PDP-8 Home Page   PDP-8 Site Map   PDP-8 Site Search