.if !\n(xx .so tmac.p
.ND
.nr H1 1
.NH
Implementation
.PP
.Xp
is written in the
.UX
systems programming language C.
The structure of
.XP
is very similar to that of the Pascal translator
.PI .
In fact
.XP
was, until recently, a conditional compilation of
.PI .
.Xp
uses the same parser and scanner as
.PI ;
these are described in [1].
.PP
The major changes to the compiler in writing
.XP
were the rewriting of the
semantic routines of the compiler and the addition of three
``clusters''
of routines;
one for profiling,
one for the production of the restructured program listing,
and one for the processing of comments.
These ``clusters''
are written with local data bases, are largely
independent of the other parts of the program, and consist
of a large number of small, related routines.
By structuring the program in this way, functional
independence is obtained.
Clusters are organized separately
from the rest of the program, and accessed through a high-level and abstract
interface.
The concrete implementation of a cluster is not fixed by these
external specifications, and could be changed without the need to
rewrite any other
part of the program provided that the routines then implemented
a suitable interpretation of the same abstraction.
This organization provides a powerful and convenient decomposition
of the profiling process.
.PP
The print cluster routines are kept in the file
.I pp.c.
Their basic function is to provide
an interface to a high-level
printing automaton, with primitives such as 
``print keyword''
and
``print number.''
Information about the macro- and micro-structure of the output is passed to
the routines implicitly by calling the appropriate entry points.
Thus, after a statement is processed, the routines are informed that an
``item'' is finished so that an intelligent decision can be made about
where to place the statement.
Similarly, when processing a list of items such as a constant type
declaration, the print cluster is given information as to where
the ``left bracket,'' i.e. `(' occurs, where each ``separator,'' i.e. `,'
occurs, and, when the final `)' occurs, this is noted as a ``right bracket.''
With this information, an implementation of the printing cluster can be
provided which sensibly divides long structured constructs even though it has
no knowledge of the high-level language structure of its input.
.PP
The profile cluster, which is kept in the file
.I pmon.c,
has a number of related functions.
Initially, it deals with the problem of extracting the count information
from the profile data file.
If the execution of the interpreter terminated abnormally, then the data must
be extracted from the
.I core
process image file.
There is an image of the profile data file in the
.I core
image.
To extract the data from
.I core,
.I pxp
need only determine the offset of this image in the file.
.PP
After extracting the data from the file, the profile cluster provides
operations to return successive counters from the count buffer.
In addition it
builds the data structure for storing the information used to print
the optional table summarizing
.B procedure
and
.B function
invocation counts.
Other functions provided include
saving and restoring counts before
and after structured statements,
determining when embedded
.B goto
statements will have caused extra counts to be generated,
generating pseudo-counters for the
.B else
parts of
.B if
statements,
and controlling when the counts are to be printed in the listing
by keeping track of whether each count has been printed.
All manipulation of counts and counters is handled by these routines,
and focuses on a single counter structure called
.PX
which contains the current count.
.PP
In section 2.1 the profile data format and
the process of getting the data from the profile
data file or the
.PX
process
.I core
image are discussed.
Section 2.2 gives the data and control structures of the profiling
and printing clusters, and section 2.3 describes the tree walk
at the function level, and at illustrative parts of the statement
and expression levels.
The implementation description concludes in section 2.4 with
a note on the handling of multi-file programs.
.NH 2
External data structures
.PP
The following sections deal with the recovery of the profile data.
The simplest case, that of normal termination, is dealt with in section 2.1.1.
The remaining sections deal with the handling of
.I core
process image files.
These are likely to be of interest to
system programmers only.
.NH 3
Format of the profile data file
.PP
The
.I pmon.out
data file,
is normally written by the Pascal system at the end of profiling
execution.
The record structure of the header at the beginning of the file is as
follows:
.LS
.ta 8n 16n 40n
\*bstruct\fP {
	\*bint\fP	magic;	/* 0426 */
	\*bint\fP	magic2;	/* 0 */
	\*blong\fP	proftime;	/* Time profiled */
	\*bint\fP	cnt;	/* # statement cntrs */
	\*bint\fP	pfcnt;	/* # procedures+functions + 1 */
} header;
.LE
Each counter is a long integer so the rest of the file is essentially
an array:
.LS
\*blong\fP counts[header\*b.\fPcnt];
.LE
.PP
As an error check,
.XP
first insures that the first word is 0426
and the second is a 0.
If this is true, and the size of the remainder of the file is the size of
the
.I counts
array as implied by the number
.I cnt
in the header, then
profiling can proceed.
If not, then the specified profile data file has a bad format, and
.XP
reports this fact.
.NH 3
Core data files
.PP
.Xp
has the capability to
extract the profile information from the
.I core
data file dumped by an
abnormal
.PX
termination.
This is greatly simplified by the fact that there is, in the core image,
an exact replica of the
.I pmon.out
file which would have been written at normal termination.
In order to extract the profile data, it is only necessary
to locate this buffer.
.PP
The first 1024 bytes of the core image file is a copy of
the system's per-user data for the process, including the registers
as they were at the time of the fault.
The remainder of the file represents the actual contents of the user's
core area when the core image was written.
If the text segment of the process was write-protected and shared,
it will not be present; otherwise the entire address space will have
been dumped.
.NH 3
Shared text programs
.PP
It is possible in
.UX
for the text image of a program to be shared by all of the persons
who invoke it.
The instructions will be write protected, and each process which
executes this
.I pure
text will have only a data area, sharing one
common text.
Thus, when such a process is swapped out, only its data area need
be swapped.
The text space can always be abandoned and read in from external
storage when needed, as it cannot have changed.
.PP
When a core image is dumped,
the text space is not dumped if the process was running as shared text.
This is the situation with the installed version of
.PX .
The disadvantage of shared text is that breakpoint debugging
is impossible in this case.
For this reason
.X
is most often debugged
as non-shared text.
It is desirable, therefore, to have
.XP
be able
to extract data correctly in either case.
.PP
The important fact here, which allows a number of simplifications
to the extraction method, is that
the non-stack data space of the process
is dumped immediately after the system area.
If the text space was not ``shared,'' then the first word dumped will
have been location 0 in the process address space, otherwise it will
have been the first data word, as the text of the process appears
first in the process space.
.NH 3
Extraction methods
.PP
The structure needed to navigate in the
.I core
image is the
.I pcx
information structure\u\s-2\*(dg\s0\d, which has the following format:
.FS
\*(dg
.I Px
was previously
.I pcx
hence the name of this structure
.FE
.LS
\*bstruct\fR {
	\*bchar\fR	*buf;
	\*bint\fR	cnt;
} pcx;
.LE
The
.I buf
pointer here points to the image of
.I pmon.out
in core, and is a memory address.
The
.I cnt
gives the size of this image,
and is not currently used by
.XP
as the size is determined from information in the image itself.
If the
.I buf
pointer is 0, i.e. a 
.I NIL
pointer, then
.PX
was not
gathering profile data in the execution which abnormally terminated.
.PP
To locate this structure, the runtime start up routine for the interpreter
was modified from the standard C start up to be as given in the following
figure.
.so pcrt0.s
This runtime start up is structured so that it is possible to determine,
from the first three words after the system area, whether
the process was executed as shared text, and the offset of the
.PX
structure in the file.
If the second word here is a 0, then this can only be a non-pure
core image; if it is a 1 then this can only be a pure core image.
If it is neither then this is not a
.PX
core image.
.PP
If the core image is pure, the first word dumped of the process
was not at location 0 in the process data space.
In this case, however, the runtime start up is set up so the
first word contains its address in memory.
Thus it is possible to can convert the third core image word, which is the index
into memory, into an index into the core image by subtracting
this offset from it.
In this way,
.XP
can find the
.I pcx
structure in the 
.I core
image.
.PP
As noted above, if the
.I buf
pointer is
NIL,
then
.PX
was not gathering profile data and an appropriate diagnostic will
be printed.
Otherwise, the
.I buf
pointer is offset by subtracting the first word
after the system area in the
.I core
image if the process was pure,
.XP
seeks to the
implied location and a routine common to the
.I core
and
.I pmon.out
profile data gathering routines 
.I pmread
is called.
The only notable difference is that after calling
.I pmread
when processing a
.I pmon.out
file,
.XP
checks that all the data on the file is
exhausted.
This is not done when reading a
.I core
data file.
.PP
If any of this seeking or reading terminates abnormally
a
.I "bad format"
diagnostic is given.
.NH 2
Internal data structures
.NH 3
External data copies
.PP
The following data items are kept internally after being read from
the
.I pmon.out
or 
.I core
data file.
They are available only by using the routines in the cluster in the file
.I pmon.c .
.PP
The integer 
.I zcnt
gives the total number of counters, the integer 
.I zpfcnt
the total number of procedures and functions.
The pointer
.I zbuf
is (effectively) a dynamic array of long integers
representing the profile counts.
In addition, the external variable
.I ptvec
contains the internal
representation of the time the profile was made.
.NH 3
Profile cluster data structures
.PP
There are two major profile cluster structures.
The first structure is the primitive unit for storing a piece
of count information and is given by:
.LS
\*bstruct\fP pxcnt {
	\*blong\fP	ntimes;
	\*bint\fP	counter;
	\*bint\fP	gos;
	\*bint\fP	printed;
};
.LE
Here
.I ntimes
is the actual count data.
The variable
.I counter
gives a unique identifying tag to this structure,
.I gos
is the number of gotos which had been encountered when this counter
was created, and
.I printed
is actually a Boolean value indicating
whether the counter is considered to have been printed for
the purposes of the profile.
.I Counter
may actually be an index into the array of
values read in from
.I pmon.out ,
or it may be a negative number indicating
that it is a counter which was generated by calculations, e.g. for
an
.B else
part of an
.B if
statement.
Other uses of this structure will be described below.
.PP
The other major structure which records information
for each
.B procedure
or 
.B function
for the summary table. Its format is:
.LS
\*bstruct\fP pftab {
	\*blong\fP	pfcnt;
	\*bint\fP	pfline;
	\*bchar\fP	*pfname;
	\*bint\fP	pflev;
} *zpf;
.LE
The field
.I pfcnt
gives the invocation count for each routine,
.I pfline
is the approximate source program line number for the routine,
.I pfname
points to the character string name of the routine, and
.I pflev
gives the nesting level of the routine so it is possible to print
the table with nesting indicating structure.
The variable
.I zpf
is actually a dynamic array holding these counters.
.NH 3
The profile cluster primitives
.PP
The following are the primitive operations of the profile cluster
kept in the source file
\fIpmon.c\fP.
They deal in particular with one special counter structure
.I px
which holds the current count information as processing progresses.
.IP
.RS
.HP "\*blong\fP nowcnt()"
.br
Returns a \*blong\fP integer which is the current count.
.RE
.IP
.RS
.HP nowcntr()
.br
Returns an integer uniquely identifying the current counter.
.IP "\*blong\fP cntof(pxc)"
.HP "\*bstruct\fP pxcnt *pxc;"
.br
Returns the \*blong\fP integer
.I count
associated with the structure addressed by
\fIpxc\fP.
.IP "setcnt(l)"
.HP "\*blong\fP l;"
.br
Makes the current counter have count
\fIl\fP.
A new, unique counter is generated for this purpose.
This is used, e.g., to assign counts to
.B else
parts of
.B if
statements.
.IP "savecnt(pxc)"
.HP "\*bstruct\fP pxcnt *pxc;"
.br
Saves the information associated with the current count
.I px
in the given structure.
.IP "rescnt(pxc)"
.HP "\*bstruct\fP pxcnt *pxc;"
.br
The inverse of
.I savecnt
setting the fields of
.I px
from the given structure.
.I Rescnt
also returns non-zero exactly when there were embedded gotos.
.IP "getcnt()"
.br
Takes the next counter from the
.I pmon.out
or
.I core
data and places it and associated information into
\fIpx\fP.
The fact that there are less counters in the file than
required by the supplied program is diagnosed as a
``Counter mismatch'' here.
.IP "cnttab(s, no)"
.HP "\*bchar\fP *s;"
.HP "\*bint\fP no;"
.br
Records the current count as being that of a
.B procedure
or
.B function
with number
\fIno\fP.
The number of
procedures and functions are also counted
here so they can be checked for consistency at the end of processing.
.RE
.NH 3
Profile-printing cluster interface
.PP
The following routines, which are part of the
.I pmon.c
cluster, interface to the printing cluster, and are used
to control the annotation of the program listing with
profile data.
.IP
.RS
.HP "unprint()"
.br
Causes the current counter to become logically ``unprinted''
so that it will be printed again.
Used, e.g., after restoring a count saved before a loop
to force it to be printed again.
.RE
.IP
.RS
.HP "baroff()"
.br
Turns the printing of the bar `|' in the profile off.
.IP "baron()"
.br
The inverse of
\fIbaroff\fP.
.IP "shudpcnt()"
.br
Returns an integer indicating the type of annotation of the
current line which is desired.
A return value of 0 indicates that only the bar `|' is to
be printed, a value of 1 that the current count and the
bar `|' are to be printed, and a value of -1 indicates that
only white space is to be printed.
A flag set by the routines
.I baroff
and 
.I baron
is inspected here.
If the bar `|' to be printed is printed, it is noted that the
current counter was printed so the count will not be printed again.
.RE
.NH 3
The printing cluster
.PP
The file
.I pp.c
contains the cluster which performs the nitty-gritty business
of preparing the output to be printed.
It was the author's intention, when he wrote the program, to
pass sufficient information to this cluster to allow it to do
the job of breaking up long statements and of placing multiple
statements on each line.
This has not been implemented yet.
The description of how the routines of this cluster
work together to produce the profile is deferred to a later, higher level
view.
.PP
This cluster currently has a very minimal set of data structures.
These structures would expand greatly if the statement folding and 
multiple statement placement were implemented.
As it stands
the input is interpreted only in a very simpleminded way.
For this purpose it is only noted whether anything is being printed
at all, indicated by the flag
\fInopflg\fP,
which if non-zero indicates that no printing is to occur, and some information
about the way in which the listing is to be indented.
This is contained in an array giving the indenting in spaces for
each of three levels, i.e.:
.LS
\*b#define\fP DECL    0
\*b#define\fP STAT    1
\*b#define\fP PRFN    2

\*bint\fP pplev[3];
.LE
.PP
These levels have rough interpretations as follows.
The white space generated at the very left by the indenting
of nested
.B procedure
and
.B function
declarations is assigned to
\fIpplev[PRFN]\fP,
the white space generated in declaration parts by structured declarations
such as records to DECL, and the white space generated by indenting
of structured statements to STAT.
.PP
The white space is dispersed on the line, separating the left margin,
labels, the profile information, and the program text graphically
given below.
.DS C
line#  PRFN  label:  STAT  999.---|  DECL  text
.DE
Thus by indenting in the DECL part deeper text nesting can be shown
without the bar wandering to the right, and when indenting in the
STAT part the bar is moved over.
Similarly, the option to indent nested procedures and functions
is trivially handled by indenting in PRFN.
.NH 3
Printing cluster primitives
.PP
There are two kinds of routines in the printing cluster.
One kind deals with printing the various kinds of tokens in
Pascal programs, e.g. keywords, strings, etc.
The other set of routines deals with the specifics of printing
the profile, i.e. turning printing on and off, indenting, and
the nasty details like the placement of labels.
.IP
.RS
.HP "ppkw(s)"
.HP "\*bchar\fP *s;"
.br
Prints the character string representing the keyword.
Underlining and overstriking of keywords is also handled.
This routine facilitate the suppression of blank lines at the beginning
of partial profiles,
setting a flag the first time it prints a keyword.
Since any solid output always begins with a keyword,
.I ppnl
refuses to print a newline until a keyword is printed.
.IP "ppid(s)"
.HP "\*bchar\fP *s;"
.br
Prints the identifier
.I s
or `{identifier}' if
.I s
is null because of a syntax error.
.IP "ppop(s)"
.HP "\*bchar\fP *s;"
.br
Prints operators.
.IP "ppnumb(s)"
.HP "\*bchar\fP *s;"
.br
Prints numbers.
.IP "ppstr(s)"
.HP "\*bchar\fP *s;"
.br
Prints strings, dealing with doubling of the string metacharacter.
.IP "pplab(s)"
.HP "\*bchar\fP *s;"
.br
Prints a label.
.IP "ppbra(s)"
.HP "ppket(s)"
.HP "ppsep(s)"
.HP "\*bchar\fP *s;"
.br
These routines
are used to indicate the recursive structure of the input
at the microscopic level.
Thus, when printing out the argument list to a
.B procedure
or
.B function,
the opening `(' would be printed with
\fIppbra\fP,
each separating `,' with
\fIppsep\fP,
and the closing `)' with
\fIppket\fP.
This would, conceivably, allow this cluster to break the output sensibly
to conform with the nature of the output medium without having
to deal with the passed-through data at a more difficult macroscopic
level.
.IP "ppspac()"
.HP "pptab()"
.HP "ppitem()"
.HP "ppnl()"
.br 
These routines are all used to put separation into the output stream.
In the initial design, the difference between the routines
.I ppitem
and
.I ppnl
was that
.I ppnl
would always force a new-line, while
.I ppitem
separated major units such as statements, but didn't require the
following statement to start on a new line, leaving the possibility
that it be placed on the same line.
In the current implementation, however, both
.I ppitem
and
.I ppnl
force the output to be on a new line.
Note that forcing the output to a new line does not force the
leading white space to be printed!
.RE
.PP
The utility routines which don't deal directly with
the printing of the actual program text follow.
.IP
.RS
.HP "setprint()"
.br
Is called at the beginning of each
.B procedure
or
.B function
body and examines the global environment and option flags
to decide whether that routine should be printed in the profile.
.RE
.IP
.RS
.HP "printon()"
.br
Turns the printing on.
If profiling is notoccuring, a summary table is desired
then this actually turns the printing off!
If neither profiling or a summary table is being producd,
then this call has no effect.
.IP "printoff()"
.br
Turns the printing of the profile off.
.IP "indent()"
.br
If printing, ask
.I linopr
to print the line number.
If producing a profile (rather than just a pretty-print)
indent over
.I
pplev[PRFN] + pplev[STAT]
.R
spaces, and then, depending on the result of a call to
.I shudpcnt
print either a count, some dashes and a bar, just a bar,
or spaces.
Finally, indent
.I pplev[DECL]
more spaces and return.
.IP "dashes(c)"
.HP "\*bchar\fP c;"
.br
Spaces over an amount determined by the indenting level using
the character given.
.IP "indent1(in)"
.HP "\*bint\fP in;"
.br
Actually advance the indent by
.I in
spaces.
If pretty-printing
it is safe to optimize the output by producing tabs,
otherwise spaces must be used.
.IP "linopr()"
.br
Prints the line number if required.
.IP "indentlab()"
.br
Indents for a label print,
.I pplev[PRFN]
spaces using
\fIindent1\fP.
.IP "ppgoin(lv)"
.HP "ppgoout(lv)"
.br
These routines each take one of PRFN, STAT or DECL and increase
or decrease the indentation at that level respectively.
.RE
.NH 2
Producing the profile
.PP
It should be obvious from the discussion above, that little
difference can be discerned at the top level between producing
a profile and prettyprinting.
When prettyprinting, a large
number of routine calls return without doing any real work.
.PP
The production of the profile is discussed at each of four levels.
The first level is the main routine and option processing.
This is discussed only to give an outline of the work done here.
.PP
The second level is the level of procedures and functions and
involves such considerations as the 
.B z
command line option with list of
.B procedure
and
.B function
names, forward declarations, and the recording, saving and
restoring of count information.
.PP
The third level is that of individual statements, and the final
level is that of expressions.  These levels are illustrated
with actual code to make the discussion more concrete.
.NH 3
Main routine and option processing
.PP
The main routine sets up the default options such as setting
the indenting to 4 spaces and turning on nested
.B procedure
and
.B function
indents.
It then examines the arguments and,
importantly, sets the variable
.I profile
if profiling and
.I table
if producing a table of procedure and function counts.
.PP
If a list of
.B procedure
and
.B function
names is given it is saved as referenced by the variables
.I pflist
and
.I pflstc
for examination by the routine
.I inpflist
which is called by routines at
.B procedure
and
.B function
entry and exit.
.PP
After processing all the options, the main routine makes a call
to the
.I pmon.c
cluster to get the profile data if appropriate.
It sets up the input and does some special processing for processing
of
.I include
files as described further in section 2.4 below.
It finally calls the parser which completes the processing of the profile.
.NH 3
Procedure and function level processing
.PP
The
.B procedure
and 
.B function
level processing routines are contained in the file
\fIfdec.c\fP.
The routines here and their functions are:
.IP
.RS
.HP "funchdr(r)"
.HP "\*bint\fP *r;"
.br
Called with a tree node representing the initial declaration
of a function or procedure, e.g.:
.LS
\*bfunction\fP sine(a: angle): \*breal\fP;
.LE
this routine first determines if the routine is in the list
of procedures and functions given on the command line.
If it is, then the
.B z
option value is saved on a stack,
and then turned on.
It then gets the counter associated with the procedure header
and calls a routine in the print cluster to determine whether this
routine should be printed or not.
.IP
It then saves the count information for this routine at its level
in a global array of
.I pxcnt
structures called
\fIpfcnts\fP.
This counter will be restored later when the body of the routine
is encountered.
The printing of the header is also processed here, but this is
similar to other processing to be described later.
This printed header is indented if nested definitions are being indented,
to be unindented after completing the printing of the header.
.RE
.IP
.RS
.HP "funcfwd(fp)"
.HP "\*bchar\fP *fp;"
.br
This routine prints the keyword
.B forward
indented an appropriate amount.
It returns its argument.
.IP "funcbody(fp)"
.HP "\*bchar\fP *fp;"
.br
This routine, called when the actual, resolving declaration of
a
.B procedure
or
.B function
is encountered, indents if the indent nested procedures option
is set, and increments the structured statement level, returning
its argument.
.IP "funcend(fp, blk)"
.HP "\*bchar\fP *fp;"
.HP "\*bint\fP *blk;"
.br
This routine sets up for all of the actual work in printing the body
of procedures and functions.
It restores the count saved by
.I funchdr
from the
.I pfcnts
array,
.I unprints
the count to force it to come out again if there
were any nested procedures or functions, (if the last block number in the
variable
.I lastbn
is not the current block number)
and then prints the body of the procedure or function.
.IP
To print the body, it
\fIindents\fP,
prints the keyword
.B begin,
and if there were nested sections prints
the name of this routine in a comment.
It then goes in a level in DECL (without shifting the bar over!)
and prints the statement list given by the parameter
\fIblk\fP.
.RE
.PP
This is an appropriate place to note the following important fact:
When a routine is called to put out an item at the statement level,
the
.I "output cursor"
is usually at the end of the previous line, and if the routine
wants to print on a clean line, then it calls
.I ppnl
before it begins.
If it is willing to print on the same line then it can call
\fIppitem\fP.
It also turns out that this structure is critical for the processing
of comments which is described in section 3.
A delay in printing
the new line character allows the comment processing to function
correctly.
.PP
Thus, after the routine
.I statlist
processes the parameter
.I blk
the rotuines
\fIppnl\fP,
an
\fIindent\fP,
are called and the keyword
.B end
is printed followed by the routine name in a comment and a final
.I ;
or
.I \&.
character.
Finally unwind from any indenting that may have been done due
to the indent nested sections option occurs, and the
.B z
option stack is popped if this routine was in the command line
.B procedure
and
.B function
list.
.NH 3
Statement processing
.PP
The statement level processing is done by the routines in the file
.I stat.c
and for case statements by the code in the file
\fIcase.c\fP.
As noted above, the cursor for each statement is generally left
on the previous line so that a statement will ask for the cursor
to be advanced to the next line if so desired.
This is also necessary to make the placement of
.B begin
and
.B end
pairs work as desired.
The basic loop for processing a group of statements is as follows:
.LS
statlist(sl)
        \*bregister\fP \*bint\fP *sl;
{
        \*bif\fP (sl != NIL)
                \*bfor\fP (;;) {
                        statement(sl[1]);
                        sl = sl[2];
                        \*bif\fP (sl == NIL)
                                \*bbreak\fP;
                        ppsep(";");
                }
        \*belse\fP
                statement(NIL);
}
.LE
This is quite simple.
A pointer to a tree structure is received, treated as an array
of integers.
The first word of each such node is a magic number giving the
kind of node, the second word points to a statement and the third
word links to the next such node, or NIL if this is the last node
in the statement list.  This is more fully described in [1],
the nodes here being of type T_LISTPP.
.PP
To illustrate the processing for statements in general,
a subset of the code from the
.I stat.c
file comprising that for
.B if
statements, assignment statements and
.B "begin-end"
blocks within
.B if
statements follows.
This is illustrative of the work here in general.
.so stat.c
.PP
.I Statement
receives as argument a pointer to a tree node.
For the purposes of this discussion, assume that this node is either of type
T_IF, T_IFEL, or T_ASGN.
The node type T_IFEL was added to the parser because
of the problematic case of empty
.B else
clauses.
When these empty clauses are present, it is impossible
to present the data for both
.B if
statements without
.B else
and with
.B else
parts in the one T_IF structure.
An
.B if
statement without an
.B else
part looks the same as an
.B if
statement with an empty
.B else
part in this case.
This is a problem because
.I pxp
does not realize that the discarding of such empty
.B else
parts can affect the matching of outer
.B else
clauses with
.B if
parts and alter the meaning of the program!
.PP
Now, if the argument to
.I statement
is a NIL pointer, \fInull\fR is printed,
a call on a built-in procedure that does nothing.
The fact that a
.I ppitem
rather than a
.I ppnl
is done here indicates that it does not matter if this is on the same line
with something else.
If the argument pointer is not NIL a switch is done based
on which type of statement is involved.
.PP
For assignments, as for the \fInull\fR above,
the statement can appear on the same line with something else.
Thus
.I ppitem
is called.
To print an assignment first an
.I lvalue
(essentially a variable)
and then an
.I rvalue
(an expression)
must be printed, separated by a `:='.
This is as simple as knowing which fields to pass to
procedures
.I lvalue
and
.I rvalue .
.PP
Note that
.I rvalue
here takes two arguments.
The second argument is a flag indicating whether the
expression is to be surrounded by parentheses if
it is not-atomic.
Since no ambiguity can possibly result, no parenthesis are required here.
.NH 3
If\-then\-else's
.PP
It is required that an
.I if
statement appear on a separate line by calling
.I ppnl .
The routine
.I ifop
begins by printing:
.LS
\*bif\fP <expression> \*bthen\fP_
.LE
where the `_' will be used to represent the invisible output cursor.
The expression here also need not be parenthesized.
.I Ifop
then saves the count before the statement by calling
\fIsavecnt\fP,
and gets the count for the
.I then
part of the statement by calling
\fIgetcnt\fP.
The
.B then
part can now be handled.
.PP
If the
.B then
part is a
.B begin\-end
block, it can be started on this line.
Thus, in this case, the routine
.I ppstbl1
can be called with the
.I then
part as argument.
It is also passed STAT, indicating that the indent it will do
is to be reflected in the position of the bars on the left.
(For
.B with
statements among others, the bars do not move over
the text is indented.)
Now
.I ppstbl1
prints out the keyword 
.I begin
and does the indent discussed above, but does not put out a newline.
That is up to the routine
.I statement
which will be called by
\fIstatlist\fP.
The
.B end
is not put out yet because the counter for the
.B else
part belongs on the line with
it if there is an
.B else
part and that count has not been set up yet.
.PP
If the statement in the
.B then
part is not a
block then the processing here is much simpler;
it is only necessary to indent a level in the STAT part as a block,
call 
.I statement
and then unindent the level.  Thus a typical
position after this part is completed for a
.B then
part which is a block would be:
.LS
\*bif\fP <expression> \*bthen\fP \*bbegin\fP
        stat1;
        stat2_
.LE
with the cursor convention as before.
.PP
If this
.B if
statement does not have an
.B else
part the rest of the processing is simple.
The count saved before the statement is restored, doing a
.I getcnt
if there were one or more
.B goto
statements in the body of the
statement to get the count after the statement.
If the
.B then
part was a block,
.I ppstbl2
is called to put the keyword
.B end
indented on a new line with the restored count.
This finishes the processing in this case.
.PP
If the statement has an
.B else
part then the count for this part must first be calculated.
This is done by taking the
.I cntof
the saved structure from before the
.B if
statement and subtracting the current count which is that of the
.B then
part.
This becomes the new counter generated for the
.B else
part.
Processing of the
.B then
part is then finished either by printing a
.B end
with
.I ppstbl2
as above, followed by a space,
or by forcing a new line in the output and calling
.I indent.
The keyword
.B else
is printed in the output and followed with a space.
.I Unprint
is called so that the count for an
.B else
part prints not only with the
.B else
but also on the statement in the
.B else
part.
This significantly improves the readability of bushy if-then-elses.
.PP
The special case of an empty statement in the
.B else
part is caught and a null statement is put out in this case.
Otherwise a check is made to see if the
.B else
part is a block, and if so, handled in a manner identical to the processing
for the 
.B then
part.
Nested
.B if
statements are then caught, and are recursive call is made without
doing any indentation.
This accomplishes the goal of not ``wandering across the page''
in if-then-else statements.
.PP
Noting that normal statements are treated as before,
the
.B else
part has been successfully completed except for some cleanup.
This cleanup involves restoration of the count and printing of the
keyword
.B end.
Note that the pre
.B if
statement count must be unprinted whenever an
.B else
occurs.
This is because of the way
.B else
parts are printed, lined up with the
.B if
parts and obstructing their counts.
.NH 3
Expression processing
.PP
The final part of the profiling process to be discussed
is the printing of expressions.  This is quite simple,
actually, with the only interesting code being that which
determines what parenthesization to do.  This happens as follows.
.PP
Whenever a binary operator is encountered each of its operands
is processed in turn.
The following cases are from the
.B switch
in the routine
.I rvalue
and indicates the code for processing binary operators.
.LS
\*bcase\fP T_AND:
\*bcase\fP T_OR:
\*bcase\fP T_MULT:
\*bcase\fP T_ADD:
\*bcase\fP T_SUB:
\*bcase\fP T_DIVD:
\*bcase\fP T_MOD:
\*bcase\fP T_DIV:
\*bcase\fP T_EQ:
\*bcase\fP T_NE:
\*bcase\fP T_GE:
\*bcase\fP T_LE:
\*bcase\fP T_GT:
\*bcase\fP T_LT:
\*bcase\fP T_IN:
        al = r[2];
        rvalue(al, prec(al) < prec(r) || opt('f'));
        ppspac();
        \*bif\fP (alph(opname[0]))
                ppkw(opname);
        \*belse\fP
                ppop(opname);
        ppspac();
        al = r[3];
        rvalue(al, prec(al) <= prec(r) || opt('f'));
        \*bbreak\fP;
.LE
.PP
The routine
.I prec
returns an integer representing the precedence of the
operator given.
It is defined by:
.LS
prec(r)
        \*bregister\fP \*bint\fP *r;
{
        \*bif\fP (r == NIL)
                \*breturn\fP;
        \*bswitch\fP (r[0]) {
                \*bcase\fP T_NOT:
                        \*breturn\fP (3);
                \*bcase\fP T_MULT:
                \*bcase\fP T_DIVD:
                \*bcase\fP T_DIV:
                \*bcase\fP T_MOD:
                \*bcase\fP T_AND:
                        \*breturn\fP (2);
                \*bcase\fP T_ADD:
                \*bcase\fP T_SUB:
                \*bcase\fP T_OR:
                        \*breturn\fP (1);
                \*bdefault\fP:
                        \*breturn\fP (0);
        }
}
.LE
Thus, with a binary operator,
parentheses are needed around the left operand if it is non-atomic
and its operator has lower
.I prec
than the current operator.
Parentheses are needed around the right operand if it is non-atomic
and its operator has fewer or the same
.I prec
as the current operator.
This equality condition reflects Pascal associativity.
.NH 2
Multiple file processing
.PP
.I Pxp
has the capability of handling
.I include
files.
Just as programs to
.PI
may be split into many pieces, they may be so split and then
processed by
\fIpxp\fP.
In addition a capability for pretty printing of one piece of
a program has been included in
\fIpxp\fP.
If
.I pxp
is pretty printing and the file being pretty printed
has a name ending in 
.I \&.i
then
.I pxp
will place the line
.LS
\*bprogram\fP x(output);
.LE
into the source stream before the first line of the file
and the line
.LS
\*bbegin\fP \*bend\fP.
.LE
after the last line.
In this way, if the contents of the
.I include
corresponds to a global declaration part and/or
a group of procedures and functions, the pretty print
can proceed without modifications to the source text.
This is the only case in which the pretty printer will
take anything other than a complete Pascal program.
.I Pxp
suppresses printing back out the inserted and appended lines
in this case.
.PP
.Xp
does not normally process
.I include
directives but rather prints them back out as includes.
An option to eliminate the includes is also available.
.bp
