2. Basic UNIX Pascal

The following sections explain the basics of using Berkeley Pascal. In examples here we use the text editor ex (6). Users of the text editor ed should have little trouble following these examples, as ex is similar to ed . We use ex because it allows us to make clearer examples. *

* Users with CRT terminals should find the editor vi more pleasant to use; we do not show its use here because its display oriented nature makes it difficult to illustrate.

The new UNIX user will find it helpful to read one of the text editor documents described in section 1.4 before continuing with this section.

2.1. A first program

To prepare a program for Berkeley Pascal we first need to have an account on UNIX and to `login' to the system on this account. These procedures are described in the documents Communicating with UNIX and UNIX for Beginners.

Once we are logged in we need to choose a name for our program; let us call it `first' as this is the first example. We must also choose a name for the file in which the program will be stored. The Berkeley Pascal system requires that programs reside in files which have names ending with the sequence `.p' so we will call our file `first.p'.

A sample editing session to create this file would begin:


% ex first.p
"first.p" No such file or directory
:

We didn't expect the file to exist, so the error diagnostic doesn't bother us. The editor now knows the name of the file we are creating. The `:' prompt indicates that it is ready for command input. We can add the text for our program using the `append' command as follows.


:append

program first(output)
begin
	writeln('Hello, world!')
end.
.

:

The line containing the single `.' character here indicated the end of the appended text. The `:' prompt indicates that ex is ready for another command. As the editor operates in a temporary work space we must now store the contents of this work space in the file `first.p' so we can use the Pascal translator and executor pix on it.


:write
"first.p" 4 lines, 59 characters
:quit
% 

We wrote out the file from the edit buffer here with the `write' command, and ex indicated the number of lines and characters written. We then quit the editor, and now have a prompt from the shell. *

* Our examples here assume you are using csh.

We are ready to try to translate and execute our program.


% pix first.p
     2  begin
e --------- Inserted ';'
Execution begins...
Hello, world!
Execution terminated
1 statement executed in 0.04 seconds cpu time
%

The translator first printed a syntax error diagnostic. The number 2 here indicates that the rest of the line is an image of the second line of our program. The translator is saying that it expected to find a `;' before the keyword begin on this line. If we look at the Pascal syntax charts in the Jensen-Wirth User Manual , or at some of the sample programs therein, we will see that we have omitted the terminating `;' of the program statement on the first line of our program.

One other thing to notice about the error diagnostic is the letter `e' at the beginning. It stands for `error', indicating that our input was not legal Pascal. The fact that it is an `e' rather than an `E' indicates that the translator managed to recover from this error well enough that generation of code and execution could take place. Execution is possible whenever no fatal `E' errors occur during translation. The other classes of diagnostics are `w' warnings, which do not necessarily indicate errors in the program, but point out inconsistencies which are likely to be due to program bugs, and `s' standard-Pascal violations. *

* The standard Pascal warnings occur only when the associated s translator option is enabled. The s option is discussed in sections 5.1 and A.6 below. Warning diagnostics are discussed at the end of section 3.2, the associated w option is described in section 5.2.

After completing the translation of the program to interpretive code, the Pascal system indicates that execution of the translated program began. The output from the execution of the program then appeared. At program termination, the Pascal runtime system indicated the number of statements executed, and the amount of cpu time used, with the resolution of the latter being 1/60'th of a second.

Let us now fix the error in the program and translate it to a permanent object code file obj using pi . The program pi translates Pascal programs but stores the object code instead of executing it *

* This script indicates some other useful approaches to debugging Pascal programs. As in ed we can shorten commands in ex to an initial prefix of the command name as we did with the substitute command here. We have also used the `!' shell escape command here to execute other commands with a shell without leaving the editor.


% ex first.p
"first.p" 4 lines, 59 characters
:1 print
program first(output)
:s/$/;
program first(output);
:write
"first.p" 4 lines, 60 characters
:!pi %
!pi first.p
!
:quit
%

The first command issued from ex with the `!' involved the use of the `%' character which stands in this command for the file we are editing. Ex made this substitution, and then echoed back the expanded line before executing the command. When the command finished, the editor echoed the character `!' so that we would know it was done.

If we now use the UNIX ls list files command we can see what files we have:


% ls
first.p
obj
%

The file `obj' here contains the Pascal interpreter code. We can execute this by typing:


% px obj
Hello, world!
1 statement executed in 0.02 seconds cpu time
%

Alternatively, the command:


% obj

will have the same effect. Some examples of different ways to execute the program follow.


% px
Hello, world!
1 statement executed in 0.02 seconds cpu time
% pi -p first.p
% px obj
Hello, world!
% pix -p first.p
Hello, world!
%

Note that px will assume that `obj' is the file we wish to execute if we don't tell it otherwise. The last two translations use the -p no-post-mortem option to eliminate execution statistics and `Execution begins' and `Execution terminated' messages. See section 5.2 for more details. If we now look at the files in our directory we will see:


% ls
first.p
obj
%

We can give our object program a name other than `obj' by using the move command mv (1). Thus to name our program `hello':


% mv obj hello
% hello
Hello, world!
% ls
first.p
hello
%

Finally we can get rid of the Pascal object code by using the rm (1) remove file command, e.g.:


% rm hello
% ls
first.p
%

For small programs which are being developed pix tends to be more convenient to use than pi and px . Except for absence of the obj file after a pix run, a pix command is equivalent to a pi command followed by a px command. For larger programs, where a number of runs testing different parts of the program are to be made, pi is useful as this obj file can be executed any desired number of times.

2.2. A larger program

Suppose that we have used the editor to put a larger program in the file `bigger.p'. We can list this program with line numbers by using the program num i.e.:


% num bigger.p
(*
 * Graphic representation of a function
 *    f(x) = exp(-x) * sin(2 * pi * x)
 *)
program graph1(output);
const
	d = 0.0625;   (* 1/16, 16 lines for interval [x, x+1] *)
	s = 32;       (* 32 character width for interval [x, x+1]
	h = 34;       (* Character position of x-axis *)
	c = 6.28138;  (* 2 * pi *)
	lim = 32;
var
	x, y: real;
	i, n: integer;
begin
	for i := 0 to lim begin
		x := d / i;
		y := exp(-x9 * sin(i * x);
		n := Round(s * y) + h;
		repeat
			write(' ');
			n := n - 1
		writeln('*')
end.
%

This program is similar to program 4.9 on page 30 of the Jensen-Wirth User Manual . A number of problems have been introduced into this example for pedagogical reasons.

If we attempt to translate and execute the program using pix we get the following response:


% pix bigger.p
   9          h = 34;       (* Character position of x-axis *)
w ------------------------------ (* in a (* ... *) comment
  16          for i := 0 to lim begin
e --------------------------------- Inserted keyword do
  18                  y := exp(-x9 * sin(i * x);
E --------------------------------- Undefined variable
e ------------------------------------------------ Inserted ')'
  19                  n := Round(s * y) + h;
E ---------------------------- Undefined function
E ------------------------------------------- Undefined variable
  23                  writeln('*')
e ----------------------- Inserted ';'
  24  end.
E ------- Expected keyword until
e ---------- Inserted keyword end matching begin on line 15
In program graph1:
  w - constant c is never used
  E - x9 undefined on line 18
  E - Round undefined on line 19
  E - h undefined on line 19
Execution suppressed due to compilation errors
%

Since there were fatal `E' errors in our program, no code was generated and execution was necessarily suppressed. One thing which would be useful at this point is a listing of the program with the error messages. We can get this by using the command:


% pi -l bigger.p

There is no point in using pix here, since we know there are fatal errors in the program. This command will produce the output at our terminal. If we are at a terminal which does not produce a hard copy we may wish to print this listing off-line on a line printer. *

* At Berkeley, the line printer for the Cory Hall system is in Room 199B. The line printers for the Computer Center systems are in the basement of Evans Hall.

We can do this with the command:


% pi -l bigger.p | lpr

In the next few sections we will illustrate various aspects of the Berkeley Pascal system by correcting this program.

2.3. Correcting the first errors

Most of the errors which occurred in this program were syntactic errors, those in the format and structure of the program rather than its content. Syntax errors are flagged by printing the offending line, and then a line which flags the location at which an error was detected. The flag line also gives an explanation stating either a possible cause of the error, a simple action which can be taken to recover from the error so as to be able to continue the analysis, a symbol which was expected at the point of error, or an indication that the input was `malformed'. In the last case, the recovery may skip ahead in the input to a point where analysis of the program can continue.

In this example, the first error diagnostic indicates that the translator detected a comment within a comment. While this is not considered an error in `standard' Pascal, it usually corresponds to an error in the program which is being translated. In this case, we have accidentally omitted the trailing `*)' of the comment on line 8. We can begin an editor session to correct this problem by doing:


% ex bigger.p
"bigger.p" 24 lines, 512 characters
:8s/$/ *)
        s = 32;       (* 32 character width for interval [x, x+1] *)
:

The second diagnostic, given after line 16, indicates that the keyword do was expected before the keyword begin in the for statement. If we examine the statement syntax chart on page 118 of the Jensen-Wirth User Manual we will discover that do is a necessary part of the for statement. Similarly, we could have referred to section C.3 of the Jensen-Wirth User Manual to learn about the for statement and gotten the same information there. It is often useful to refer to these syntax charts and to the relevant sections of this book.

We can correct this problem by first scanning for the keyword for in the file and then substituting the keyword do to appear in front of the keyword begin there. Thus:


:/for
	for i := 0 to lim begin
:s/begin/do &
	for i := 0 to lim do begin
:

The next error in the program is easy to pinpoint. On line 18, we didn't hit the shift key and got a `9' instead of a `)'. The translator diagnosed that `x9' was an undefined variable and, later, that a `)' was missing in the statement. It should be stressed that pi is not suggesting that you should insert a `)' before the `;'. It is only indicating that making this change will help it to be able to continue analyzing the program so as to be able to diagnose further errors. You must then determine the true cause of the error and make the appropriate correction to the source text.

This error also illustrates the fact that one error in the input may lead to multiple error diagnostics. Pi attempts to give only one diagnostic for each error, but single errors in the input sometimes appear to be more than one error. It is also the case that pi may not detect an error when it occurs, but may detect it later in the input. This would have happened in this example if we had typed `x' instead of `x9'.

The translator next detected, on line 19, that the function Round and the variable h were undefined. It does not know about Round because Berkeley Pascal normally distinguishes between upper- and lower-case. On UNIX lower-case is preferred*, and all keywords and built-in procedure and function names are composed of lower-case letters, just as they are in the Jensen-Wirth Pascal Report . Thus we need to use the function round here. As far as h is concerned, we can see why it is undefined if we look back to line 9 and note that its definition was lost in the non-terminated comment. This diagnostic need not, therefore, concern us.

* One good reason for using lower-case is that it is easier to type.

The next error which occurred in the program caused the translator to insert a `;' before the statement calling writeln on line 23. If we examine the program around the point of error we will see that the actual error is that the keyword until and an associated expression have been omitted here. Note that the diagnostic from the translator does not indicate the actual error, and is somewhat misleading. The translator made the correction which seemed to be most plausible. As the omission of a `;' character is a common mistake, the translator chose to indicate this as a possible fix here. It later detected that the keyword until was missing, but not until it saw the keyword end on line 24. The combination of these diagnostics indicate to us the true problem.

The final syntactic error message indicates that the translator needed an end keyword to match the begin at line 15. Since the end at line 24 is supposed to match this begin , we can infer that another begin must have been mismatched, and have matched this end . Thus we see that we need an end to match the begin at line 16, and to appear before the final end . We can make these corrections:


:/x9/s//x)
                y := exp(-x) * sin(i * x);
:+s/Round/round
                n := round(s * y) + h;
:/write
                        write(' ');
:/
                writeln('*')
:insert
                until n = 0;
.
:$
end.
:insert
        end
.
:

At the end of each procedure or function and the end of the program the translator summarizes references to undefined variables and improper usages of variables. It also gives warnings about potential errors. In our program, the summary errors do not indicate any further problems but the warning that c is unused is somewhat suspicious. Examining the program we see that the constant was intended to be used in the expression which is an argument to sin , so we can correct this expression, and translate the program. We have now made a correction for each diagnosed error in our program.


:?i ?s//c /
		y := exp(-x) * sin(c * x);
:write
"bigger.p" 26 lines, 538 characters
:!pi %
!pi bigger.p
!
:quit
%

It should be noted that the translator suppresses warning diagnostics for a particular procedure , function or the main program when it finds severe syntax errors in that part of the source text. This is to prevent possibly confusing and incorrect warning diagnostics from being produced. Thus these warning diagnostics may not appear in a program with bad syntax errors until these errors are corrected.

We are now ready to execute our program for the first time. We will do so in the next section after giving a listing of the corrected program for reference purposes.


% number bigger.p
     1  (*
     2   * Graphic representation of a function
     3   *    f(x) = exp(-x) * sin(2 * pi * x)
     4   *)
     5  program graph1(output);
     6  const
     7          d = 0.0625;   (* 1/16, 16 lines for interval [x, x+1] *)
     8          s = 32;       (* 32 character width for interval [x, x+1] *)
     9          h = 34;       (* Character position of x-axis *)
    10          c = 6.28138;  (* 2 * pi *)
    11          lim = 32;
    12  var
    13          x, y: real;
    14          i, n: integer;
    15  begin
    16          for i := 0 to lim do begin
    17                  x := d / i;
    18                  y := exp(-x) * sin(c * x);
    19                  n := round(s * y) + h;
    20                  repeat
    21                          write(' ');
    22                          n := n - 1
    23                  until n = 0;
    24                  writeln('*')
    25          end
    26  end.
%

2.4. Executing the second example

We are now ready to execute the second example. The following output was produced by our first run.


% px
Execution begins...
Floating divide by zero

        Error at "graph1"+2 near line 17

Execution terminated abnormally
2 statements executed in 0.04 seconds cpu time
%

Here the interpreter is presenting us with a runtime error diagnostic. It detected a `division by zero' at line 17. Examining line 17, we see that we have written the statement `x := d / i' instead of `x := d * i'. We can correct this and rerun the program:


% ex bigger.p
"bigger.p" 26 lines, 538 characters
:17
        x := d / i
:s'/'*
        x := d * i
:write
"bigger.p" 26 lines, 538 characters
:!pix %
!pix bigger.p
Execution begins...
                                  *
                                              *
                                                      *
                                                           *
                                                           *
                                                        *
                                                  *
                                          *
                                  *
                           *
                      *
                   *
                   *
                     *
                         *
                             *
                                  *
                                      *
                                         *
                                           *
                                           *
                                          *
                                        *
                                     *
                                  *
                               *
                              *
                             *
                            *
                             *
                               *
                                *
                                  *
Execution terminated
2550 statements executed in 0.21 seconds cpu time
!
:q
%

This appears to be the output we wanted. We could now save the output in a file if we wished by using the shell to redirect the output:


% px > graph

We can use cat (1) to see the contents of the file graph. We can also make a listing of the graph on the line printer without putting it into a file, e.g.


% px | lpr
Execution begins...
Execution terminated
2550 statements executed in 0.31 seconds cpu time
%

Note here that the statistics lines came out on our terminal. The statistics line comes out on the diagnostic output (unit 2.) There are two ways to get rid of the statistics line. We can redirect the statistics message to the printer using the syntax `|&' to the shell rather than `|', i.e.:


% px |& lpr
%

or we can translate the program with the p option disabled on the command line as we did above. This will disable all post-mortem dumping including the statistics line, thus:


% pi -p bigger.p
% px | lpr
%

This option also disables the statement limit which normally guards against infinite looping. You should not use it until your program is debugged. Also if p is specified and an error occurs, you will not get run time diagnostic information to help you determine what the problem is.

2.5. Formatting the program listing

It is possible to use special lines within the source text of a program to format the program listing. An empty line (one with no characters on it) corresponds to a `space' macro in an assembler, leaving a completely blank line without a line number. A line containing only a control-l (form-feed) character will cause a page eject in the listing with the corresponding line number suppressed. This corresponds to an `eject' pseudo-instruction. See also section 5.2 for details on the n and i options of pi .

2.6. Execution profiling

An execution profile consists of a structured listing of (all or part of) a program with information about the number of times each statement in the program was executed for a particular run of the program. These profiles can be used for several purposes. In a program which was abnormally terminated due to excessive looping or recursion or by a program fault, the counts can facilitate location of the error. Zero counts mark portions of the program which were not executed; during the early debugging stages they should prompt new test data or a re-examination of the program logic. The profile is perhaps most valuable, however, in drawing attention to the (typically small) portions of the program that dominate execution time. This information can be used for source level optimization.

An example

A prime number is a number which is divisible only by itself and the number one. The program primes , written by Niklaus Wirth, determines the first few prime numbers. In translating the program we have specified the z option to pix . This option causes the translator to generate counters and count instructions sufficient in number to determine the number of times each statement in the program was executed. *

* The counts are completely accurate only in the absence of runtime errors and nonlocal goto statements. This is not generally a problem, however, as in structured programs nonlocal goto statements occur infrequently, and counts are incorrect after abnormal termination only when the upward look described below to get a count passes a suspended call point.

When execution of the program completes, either normally or abnormally, this count data is written to the file pmon.out in the current directory. *

* Pmon.out has a name similar to mon.out the monitor file produced by the profiling facility of the C compiler cc (1). See prof (1) for a discussion of the C compiler profiling facilities.

It is then possible to prepare an execution profile by giving pxp the name of the file associated with this data, as was done in the following example.


% pix -l -z primes.p
Berkeley Pascal PI -- Version 1.1 (January 4, 1979)

Sat Mar 31 11:50 1979  primes.p

     1  program primes(output);
     2  const n = 50; n1 = 7; (*n1 = sqrt(n)*)
     3  var i,k,x,inc,lim,square,l: integer;
     4      prim: boolean;
     5      p,v: array[1..n1] of integer;
     6  begin
     7     write(2:6, 3:6); l := 2;
     8     x := 1; inc := 4; lim := 1; square := 9;
     9     for i := 3 to n do
    10     begin (*find next prime*)
    11        repeat x := x + inc; inc := 6-inc;
    12           if square <= x then
    13              begin lim := lim+1;
    14                 v[lim] := square; square := sqr(p[lim+1])
    15              end ;
    16           k := 2; prim := true;
    17           while prim and (k v[k]
    21           end
    22        until prim;
    23        if i <= n1 then p[i] := x;
    24        write(x:6); l := l+1;
    25        if l = 10 then
    26           begin writeln; l := 0
    27           end
    28     end ;
    29     writeln;
    30  end .
Execution begins...
     2     3     5     7    11    13    17    19    23    29
    31    37    41    43    47    53    59    61    67    71
    73    79    83    89    97   101   103   107   109   113
   127   131   137   139   149   151   157   163   167   173
   179   181   191   193   197   199   211   223   227   229
Execution terminated
1404 statements executed in 0.16 seconds cpu time
%

Discussion

The header lines of the outputs of pix and pxp in this example indicate the version of the translator and execution profiler in use at the time this example was prepared. The time given with the file name (also on the header line) indicates the time of last modification of the program source file. This time serves to version stamp the input program. Pxp also indicates the time at which the profile data was gathered.


% pxp -z primes.p
Berkeley Pascal PXP -- Version 1.1 (November 6, 1978)

Sat Mar 31 11:50 1979  primes.p

Profiled Sat Mar 31 13:02 1979

     1        1. =>|program primes(output);
     2             |const
     2             |    n = 50;
     2             |    n1 = 7; (*n1 = sqrt(n)*)
     3             |var
     3             |    i, k, x, inc, lim, square, l: integer;
     4             |    prim: boolean;
     5             |    p, v: array [1..n1] of integer;
     6             |begin
     7             |    write(2: 6, 3: 6);
     7             |    l := 2;
     8             |    x := 1;
     8             |    inc := 4;
     8             |    lim := 1;
     8             |    square := 9;
     9             |    for i := 3 to n do begin (*find next prime*)
     9           48. =>|    repeat
    11               76. =>|    x := x + inc;
    11                     |    inc := 6 - inc;
    12                     |    if square <= x then begin
    13                    5. =>|    lim := lim + 1;
    14                         |    v[lim] := square;
    14                         |    square := sqr(p[lim + 1])
    14                     |    end;
    16                     |    k := 2;
    16                     |    prim := true;
    17                     |    while prim and (k < lim) do begin
    18                  157. =>|    k := k + 1;
    19                         |    if v[k] < x then
    19                       42. =>|    v[k] := v[k] + 2 * p[k];
    20                         |    prim := x <> v[k]
    20                     |    end
    20                     |until prim;
    23                 |    if i <= n1 then
    23                5. =>|    p[i] := x;
    24                 |    write(x: 6);
    24                 |    l := l + 1;
    25                 |    if l = 10 then begin
    26                5. =>|    writeln;
    26                     |    l := 0
    26                 |    end
    26             |    end;
    29             |    writeln
    29             |end.
%

To determine the number of times a statement was executed, one looks to the left of the statement and finds the corresponding vertical bar `|'. If this vertical bar is labelled with a count then that count gives the number of times the statement was executed. If the bar is not labelled, we look up in the listing to find the first `|' which directly above the original one which has a count and that is the answer. Thus, in our example, k was incremented 157 times on line 18, while the write procedure call on line 24 was executed 48 times as given by the count on the repeat .

More information on pxp can be found in its manual section pxp (6) and in sections 5.4, 5.5 and 5.10.