''] end;
But in many implementations of Pascal (including the original) this code fails
because sets are just too small. Accordingly, sets are generally best left
unused if one intends to write portable pro grams. (This specific routine
also runs an order of magnitude slower with sets than with a range test or
array reference.)
2.6. There is no escape
There is no way to override the type mechanism when necessary, nothing
analogous to the ``cast'''''''' mechanism in C. This means that it is not possible
to write programs like storage alloca tors or I/O systems in Pascal, because
there is no way to talk about the type of object that they return, and no way
to force such objects into an arbitrary type for another use. (Strictly
speaking, there is a large hole in the type-checking near variant records,
through which some otherwise illegal type mismatches can be obtained.)
3. Control Flow
The control flow deficiencies of Pascal are minor but numerous -- the death of
a thousand cuts, rather than a single blow to a vital spot.
There is no guaranteed order of evaluation of the logical operators and and or
-- nothing like && and || in C. This failing, which is shared with most other
languages, hurts most often in loop control:
while (i <= XMAX) and (x[i] > 0) do ...
is extremely unwise Pascal usage, since there is no way to ensure that i is
tested before x[i] is.
By the way, the parentheses in this code are mandatory -- the language has
only four levels of operator precedence, with relationals at the bottom.
There is no break statement for exiting loops. This is consistent with the
one entry-one exit philosophy espoused by proponents of structured
programming, but it does lead to nasty cir cumlocutions or duplicated code,
particularly when coupled with the inability to control the order in which
logical expressions are evaluated. Consider this common situation, expressed
in C or Ratfor:
while (getnext(...)) { if (something) break rest of loop }
With no break statement, the first attempt in Pascal is
done := false; while (not done) and (getnext(...)) do if something then done
:= true else begin rest of loop end
But this doesn''''t work, because there is no way to force the ``not done'''''''' to be
evaluated before the next call of getnext. This leads, after several false
starts, to
done := false; while not done do begin done := getnext(...); if something then
done := true else if not done then begin rest of loop end end
Of course recidivists can use a goto and a label (numeric only and it has to
be declared) to exit a loop. Otherwise, early exits are a pain, almost always
requiring the invention of a boolean vari able and a certain amount of
cunning. Compare finding the last non-blank in an array in Ratfor:
for (i = max; i > 0; i = i - 1) if (arr(i) != '''' '''') break
with Pascal:
done := false; i := max; while (i > 0) and (not done) do if arr[i] = '''' '''' then
i := i - 1 else done := true;
The index of a for loop is undefined outside the loop, so it is not possible
to figure out whether one went to the end or not. The increment of a for loop
can only be +1 or -1, a minor restriction.
There is no return statement, again for one in-one out reasons. A function
value is returned by setting the value of a pseudo-variable (as in Fortran),
then falling off the end of the function. This sometimes leads to contortions
to make sure that all paths actually get to the end of the function with the
proper value. There is also no standard way to terminate execution except by
reaching the end of the outermost block, although many implementations provide
a halt that causes immediate termination.
The case statement is better designed than in C, except that there is no
default clause and the behavior is undefined if the input expression does not
match any of the cases. This crucial omission renders the case construct
almost worthless. In over 6000 lines of Pascal in Software Tools in Pascal, I
used it only four times, although if there had been a default, a case would
have served in at least a dozen places.
The new standard offers no relief on any of these points.
4. The Environment
The Pascal run-time environment is relatively sparse, and there is no
extension mechanism except perhaps source-level libraries in the ``official''''''''
language.
Pascal''''s built-in I/O has a deservedly bad reputation. It believes strongly
in record oriented input and output. It also has a look-ahead convention that
is hard to implement prop erly in an interactive environment. Basically, the
problem is that the I/O system believes that it must read one record ahead of
the record that is being processed. In an interactive system, this means that
when a program is started, its first operation is to try to read the terminal
for the first line of input, before any of the program itself has been
executed. But in the program
write(''''Please enter your name: ''''); read(name); ...
read-ahead causes the program to hang, waiting for input before printing the
prompt that asks for it.
It is possible to escape most of the evil effects of this I/O design by very
careful implemen tation, but not all Pascal systems do so, and in any case it
is relatively costly.
The I/O design reflects the original operating system upon which Pascal was
designed; even Wirth acknowledges that bias, though not its defects. 15 It is
assumed that text files consist of records, that is, lines of text. When the
last character of a line is read, the built-in function eoln becomes true; at
that point, one must call readln to initiate reading a new line and reset
eoln. Similarly, when the last character of the file is read, the built-in eof
becomes true. In both cases, eoln and eof must be tested before each read
rather than after.
Given this, considerable pains must be taken to simulate sensible input. This
implementa tion of getc works for Berkeley and VU I/O systems, but may not
necessarily work for anything else:
{ getc -- read character from standard input } function getc (var c :
character) : character; var ch : char; begin if eof then c := ENDFILE else if
eoln then begin readln; c := NEWLINE end else begin read(ch); c := ord(ch)
end; getc := c end;
The type character is not the same as char, since ENDFILE and perhaps NEWLINE
are not legal values for a char variable.
There is no notion at all of access to a file system except for predefined
files named by (in effect) logical unit number in the program statement that
begins each program. This apparently reflects the CDC batch system in which
Pascal was originally developed. A file variable
var fv : file of type
is a very special kind of object -- it cannot be assigned to, nor used except
by calls to built-in pro cedures like eof, eoln, read, write, reset and
rewrite. (reset rewinds a file and makes it ready for re-reading; rewrite
makes a file ready for writing.)
Most implementations of Pascal provide an escape hatch to allow access to
files by name from the outside environment, but not conveniently and not
standardly. For example, many sys tems permit a filename argument in calls to
reset and rewrite:
reset(fv, filename);
But reset and rewrite are procedures, not functions -- there is no status
return and no way to regain control if for some reason the attempted access
fails. (UCSD provides a compile-time flag that disables the normal abort.)
And since fv''''s cannot appear in expressions like
reset(fv, filename); if fv = failure then ...
there is no escape in that direction either. This straitjacket makes it
essentially impossible to write programs that recover from mis-spelled file
names, etc. I never solved it adequately in the Tools revision.
There is no notion of access to command-line arguments, again probably
reflecting Pascal''''s batch-processing origins. Local routines may allow it by
adding non-standard procedures to the environment.
Since it is not possible to write a general-purpose storage allocator in
Pascal (there being no way to talk about the types that such a function would
return), the language has a built-in proce dure called new that allocates
space from a heap. Only defined types may be allocated, so it is not possible
to allocate, for example, arrays of arbitrary size to hold character strings.
The point ers returned by new may be passed around but not manipulated: there
is no pointer arithmetic. There is no way to regain control if storage runs
out.
The new standard offers no change in any of these areas.
5. Cosmetic Issues
Most of these issues are irksome to an experienced programmer, and some are
probably a nuisance even to beginners. All can be lived with.
Pascal, in common with most other Algol-inspired languages, uses the semicolon
as a state ment separator rather than a terminator (as it is in PL/I and C).
As a result one must have a rea sonably sophisticated notion of what a
statement is to put semicolons in properly. Perhaps more important, if one is
serious about using them in the proper places, a fair amount of nuisance edit
ing is needed. Consider the first cut at a program:
if a then b; c;
But if something must be inserted before b, it no longer needs a semicolon,
because it now pre cedes an end:
if a then begin b0; b end; c;
Now if we add an else, we must remove the semicolon on the end:
if a then begin b0; b end else d; c;
And so on and so on, with semicolons rippling up and down the program as it
evolves.
One generally accepted experimental result in programmer psychology is that
semicolon as separator is about ten times more prone to error than semicolon
as terminator. 16 (In Ada, 17 the most significant language based on Pascal,
semicolon is a terminator.) Fortunately, in Pascal one can almost always
close one''''s eyes and get away with a semicolon as a terminator. The excep
tions are in places like declarations, where the separator vs. terminator
problem doesn''''t seem as serio