0% found this document useful (0 votes)
209 views688 pages

Principles of Computer Programming - A Mathematical Approach

Uploaded by

chesterpeters
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
209 views688 pages

Principles of Computer Programming - A Mathematical Approach

Uploaded by

chesterpeters
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 688

University of Tennessee, Knoxville

Trace: Tennessee Research and Creative


Exchange
The Harlan D. Mills Collection Science Alliance

1988

Principles of Computer Programming: A


Mathematical Approach
Harlan D. Mills

Victor R. Basili

John D. Gannon

Richard D. Hamlet

Follow this and additional works at: http://trace.tennessee.edu/utk_harlan


Part of the Mathematics Commons, and the Software Engineering Commons

Recommended Citation
Mills, Harlan D.; Basili, Victor R.; Gannon, John D.; and Hamlet, Richard D., "Principles of Computer Programming: A Mathematical
Approach" (1988). The Harlan D. Mills Collection.
http://trace.tennessee.edu/utk_harlan/7

This Book is brought to you for free and open access by the Science Alliance at Trace: Tennessee Research and Creative Exchange. It has been accepted
for inclusion in The Harlan D. Mills Collection by an authorized administrator of Trace: Tennessee Research and Creative Exchange. For more
information, please contact trace@utk.edu.
conditional assignment
(EOLN -+Number := 1 0 1 ) (NOT(EOLN) -+ Number,Nc := m mod 10, c)
where m is the length of the future string of INPUT up to the next line marker, and c is the last
haracter before that line marker . (The effect of Count on INPUT is ignored here .)
Substituting f for ICounii in the recurrence equation, consider two cases, an empty and
onempty future INPUT strmg up to the first line marker.
Case 1: L is empty. Then for state s, the left side of the recurrence equation in f is
!( s) = t, where t is the same as s except that the value of Number is the character
representing 0 mod 10, i.e ., 0.
The right side is covered by the first set, in which IEOLNj( s) = TRUE, and in that set s is paired
ith this same t. That is, in Case 1 f satisfies the recurrence equation.
Case 2: L is nonempty. Let the length of L be k > 0, and let its last character be c. Then for
~ate s, the left side of the recurrence equation in f is

!( s) = t, where
t is the same as s except that the value of Number is the representative for k
mod 10, and the value of Nc is c.
I
~ e rig_ht side is covered by the second set, in which EOLNj{ s) =FALSE, and there the value paired
t h SIS
t = (I READ (Nc) lo f o INextDigi t (Number) j)(s).
~ can be worked out using trace tables.

CASE 2.1
Part Condition IN 2 Nc Number
L \l I
READ(Nc) L\ll # tt A(L \1 I) e(L \lI)
f e(A(L \lI))= I c 0
NextDigit ... 1

Condition: ( (L \l/) # tt) AND (9(A(L\l/)) = /)


Assignment: Number, Nc : = 1 1 1 , c
~ e condition is only true if L is a 1-string, because
A(L\ll) = \ll

e(A(L \l I)) =I
::-aluates to TRUE. Also, since L is a 1-string, e(L \l I) = c is the only character in L (before the
· e marker). Thus, ignoring the effects on INPUT, the conditional assignment is
(length (L) = 1 -+ Number, Nc := 1 1 1 , c)

CASE 2.2
Part Condition IN 2 Nc Number
L\lj
READ(Nc) (L \lI)# tt A(L \l /) 8(£ \lI)
J (e(A(L \l /))) # I c length(A(L)) mod 10
NextDigi,t ... (length(A(L)) mod 10 + 1)
mod 10

_ extDigi t increments Number except for value 9, which becomes 0. This behavior is addition
.::nodulo 10.)

11.4.2 A Recursive Procedure Verification Rule 327


Condition: ((L \7/) # tt) AND (e (A (L)) # /)
Assignment: Number, Nc := ((length(A(L)) mod 10) + 1) mod 10, c
The condition can be simplified by observing that L must be at least a 2-string because A(L) does
not start with a line marker. Thus, ignoring the effects on INPUT, the conditional assignment is
(length (L) > 1 - Number, Nc : = ((length( A(£)) mod 10)+1) mod 10, c)
Note that
length(A(L)) mod 10 + 1 =length(£) mod 10
whenever length(£ )>1, and that applying a second mod function has no effect. Cases 2.1 and 2.2 can
be combined to yield:
(length(£)>= 1 - Number,Nc := length(L)mod10,c)
This is exactly what was obtained for the left side, so f satisfies the recurrence equation in case 2. It
has been shown that the function f derived from a comment in LengthOfinput satisfies the
recurrence equation for ICount! in that program.
Care is required, as 1t was with the WHILE statement, however, since the recurrence equation
is only a necessary condition. It could happen that a function satisfies the equation, but is not at all
the meaning of the procedure from which the equation was derived. The crucial example is the
procedure
PROCEDURE Forever;
BEGIN
Forever
END;
whose recurrence equation is
!Forever!= IForever!
and any function whatsoever is a solution. For this procedure the meaning should be the empty
function, since the recursion never terminates. By analogy with the WHILE statement case, this
suggests a verification rule: the part function for a procedure statement involving a recursive call is
a solution to the recurrence equation whose domain agrees with the one for the procedure .
Let P be a procedure statement for a procedure without parameters and with one recursive
call, having the recurrence equation fPl =- M( fPl ). M( 0 ) represents the result of calculating the
part function of the procedure <blo~, in ~ch P occurs just once. This calculation must be
carried out until0 occurs explicitly, as in the example above. A function f = 0 if and only if:
1) domain(.!)= domain( 0), and
2) f is a solution to the recurrence equation.

11.4.3 Examples of Recursive Procedure Verification


To illustrate the rule, it has been shown above that the function f:
(EOLN -Number := '0') (NOT(EOLN) - Number,Nc := m mod 10, c)
satisfies the recurrence equation in ICount!:
ICount!= {<8, t>: IEOLNj( 8) =TRUE, t =I Number : = '0' j( 8)}
U {<8, t>: lEOLNj(8) =FALSE,
t = ( IREAD (Nc) Io ICount l o ,...,N_e_x_t_D_i_g_it--:-(N_u_m_b_e_r--,)j)( 8)}.
This is condition 2) of the verification rule . Therefore, to complete a proof that f =I Coun~l requires
only checking condition 1). The domains off and lcountlare evidently the same, smce oth have
the same conditions on EOLN as part of the ir definitiOns.

328 PROGRAMMING WITH RECURSION


As another example of a recursive procedure verification, consider a procedure Rev that is ·
int ended to reverse a line of INPUT to OUTPUT:
PROCEDURE Rev;
VAR
Ch : CHAR;
BEGIN {Rev}
IF NOT (EOLN)
THEN
BEGIN
READ(Ch);
Rev;
WRITE(Ch)
END
END {Rev}
The recurrence equation for a procedure statement using Rev is:
jRevj=jVAR Ch: CHARjojBEGIN IF ... ENDjo.-jV_AR_C_h_:_C_HAR----,jT
where
IBEGIN IF . . . END!
= {<s, s>: jEOLNj(s) =TRUE} U
{<s, t>: jEOLNj(s) = FALSE, t = ( jREAD (Ch) loiRevlojWRITE (Ch) l)(s)}.
~ubstituting in the right side of the recurrence equation, the first set makes no changes in the state ,
so t he jvAR Ch: CHARland its transpose cancel, giving the equation as
jRevl= {<s, s>: jEOLNj(s) =TRUE} U
{<s, t>: jNOT (EOLN) j(s), t = ( jr-v_AR_C_h_:-C-HAR-----,jo
jREAD(Ch) jojRevjojWRITE (Ch) jojVAR Ch : CHARjT)(s)} .
The intended function for Rev is
f = {<s, s>: IEOLNI(s)} U
{<s, t>:INOT(EOLN) l(s),.-ji_N_P-UT--,I(s) = <x,((y \7 c) \7/) & z,R>,
IINPUTj(t) = <x & (y \7 c),(\7 /) & z,R>,
jouTPUTj(th = ( joUTPUTj(sh \7 c) & reverse(y)},
he re x and z are strings, y is a string containing no line markers /, c is a character, and reverse(y)
- t he reversal of string y.
It remains to prove that f = jR;vl using the verification rule. ·
j
Condition ':gurs that the omains of these function ag,ee. Both domains a<e evidently all
t a tes for which EOLN is defined. (The condition on INPUT in f is not restrictive, but merely gives
t he general form w en EOLN is FALSE.)
Condition 2 requires that f satisfy the recurrence equation. Substituting, this is:
j = {<s, s>: ~(s) =TRUE} U
{<s, t>: jNOT (EOLN) l(s) , t = ( jr-v_AR_C_h_:-C-HAR-----,Io

IREAD ( Ch) I f IWRITE ( Ch) I Iv AR


0 0 0 Ch : CHAR IT)( s)}.
T here a re two cases, d ependin g on whether INPUT is at end of line or not .
Case 1: INPUT is a t end of line in stat e s.
T he left side of the recurr enc e equation in f is f( s) = s, because in the definition of f , the first set
a pplies. On the right side of the equation the first set also applies, with the result s. That is, in case
1 f satisfies the equation.

11.4.3 Examples of Recursive Procedure Verification 329


..........
~
,_.
. .-
. ,~

Case 2: INPUT is not at end of line in state s.


The left and right sides of the equation can be worked out in trace tables, with two conditions for
the right side depending on whether INPUT has more than one character:

Left side of recurrence equation


Part Condition INPUT OUTPUT
<x,((y V' c) V' /) & z,R> <u,tt,w>
NOT(EOLN)
f <x & (y Y' c),(Y' /) & z,R> <( u V' c) & reverse(y),tt,w>

Right side of recurrence equation (one-character input line)


Part Condition INPUT OUTPUT Ch
<x,((V' c) V' /) & z,R> <u,tt,w>
NOT(EOLN)
VAR Ch: CHAR ?
READ(Ch) <x V' c,(V' /) & z,R> c
EOLN
f <x V' c,(Y' /) & z,R>
WRITE{Ch) <u Y' c,tt,w>

Right side of recurrence equation (y not empty)


Part Condition INPUT OUTPUT Ch
<x,((y V' c) V' /) & z,R> <u,tt,w>
NOT(EOLN)
VAR
Ch: CHAR ?
READ(Ch) <x V'(8 y), 8y
(((A y) V' c) V' /) & z,R> .
NOT(EOLN)
f <x & (y V' c), <(u V' c) & reverse(A y),
(V' /) & z ,R;> tt,w>
WRITE{Ch) <(u V' c) &
(reverse(A y) V' (8 y)),
tt,w>
In these tables, x, u and z are strings, y is a string containing no line markers /, and c is a
character.
When there is a single character in the input line, y is empty, and the values on the left and
right side reduce to the same thing. When there is more than one character, the values for INPUT
are the same, and the right side OUTPUT is
<( u V' c) & (reverse( A y) Y' (8 y)),tt,w>
but
reverse(A y) V' (8 y) = reverse(y)
so the two sides are the same in this case, too. Thus condition 2 of the verification theorem IS
satisfied, and the intended function f is the same as the actual part function jRevl.

PROGRAMMING WITH RECURSION


1.4.4 Proof of the Recursive Procedure Verification Rule
~ e verification rule applies to a procedure statement p for a mcedure without parameters and
· h one recursive call, having the recurrence equation ~ = M( ~ ). A function f = if and only 0
1) domain(./) = domain( 0 ), and
2) f is a solution to the recu.rrence equation.
The only-if direction of the proof is trivial, since 0 satisfies both conditions, and hence so does
_.· = r;l~r
the other direction, assume conditions 1) and 2). Consider any s E domain( When 0 ).
- _e right-hand side of the recurrence equation is applied to s, by the time 0
itself is applied, the
~ate may have been altered (by other statements in M( 0))
to s'. In evaluating 0(s') again use
- ~e right side of the recurrence equation, and continue this process of evaluating the parts of M( fPl)
"" d substituting the right side of the recurrence equation wheneverJfl itself is to be applied to~e
edified state. The process cannot continue indefinitely, because lfrrs defined at s; the recursion
~ust terminate. That is, after (say) k substitutions in the right side of the recurrence equation, the
_art of M( rPl) involving 0
itself will not appear, and the calculation of the resulting state can be
- mpleted. ~at is, combimng these substitutions into a k-fold application of the modification M,
0(s) = (Mk( 0))(s) = t,
here the state t involves no application of 0.
Now consider writing the recurrence equation in terms of f instead of 0. Since f solves the
e ation by condition 2), making k substitutions gives
!( s) = (Mk(j) )( s) = t,
-· e same state obtained when using the equation for 0,
because this state in no way depends on
- · e application of either for 0,
only on the other statements from M( rPl ). Thus 0(
s) = !( s ), and
_- ce s was an arbitrary point m their common domain, 0
= f as was t6o'e proved.
Another way to characterize the function that captures the meaning of a recursive procedure
_all is that it is the least-defined solution to the procedure's recurrence equation. That is, F is the
:unction satisfying the equation which is undefined as often as possible. (No other function satisfying
- e equation is a subset of F.) To see why this statement is equivalent, notice that if F has the same
omain as fPl, F satisfies the verification rule, and hence F = 0.
Suppose then that domain( F)
ere strict~maller than domain( 0 ),
that there were a point x for which 0
is defined but F is
ot. Substitute in the recurrence equation for 0
until it disappears for argument x as above, _
0(x) = y,
where y does not involve an application of 0. But since F satisfies the same equation,
F(x) = y
as well, and thus F £s defined at x . The same argument applies to any point in domain( so the 0 ),
least solution F has this domain.
In a practical verification, the original form of the rule is more useful than the least-defined
~orm, because it is easier to identify the domain of the actual procedure statement part function
han to show that a trial solution to the recurrence equation is the least-defined one.
The meaning of a procedute statement for a recursive procedure must therefore be obtained by
fin ding a trial function, then verifying that it is the meaning by use of the above rule. The situation
is similar to that for WHILE statements presented in Section 8.4.2. However, the WHILE statement
verification rule applied to the general case, and the result above is restricted to a single recursion
where there are no parameters. The technical details of the general case for recursion are
considerable, since it can involve several simultaneous recurrence equations for a multiple recursion,
and different parameters to the same procedure if it occurs several times in these equations.

11.4.5 Exercises 331


11.4.5 Exercises
11.4.1 Calculate jNeverj, for
PROCEDURE Never;
BEGIN
IF 'O' = 1 1 1
THEN
Never
END
in two ways:
a) by explicitly unwinding the recursion, and
b) using the verification rule for recursive procedures.
11.4.2 For the recurrence equation orj Countjin LengthO finput,
a) Show that the empty function is not a solution.
b) Show that the identity function is not a solution.
11.4.3 Rewrite the procedure RSort of Section 11.1.1 so that it has no parameters, but instead
uses a global variable.
a) Write the recurrence equation for the procedure of a) and expand the calculation until
jRsortl appears explicitly on the right side.
b) Give a candidate function for the meaning of RSort.
c) !Using t r verification rule for recursive procedures, prove that the candidate of b) is in fact
RSort
11.4.4 Repeat the previous exercise for the procedure Palindrome of Section 11.1.2.

11.5 Chapter Summary


Recursion is a programming technique that can often replace iteration to solve a problem in a simple
and elegant way. Recursive solution~ are appropriate when the problem can be broken into solving a
simpler version of itself, and then modifying that simple solution to get the solution to the actual
problem.
CF Pascal procedures may invoke themselves, and a recursive procedure is the mechanism to
implement a recursive problem solution. An important aspect of programmed recursion is that each
new invocation of a recursive procedure includes local variables that are distinct from those of
previous invocations. This feature is essential if the many procedures, active at the same time, are
to keep straight their separate tasks.
Sorting and reversing using recursion are good examples of the technique. The Towers of Hanoi
puzzle has a particularly elegant recursive solution.
Mathematical induction can be useful in proving properties of recursive problems.
A verification rule for simple recursive procedures is very similar to the WHILE statement
verification rule given in Chapter 8.

332 PROGRAMMING WITH RECURSION


CHAPTER 12

SYSTEMATIC PROGRAM DESIGN AND CORRECTNESS

Chapter Preview

The techniques of program design, and verification that a design is correct, can be formalized.
The specification for a program, a statement of the problem and what the program should do,
plays the central role in this formalization . Careful mathematical formalism plays a role even
in large designs where full details are impractical to obtain.

12.1 Program Specifications

Preview

A program specification describes a problem and its solution as a relation between input and
output strings.
New ideas: specification.
Any program has a purpose. Usually, that purpose is to solve some problem. Data for the
_ oblem will be given in the form of an input character string, and the solution will be described by
"ving the output string the execution of the program should produce. These input and output
::-rings may contain words and numbers which are meaningful to the problem solver but which are
:-"'a.d and written as strings of characters, one at a time, by the program . A program specification for
- problem defines the set of input strings which represent meaningful data for the problem and for
,:oa.ch input string defines the set of (one or more) output strings which are acceptable solutions for
- a.t problem statement. Thus a specification is a relation consisting of all pairs of acceptable
t ances of input data and output data for a problem . The domain of this relation is all acceptable
puts. For character strings not in the domain of this relation, there is no required program
- havior.
Here are three examples:
A specification for sorting character strings. The input is any character string. For each input
string only one output string is acceptable, namely the character string with exactly the same
elements as the input but in sorted order . The specification will be a function in this case
because every input defines a unique output.
A specification to evaluate an arithmetic expression written with natural numbers. Most
character strings are not allowed as inputs, only those that represent the arithmetic
expressions with natural-number ope rands. (These might be precisely described using BNF,
much as the <expression>s of OF Pascal are described .) The output string must be a
representation of a single natural number, which is the value of the evaluated input expression.
Unless there is latitude given about how the result is to appear in the string, its base, etc., this
specification is a function.
A specification to evaluate a character set expression. The input will be only those strings
representing set expressions, with the set elements listed explicitly. The output is to represent
the result of evaluating the expression . The output may not be unique because the elements of
sets can be list ed in different orders, so a set with two or more elements can be represented by
more t han one character string. In this case the specification is a relation that isn't a function .
..,...hese three examples illustrate that program specifications may be functions or relations and their
omains may be restricted.

12.1 Program Specifications 333


Although some specifications seem to make more sense than others in a given problem context,
there are no ironclad rules about specifications. For example, in a specification concerning the
evaluation of arithmetic expressions, it may be important to recognize the strings that are not
arithmetic expressions and produce error messages. The very set of error messages produced for
various deviations from correct arithmetic syntax could be part of the specification. Thus, two
specifications which both required the same arithmetic but differed in their treatment of incorrect
syntax would be different specifications. A specification might even be in the form of "if the
arithmetic syntax is incorrect put out an error message." Then the programmer is free to create any
error message(s). The specification is a relation in which every correctly formed arithmetic
expression is associated with a unique output but an incorrectly formed arithmetic expression
corresponds to any output which is recognizable as an error message. (Evidently it would be unwise
to number the errors and have messages like 125 that could be confused with expression values.)
For problems involving complex text inputs or outputs, BNF is useful for describing the domain
and range of a specification . However , BNF usually cannot make the correspondence between inputs
and outputs that is an essential part of specification. Whenever specifications include informal,
English-language parts, there is danger of ambiguity. For example, a specification for sorting words
in text could define the domain and range strings using BNF. But the additional statement that the
output is to be the words of the input in sorted order raises several questions: Should repeated words
in the input string be repeated in the output or should duplicate words be eliminated? Is the number
of blanks separating the output words important? Is the sort to be in ascending or descending order?
It is not always desirable to eliminate ambiguities from program specifications. What is needed
are progr am specifications that are well conceived with respect to the problem being solved. If the
output is to be used by another program, then exact format may allow another program to be
written more simply. If the output is to be used by a human, then a program may be simpler if
given more freedom to provide output in the most convenient of various possible forms.

12.1.1 A Specification for RemoveExtraBlanks


The problem solved by RemoveExtraBlanks in Section 10.1.3 was described in English, and BNF
for its input and output forms was developed as part of the design. Attaching the subscript S for
specification, it was:
1. <Fileln> s ::= <WBlist> <blank> : <blanks> <WBlist> <blank>
2. <WBlist> ::=<empty string>: <word> <blanks> <WBlist>
3. <word> ::= <nonblank>: <word> <nonblank>
4. <blanks> ::= <blank> : <blanks> <blank>
5. <FileOut> s ::= <Wlist> : <empty string>
6. <Wlist> ::=<word>: <word> <blank> <Wlist>

CR. The <word>s of <Wlist> must be the same as those of <WBlist>, in the same order.
The BNF desc ription is very precise and formal, but the context r ule could be improved . The
appropriate level of formality depends upon the knowledge and understanding of the specification's
reader, and its importance to the people for whom the problem is being solved. One way to make
the description of the words and their order more formal is to define a function that maps the words
of a string to a list of words, preserving the order. Call this function words. For example:
words(t Thi s is time is t) = <tTh ist, t i st, ttime t, ti s t>.
When we want to refer to all the possible strings that are defined by a BNF rule, we use the rule's
name, for example we talk about a <block> in CF Pascal. Then the input for the problem of
removing extra blanks is a <Fileln> sand the output is a <FileOut> 5 , related by the context rule
CR. words( <Fileln> 5 ) = wo rds( <FileOut> 5 ),
wit h the meaning that the strings on which words operates are those descr ibed by a ny < Fileln> s

334 SYSTEMATIC PROGRAM DESIGN AND CORRECTNESS


string and corresponding <FileOut> s string, respectively . This specification can be written as:
r = { < <Fileln> 8 , <FileOut> 5 >: words( <Fileln> 5 ) = words( <FileOut> 5 )}.
A program specification is a relation describing the inputs and acceptable outputs for a
problem. It may be difficult to write down precisely, but it is a mathematical relation nevertheless.
The specification of a large system may occupy hundreds of pages of English, augmented by
engineering drawings, mathematics, or descriptions drawn from the field of the problem, but it is still
just a relation.

12.1.2 Exercises
12.1.1 Write a program specification for addition of several natural numbers, where the problem is
presented in infix notation. Describe the syntax of input and output strings using BNF, and define a
function number that maps strings representing numbers to the numbers. Use these elements in the
specification.
12.1.2 Write a program specification for printing all four-letter words found in a character string, in
t he same order as found.
12.1.3 Write a program specification for the problem of Sara Revere's Letter from Section 2.3.3.
12.1.4 Write a program specification for the problem of the Towers of Hanoi from Section 11.3.1.
12.1.5 Given a program P, we have learned in the previous chapters how to calculate ~.
Comment on the following statement: Since P has been written to solve some problem, and smce
[!j is a function, 0should be used for the specification of that problem.
12.1.6 Criticize the following as a specification of sorting characters from one file into another,
where duplicate characters of the input are to occur just once in the output:
The output file is to be a sequence of characters such that each character in the sequence
comes strictly after the one before it in alphabetical order .
Give a more precise specification for this problem.
12.1.7 Some people believe that specifications should never be relations that are not functions,
because a function better pins down exactly what is to be done . Give a brief argument against this
position.
12.1.8 Specifications often have "don't care" parts. For some inputs, the output is of no interest, so
-he author of the specification, if questioned about what should be done for those inputs, answers "I
don't care." But how should this be reflected in the specification relation? Two possibilities suggest
hemselves: (1) the relation should include all possible values for output paired with that input; or,
(2) the relation should include no pairs with that input. Explain which you think is a better
definition to take.
12.1.9 Describe a problem whose specification relation might be said to be a "specification for the
programming language CF Pascal." What are the input values, and what are the outputs? This
specification should certainly include the BNF description of CF Pascal. What is missing if only the
BNF were used?

12.2 Program Correctness

Preview

Formally, correctness is a property of the progr am function and the specification relation, a
property that can be proved using set theory. Effort should be split wisely between program
design a nd program prov ing. The usual mistake is a too ambitious design that gets out of
i intellect ual control.

12.2 Program Correctness 335


The specification relation describes what a program should do; it expresses intentions that are
yet to be realized. It exists apart from any program that might be written to realize it. In fact, the
specification is the starting point for programming. It is sound practice to have the specification well
in mind before designing a program or developing a strategy for its design.
On the other hand, once a program exists, its program fun ction defines what the program does
do, without regard for intentions the programmer may have had. Furthermore, using the program
calculus the program function can be calculated directly from the text itself. Neither the
specification relation nor the progr am function describes how the program accomplishes what it does.
But the program function calculation depends on full details of the program's inner workings.
A central question of programming and certainly a central question for a programmer trying to
meet a specification, can be simply stated in these terms:
Given a specification and a program does the program fulfill that specification?
Informally, if the answer to the question is "yes," then the program is said to be correct with respect
to the specification. More formally, given a program specification relation r and a program P, we
say that P is correct with respect to r if, for every member x of the domain of r (an instance of
input data), P produces some member of the range of r which corresponds to x. That is, for each
input x, P produces result y such that <x, y> E r. What P does to input data not in the domain of
r is irrelevant since r defines all behavior important to the problem solver. The definition is reduced
to a purely set-theoretic idea in the following theorem:
Correctness Theorem. Program P is correct with respect to specification relation r if and only if
domain( r n 0) = domain( r ).
Proof: The ~x ression (r n 0) identifies all acceptable pairs of r computed by P. Therefore
domain(r n P ) identifies the set of input data for which P produces acceptable output data. Since
domain(r) is t e set of input data for which r specifies acceptable output data, the condition
domain(r n0) = domain(r)
ensures that P produces acceptable output data for every instance of input data defined by r. This
completes the proof of the theorem.
P may: execute successfully for input data not identified by r, but such pairs of IPJ are screened
out of (r n IPJ) by r. If P produces an unacceptable instance of output data, no m~er of r with
that input :rata can be in (r n 0),
and therefore domain(r n ~) cannot equal domain(r). For
example, if
r = {<A, tAt>, <B, tAt>, <A, tBt>, <B, tBt>}
and
0 = {<x, V x>: xis a character},
then
r n0= {<A, tAt>, <B, tBt>}
and domain (r n~) =domain (r) = {B, A}.
In case the program specification is a function, the condition for program correctness can be
simplified as follows:
Corollary: Program P is correct with respect to specification function f if and only if
JC0.
Proof: The expression f n 0 identifies all acceptable pairs of f computed by P, which must be f,
itself. That is, P is correct w1th respect to f if and only if
Jn~ = f.
Therefore f C 0, which completes the proof of the corollary.

336 SYSTEMATIC PROGRAM DESIGN AND CORRECTNESS


~onsider the corre~tness ?f RemoveExtraBlanks whose specification r was given above.
Analysis ?f the program m Sect10n 10.1.5 showed that the input and output files actually processed
are descnbed by the BNF rules (using P for the program):
<Fileln>p ::= <Filel> I <blanks> <Filel>
<Filel> ::= <R> I <blanks>
<R> ::=<word> <blanks> <R> I <word> <blanks>
<FileOut>p ::= <File2>
<File2> ::= <S> I <empty string>
<S> ::=<word> <blank> <S> I <word>
and that the <word>s moved from input to output are the same. That is,
[B= {<<Fileln>p, <FileOut>p>: words(<Filein>p) = words(<FileOut>p)}.
<F~leOut>p and <File0ut> 8 are evidently the same, since <S> and <Wlist> are the same.
<Fileln>p and <Fileln>~ are _also the same, because <R> describes the same strings as <WBlist>
<blanks>. Thus the spec1ficat10n and program function are the same,
[B=r,
a nd RemoveExtraBlanks is correct.
I
12.2.1 Mathematical Formality and Proof 1
:\.i~the~atical formality is a limited resource, to be used wisely just as is comlJuter time for testing.
It ~s pomtless to ~e extremely formal, then run out of time with a program oryly ten percent proved.
It IS better to adjust the level of formality to the circumstance. A simple data-transfer operation
may be verified by direct inspection, while a complex priority-queue sorting pperation may require a
full-scale conditional trace table treatment. The budget between program design and program
pro~·iag should affio be in balance. It may be f>etter to design a fess capapie program and do more
formal proving, than to attempt an ambitious design that gets out of intell~ctual control.
1
Long division is mathematics, and so is proving that a program is correct with respect to a
specification. When we do long division, we can make mistakes-for example, we might set down 9 x
6 = 63 by mistake, an error of execution . But a more serious mistake is to put the digits 6 and 3 in
he wrong columns of a long division layout, a mistake in methodology. We can catch each other's
mistakes of execution more reliably than mistakes in methodology. For example, an onlooker might
im agine a methodological mistake to be another legitimate way of solving the problem. At the point
where the onlooker ceases to understand the method, all checking stops. Similarly, in showing a
program correct, we might decide that two conditional assignments are the same, but be mistaken.
But a more serious mistake is to compare two functions when we should be composing them and
comparing the result to one (as in the WHILE verification rule)- then the method has gone wrong
a nd the onlooker ceases to understand. Once precise communication breaks down, anything can be
proved" in the world of vague ideas.
Doing mathematics is a human activity subject to human fallibility. But it is a human activity
l'iith centuries of experimental validation and represents the best knowledge of humanity about
logical reasoning processes. Mathematics permits a team activity with minimum emotional
at tachment and defense of mistakes.

12.2.2 Mathematical Proofs


A mathematical proof is an attempt at human persuasion, which can be regarded as a repeatable
experiment, much like those of the chemistry laboratory. The difference is, instead of physical
objects, the expe rimental subje ct is a human being. The experiment consists of a sequence of
asse rtions of the form "from so-and-so it follows that such-and-such," and if the subject agrees, the
1 '
experiment continues. If successful, the experiment leads to agreement on the entire sequence. If the
subject does not agree with some assertion, the experiment fails. The experiment may be successful
with an incorrect proof, or it may fail with a correct proof, because of human fallibility.

12.2.2 Mathematical Proofs 337


Mathematical notation plays no direct role in a proof except to facilitate reasoning and
calculation by previously agreed-upon procedures. Indeed, mathematical logic only formalizes
previously accepted rules for "agreeing it is obvious," and computers can be used to follow such rules
when they are made precise enough. As a human activity, the design of a mathematical proof shows
many similarities to the design of a program. A complex proof, as a complex program, can only be
understood as a hierarchy of subproofs. But whereas programs are executed by machines, proofs are
executed by human beings, and not only human fallibility, but human attention span and motivation
need to be considered. It does no good to produce a perfect 1000-assertion proof if the subject loses
interest and stops listening after 100 assertions. It does no good to produce a perfect 10-step proof if
the steps are too large for the subject to accept. And just as the level of formality should differ for
different uses of the same program, a subject will tolerate more proof steps for the same proof on one
occasion than on another, depending on its importance at the moment. So the strategy of proof
should account for the occasion. There is no right level for every occasion, and no ultimate level of
proof.
In one circumstance the best strategy for proving a program correct might be to make the
assertion: "see for yourself, this program is obviously correct with respect to its specification." It is
a legitimate proof, and for a simple program no more effort may be warranted. However, the total
effort required to understand and agree on the correctness of a program is usually decreased by a
proof of some moderate length. In fact, a proof of correctness serves as the best documentation for a
program. So an outline of the main design's statement functions serves as the minimal proof, i.e.,
"these statement functions obviously combine to satisfy the specification."
All this is to say that proving program correctness explicitly is not a luxury of logicians, but a
necessity for prudent programmers. No proof at all is really the one-assertion proof "it is obvious."
Any documentation is a kind of proof. So the question is not whether proofs should be made, but
only what form they should take.

12.2.3 Exercises
12.2.1 Explain why the following conditions are not satisfactory as definitions for program
correctness:
a)rn0=r
b) range( r
c)rn0= P
nt) = range( r)

d) domain( r n p ) = domain( 0)
12.2.2 Give a formal definition of "program Pis correct with respect to specification r at input x. "
Show that your definition, prefaced with "for every x," is equivalent to the one given in this section.
12.2.3 When a programmer writing program P "omits a case" in solving a problem, because he or
she did not think of the possibility of some input value x, what does this mean about pairs in !Plwith
input value x? Suppose that in fact the specification relation for P does not include any su~pairs
with input x. If the program has no other mistakes, explain why it is then correct, using the
definition of this section.
12.2.4 A particular specification includes some "don't-care" inputs, pairing them with all possible
output values. Explain why it is still possible to write a program that is not correct for one of the
don't-care inputs.

338 SYSTEMATIC PROGRAM DESIGN AND CORRECTNESS


II

12.3 Program Design

Preview
------------------------------------------------------------------------------------
Program parts may be given specifications, which may be conditional assignments. Then a
program part can be proved correct . The design of a program using stepwise composition is a
process of choosing a function, then refining it into simpler functions that compose it. These
functions are the specifications to which program parts must be written.

The program calculus has been developed for CF Pascal programs in complete detail, but to
handle large program designs its level of formality is too high. A way is needed to scale up to large
programs without losing the mathematical discipline of designing to precise functions. Because the
calculus works with sets and functions, these can be described less formally, yet still maintain the
design discipline.

12.3.1 Correctness of Program Parts


Program correctness can be carried over to correctness of program parts. Program meanings map
in put strings to output strings, but program-part meanings map execution states to execution states.
A specification for a program part will therefore be a relation whose domain and range are both sets
of execution states. A program-part meaning can be more precise and constrained than that of a
program which involves an outside problem statement and solution for a user. Nevertheless, there
m ay be cases where a specification is better stated with the freedom of a relation than with a
function.
Good techniques for the specification of program parts are similar to those for the specification
.or programs. If strings are contained in the execution states then BNF can be helpful in describing
hem. The construction of auxiliary functions or relations related to the problem being solved will
often be useful in describing the specification beyond the BNF.

12.3.2 Conditional Assignments as Specifications


The conditional assignment is powerful enough to express the effect of any CF Pascal program part .
Conditional assignments serve two simultaneous purposes in designing CF Pascal programs:
1. As the components of a high-level design, and
2. As intended meanings for program parts that are to implement them.
In decomposing a problem into a design at any level, the conditional assignments serve as the
components of the design, and allow calculation of its effect. Then these same assignments,
onsidered as intended meanings, can be used to verify the parts that implement them.
Conditional assignments can be described in any convenient mixture of CF Pascal,
mathematics, and English . For example, the procedure
PROCEDURE Sort2{VAR Chl, Ch2: CHAR);
VAR
Temp: CHAR;
BEGIN
IF Chl > Ch2
THEN
BEGIN
Temp := Chl;
Chl := Ch2;
Ch 2 := Temp
END
END
ca n be expressed as the design

12.3.2 Conditional Assignments as Specifications 339


PROCEDURE Sort2(VAR Chl, Ch2: CHAR);
VAR
Temp: CHAR;
BEGIN
{(Chl > Ch2 -->
Chl,Ch2,Temp := Ch2, Chl, Chl)
(Chl <= Ch2 --> )}
END
or
PR~CEDURE Sort2(VAR Chl, Ch2: CHAR);
VAR
Temp:' CHAR;
BEGIN
IF Chl > Ch2
THEN
{Chl,Ch2,Temp := Ch2,Chl,Chl}
END
or even
PROCEDURE Sort2(VAR Chl, Ch2: CHAR);
BEGIN
IF {Chl <> min(Chl,Ch2)}
THEN
{Chl,Ch2 := Ch2,Chl}
END

12.3.3 Program Design by Function Refinement


Since any CF Pascal program has a program function, the task of program design can be treated in
two steps:
1. Find a function that satisfies the program specification.
2. Design a program which has this function as its meaning.
In some cases the function found may be influenced by the program design itself. For example, if a
program is required to produce the divisors of any input number, any function of the form
{<input number, list of divisors>}
will do, where the format, in particular the list order, is not material. But there may be a simple
way to determine the divisors in numerical order, and that particular function will have the simplest
program design. In other cases, the choice of the function may not be so easily seen, but may only
become clear after considerable thinking and experimentation with design ideas.
In any case, we concentrate on Step 2 of the design task, noting that the BEGIN statement of
a CF Pascal program {or procedure) which determines its meaning is composed of composite
statements of just three kinds:
1. BEGIN statements
2. IF statements
3. WHILE statements
Each of these is in turn composed of composite statements of the same kinds.
The program calculus allows us to redescribe the process of stepwise composition as a process
of function refinement. At each step, an intended function is given, and a composite statement type
is selected. Then a CF Pascal design of that statement is created, defining appropriate functions for
the constituent statements. If a procedure statement is used as a constituent, it will be ultimately
treated as a BEGIN st at ement. Any other statements are comp'=''~ i;<.' ; ~ !· ·~im;"J l<" st atements. The
refinement stops with tb e simph~ sta,te ments (whose part functioP.s f~r\: cu <:·:T•:·n:lmgly simple), while

340 SYSTEMATIC PROGRAM DESIGN AND CORRECTNESS


t he refinement continues for any composite statement introduced. At first glance, the shift from
stepwise composition to function refinement may seem to complicate matters. But its practice, while
frustrating and seemingly unnatural at first, soon leads to a new capability for abstraction and self-
discipline in program design .
P rogr am design by function refinement requires the designer to think about programs in
"wholes," that is, about the total program behavior, for all possible input data, and all possible cases
of execution.

12.3.4 Exercises
12.3.1 Are the mappings that go with conditional assignments ever relations that are not functions?
Explain .
12.3.2 When conditional assignments are used in designs as comments, the design could be executed
if correct actual Pascal statements replaced the comment. Could this be done automatically? That
is, could a program be written to take as input a design containing conditional-assignment
comments, and create as output correct Pascal?
a) Give a specification for such a program, using the box notation.
b) Explain why such a program cannot be written .

12.4 Chapter Summary


_-\. specification is a relation containing those pairs of input and output strings that are acceptable.
A program is correct with respect to a specification if for each input represented in the specification
re lation, the program output agrees with one of the associated specification values.
Different degrees of mathematical formality are appropriate to different situations, but some
orm of proof of a program's correctness is always appropriate. The proper level of proof depends on
~h e resources available, the audience for the proof, and the importance of the problem . and its
solution .
Stepwise composition may be alternately viewed as a process of functional refinement , in which
top-level functions serve as intentions for lower-level designs, and as specifications against which the
im plementations can be proved .

12.4 Chapter Summary 341


PART IV: PROGRAM DESIGN IN D PASCAL

Data Abstractions
Systematic program development in CF Pascal is accomplished through the stepwise refinement of
programs. The program calculus and design rules derived from it provide a mathematical basis for
writing correct programs in CF Pascal. In Part IV, these design concepts are extended to a more
powerful idea, namely that of high-level data and operations in a more powerful language called D
(Design) Pascal.
The strategy of stepwise refinement is to solve a problem by defining a small program whose
parts are to be designed later. Each part could be later designed as a procedure; the term procedural
abstraction describes the stepwise-refinement breakdown into subtasks. In any case, whether
designed as a Pascal procedure or not, the subprogram implementing the subtask will be a program
part with a program part function. Indeed, in stepwise refinement, a program part function is
defined to meet the need of the problem being solved, and then a program part, which will have
exactly this function, is designed later.
The new design concept in Part IV is data abstraction. Whereas a procedure abstraction can be
defined by a single Pascal procedure, a data abstraction must be defined by a system of procedures
and data declarations, in such a way that these procedures provide the only means of access to this
data. Such a system of declarations and procedures is called a program module. The advantage of
data abstraction over procedure abstraction is that it can be used to retain data for later use,
rather than just making calculations as a procedure abstraction does. Files are one data abstraction
built into Pascal. Predefined statements (RESET, READ, etc .) manipulate files declared as variables.
These statements can be used to store and retrieve data, and the collection of operations and a file
declaration defines that file as a data abstraction.
Data abstraction is a more powerful design concept than procedure abstraction because the
underlying mathematical idea is a state machine r ather than a function . A function always produces
the same output when presented with the same input:

input
----~·~~~---f_u_n_c_t-io_n--~r---~~~ output

A state machine can accept an input, produce an output depending on the input and its persistent
internal state, and possibly change that internal state:

transition
input output
function

persistent
data

For example, with a file, consecutive READ statements do not produce the same response. In t he fi le
da ta abstraction, the internal state of the data abstraction will be the file value: a string with
cursor and the current file access mode.

342 PART IV: PROGRAM DESIGN IN 0 PASCAL


D (Design) Pascal
Part IV also extends CF Pascal to a larger subset of Pascal, D Pascal. D Pascal will generally
permit smaller and simpler designs for the same part functions than is possible in CF Pascal. The
principal extension of D Pascal is in new data types. In addition to the CHAR and TEXT of CF
P ascal, D Pascal contains built-in types INTEGER, BOOLEAN, FILE, SET, and REC()RD. But I)
P ascal also provides for custom data types of any size and complexity. These custom data types are
built up from smaller data types to fit the needs of the problem to be solved. By suitably
interpreting the values of a custom data type, it can serve as the basis for a data abstraction. D
Pascal also contains additional composite statements and program-structure facilities.
D Pascal is not the full Pascal language, which will appear only in Part V. The subject of this
book is the principles of program design, and only incidentally the language Pascal. D Pascal
contains the features needed to discuss the important design idea of data abstraction, which is the
su bject of this part. You will only learn to design programs once, but you will learn several
programming languages. Even though Pascal is a good language, it should not create a set of
blinders for thinking or programming.

D (Design) Pascal 343


CHAPTER 13

PROGRAM MODULES

Chapter Preview

The most difficult part of programming is maintaining intellectual control over the emerging
program, as more and more details are incorporated into it. To maintain control, it is essential
that parts of the program be designed and put away, so that they need not be examined in
detail again. The program module is a mechanism for encapsulating program ideas in just this
way. Several example modules, one to manipulate a counter, others to maintain a queue, will be
developed in this chapter. A module differs from a procedure in that it can retain information
between successive operations it performs. In this regard it behaves like a formal object called a
state machine.

The strategy of stepwise program refinement is to design any program as a hierarchy of design
steps, which define tasks, then expand tasks into design parts to meet the task requirements. Each
expansion of a task is a program part which will be specified in terms of the part function it must
satisfy. This function describes the data transformation performed by the program part.
The main idea in this chapter is to extend the concept of a program part to a new, more
powerful design construct, the program module. A program module, or more simply a module, is
defined to be a group of declarations of data and procedures which are specified in a single block of a
program with the following restrictions:
Data declared with the module will be accessed only by the statements (in the procedures) of
the module (and by no statements outside the module).
The statements (in the procedures) of the module will access only parameters and data of the
module (but no other data outside the module).
A program module provides data storage, processing, and retrieval service for the rest of the
program. An illustration of such a service is provided by the declaration of TEXT files and the
READ and WRITE statements which manipulate TEXT files. These statements provide the only
way to store and retrieve data in the files. Communication between the module and the remainder
of the program is provided by the character parameters of the READ and WRITE statements.
The definition of a program module is not explicitly part of the syntax of Pascal, any more
than a design part in stepwise refinement is. Rather, it provides another design discipline for
organizing programs in an additional, complementary way to stepwise refinement .
The module restrictions permit the module to protect its data from all other parts of the
program; that is, once the procedures of the module have been carefully worked out, there will be no
danger of the module's data being inadvertently altered or destroyed. This method of defensive
programming is called information hiding, and the data is said to be encapsulated in the module .
This discipline forces all communication with a module to take place through its procedures
and their parameters. Therefore, it is not possible for any data in the module to be altered except
by a procedure statement that names a procedure of the module. Nor is it possible for such a
procedure statement to affect any data outside the module except through the parameters.
A module with only pure storage and retrieval is an important special case. Data, enters the
module through procedure parameters, and may later be returned unchanged through other
parameters. CF Pascal files exemplify this property: the data put into a file always reappears in
exactly the same form when it is taken out .
Another important special case of a module is one which has no data declarations, in which
pure computation is carried out . A procedure using no global variables satisfies the restrictions all
by itself. A group of procedures that form a library, for example, those for natural-number
processing suggested in Chapter 9, could constit ute a pure-computation module.

344 PROGRAM MODULES


Modules provide a new capability for programming design. They allow the invention of data
operations at any level needed to solve a problem. For example, if a problem deals with words in a
t ext, modules can be designed to handle the words as single units. Data operations for a crossword
puzzle, a chessboard, or a baseball game can all be treated in terms of modules, with data and
procedures to deal with the peculiar properties of crossword puzzles, chessboards, or baseball games.

13.1 Numbers

Preview

To illustrate the idea of a module, a three-digit counter will be developed and used.

As an illustration of a simple module that provides both storage/retrieval and computation,


consider a programming problem that involves counting up to 999, for example, counting the number
of blanks in a string. On certain occasions one needs to increase the counter by one, or to determine
he value presently held by the counter, or to reset the counter to zero.
A module can be designed in which the count is maintained in three character var.ia,.bles, as in
CountChars in Section 1.6.3:
VAR
Ones, Tens, Hundreds: CHAR
and procedures:
Start {reset counter to zero}
Bump {increase counter by one}
Value(VAR VlOO, VlO, Vl: CHAR) {return counter value}
The counter module is designed so that once Start is executed, the counter holds a three-digit
natural number. If the counter holds 999, Bump leaves the counter unchanged. The declaration of
he procedures for the counter follow. In an actual Pascal program using this module, the variables
within the module would have to be placed in the VAR section.
{begin counter module}
{In the VAR section:}
Ones, Tens, Hundreds: CHAR;
{After the VAR section : }
PROCEDURE Start;
{reset counter to zero}
BEGIN {Start}
Ones := 10 I;
Tens :=
1
01;
Hundreds := 0 1
1

END; {Start}

13.1 Numbers 345


PROCEDURE Bump;
{increase 3-digit counter defined by Hundreds, Tens,
Ones by one if it is in the range 0 through 999}
PROCEDURE NextDigit(VAR Digit: CHAR);
BEGIN {NextDigit}
IF Digit = 1 0 1 THEN Digit .-
I 1 I
ELSE
IF Digit= 1 THEN Digit := I 2 I ELSE
1 1

IF Digit= 1 2 1 THEN Digit := I 3 I ELSE


IF Digit= 3 THEN Digit := 141 ELSE
1 1

IF Digit = 4 THEN Digit := 151 ELSE


1 1

IF Digit = 1 5 1 THEN Digit := I 6 I ELSE


IF Digit= 1 6 1 THEN Digit := 171 ELSE
IF Digit = 1 7 1 THEN Digit : = 18 I ELSE
IF Digit = 1 8 1 THEN Digit : = I 91 ELSE
IF Digit = 1 9 1 THEN Digit := 101
END; {NextDigit}
BEGIN {Bump}
NextDigit(Ones);
IF Ones = 1 0 1
THEN
BEGIN
NextDigit(Tens);
IF Tens = 1 0 1
THEN
BEGIN
NextDigit(Hundreds);
IF Hundreds = 1 0 1
THEN
BEGIN
Hundreds:= 1 9 1 ;
Ones := 1 9 1 ;
Tens := 1 9 1
~ND
END
END
END; {Bump}
PROCEDURE Value(VAR VlOO, VlO, Vl: CHAR);
{return counter value}
BEGIN {Value}
VlOO := Hundreds;
VlO := Tens;
Vl := Ones
END {Value}
{end counter module}
This declaration and three procedures define a module for a counter which can be inserted into
any program in which such a counter is needed . To observe the module restrictions only requires
that the rest of the program make no references to the global variables Ones, Tens, or
Hundreds. Then the counter is completely hidden behind the three procedures Start, Bump, and
Value.
This counter module might be used to solve the problem of counting the number of blanks in an
input.

346 PROGRAM MODULES


PROGRAM CountingBlanksinText(INPUT,OUTPUT);
VAR
Ch, XlOO, XlO, Xl: CHAR;
{include counter module};
BEGIN {CountingBlanksinText}
Start; {reset counter to zero}
WHILE NOT EOF
DO
BEGIN
WHILE NOT EOLN
DO
BEGIN
READ(Ch);
IF Ch = I
THEN
BEGIN
Bump; {increase counter by one}
Ch := '#'
END;
WRITE(Ch)
END;
READLN;
WRITELN
END;
WRITELN;
Value(XlOO, XlO, Xl); {get counter value}
IF (Xl00='9') AND (X10='9') AND (X1='9')
THEN
WRITELN('The number of blanks is at least 999')
ELSE
WRITELN('The number of blanks is XlOO, XlO, Xl)
END. {CountingBlanksinText}
INPUT Now is
the time for
all good men.
OUTPUT:##Now#is#
the##time#for
all#good##men.

The number of blanks is 010


The procedure statements involving the counter are given extra comments to distinguish them.
the echo, blanks are replaced by # so that they may be seen. The principal cost of using the
odule in the assembled program is the duplication of variables Hundreds, Tens, and Ones in
~he module by XlOO, XlO, and Xl in the block outside the module. If the call on Value were
::emoved, XlOO, XlO, and Xl could be removed, and the values of Hundreds, Tens, and Ones
rinted directly. But this would violate the module restriction that data declared in a module is
acc essed only by the statements in the module. A few extra variables is a small price to pay for a
reusable design module that can be carefully checked and tested once and for all, and used over and
ver verbatim in other programs. A module library can be even more useful than a procedure library,
if t he right modules are identified and written.

13.1.1 Exercises 347


13.1.1 Exercises
13.1.1 Explain why all three procedures Start, Bump, and Value are needed for a useful counter
module by describing the deficiences created by the absence of each.
13.1.2 Consider adding a new procedure called BumpTen (to increase the counter by ten) to the
counter module.
a) What restrictions must be observed in writing BumpTen so that the collection remains a
module?
b) Can the text of the new procedure consist of ten procedure statements
Bump;
Bump;

Bump
in sequence? Explain why or why not.
c) Write the full text of BumpTen without usmg Bump, and show how it is to be incorporated
into the module .
13 .1.3 Can a module procedure be recursive without violating the access rules?
13.1.4 Suppose a module has just one procedure . Can the data storage for the module be
incorporated into the procedure as local variables? Explain .
13.1.5 Create a switch module with variable:
VAR
Switch: CHAR;
and procedures:
PROCEDURE Set;
{set switch to down}
PROCEDURE Flip;
{if the switch is down, set it to up
if the switch is up, set it to down}
PROCEDURE Value(VAR Sw: CHAR);
{Sw := 'T' if switch is up, 'F' if down}
13.1.6 Use the switch module of the previous exercise to create a program that cop1es the odd
characters of one line of input to the output.
13.1.7 Use the counter module in writing a program to count the number of reversals in a character
string. A reversal is three successive characters such that the middle character is either greater or
less than the other two characters .
13.1.8 Suppose a certain module contains the following:
VAR
Safe: CHAR;
{declare other variables of the module here}
FUNCTION Check(VAR OK: CHAR);
BEGIN {Check}
IF Safe = 'Y'
THEN
OK .- 'T'
ELSE
OK .- 'F I
END; {Check}
{other functions and procedures of the module}
Users of this mof:lule are expected to write code like:

348 PROGRAM MODULES


Check(Status);
IF Status = 'T'
THEN {call a procedure of the module}
=o t hat other proceduru:, of the module can "turn off" its actions by setting Safe. A user decides to
-~ eed up module usage by writing instead:
IF Safe = 'Y'
THEN {call a procedure of the module}
a) What rule of module programming is violated by such a user?
b) Explain the purpose of the violated rule, with specific reference to this example.
~3. 1.9 Design a module for a timer, which keeps time to the nearest minute in days, hours, and
· utes, with procedures to reset the timer, add a minute, add an hour, add a day, and return the
-:me. Use the module to calculate elapsed time from an input log of time intervals given in the
mat nu where n is a digit and u is one of d, h, or m (days, hours, minutes), each pair of intervals
p ara ted by a blank. For example, a log entry might be 1h 1mD2d2h with the result 2 days, 3
_ n.rs, 1 minute.

3 .2 Data Abstractions

Preview

A module may be thought of as introducing a whole new level of objects into a program- the
abstract level. In abstract terms, the module contains new, higher-level objects than were
available before it was written, and when these objects are used (through the module's
operations), none of the lower-level details need be considered.

ause of the data-access discipline in a module, its procedure part functions can be determined
_ -i:rely from the module itself wit hout reference to the data declared outside. However, two
- ""erent views need to be considered- those of the modu le's implementor and those of its users. To
r k on a module itself, an implementor needs comments that describe the effect of the module's
~ ocedures on its hidden data (which is sometimes called its concrete data). Since the users of a
='Xlule cannot reference this hidden data, comments written for them, explaining how to use an
=... eration, must be written in terms of the abstract data provided by the module. In the counter
-odule, the implementor's comments would describe the effect of each procedure in terms of Ones,
= ens , and Hundreds, while the users' comments wou ld detail the effects of the same operations
=.:o:ing an abstract object called Counter . Counter is not like the other variables in the state in
-_at it was not declared and cannot be directly used in Pascal statements. Instead, the programmer
t he value of Counter indirectly via Start and Bump, and accesses its value using Va l ue.
For example, the original Start comment:
{reset counter to zero}
_:m be replaced by the more precise comments:
{concrete: Ones,Tens , Hun dred s : = '0', '0', ' 0'}
{abstract: Counter : = 0}
-:..e first comment is a concurrent assignment that summarizes actual CF Pascal statements. But
- e second comment uses a variable that is thought of as a natural number,- with a natural-number
3.lue, not available in CF Pascal. Implementors think in the former terms, users in the latter.
The value of an abstract object can be constructed by mapping the values of the variables used
· _ r epresent t he object t o values of the object. This mapping is part of a representation function
-- at maps (concrete) sta tes to (abst ract) states. A representation function extracts an abstract
alue from a state and hides the component variables that comprise an abstract object (which is
::arned by a new identifier). For example, in CountingBlanksinText, the following states might
:.x:ist after the call to Value:

13.2 Data Abstractions 349


(Abstract state)
{counter·210, Ch·#, Xl00·2, Xl0·1, Xl·O }

represen ta t ion function

{Hundreds·2, Tens·1, Ones·O, Ch·#, X100·2, Xl0·1, Xl·O }


(Concrete state)

(INPUT and OUTPUT'have been omitted to save space.)


To define the representation function formally, it will be convenient to let Number map a list
of characters into the, natural number it represents in base-ten notation. For example,
Number(<'Ol,, '01, 'O'>) = 0 (zero)
Number(<'O', '1', '3'>) = 13 (thirteen).
The representation-function R for the counter abstraction above can then be written:
R = {( s, t): t is, the same as s except it contains a new identifier Counter, and t(Counter) =
Number(< IHundredsl(s), ITensj{s), lonesl(s)>) and t does not contain identifiers
Hundreds, Tens, Ones}
R maps three character variables to a single integer variable.
The concrete part function for Value is:
VlOO,VlO,Vl := Hundreds,Tens , Ones
and the abstract part function is:
(O<=Counter<=999 -+ VlOO,VlO,Vl :=
character representation of hundreds digit of Counter,
character representation of tens digit of Counter,
character representation of units digit of Counter )
Even though the abstract part fun ction is defined only for digits, there is no requirement in the
concrete part function that Hundreds, Tens, and Ones be digits. If Value were executed before
Start, the values in Hundreds, Tens, and Ones would be accessed before being set and the
function would return unknown values.
The complications in the concrete part function for Bump arise not only from the arithmetic
th at must be simulated if Ones, Tens, and Hundreds are digits, but from dealing· with cases
where one or more of these values are not digits. ,
( '0' <=Ones<' 9' -+ Ones : = next (Ones ))
(Ones='9' AND ' O'<=Tens< ' 9 ' -+ Ones, Tens : = 'O',next(Tens ))
(Ones= ' 9' AND Tens='9 ' AND 1 0'<=Hundreds<'9 ' -+
Ones,_Tens, Hundreds : = '0 1 , 1 0 1 , next (Hundreds) )
(Ones = '9' AND Tens= 1 9 1 AND Hundreds= 1 9 1 -+ ) )
(
1
0'>0nes OR Ones>'9' -+ )
,where next is defined as:
next= {(x, y): x and yare digits andy follows x in the sequence 01234567890 }
On the other hand, the abstract comment for Bump is quite compact .
(O<=Counter<999 -+ Counter := Counter + 1 )
(Counter < 0 OR Counter >= 999 -+ )

350 PROGRAM MODULES


3.2.1 Exercises
3.2.1 Suppose that the counter module is used in a program whose concrete state at some instant

{INPUT·<tt,tt,R>, OUTPUT·<tso fart,tt,w>, NewCh·?, Hundreds·O, Tens·1, Ones·3,


OldCh·#}
a) What will be the abstract state under the representation mapping R'?
b) Give the concrete and abstract states following the aliased call:
Value(NewCh, NewCh, NewCh)
:3.2.2 Consider a module and its abstraction called StringS which contains strings up to length
- with the operations:
PROCEDURE Empty;
{assign StringS the empty string}
PROCEDURE Append(VAR Ch: CHAR);
{if StringS not full, add Ch to end of StringS}
PROCEDURE Head(VAR Result: CHAR);
{if StringS empty, Result := 1 1
otherwise StringS,Result := tail(StringS),head(StringS)}
PROCEDURE IsFull(VAR Result: CHAR);
{if StringS is full, Result:= 1 Y 1 ,
otherwise Result := 1 N 1 }
a.) Give the necessary variable declaration(s) and write the text of each procedure.
b) Give concrete and abstract comments for each procedure.
c) What is the representation function for StringS?

3.3 State Machines

Preview

he action of a program module is like a formal machine with the property that its responses to
· put depend on its internal state. As each operation is done, the internal state may change,
and thus past actions can influence future ones.

A state machine is a mechanism (real or imagined) that contains a distinguished state called
"' start state, can accept an input, then produce a new state and an output which depend only on
-"' previous state and input. That is, a state machine is defined by a function that maps a
_put, state) pair into an (output, state) pair. For example, a TEXT file behaves as a state machine
which the state is the value of the file, and the inputs and outputs are defined by READ and
·"RITE statements. Taking a file Fl in execution state s with the value:
!Flj(s) = <tABt, tent, R>
d the statement READ (F 1, Ch) as input to the TEXT-file machine, the result IS a new state t
-~ h that

IFlj{t) = <tABct, tnt, R>.


_ e output of the TEXT-file machine could be thought of as the value of Ch:
§j(t) = c.
_ -e same input again (in state t) will not produce the same result because the internal state IS
- :rerent.

13.3 State Machines 351


The foregoing suggests the view that execution of a Pascal program can be viewed as the
successive actions of a state machine whose state is the execution state of the program. The inputs
are the declarations and statements of the program, and there need be no outputs, since full
information appears in the program state.
Program modules can be directly modeled as state machines, in the following way:
The names and values of the data objects protected by a module are the states of the machine .
Each procedure statement using a module procedure is an input that causes the machine to
change state and (possibly) produce an output.
For example, in the counter module, the state of the counter module state machine is given by the
(named) values of Hundreds, Tens, Ones, e.g., as a concrete module state that is a subset of the
execution state for the whole program, say:
{Hundreds·3, Tens·S, Ones·2}
(so that the corresponding abstract state contains Counter·352). The procedure statement
Value(VlOO,VlO,Vl)
is an input that leaves the module state unchanged and returns as output values for VlOO, VlO,
and Vl , namely 3, 5, and 2 respectively. In contrast, the procedure statement Bump is an input
that creates a new concrete state:
{Hundreds·3, Tens·S, Ones·3}
and produces no output.
A state machine with a finite number of states is called a fin£te state machine. The machine
may be described by a diagram in which each state is represented by a circle containing its name
and each transition from one state to another is represented by an arc labeled by the input symbols
that cause the machine to change state. In the following diagram, the symbol x causes a state
change from S to T.

One state of the machine is designated as the start state-it will have an unlabeled arc coming
into it that does not originate at another state. For example, S is a start state in:

Several states of a finite state machine may be final states-they are represented by double circles.
The same state can be both the start state and a final state as in the following sample machine.

352 PROGRAM MODULES


A machine that begins in its start state, makes a transition for each symbol of the input, and
::nishes in one of its final states is said to have recognized or accepted its input. The machine
~ ictured above starts in state S and stops in state S if its input is the empty string or a string
:onsisting of an even number of x.
Machines like the one above are called recognizers or acceptors-they provide a single output:
~ ccess if they are in a final state at the end of the input, and failure otherwise. Other machines,
_ lied transducers, may produce an output symbol for each input symbol read. Labeling an arc with
symbols separated by I indicates that when the first symbol is read the second symbol is written.
~he recognizer above can be made into a transducer that outputs half as many # as it receives
put x.

X I#
A module with only CHAR internal data, like the counter module, can be modeled by a finite
~a.te machine. A module with TEXT internal data cannot be modeled by a finite state machine
cause there is no bound on the length of a TEXT file, and hence no bound on the number of ·
~-ossible states. Finite state machines are extremely useful for describing situations in which a fixed
ber of choices arise from input strings of arbitrary length. For example, a finite state machine
be used to determine if an input string of characters contains an ascending run of length at least
The state can be the last 9 characters. There are a finite number of such states because the
ber of possible characters is itself limited. As each input character arrives; the decision can be
a.de that the 9 saved characters plus the arrival do or do not make up the run of 10. If they do the
er is yes; if no run has yet occurred and the string ends, the answer is no. The new state
= a ins the newly arrived character and drops the oldest character. On the other hand, a finite
e machine cannot be used to determine if a string is a palindrome by keeping the first .half of the
:ing as a state. No matter how large a state is defined, an even larger string may be used as input.
A finite state machine can also help guide program testing. At the minimum, each transition
- uld be explored once by test data. The following BEGIN statement achieves this goal for the
ter module.
BEGIN {test counter module}
Start;
Value(Chl,Ch2,Ch3);
WRITELN(Chl,Ch2,Ch3);
WHILE (Chl<>'9' OR Ch2 <>'9' OR Ch3<>'9')
DO
BEGIN
Bump;
Value(Chl,Ch2,Ch3);
WRITELN(Chl,Ch2,Ch3)
END;
Bump;
Value(Chl,Ch2,Ch3);
WRITELN(Chl,Ch2,Ch3)
END {test counter module}
_:is test tries all the counter values and the transition to the next, but it omits an unlimited
her of t ransitions t hat the mod ule can make, namely from t he 999 state to itself; the test tries
one such t ra nsition . Still, if the output has been carefully observed, this seems a good test of
module.

13.3.1 Exercises 353


13.3.1 Exercises
13.3.1 Explain how a simple variable of type CHAR can be viewed as a state machine.
13.3.2 Which of the following conditions on a given string can be recognized by a finite state
machine?
a) has a unique maximum element;
b) first element greater than last element;
c) has no repeated patterns;
d) has more letters than digits;
e) has more than a million letters.
13.3.3 Create and diagram a recognizer for Pascal identifiers.
13.3.4 Create and diagram a recognizer for Pascal character constants.
13.3.5 What strings does the following finite state machine recognize?

13.4 Queue Modules

Preview

The queue is a useful data structure for holding information in an ordered sequence, much the
way people stand in a line. A queue of characters will be made into a module, implemented in
two different ways, and used to solve the problem of removing extra blanks from text.

A variable of type TEXT provides a facility for storing and retrieving characters in a sequenc
using the special syntax of READ /WRITE statements. TEXT variables and operations observe th
rules of a program module, because the only way to store or retrieve data in a file is through READ
and WRITE.
A queue is a similar storage-and-retrieval facility. It operates first-in, first-out (FIFO
elements are added to the rear of a queue and retrieved from the front of a queue . Thus, a queue ~
like a file except that additions and deletions may be interspersed with one another. For example
consider a queue whose elements are characters. A typical set of operations includes:

Operation Effect
EmptyQ initialize queue to empty
AddQ(VAR Elt: CHAR) append El t value to queue
DelQ remove first element of queue
HeadQ(VAR Elt: CHAR) set E l t to value of queue's first element

If the queue contains one or more elements, DelQ removes the first from the queue and Headx.
returns the first element in the queue by copying it into E l t (but does not alter the queue). If th"'
queue is empty, DelQ has no effect and HeadQ returns the character #.
In the following sections two module implementations are given for a queue of characters. T h-
first implementation, called S l owQueue, is based on a very simple data representation which d
not permit much optimization or efficiency in the procedures of the program modu le. The secon-
implementation (FastQueue) uses a more complex data representation and more comple

354 PROGRAM MODULES


_ rocedures in order to increase the speed of the operations. Although quite different internally, these
o implementations have exactly the same external appearance. The only difference is the time
~equired for execution, not the function or outputs produced . That is, the two modules are
terchangeable in any program that uses them. When FastQueue replaces SlowQueue, no other
_· a nges need be made to a program using the module. The program is isolated fr0m .t he module
_ ange because both observe our module rules.

3.4.1 SlowQueue
? or a first implementation of a queue consider a strategy of maintaining the queue in a single text
- e named Q:
VAR
Q: TEXT
empty queue is represented as a file containing a single line marker. Since the end of queue is
arked in this way, no queue can contain internal line breaks. Each procedure ends with .a RESET
- t he file so that when any procedure starts execution it can assume the file is ready far reading.
_ dding an element to, or deleting an element from the queue, requires WRITE statements. Since the
-:-EXT file representing the queue is open for reading, the current contents of the file must be copred
- a temporary file and then back to the original file. The outline of the queue module with abstract
mments is shown below.
{queue module}
VAR
Q: TEXT;
{include PROCEDURE CopyOpen(VAR Fin, Fout: TEXT)
Append the future string of Fin to Fout.}
PROCEDURE EmptyQ;
{Queue := < >}
PROCEDURE AddQ(VAR Elt: CHAR);
{Queue := Queue & <Elt>}
PROCEDURE DelQ;
{(Queue=<> --> )
(Queue= <X>&Y -->Queue := Y )}
PROCEDURE HeadQ(VAR Elt: CHAR);
{(Queue=<>--> Elt := ' # ' ) I
(Queue= <X>&Y --> Elt :=X)}
{end queue module}
The concrete functions corresponding to these abstract functions will now be implemented.
~ptyQ rewrites Q as a single empty line. In the concrete comment, / is used to indicate the end of
line.
PROCEDURE EmptyQ;
{Q := <,/,R>}
BEGIN {EmptyQ}
REWRITE(Q);
WRITELN(Q);
RESET(Q)
END {EmptyQ}
AddQ copies Q to a temporary file and appends the contents of El t. Then the temporary file
-- copied back to Q.

13.4.1 SlowOueue 355


PROCEDURE AddQ(VAR Elt: CHAR};
{Q=<,x/,R> where x is a string AND Elt = a -->
Q := <,xa/,R> }
VAR
Temp: TEXT;
BEGIN {AddQ}
REWRITE(Temp) ;
CopyOpen(Q,Temp);
WRITELN(Temp,Elt);
{copy Temp to Q}
RESET(Temp);
REWRITE(Q);
CopyOpen(Temp,Q);
WRITELN(Q);
RE.SET (Q)
END {AddQ}
After removing the first character from Q, DelQ copies the remainder of Q to a temporary
file, and then copies the temporary back to Q.
PROCEDURE DelQ;
{ (Q=< , I, R> - - > ) I
(Q=<,ax/,R> where a is a character and x is a
(possibly empty) string of characters -->
Q := <,x/,R>) }
VAR
Temp: TEXT;
Ch: CHAR;
BEGIN {DelQ}
{remove first elt from Q};
READ(Q,Ch);
IF NOT EOF (Q)
THEN {not empty}
BEGIN
REWRITE(Temp);
CopyOpen(Q,Temp);
WRITELN(Temp);
{copy Temp to Q}
RESET(Temp);
REWRITE(Q);
CopyOpen(Temp,Q);
WRITELN (Q)
END;
RESET (Q)
END {DelQ}
HeadQ is much like DelQ except that instead of simply removing the first character from Q
HeadQ copies it to E1 t.

356 PROGRAM MODULES


PROCEDURE HeadQ(VAR Elt: CHAR);
{ (Q=<,/,R> --> Elt := '#') I
(Q=<,ax/,R> where a is a character and x is a
(possibly empty) string --> Elt . - 'a') }
BEGIN {HeadQ}
READ(Q,Elt);
IF EOF (Q)
THEN
Elt := '#';
RESET (Q)
END {HeadQ}
Another procedure, Wr i teQ, can be added to the queue module for diagnostic purposes.
Wr i teQ treats the queue as a list of characters rather than as a queue, writing out one character at
a time until end of file is encountered. Wr i teQ has no abstract comment since it is not really an
operation on queues, but on raw TEXT files. Wr i teQ eases program testing by permitting the
contents of Q to be observed at any point in the computation. Users need not know how the queue
is stored in order to print it, but at the same time Wr i teQ is so simple that it isn't likely to
contribute any problems of its own. When program development is finished, Wr i teQ '"" can be
commented out of the module.
PROCEDURE WriteQ;
{Q=<,x/,R> and OUTPUT=<y, ,W> where x andy are
(possibly empty) strings--> OUTPUT : = <y & xj,,W>}
BEGIN {WriteQ}
CopyOpen(Q,OUTPUT);
WRITELN(OUTPUT);
RESET (Q)
END {WriteQ}

13.4.2 FastQueue
In SlowQueue, AddQ and DelQ do too much file copying, degrading the performance of a user's
program. The number of copies can be reduced in two ways: (1) using two files and a switch to
remember which file holds the current contents of the queue frees us from copying the updated file
back into the original file; and (2) using a last-operation indicator permits sequences of just AddQs
or just DelQs to be performed without file copies.
The underlying data structure to support this more complex operation must now be more than
just the file Q of the slow version:
VAR
Ql, Q2: TEXT;
Switch, LastOp: CHAR;
Either Ql (if Switch has the value 1) or Q2 (if Switch has the value 2) holds the contents of
the queue. To add an element to the queue whose contents are in the TEXT file Ql, the contents of
Ql are copied to Q2, a new element is appended to Q2, and Switch is assigned 2 to indicate that
Q2 rather than Ql holds the contents of the queue. A similar sequence of operations can be used to
add an element to Q2. Thus, the AddQ and DelQ operations now need only one copy operation
instead of the two copy operations that were needed in the slower implementation.
Consecutive applications of either AddQ or DelQ offer another opportunity to improve the
performance of this implementation. AddQ copies one file to another and appends the value to the
new file . Instead of resetting the new file, AddQ can leave it open for writing. If the next operation
is also an AddQ , the new value can be written on the end of the open file. If an AddQ is followed by
any other operation, an end marker is written on the file holding the queue's values and the file is
reset before the new operation begins. Similarly, successive DelQ operations can be implemented by
continuing to read from the same file . When an operation other than DelQ follows a DelQ, the
part of the file remaining to be read is copied to the other file before the new operation bep:ins. (The

13.4.2 FastOueue 357


part of the file already read has been deleted.) LastOp is used to record the last operation so that
these rules can be implemented. Its values are:

LastOp
A AddQ
D DelQ
E EmptyQ
H HeadQ
w WriteQ

The operations for this faster version of queue are much more complicated than the
corresponding ones for the slow version because the operations use two files instead of one to hold the
queue values and these files are not always reset at the end of an operation. EmptyQ, HeadQ, and
Wr i teQ reset the file holding the queue's values so that at the end of one of these operations the file
has the form:
<tt,x V /,R> where x is a string.
Since AddQ leaves the file open for writing, the file has the form:
<x,tt,w> where xis a string.
After DelQ, part of the file may have already been read. This operation leaves the file in the form:
<x,y V /,R> where x and y are strings.
Each of these possibilities must be considered in writing the operations for the que.ue.
EmptyQ arbitrarily chooses Q1 to hold the initial value of the queue.
PROCEDURE EmptyQ;
{Q1,Switch,Last0p := <,/,R>, '1', 'E'}
BEGIN {EmptyQ}
REWRITE(Q1);
WRITELN(Q1);
RESET(Q1);
Switch:= '1';
LastOp := 'E'
END {EmptyQ}
Although Wri teQ is merely a testing aid, not part of the final queue module, it will be
designed next. The reason is twofold: it will help in testing the other procedures to be developed;
and, to write it requires a solid understanding of the representation being used for the queue. In
designing Wri teQ, the major obstacle to be faced is that the file containing the queue's values can
be in any one of three states:
1. <x,y V /,R> (partially read) if the last operation was DelQ .
2. <tt,y V /,R> (ready to read) if the last operation was Wr i teQ, HeadQ, or EmptyQ.
3. <y,tt,w> (partially written) if the last operation was AddQ.
Converting cases 1 and 3 to case 2 greatly simplifies the design task. Since the other queue
operations have similar problems, two procedures, CloseDel and CloseAdd, are introduced .
CloseDel converts case 1 to case 2, if necessary, and CloseAdd converts case 3 to case 2, if
necessary. Therefore, Wri teQ begins with CloseDel and CloseAdd. Once the file containing
the queue's values is in a state corresponding to case 2, CopyOpen can be used to write the values
from Q1 or Q2 to OUTPUT.

358 PROGRAM MODULES


PROCEDURE WriteQ;
BEGIN {WriteQ}
CloseAdd;
CloseDel;
LastOp := 'W';
IF Switch= '1'
THEN
BEGIN
Copy0pen(Q1,0UTPUT);
WRITELN(OUTPUT);
RESET (Q1)
END
ELSE
BEGIN
Copy0pen(Q2,0UTPUT);
WRITELN(OUTPUT);
RESET (Q2)
END
END {WriteQ}
If the last operation was an AddQ, CloseAdd closes out the appropriate file and resets it.
Otherwise CloseAdd does not affect the queue.
PROCEDURE CloseAdd;
BEGIN {CloseAdd}
IF LastOp = 'A'
THEN
IF Switch= '1'
THEN
BEGIN
WRITELN(Ql);
RESET (Ql)
END
ELSE
BEGIN
WRITELN(Q2);
RESET (Q2)
END
END {CloseAdd}
...-r i teQ could be made slightly more efficient by test ing to determine if the last operation was an
_..._ddQ before executing the procedure statement. However, since this test would have been repeated
· many of the other functions, it was placed inside the body of CloseAdd. CloseDel has the
=arne basic structure as CloseAdd-a test to determine if any action is to be taken with the
:mccessful outcome resulting in the queue's values being placed in a file that is open for reading.

13.4.2 Fastaueue 359


Design Part 1
PROCEDURE CloseDel;
BEGIN {CloseDel}
IF LastOp = 'D'
THEN
IF Switch= '1'
THEN
BEGIN
Switch:= '2';
{copy remainder of Ql to Q2 and reset Q2}
END
ELSE
BEGIN
Switch:= '1';
{copy remainder of Q2 to Ql and reset Ql}
END
END {CloseDel}
The remaining desjgn parts of CloseDel are trivial.
Design Part 1.1
{copy remainder of Ql to Q2 and reset Q2}
REWRITE(Q2);
Copy0pen(Ql,Q2);
WRITELN(Q2);
RESET (Q2)
Design Part 1.2
{copy remainder of Q2 to Ql and reset Ql}
REWRITE(Ql);
Copy0pen(Q2,Ql);
WRITELN(Ql);
RESET (Ql)
HeadQ also uses CloseAdd and CloseDel to reduce the number of possible states of the file
containing the queue's values.
Design Part 2
PROCEDURE HeadQ(VAR Elt: CHAR);
{(Switch=l and x,y are (possibly empty) strings
of characters and
((Ql=<x,y/,R> and LastOp=D) or
(Ql=<y,,W> and LastOp=A) or
((Ql=<,y/,R> and
(LastOp=W or LastOp=H or LastOp=E))) -->
Ql, Elt, LastOp := <,yj,R>, head(y & '#'), 'H')
(Switch=2 and x,y are (possibly empty) strings
of characters and
((Q2=<x,yj,R> and LastOp=D) or
(Q2=<y,,W> and LastOp=A) or
((Q2=<,y/,R> and
(LastOp=W or LastOp=H or LastOp=E))) -->
Q2, Elt, LastOp := <,yj,R>, head(y & '#'), 'H') }
BEGIN {HeadQ}
CloseAdd;
CloseDel;
LastOp : = 'H';

360 PROGRAM MODULES


IF Switch= '1'
THEN
{Elt, Ql := head(Ql), RESET(Ql)}
ELSE
{Elt, Q2 : = h e ad(Q2), RESET(Q2)}
END {HeadQ}
Since the two actions remaining to be developed are so similar, it is natural to think of designing
t hem as procedure statements.
Design Part 2.1
{Elt, Ql := head(Ql), RESET(Ql)}
Head(Ql,Elt)
Design Part 2.2
{Elt, Q2 := head(Q2), RESET(Q2)}
Head(Q2,Elt)
~o other program part needs Head, so its declaration can be nested inside that of HeadQ.
Design Part 2.3
PROCEDURE Head(VAR Q: TEXT; VAR Ch: CHAR);
{ (Q=<,/,R> --> Elt := '#') I
(Q=<,ax/,R> where a is a character and
x is a (possibly empty) string of
characters - - > Elt := 'a') }
BEGIN {Head}
IF EOLN(Q)
THEN
Ch := '#'
ELSE
READ(Q,Ch);
RESET(Q)
END {Head}
AddQ either writes its new character value on the end of the current file (if the last operation
as an AddQ), or appends it to a copy of the current file made on the other file (if the last operation
as not an AddQ).

13.4.2 FastOueue 361


Design Part 3
PROCEDURE AddQ(VAR Elt: CHAR);
{ (LastOp=A -->
(Switch=l and Ql=<x,,W> where xis a (possibly empty)
string of characters and Elt=a -->
Ql : = <x & 'a', , W> ) I
(Switch=2 and Q2=<x,,W> where xis a (possibly empty)
string of characters and Elt=a -->
Q2 : = <x & 'a', , W> ) ) I
(LastOp<>A -->
(Switch=l and either (Ql=<x,yj,R> and LastOp=D)
or (Ql=<,y/,R> and LastOp<>A and LastOp<>D)
where x,y are (possibly empty) strings of
characters and Elt=a -->
Ql, LastOp := <y & 'a',,W>, 'A') I
(Switch=2 and either (Q2=<x,yj,R> and LastOp=D)
or (Q2 = <,y/,R> and LastOp<>A and LastOp<>D)
where x,y are (possibly empty) strings of
characters and Elt=a -->
Q2, Las tOp . - <y & 'a' , , W>, 'A') ) ) }
BEGIN {AddQ}
IF LastOp = 'A'
THEN {consecutive AddQs}
{append Elt to the appropriate file}
ELSE
BEGIN
CloseDel;
Las tOp : = 'A' ;
{copy one file to the other and append Elt}
END
END {AddQ}
Design Part 3.1
{append Elt to the appropriate file}
IF Switch= '1'
THEN
WRITE(Ql,Elt)
ELSE
WRITE(Q2,Elt)
After one file is copied to the other and the contents of El t appended to it, Switch must be set to
indicate the new file contains the elements in the queue.

362 PROGRAM MODULES


Design Part 3.2
{copy one file to the other and append Elt}
IF Switch= '1'
THEN
BEGIN
Add(Q1,Q2,Elt); {copy Q1 and Elt to Q2}
Switch := '2'
END
ELSE
BEGIN
Add(Q2,Q1,Elt); {copy Q2 and Elt to Q1}
Switch := '1'
END
Add is similar to the version of AddQ used in the slow queue except that the file into which
a.lues are copied is left open for further writing rather than being reset and copied back into the
riginal file.
Design Part 3.2.1
PROCEDURE Add(VAR QFrom, QTo: TEXT; VAR Elt: CHAR);
BEGIN {Add}
REWRITE(QTo);
CopyOpen(QFrom,QTo);
WRITE·(QTo, El t)
END {Add}
If the last operation was a DelQ, another DelQ removes another character from either Ql or
x2, but Switch is not changed since the same file continues to hold the remaining values of the
eue.

13.4.2 FastOueue 363


Design Part 4
PROCEDURE DelQ;
{(LastOp<>D --> ~
(Switch=l and (Ql=<y,,W> or Ql=<,yi,R>} where
y is a (possibly empty) string of characters ~->
Ql, La stOp : = <, tai 1 (y) & 1 I 1 , R>, ·1 D 1 ) 1 ,
(Switch=2 and (Q2=<y,,W> or Q2=<,yi,R>) where
y is a (possibly empty) string of characters -~>
Q2, La stOp : = <, tai 1 (y) & 1 I 1 , R>, 1 D 1 ) ) 1
(LastOp=D -->
(Switch=l and Ql=<x,yi,R> -->
(y is a non-empty string of characters -->
Ql := <x comp(head(y)),tail(y)& 1 I 1 ,R>
(y is the empty string --> ) ) I
(Switch=2 and Q2=<x,yi,R> -->
(y is a non-empty string of characters -->
Q2 := <x comp(head(y)),tail(y)& 1 I 1 ,R>)
(y is the empty string ~-> ) ) ) }
BEGIN {DelQ}
IF LastOp = 1 D 1
THEN {consecutive DelQs}
{read first element from appropriate file}
ELSE
BEGIN
CloseAdd;
LastOp := 1 D 1 ;
{copy all but the first element of one file
to the other}
END
END {DelQ}
Design Part 4.1
{read first element from appropriate file}
IF Switch= 1 1 1
THEN
ReadOne (Ql}
ELSE
Read0ne(Q2}
Reading the first element from a file can be packaged as a procedure.
Design Part 4.1.1
PROCEDURE ReadOne(VAR Q: TEXT);
VAR
Ch: CHAR;
BEGIN {ReadOne}
IF NOT EOLN(Q)
THEN
READ(Ch}
END {ReadOne}
When one file is copied to the other, the value of Switch must be changed.

364 PROGRAM MODULES


Design Part 4.2
{copy all but the first element of one file to the other}
IF Switch= '1'
THEN
BEGIN
{copy Q1 to Q2 removing first element};
Switch := '2'
END
ELSE
BEGIN
{copy Q2 to Q1 removing first element};
Switch := '1'
END
Copying one file to another while removing the first element can be packaged as a procedure.
Design Part 4.2.1
{copy Q1 to Q2 removing first element} cO

Del(Q1,Q2);
Design Part 4.2.2
{copy Q2 to Q1 removing first element}
Del(Q2,Q1);
Design Part 4.2.3
PROCEDURE Del(VAR QFrom, QTo: TEXT);
{copy QFrom to QTo removing first element}
VAR
Ch: CHAR;
BEGIN {Del}
RESET(QFrom);
REWRITE(QTo);
READ(QFrom,Ch);
CopyOpen(QFrom,QTo);
WRITELN(QTo);
RESET(QTo)
END {Del}
The structure of the fast queue module is shown below. The procedures Head, Add, Del, and
.?eadOne are hidden from the user by nesting their declarations inside the declarations of other
:=' ocedures. CloseAdd and CloseDel must remain exposed since Wri teQ, HeadQ, AddQ, and
:>elQ must all be able to invoke them .
{Include PROCEDURE CopyOpen(VAR Fin, FOut: TEXT)}
PROCEDURE EmptyQ;
PROCEDURE CloseAdd;
PROCEDURE CloseDel;
PROCEDURE WriteQ(VAR FOut: TEXT);
PROCEDURE HeadQ(VAR Elt: CHAR);
PROCEDURE Head(VAR Q: TEXT; VAR Ch: CHAR);
PROCEDURE AddQ(VAR Elt: CHAR);
PROCEDURE Add(VAR QFrom, QTo: TEXT;
VAR Elt: CHAR);
PROCEDURE DelQ;
PROCEDURE Del(VAR QFrom, QTo: TEXT);
PROCEDURE ReadOne(VAR Q: TEXT);

13.4.3 RemoveExtraBianks 365


13.4.3 RemoveExtraBlanks
If RemoveExtraBlanks as designed in Section 10.1.3 is restricted to processing single lines it can
be implemented using a queue. If the text from which blanks are to be removed is already in the
queue, then the characters can be deleted from the queue, tested, and either appended to the rear of
the queue (if they are not blanks) or discarded (if they are blanks). A special marker must be
inserted so the end of the original input ·text can be detected, and one blank must be inserted
between every two words. If the input text is
t now is the timet
and the marker is $, then the queue initially appears as
.n.o.w . . . i.s . . t.h.e . . . t.i.m.e . . $
(periods are shown to visually separate the 20 queue elements-these are not present in the queue).
After 10 elements have been processed from the front, it appears as:
h.e . . . t.i.m.e . . $.n.o.w . . i.s . . t
The design for RemoveExtraBlanks appears below.
Design Part 5
PROCEDURE RemoveExtraBlanks;
{Remove extra blanks between words
on a single line as held in a queue}
VAR
Ch: CHAR;
Blank, LineEnd: CHAR;
BEGIN {RemoveExtraBlanks}
Blank := 1 1 ;
1 1
LineEnd := $ ;

AddQ(LineEnd); {mark end of text in queue}


HeadQ(Ch);
{flush blanks}
WHILE Ch <> LineEnd
DO
BEGIN
{read word};
{flush blanks};
{insert blank between words}
END;
{remove LineEnd from queue}
END {RemoveExtraBlanks}
The HeadQ (Ch) executed before flushing blanks puts the value of the first queue element in Ch
but does not remove it from the queue. If this value is a blank, it must be removed from the que ue
and the new value at the head of the queue assigned to Ch.
Design Part 5 .1
{flush blanks}
WHILE Ch = Blank
DO
BEGIN
DelQ;
HeadQ(Ch)
END
The variable Blank is used instead of the explicit blank character constant; this variable IS

initialized at the start of the procedure .

366 PROGRAM MODULES


Design Part 5.2
{read word}
WHILE (Ch <> Blank) AND (Ch <> LineEnd)
DO
BEGIN
AddQ(Ch);
DelQ;
HeadQ(Ch)
END
Design Part 5.3
{insert blank between words}
IF Ch <> LineEnd
THEN
AddQ(Blank)
e fully assembled program is shown below.
PROCEDURE RemoveExtraBlanks;
{Remove extra blanks between words
on a single line as held in a queue}
VAR
Ch: CHAR;
Blank, LineEnd: CHAR;
BEGIN {RemoveExtraBlanks}
Blank : = 1 1 ;
LineEnd := 1 $ 1 ;
AddQ(LineEnd); {mark end of text in queue}
HeadQ(Ch);
{flush blanks}
WHILE Ch = Blank
DO
BEGIN
DelQ;
HeadQ(Ch)
END;
WHILE Ch <> LineEnd
DO
BEGIN
{read word}
WHILE (Ch <> Blank) AND (Ch <> LineEnd)
DO
BEGIN
AddQ(Ch);
DelQ;
HeadQ(Ch)
END;
{flush blanks}
WHILE Ch = Blank
DO
BEGIN
DelQ;
HeadQ(Ch)
END;
{insert blank between words}
IF Ch <> LineEnd

13.4.3 RemoveExtraBianks 367


THEN
AddQ(Blank)
END;
DelQ {remove LineEnd from queue}
END {RemoveExtraBlanks}
In order to test RemoveExtraBlanks, it is enclosed in a test driver program which transfe rs
characters from INPUT to a queue, calls RemoveExtraBlanks, and prints the contents of the
queue.
PROGRAM TestRemove(INPUT,OUTPUT);
{Reads several lines of input and passes
them to RemoveExtraBlanks with the line
markers replaced by blanks}
VAR
Ch: CHAR;
{include either slow queue or fast queue}
{include PROCEDURE RemoveExtraBlanks}
BEGIN {TestRemove}
EmptyQ;
.
WRITE('The input is • I ) ,•
WHILE NOT EOF
DO
BEGIN
WHILE NOT EOLN
DO
BEGIN
READ(Ch);
WRITE(Ch);
AddQ(Ch)
END;
READ(Ch);
IF NOT EOF
THEN {insert blank between lines}
BEGIN
WRITE(Ch);
AddQ(Ch)
END
ELSE {no blank after last line}
WRITELN
END;
RemoveExtraBlanks;
WRITE('The output is:');
HeadQ(Ch);
WHILE Ch <> '#'
DO
BEGIN
WRITE(Ch);
DelQ;
HeadQ(Ch)
END;
WRITELN
END. {TestRemove}

368 PROGRAM MODULES


Execution
INPUT :The input is : Now is the time for all good
OUTPUT:The output is:Now is the time for all good
The original input was the three-line string:
t Now is t
tthe time for allt
tqoodt

13.4.4 Exercises
13.4.1 Suppose that the operations DelQ and HeadQ were combined in PullQ:
PROCEDURE PullQ(VAR Elt: CHAR)
{Remove first element of queue, and
return its value in Elt}
a) Is this formulation of a queue equivalent to the that of Section 13.4, in the sense that anything
one can do, the other can do? Explain.
b) Suppose that PullQ is to be added to the queue module as another operation. Give the
shortest implementation you can for it.
c) Explain why an implementor who decided to replace DelQ and HeadQ with PullQ, would
not be observing the spirit of the rules for constructing modules.
d) Suppose that an implementation includes PullQ, but not DelQ or HeadQ. Can a module
user (not implementor) obtain the exact actions of the latter operations in terms of what exists
in the implementation? Explain why not, or else give the statements that will do the job.
13.4.2 Compare the performance of SlowQueue and FastQueue when included m
RemoveExtraBlanks, for the input shown at the end of Section 13.4.3. Count the number of
r ead/write operations in each case.
13.4.3 Describe a situation in which FastQueue would be slower than SlowQueue . (Hint: m
what sequence of operations would all the tricks FastQueue uses be to no avail?)
13.4.4 Using an endline marker in RemoveExtraBlanks makes is impossible to handle strings
t hat include this marker as a character.
a) Explain why the endline marker cannot be eliminated by using a test for end of file.
b) Is there a way to avoid wasting a character as an endline marker?
13.4.5 Give an implementation of a queue of characters that holds the queue in five CHAR
variables, and limits the queue size to five.
13.4.6 Use a queue module to write a procedure that reverses a TEXT file. Use diagrams and
English description to explain the idea you plan to use, and then do a careful design by stepwise
refinement.

13.5 Other Storage and Retrieval Structures

Preview

Queues are not the only useful, simple structures that can profitably be implemented as
modules. Sets, stacks, and relations are others.

There a re several simple storage and retrieval structures that can be implemented directly
with modules: sets, stacks, priority queues, relations, and functions.

13.5 Other Storage and Retrieval Structures 369


A stack is a storage and retrieval structure with last-in, first-out (LIFO) access. It can be
visualized as a string or list in which elements are inserted and removed from the same end.
A set is not ordered, but it can be stored as an ordered list or file in many ways, so long as care
is taken in finding, adding, and removing elements no matter where they fall in the order .
The element at the "front" of a priorz.ty queue is determined first by an arbitrary ordering
among elements called priority, and second by time of entry into the queue. Implementing a delete
from such a queue involves finding a minimum (or maximum) priority among the members of the
queue. One implementation would store the members sorted by priority, but in arrival order within
priority.
Relations and functions provide storage and retrieval models in which pairs are added to a set-
like structure, and elements retrieved by matching the first member of the pair. In the case of a
function, a member may not be added if it conflicts with a pair already present. Among the methods
of implementing relations and functions is that of maintaining two files, one for the members of the
domain and the other for the members of the range, keeping the pairs in the same relative location
in the two files. In this way the members of the relation or function can be obtained by reading the
files concurrently. However, questions of the range or domain can be handled by dealing with only
one file.

13.5.1 Exercises
13.5.1 Discuss the ideas needed to add, remove, and test for elements in a set-of-characters module ,
implemented as a TEXT file in the order the characters are added, with
a) duplicates eliminated in the file;
b) duplicates entered in the file.
13.5.2 Create and test a module for strings with the following procedures:
PROCEDURE MakeString(VAR Raw: TEXT)
{Beginning at the current input position in Raw,
create the string from the characters up to but
not including the first $ character.
MakeString is to be undefined if no $ occurs
in the future string of Raw. Leave Raw positioned
following the $ .}
PROCEDURE LeadingUpto(VAR Marker: CHAR)
{Replace the string with its shortest initial
substring that ends just before Marker; if no
Marker value appears, leave the string unchanged.}
PROCEDURE EndingWith(VAR Marker: CHAR)
{Replace the string with the longest substring
beginning with Marker and extending to the end;
if Marker does not appear, replace the string with
the empty string.}
PROCEDURE HeadString(VAR HChar: CHAR)
{If the string has a length of at least 1,
return its first character in HChar, and
remove the first character, reducing its length by 1.
If the string is empty, neither the string nor
HChar should change and an error message
should be printed.}
PROCEDURE TailChar(VAR TChar: CHAR)
{Replace the string with another formed by
adding TChar as an additional, last character.}

370 PROGRAM MODULES


PROCEDURE PrintString
{Write the string to the output (without a newline)}
The string should be stored as a TEXT file with appropriate format and discipline. Describe the
ideas to be used, par ticularly the format and discipline of the TEXT file, using English and diagrams,
before you begin the design. Then do a careful design, including with each procedure both abstract
and concrete comments, being as formal as possible; that is, using string and file mathematical
descriptions.
13.5.3 Create a module to manage a priority queue of characters. Use the same operations as those
in Section 13.4, but give letters A-Z and a-z the lowest priority, digits 0-9 the middle priority, and
all other characters the highest priority. Follow the suggestions of the previous exercise for
documentation.

13.6 Chapter Summary


A program module is a system of procedures and data declarations organized so that the procedures
provide the only means of access to the data. Modules provide two views of the data: implementors
see concrete data components and users see abstract objects. These views are related by a
r epresentation function that combines concrete components into abstract objects, and hides the
concrete components. Separating the representation details of abstract objects from algorithms that
manipulate the objects permits changes to be made in the representation without affecting the
algorithms.
Data abstraction is a more powerful design concept than procedure abstraction because its
mathematical abstraction is a state machine rather than a function . A function always produces .the
same output when presented with the same input. However, a state machine can accept an input
hen produce an output depending on the input and its internal state, and possibly change its
internal state to another state.

13.6 Chapter Summary 371


CHAPTER 14

ORDINAL DATA TYPES

Chapter Preview

D Pascal extends CF Pascal largely by adding new kinds of objects, data types in addition to
characters and files. The ordinal types are those in which the objects are simple rather than
compound-each object is an entity, not composed of other objects. An enumerated type
contains a fixed, finite collection of named objects. The BOOLEAN type has just the two objects
TRUE and FALSE. The INTEGER type is an approximation to the positive and negative whole
numbers. Finally, a portion of a type can be made a type in its own right, a subrange type. Of
course, the types include operations that define what can be done with the objects. For
BOOLEAN, INTEGER, etc., these operations are well known intuitively.

Data types describe sets of values and the operations that can be applied to them. CF Pascal
introduced the data types CHAR and TEXT . The values of type CHAR are the set of legal
characters of the Pascal machine and the operations on these values are the relational operators:
< > <= >= <>
whose meanings are the corresponding mathematical operations, with the inequalities signifying
alphabetical sequence. The values of the data type TEXT are sequences of lines, each a sequence of
characters. The operations on these values are:
RESET REWRITE READ WRITE WRITELN EOF EOLN
Data types offer the advantages of abstraction, replication, and authentication. Data typ e~
abstract important properties of data. For example, to understand the comparison
'A' < 'B'
it is not necessary to know how the characters are represented on the Pascal machine-the collatina
sequence defines the meaning of this operation. Users need not be concerned with the number of biL
used to represent a character value, what is done with any extra bits in a word, whether the bi
pattern for B is exactly one greater than that for A, and so on.
Modules achieve a kind of data abstraction, but data types offer something more. Declaratioll5
of variables of a data type permit the storage and manipulat ion of any number of values of the ty pe
through these variables. Each use of a variable or constant in a program is authenticated to ensure
that only proper operations are applied to it. The context of an operand in a program implies t he
type of the operand; this implied type is redundant information that can be checked against t he
declared type of the operand to prevent operations on values of the wrong type. Consider t he
following program fra gment:
PROGRAM Typex(INPUT,OUTPUT);
VAR
Ch: CHAR;
F: TEXT;
BEGIN {Typex}

IF Ch = F THEN

END. {Typex}
Since = is only defined for character operands in CF Pascal, the appearance of Ch and F as iL
operands implies that both must have values of type CHAR. While Ch is declared to have the righ-
type, F is declared as TEXT so :'en inc0nsistcrcy ·s detected.

372 ORDINAL DATA TYPES


There are two kinds of data types in D Pascal: simple types and aggregate types. Values of
simple types do not consist of finer parts that can be separately manipulated. CHAR is a simple
~ype. Values of aggregate types are formed by combining values of other types.' TEXT is an
~ggregate type, because characters are components of its strings.
The values of simple types are ordered, that is, for each pair of values of the type x, y, exactly
ne of x < y, x = y, or x > y holds. These are therefore called ordinal types. Pascal contains three
~redefined ordinal types denoted by the identifiers: CHAR, INTEGER, and BOOLEAN. In addition
·o t he predefined ordinal types, Pascal has two methods for programmers to define new ordinal types,
~am ely:

1. Enumerated types whose values are unique Pascal identifiers.


2. Subrange types whose values are some of the consecutive values of another ordinal type.
That is, new ordinal types may be defined by enumerating the constants of the type or by specifying
· at the values of the type come from an already-defined subrange of values of an existing type. The
syntax for denoting these ordinal types is:
<type denoter > ::= <type identifier> l <new type>
<type identifier> :: = <identifier>
<new type> ::= <enumerated type> l <subrange type>
e form for <enumerated type> and <subrange type> will be given in the sections below.
When new types are defined, they can be given names in type definitions. These declarations
ust precede the variable declarations in a block:
<block> ::=<type definition part> <variable declaration part>
<procedure declaration part> <statement part>
<type definition part> ::= TYPE <type definitions> l
rule shows that a <type definition part> may be empty (as it has been in all programs up to
point in the text).
<type definitions> ::= <type definitions> <type definition> l <type definition>
<type definition> ::= <identifier> = <type denoter > ;
e context rule that goes with this syntax is that only certain <identifier >s are <type
entifier >s:
To be used as a <type identifier>, an <identifier> must have previously appeared in a <type
definition>.
us m:
TYPE
Tl = CHAR; -
. . .....
_

T2 = Tl;
h Tl and T2 are type identifiers and may be used in the current block to declare variables and
rm al parameters just as CHAR is used.

4.1 Enumerated Types

Preview

T he sit ua tion ofte n arises t hat a small collection of values is needed, each value having a
mnemonic name. Instead of using single-character constants, Pascal programmers may employ
a rbitrary identifiers for these constants.

14.1 Enumerated Types 373


Any list of Pascal identifiers can be declared as a data type, with the one restriction that no
identifier being declared may appear in more than one declaration in a unit of scope. For example
the declarations:
TYPE
DayOfWeek = (Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday, Sunday);
VAR.
Day: DayOfWeek;
defines a new type named DayOfWeek whose seven constant values are written as identifiers
naming the days of the week, and a variable named Day that can take any of the constant values.
Day may only assume a value from the list given in DayO fWeek. The values of DayO fWeek are
ordered as they appear in the type declaration, that is:
Monday< Tuesday< ... <Sunday
Thus, it is possible to write statements such as:
IF Day < Saturday
THEN {not in the weekend}

In the declarations:
TYPE
DayOfWeek = (Sunday, Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday);
WeekEndDay = (Saturday, Sunday); {not legal}
VAR.
Friday: CHAR; {not legal}
the identifiers of the type WeekEndDay and the variable Friday are illegal duplicates of t he
identifiers Friday, Saturday, and Sunday that appear first in DayO fWeek. That is, identifiers
must be unique over all their uses as names of types, variables, procedures, or as constants in a
enumerated type.
Enumerated types are the "supercharacters" of Pascal. They have precisely the same uses ~
ordinary characters and the same operations. But programmers can invent any number of the m
with identifiers that spell out the interpretation of their values. In earlier chapters we used CHAR
constants for this purpose, but enumerated types are better. For example, instead of a procedure
returning Y or N to indicate success, and enumerated type with identifiers Yes and No could he
used, or even one with identifiers Success and F ai 1 ure .
Since the letters of the alphabet are identifiers, they can be used as to define a data type:
TYPE
Alphabet = (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);
VAR.
Letter: Alphabet;
This permits statements such as:
IF Letter = A
THEN
Letter := E
but not:
IF Letter = 'A'
THEN
Letter := 'E'
since A and E are constants of type Alphabet, agreeing with the type of Letter, but 'A' and
'E' are of type CHAR, not the same as t he type of Letter .

374 ORDINAL DATA TYPES


In many problems the order defined by an enumerated type can be useful. For example, in
dealing with dates, the order of the months in the calendar year is significant. While the three-
character abbreviations for the months (e.g., Jan, Feb, etc.) are readily recognized and easily used by
people, these abbreviations used as strings are ordered lexicographically:
Apr, Aug, Feb, Dec, Jan, Jul, Jun, Mar, May, Nov, Oct, Sep,
which is of no use in calendar questions. However, an enumerated type defined as:
TYPE
Month= (Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
provides an automatic way to compare variables according to the calendar, providing their values
are represented as constants of type Month.

14.1.1 Syntax and Meaning of Enumerated Type Expressions


The BNF for enumerated types restricts the constants of the type being defined to identifiers.
<enumerated type> ::= ( <identifier list> )
<identifier list> ::= <identifier list> , <identifier> I <identifier>
Context Rule:
The <identifier >s of an <enumerated type> may not duplicate other declared identifiers in a
unit of scope.
In particular, a given identifier may be part of just one enumerated type within a unit of scope . This
allows a determination of the type of an identifier, and eliminates conflicts of meaning such as that
in the DayO fWeek and WeekEndDay example above, where the ordering of Saturday and
Sunday would be different in the different types.
Enumerated-type variables and constants may be used in assignment statements and with
relational operators, but all the operands used together must be of the same enumerated type . The
value of Boolean expressions using relational operators with enumerated-type values is determined by
he order in which the values appear in the declaration. For example, in any state s,
IA < Bj( s) =TRUE if and only if (I] (s) appears before @] (s) in the declaration of the type
that A and B belong to.
T he situation is similar for the other relational ope~ators.

14.1.2 Enumerated Type Input/Output


Unlike CHAR data values which can be read and written by READ and WRITE statements, there is
no standard way to read or write enumerated type data. ·To illustrate how input/output is done, a
ead/write module will be developed for a type Month representing the months of the year. This
module has two procedures, ReadMonth and Wr i teMonth, but no data declarations. (Thus it is a
pure computation module.) ReadMonth converts three characters in a file open for reading into a
a lue of type Month, if possible. For example, if the next three characters are J, A, N, then
3-eadMonth will deliver the value Jan of type Month. However, if the three characters do not
orrespond to a value of type Month, an error will be signaled. A simple and useful way to signal
n error is to augment Month with one additional value, say NoMonth. Then if the three
haracters read are not the abbreviation for a month, ReadMonth produces the value NoMonth.
The type declaration is:
TYPE
Month = (NoMonth, Jan, Feb, Mar, Apr , May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec)
The module's procedures will most often be used to read from INPUT and write to OUTPUT, but
-he module discipline requires that these procedures be passed the files they are to read and write;

14.1.2 Enumerated Type lnputiOutput 375


otherwise they would access data outside the module.
PROCEDURE ReadMonth(VAR Fin: TEXT; VAR Mo: Month);
{Fin.3=R and length(Fin.2)>=3 -->
read three characters from Fin.2 and assign the
corresponding Month value to Mo if possible;
otherwise assign NoMonth to Mo}
VAR
Chl, Ch2, Ch3: CHAR;
BEGIN {ReadMonth}
READ(Fin,Chl,Ch2,Ch3);
IF (Chl='J') AND (Ch2='A') AND (Ch3='N')
THEN Mo := Jan ELSE
IF (Chl= IF I) AND (Ch2= IE I) AND (Ch3= I B I)
THEN Mo := Feb ELSE
IF (Chl= 'M') AND (Ch2= IA I) AND (Ch3= 'R I)
THEN Mo := Mar ELSE
IF (Chl='A') AND (Ch2='P I) AND (Ch3= 'R I)
.-
THEN Mo Apr ELSE
IF (Chl= 'M') AND (Ch2='A') AND (Ch3= I y I)
THEN Mo := May ELSE
IF (Chl='J') AND (Ch2= 1 U 1 ) AND (Ch3= IN I)
THEN Mo := Jun ELSE
IF (Chl='J') AND (Ch2= I u I) AND (Ch3='L I)
THEN Mo : = Jul ELSE
IF (Chl='A') AND (Ch2= I u I) AND (Ch3= I G I)
THEN Mo := Aug ELSE
IF (Chl='S') AND (Ch2='E I) AND (Ch3='P I)
THEN Mo := Sep ELSE
IF (Chl='O') AND (Ch2= 'C I) AND (Ch3='T I)
THEN Mo := Oct ELSE
IF (Chl= IN I) AND (Ch2='0') AND (Ch3='V')
THEN Mo := Nov ELSE
IF (Chl='D') AND (Ch2= 'E I) AND (Ch3=' c I)
THEN Mo := Dec
ELSE Mo := NoMonth
END; {ReadMonth}
PROCEDURE WriteMonth(VAR FOut: TEXT; VAR Mo: Month);
{FOut.3=W and Mo<>NoMonth -->
write the three characters corresponding to Mo's
value to FOut.l}
BEGIN {WriteMonth}
IF Mo =Jan THEN WRITE(FOut, 'Jan') ELSE
IF Mo =Feb THEN WRITE(FOut, 'Feb') ELSE
IF Mo = Mar THEN WRITE(FOut, 'Mar') ELSE
IF Mo = Apr THEN WRITE(FOut, 'Apr') ELSE
IF Mo = May THEN WRITE(FOut, 'May') ELSE
IF Mo = Jun THEN WRITE(FOut, 'Jun') ELSE
IF Mo = Jul THEN WRITE(FOut, 'Jul') ELSE
IF Mo = Aug THEN WRITE(FOut, 'Aug') ELSE
IF Mo = Sep THEN WRITE(FOut, 'Sep') ELSE
IF Mo = Oct THEN WRITE(FOut, 'Oct') ELSE
=
IF Mo Nov · THEN WRITE(FOut, 'Nov') ELSE
:::r '~· :: ·- De c THEN WRITE ( u.t .• I f' :o -:- I ~
{Wr i t eMonth}

376 ORDINAL DATA TYPES


14.1.3 Comparing Dates
This module can be used to determine the calendar order of two months, given in abbreviation in the
· put.
Design Part 1
PROGRAM CalendarOrder(INPUT,OUTPUT);
{Recognize (if possible) two three-character abbreviations
for months and write them out in calendar order}
TYPE
Month = (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
VAR
Ml, M2: Month;
{include module for Month}
BEGIN {CalendarOrder}
ReadMonth(INPUT,Ml);
ReadMonth(INPUT,M2);
{Examine Ml and M2, and write results}
END. {CalendarOrder}
~ t er both ReadMonth procedure statements have been executed, Ml and M2 each hold a value of
-ype Month (possibly NoMonth). The remainder of the code is straightforward.
Design Part 1.1
BEGIN {Examine Ml and M2, and write results}
IF (Ml=NoMonth) OR (M2 =NoMo nth)
THEN
WRITELN('Badly formed input')
ELSE
IF Ml=M2
THEN
BEGIN
WRITE('Both months are ');
WriteMonth(OUTPUT,Ml);
WRITELN
END
ELSE
{Compare distinct months Ml and M2,
and write results}
END
Design Part 1.1.1
BEGIN {Compare distinct months Ml and M2,
and write results}
WriteMonth(OUTPUT,Ml);
IF Ml < M2
THEN
WRITE(' precedes ')
ELSE
WRITE(' follows');
WriteMonth(OUTPUT,M2);
WRITELN
END

14.1.3 Comparing Dates 377


Execution
INPUT :APRJUL
OUTPUT:Apr precedes Jul
INPUT :AprJul
OUTPUT:Badly formed input
INPUT : JULAPR
OUTPUT:Jul follows Apr
INPUT :DECDEC
OUTPUT:Both months are Dec

14.1.4 Exercises.
14.1.1 Give a syntax tree for each of the following that is a <type definition>, and for the others
explain why each is not.
a)TYPE Swit.ch = (On, Off);
b) TYPE Swi.tch Value = (On, Off) ;
c)TYPE Weekend= (Dayl, Day2);
d)TYPE Weekend= (lstday, 2ndday);
14.1.2 Consider a program containing the following type declarations:
TYPE
WeekDay= (Monday, Tuesday, Wednesday, Thursday, Friday);
WeekEnd= (Saturday, Sunday);
Analyze the expression:
a)Friday <Wednesday
b)Friday <Monday
c)Monday < Saturday
14.1.3 Is the following type definition legal? What would be the consequences of permitting
enumerated types to be defined in this way?
TYPE
NewAlphabet = ( 'Z' , 'Y' , 'X' , 'W' , 'V' , 'U' , 'T' , 'S' , 'R' ,
'Q', 'P', '0', 'N', 'M', 'L', 'K', 'J', 'I',
'H' , 'G' , 'F' , 'E' , 'D' , 'C' , 'B' , 'A')·,
14.1.4 Create a module for
TYPE Switch= (On, Off);
with operations SetOn, SetOff, Flip, and Value and specifications to match the procedure
names. Provide complete instructions for an intended user of the module.
14.1.5 Create an input/output module for enumerated type Color.
TYPE
Color= (Red, Blue, Yellow, Green, Orange);
14.1.6 Consider a set of traffic signals at an intersection and a procedure that examines their values
and determines if they represent a safe combination. Use the following defi!J-itions and declarations.

378 ORDINAL DATA TYPES


TYPE
Light= (Red, Yellow; Green);
Safety= (Safe, Unsafe);
VAR
North, East, South, West: Light;
Condition: Safety;
PROCEDURE Traffic(VAR North, East, South, West: Light;
VAR Condition: Safety);
{Determine the safety of the light settings}
14.1.7 In the spirit of the previous exercise, create enumerated types, variables, and procedures to
analyze a set of traffic signals that have left turn signals as well.

14.2 Type Boolean

Preview

The Boolean operations of NOT, AND, and OR were introduced in Chapter 3. These operations
along with variables form the type BOOLEAN of D Pascal. By giving the syntax for Boolean
expressions in a somewhat different way, the associativity and precedence of the operators can
be specified.

The BOOLEAN type uses the constant values FALSE and TRUE and the operators NOT, AND,
OR, and the relationals. The two BOOLEAN constant identifiers act as though they had been
declared in the enumerated type declaration:
TYPE
BOOLEAN= (FALSE, TRUE);
(But this declaration is not given explicitly.) Thus relational operators are defined on Boolean
operands so that the value of
FALSE < TRUE
is TRUE. NOT is a unary prefix operator and AND and OR are binary infix operators. The
' unctions computed by AND, OR , and NOT are described in Section 3.3.
Pascal guarantees the order in which operators are applied, but not that each operator is
?-PPlied. Many actual Pascal machines perform "lazy" evaluations of Boolean operators. If the first
operand of an OR has value TRUE, the second operand need not be evaluated because
TRUE OR x =TRUE
no matter what x may be. Similarly, if the first operand of an AND has value FALSE, the second
operand need not be evaluated because the value of the entire expression will be FALSE. This
method of evaluation may cause expressions which would otherwise be undefined to have a value .
Boolean variables can be declared and can be assigned values computed with Boolean
expressions. For example, given the declaration:
VAR
EndWord: BOOLEAN;
t he following Pascal statement assigns either the value TRUE or FALSE to EndWord:
EndWord := (Ch = '#') OR (Ch = ' ')

14.2.1 Boolean Syntax 379


14.2.1 Boolean Syntax
The rules of syntax given in Section 3.3 do not cover the case that a BOOLEAN expression may use
variables and constants. Those rules also fail to indicate the structure of legal strings as well as
they might. New rules for BOOLEAN expressions are shown below.
<expression> ::= <simple expression>
l < simple expression> < relat ional operator> <simple expression >
<simple expression> ::= <simple expression> OR <term> l <term>
<term> ::=<term> AND <factor> l <factor>
<factor>::= <variable access> l <constant> l ( <expression> )
l NOT <factor>
It is not syntactically correct to write
X <= Y <= Z
to test that Y has a value between those of X and Z; instead,
(X <= Y) AND (Y <= Z)
is required to make this test. The parentheses in the expression are part of Pascal's syntax. At
~ttempt to construct a syntax tree for the first expression fails:

<expression>

------------- ~
<simple expression>
\
\
<relational operator> <simple expression>

\
\
\
\
\
\
<= <term>
\
\
\
\
\
\

?? <factor>

<variable access>

z
It is impossible to derive ·the string X <= Y from the first <simple expression> without introducing
extra characters into the string (e .g., parentheses). However, the othe r expression can be derived:

380 ORDINAL DATA TYPES


<expression>

<simple expression>

<term>
I
/'\-----___
<term> AND <factor>

/
<factor>
/'\-----___ ( <expression> )

( <expression>
\
\
\
\
\
---- )
''
''
y <=
''
' z

X <= y
Successive left-associative operators of the same precedence m an expressiOn are applied to
• eir operands in left-to-right order. Thus in the expression:
X OR Y OR Z
• e sub-expression:
X OR Y
ev aluated first. The syntax tree for this expression is:

14.2.1 Boolean Syntax 381


<expression>

<term>

<simple expression> <add operator> <term> <factor>

// /
""
<term>

I
<factor>
OR <factor>

<variable access>
<variable access>

z
~
<variable access> y

X
The operands for each OR in the string may be found by locating the strings derived from the
corresponding <simple expression> and <term>. The leftmost OR has operands X and Y, wh ile
the rightmost OR has operands X OR Y (i.e ., the string derived from the first <simple expression>
and Z. Thus the leftmost OR is applied to its operands before the rightmost one. (Of course, the
precedence of operators in an expression can be altered with parentheses.)
A rule of syntax is left recursive if the quantity being defined (i.e., the one to the left of ::=) also
appears as the first symboi to the right of ::=. The rule for <term> is left recursive:
<term> ::= <term> AND <factor>
When an operator is defined using left recursion, it will be left associative as shown by th
arrangement of its syntax trees.
The relative precedence of operators is also defined by the rules of syntax. The syntax tree fo~
X OR Y AND Z illustrates precedence:

382 ORDINAL DATA TYPES


<expression>

I
~X~
<simple ex~ <a/erator> <t;i.m>

<factor>
<term>

<term>
OR

~~
<mult operator> <factor>

/
"'
<variable access>

X
<factor>

"'
<variable access>
/
AND
/
<variable access>

y
Despite being the rightmost operator in the string, AND can be applied to its operands, Y and Z,
::>efore OR can be applied to its operands, X and Y AND Z. Thus AND has a highe"r precedence
- an OR in expressions.

14.2.2 The Meaning of Boolean Expressions


e meaning of a Boolean expression is given by the following recursive definition. For any execution
_ ate S:
IEl = E2l(S) = ( ~(S) =[g(S))
IEl <> E2j(S) = ( jElj(S) ;6jE2j(S))
jEl < E2j(S) = ( jElj(S) < jE2j(S))
IEl <= E2j(S) = ( lElj(S) < jE2j(S))

jEl > E2l(S) = ( ~(S) > [g(S))

jEl >= E2j(S) = ( lElj(S) > jE2j(S))

lEl OR E2j{S) = ( jElj(S) OR jE2j{S))


jEl AND E2j(S) = (!Elj(S) AND jEzj(S))
jNOT Ej(S) = NOT~(S)
(lill(S) =~(S)
T he operators on the left of this definition (e .g., NOT) are those that occur in Pascal programs, while
-hose on t he ri ght (e.g., NOT) are mathematical oper ations. This definition gives the meaning of
each operator sepa rately, but does not specify how an expression with multiple operators is to be
evaluated . The associativity and precedence of operators in an expression are defined by Pascal's
syntax rules. The program calculus requires all operators to be defined; otherwise mathematical
properties such as commutativity (which we use more often than we realize) are violated.

14.2.3 Boolean lnput'Output 383


14.2.3 Boolean Input/Output
Boolean variables cannot appear in READ statements, but Boolean expressions can appear m
WRITE statements. Boolean values are converted to characters and are right-justified m an
implementation-defined size (e.g., 10-character) string. The sequence of statements:
Condition := TRUE;
WRITE(Condition);
appends the string t TRUEt to OUTPUT.

14.2.4 Searching a File


The program SarahRevere in Chapter 2 recognizes the words land and sea in the input. The
design parts for this program have been redeveloped using Boolean variables and are reassembled
below. ·
PROGRAM SarahRevere(INPUT,OUTPUT);
VAR
Wl, W2, W3, W4: CHAR;
Looking, Land, Sea: Boolean;
BEGIN {SarahRevere}
BEGIN {Initialize Wl, W2, W3, W4,
Looking, Land, Sea}
Wl := I I ;
W2 := I
;
I
W3 := ;
W4 := I
;
Looking := TRUE;
Land := FALSE;
Sea := FALSE
END;
WHILE Looking AND NOT(Land OR Sea)
DO
BEGIN
BEGIN {move window, check end of data}
Wl := W2;
W2 := W3;
W3 := W4;
READ(W4);
Looking := W4 <> '#'
END;
BEGIN {check window for 'land'}
Land := (Wl='l') AND (W2='a') AND
(W3='n') AND (W4='d')
END;
BEGIN {check window for 'sea'}
Sea := (Wl='s') AND (W2='e') AND (W3='a')
END
END;

384 ORDINAL DATA TYPES


BEGIN {Create Sarah's message}
IF Land
THEN
WRITELN('The British are coming by land.')
ELSE
IF Sea
TH;EN
·WRITELN('The British are coming by sea.')
ELSE
WRITELN('Sarah didn' ' t say')
END
END. {SarahRevere}
. . . be WHILE statement is controlled by the expression:
Looking AND NOT(Land OR Sea)
hich allows the body of the statement to be executed as long as the value of Looking is TRUE
d the values of both Land and Sea are FALSE. Looking is assigned the value FALSE when # is
=ncountered:
Looking := W4 <> '#'
:..and or Sea is assigned the value TRUE if Wl, W2, W3, W4 contain the corresponding string, e.g.:
Sea := (Wl='s') AND (W2= ' e') AND (W3= ' a ' )
3oolean variables shorten this program from 57 lines to 43.

4.2.5 Exercises
A .2.1 Draw syntax trees for each of the following, making corrections so that each is syntactically
- rrect . In each case give the order in which the operations are performed, and the declarations
=.eeded to make the phrase correct.
a)NOT NOT A
b)A :=NOT B > C
c) Qu AND (A < B)
A.2.2 Given declarations
TYPE
Light= (Red, Yellow,Green);
VAR
North, East, South, West: Light;
n.ich describe conditions of traffic lights at an intersection, create a Boolean expression which
_- aluates to TRUE for safe conditions.
:.4.2.3 Create a window data type with operations Ini tWindow, MoveWindow, and
::l.SpectWindow so that the SarahRevere program in Section 14.2.4 could be written as shown
- low.

14.2.5 Exercises 385


PROGRAM SarahRevere(INPUT,OUTPUT);

BEGIN {SarahRevere}
{Initialize Window, Looking, Land, Sea}
InitWindow;
Looking : = TRUE;
Land : = FALSE;
Sea := FALSE
WHILE Looking AND NOT(Land OR Sea)
DO
BEGIN
{move window, check end of data}
MoveWindow;
InspectWindow(Wl,W2,W3,W4);
Looking:= W4 <> '#';
{check window for 'land'}
Land := (Wl='l') AND (W2='a') AND
(W3='n') AND (W4= 'd');
{check window for 'sea'}
Sea := (Wl='s') AND (W2= ' e ' ) AND (W3='a')
END;
BEGIN {Create Sarah's message}

END
END. {SarahRevere}

14.3 Type Integer

Preview

The INTEGER data type simplifies programming tasks in which counting is useful. All the usual
arithmetic operations are available. Arithmetic using INTEGER operands is fast because most
computers have special support for this data type. However, the payment for this speed is a
limitation on the magnitude of the integer values that may occur.

The INTEGER data type has values that are positive and negative whole numbers and zero.
Each Pascal machine restricts INTEGER values to a subset of the integers:
{-MAX/NT, ... , -2, -1, 0, 1, 2, .. .,MAX/NT}
MAX/NT is a predefined integer constant (typically a power of two) corresponding to the larges
integer value that can be represented on a particular Pascal machine. Variables declared to have
type INTEGER can be assigned any of these values:
VAR
I, J: INTEGER
The INTEGER operators are the relationals and:
+ * DIV MOD
for addition, subtraction, multiplication, division with truncation, and remainder on division.
Addition, subtraction, and multiplication are subject to overflow (see below), but DIV and MOD are
not. The operators + and - can be used both as unary prefix and binary infix operators. Thus the
expression J - I could be written - I + J. Division with truncation usually rounds toward zero
so that for example:

386 ORDINAL DATA TYPES


Operation Value
5 DIV 3 1
-7 DIV 3 -2
10 DIV -3 -3
-5 DIV -2 2

[n the expression I DIV J the result is undefined if the value of J is zero, and zero if the absolute
alue of I is less than the absolute value of J. Otherwise, the sign of the result is positive if I and
have the same sign, and negative if they have different signs.
The result of the expression I MOD J is undefined if the value of J is less than or equal to
ero; otherwise it is the remainder on dividing the value of I by the value of J. For example:

Operation Value
5 MOD 3 2
-7 MOD 3 -1

Modulus is defined using division with truncation so -7 MOD 3 could be written:


-7 - ( -7 DIV 3 * 3)
The normal. rules of arithmetic expression evaluation apply here: operators within parentheses are
e>aluated first; multiplication and division have precedence over addition and subtraction; and
perators of equal precedence are applied left to right . Thus, the value of the previous expression is:

Expression Evaluation
-7 - ( -7 DIV 3 * 3) start with expression in parentheses
-7 - (-2 * 3) DIV has the same precedence as *
-7 - (-6) evaluate expression in parentheses
-1

e integer operators are summarized below.

Integer Operator Table


Operator Domain Range Associativity Precedence
unary + - integer integer right highest
* DIV MOD integer X integer integer left
+ - integer X integer integer left
relationals integer X integer Boolean none lowest

4.3.1 Counting Blanks


Section 13.1, CountingBlanksinText determined the number of blanks in the input file, using
module to do the counting:

14.3.1 Counting Blanks 387


PROGRAM CountingBlanksinText (INPUT, OUTPUT);
VAR
Cht XlOO, XlO, Xl: CHAR;
{include counter module}
BEGIN {CountingBlanksinText}
Start; {reset counter to zero}
WHILE NOT EOF
DO
BEGIN
WHILE NOT EOLN
DO
BEGIN
READ(Ch);
IF Ch = I I
THEN
Bump; {increase counter by one}
WRITE(Ch)
END;
READLN;
WRITELN
END;
WRITELN;
VALUE(XlOO, XlO, Xl); {get counter value}
IF (Xl00= 1 9 1 ) AND (X10= 1 9 1 ) AND (Xl= 1 9 1 )
THEN
WRITELN( 1 The number of blanks is at least 999 1 )
ELSE
WRITELN( 1 The number of blanks is 1 ,XlOO,XlO,Xl)
END. {CountingBlanksinText}
The module defined an abstraction Counter with three component variables. Start assigned t .
character 0 to each of these variables; Bump simulated the addition of one to the charact -
representation of an integer value; and Value returned the value of the Counter. Th
procedures added about 50 lines to the program, making it a total of 75 lines long. With t -
INTEGER data type, Counter can be made an actual program variable:
VAR
Counter: INTEGER
and the procedures Start and Bump become assignment statements:
Counter := 0 {Start}
Counter := Counter + 1 {Bump}
Value is replaced by a reference to Counter, and Counter can be compared to the intege.
constant 999. The complete program is shown below (now some 25 lines).

388 ORDINAL DATA TYPES


PROGRAM CountingBlanksinText (INPUT, OUTPUT);
VAR
Ch: CHAR ;
Counter : INTEGER;
BEGI N rcountingBlanksinText}
Counter := 0; {reset counter to zero}
WHILE NOT EOF
DO
BEGIN
WHILE NOT EOLN
DO
BEGIN
READ (Ch);
IF Ch = I I
THEN
Counter := Counter+!; {increase counter by one}
WRITE(Ch)
I
END; I
READLN; /
WRITELN
END; I
WRITELN;
IF Counter >= 999
THEN
WRITELN('The number of blanks is at least 999')
ELSE
WRITELN('The number of blanks is ',Counter)
END. {CountingBlanksinText}
Executions
INPUT Now is
the time for
all good men.
OUTPUT: Now is
the time for
all good men.

The number of blanks is 9


INPUT
OUTPUT:
The number of blanks is 0
e test for more than three-digit values was retained to make this program look like the one with
• e Counter module; with an INTEGER variable the proper limitation is MAXINT instead. It is a
. mmon {unsafe) practice to ignore MAXINT and hope that the Pascal machine will deal with any
oblems that arise .

14.3.2 Integer Input/Output


e output from CountingBlanksinText looks somewhat strange- many blanks separate the
-:- d of the string from the start of the integer value . When integer values are written into TEXT
::_es, they are conve r ted to characters and placed right-adjust ~d in a fixed-length string {frequently
0 characters long). Leading zeros are removed . Notice the difference between the statements
·,.,~ITE ( '5') and WRITE (5). The first statement appends the string tst to OUTPUT, while the
= cond adds the string

14.3.2 Integer Input/Output 389


t St
because the constants are of type CHAR and INTEGER, respectively.
Pascal has formatting capabilities to control the size of the field in which an INTEGER value
appears right justified. Any of the integer expressions of a WRITE statement can be suffixed with a
colon and an integer expression that indicates the size of field for the expression. For example, if X
and Yare integer variables with the values 123 and 5, respectively, then WRITE (X:Y) appends the
string t 123t to OUTPUT. The new syntax rules for the WRITE statement are:
<writeln parall_leter list> ::= ( <file variable> ) :<write parameter list> :
<write parameter list> ::= ( <write item list> )
<write item list> ::=<file variable> <write parameters>
I <write parameters>
<write parameters>::= <write parameters> <write parameter>
I <write parameter>
<write parameter> ::=<expression> : <expression> I <expression>
Successive integer values can be read from a TEXT file if they are separated by blanks or line
markers. If Intl is an INTEGER variable, the statement READ (Intl) will expect to find an d
read a sequence of characters from INPUT that form a signed integer. The string of characters is
converted to an integer value before being stored in Intl. The syntax for these character
sequences in INPUT is the same as for INTEGER constants in Pascal programs:
<signed integer>::= <sign> <unsigned integer>: <unsigned integer>
<sign> ::= + I-
<unsigned integer> ::=<digit sequence>
<digit sequence> ::= <digit sequence> <digit> I <digit>
<digit> ::= o 1 1 1 z l 3 I 4 1 s I 6 I 7 I a I 9
When an INTEGER variable appears in a READ statement, blanks and line markers (i:.
necessary) are skipped in the file until a nonblank character is reached. The READ operatic
consumes those characters that can be part of an integer constant including leading signs, and stops
just before_the first -character that cannot be part of the constant . The integer value corresponding
to the characters consumed is stored in the variable. For example, given the following string ·
INPUT:
t -12 3/32/t
where /represents the line marker, and the program fragment:
VAR
Il, I2, I3: INTEGER;
BEGIN
READ (I 1 , I 2 , I 3) ;

the execution of the READ statement will produce a state with partial contents:
{Il·-12, I2·3, I3·32}.
When reading a TEXT file containing an unknown number of integers, the final line marker rema~
after the last integer is read. Thus EOF cannot be used as a test for termination, as the prograE
Readints mistakenly attempts to do:

390 ORDINAL DATA TYPES


PROGRAM Readints (INPUT, OUTPUT) ·;
{A mistaken attempt to read all the
integers from the input}
VAR
Val: INTEGER;
BEGIN {Readints}
WHILE NOT EOF DO
BEGIN
READ(Val);
WRITELN(Val)
END
END. {Readints}
After the last integer is read, the line marker remains, and EOF is still FALSE; hence the program
-eads again, and there being no integer value, the READ statement is undefined. The following is
:Jetter:
PROGRAM Readints2(INPUT,OUTPUT);
{Read a file of integers with
attention to the final line marker}
VAR
Val: INTEGER;
BEGIN {Readints}
WHILE NOT EOF DO
BEGIN
IF EOLN
THEN
READLN;
IF NOT EOF
THEN
BEGIN
READ(Val);
WRITELN (Val)
END
END
END. {Readints}
_ ow ever, even this program will fail if the final line mark does not immediately follow the last
t eger, for example, on the input string t123xt or t123 t . It is easier to read integers from a
~XT file using a sentinel value to indicate when the input data_is exhausted (e.g., -99999 or 0,
= me value that cannot legitimately appear in the file).
To illustrate computation with integers, the following program obtains an integer N from the
..::.put, then computes the sum of the first N integers:

14.3.2 Integer lnput'Output 391


PROGRAM Sum(INPUT,OUTPUT);
{ (N>=O --> I,S := N+l,~+2+ ... +N) }
VAR
I, N, S: INTEGER;
BEGIN {Sum}
READ (N);
s := 0;
I := 1;
WHILE I <= N
DO
BEGIN
S : = S + I;
I := I + 1
END;
WRITELN('The sum of the first',N:2,
'positive integers is',S:3)
END. {Sum}
Execution
INPUT :6
OUTPUT:The sum of the first 6 positive integers is 21

14.3.3 Overflow
Pascal INTEGER operations are only guaranteed to produce a correct result if all the valu -
involved lie within the range [MAXINT, MAXINT]. That is, the operands must be in range, and t -
result of the operation as well. For the addition operation, the figure shows the region of correc-
results for X + Y within the dashed lines.

392 ORDINAL DATA TYPES


y

MAXINT
r------------------
''
''
''
''
''
''
''
''
''
''
''
-MAX/NT (0, 0) '' MAXINT
''
' X
''
''
''
''
''
''
''
''
''
''
''
'' -MAXINT
''
' __________________ J

Outside the boundaries defined by MAX/NT, addition does not produce correct results. Starting
wit h operands chosen at random within range, there is 1 chance in 4 that the result wi'lL overflow
1/4 of the area bounded by ±MAX/NT in the figure represents error results).
Similarly, the region for correct results for subtraction X - Y is show-n in the figure:

14.3.3 Overflow 393


y

MAXINT

, , ------------------,
, ,,
,
, ,,
,
, ,,
,
, ,,
,
, ,,
,
-MAX/NT , ,, (0, 0) MAX/NT
,,
, , X

,,
, ,,
, ,,
,
, ,,
,
, ,,
,
, ,,
,
,,
,,
L------------------
-MAX/NT

Again there is probability .25 that overflow will occur in subtracting two random INTEGER values.
Since multiplication can generate large values more easily than addition or subtraction, t h
overflow problem is even more dramatic for this operation. The region of correct results fo
multiplication X*Y is shown below by the solid lines:

394 ORDINAL DATA TYPES


y

4
I
MAX/NT
I

MAX/NT
X
-MAX/NT

-MAX/NT

The curved boundaries are hyperbolas, and the figure distorts the scale to show unit distance from
each axis. In fact the area of correct results is surprisingly small. In one quadrant, the area under
~h e curve Y =MAX/NT /X is given by:
MAXINT
MAX/NT + I MAX/NT dX
1 X
=MAX/NT + MAX/NT ln MAX/NT- MAXINT ln 1.
bus the area under the curve is MAXINT + MAXINT ln MAXINT, while the entire area IS
_ fAXINT2, so the chance of randomly selecting two operands for which overflow will not occur is:
MAXINT + MAXINT lnMAXINT 1 + lnMAXINT
MAXINT2
MAXINT
or MAXINT=109, the chance of correct results from the multiplication of values chosen at random
f'rom type INTEGER is

1 + ln 109 ,......, 50
109 "" 109 '

about 1 in 20 million.
In cont rast to addition, subtraction, and multiplication, the division operators DIV and MOD
~an not cause overflow.

14.3.3 Overflow 395


It is clear that type INTEGER represents only a small-scale, partial model of mathematical
integers. AI:, long as the limitations are observed, the operations are perfect, but it is the
responsibility of the programmer, not the Pascal machine, to honor these limitations. It is a defect
of Pascal that the value of MAXINT must be determined by trial for each different machine, so the
programmer who wants to be careful cannot do so easily. On some machines, computation is
aborted when overflow occurs, so it . is safe to ignore its possibility as long as a surprise program
termination is not import ant. On many machines, however, the results simply stop conforming to
the expected arithmetical rules when overflow occurs.

14.3.4 Integer Syntax


The updated syntax rules for Pascal <expression> are shown below.
<expression> ::= <simple expression>
l <simple expression> <relational operator> <simple expression>
<simple expression> ::= <simple expression> <add operator> <term>
l <term> l <sign> <term>
<term>::= <term> <mult operator> <factor> l <factor>
<factor>::= <variable access> I <unsigned constant>
l ( <expression> ) I NOT <factor>
<relational operator> ::= = l <> l < l <= l > l >=
<sign> ::= + l -
<add operator> ::= + I - I OR
<mult operator> :: = * l DIY l MOD l AND
<variable access>::= <variable identifier>
<unsigned constant> ::= <unsigned number> I <constant identifier>
I <character string>
<unsigned number> :: = <unsigned integer>

14.3.5 Meaning of Integer Expressions


The meaning of an integer expression is given by the following recursive definition for state S:
jEl = E2l(S) = ( ~(S) =~(S))

lEl <> E2j{S) = ( jElj(S) #jE2l(S))

jEl < E2j{S) = ( jEll(S) < lE2l(S))

lEl <= E2l(S) = ( jElj{S) < lE2j{S))

jEl > E2j{S) = ( lElj{S) > jE2j(S))

jEl >= E2j(S) = ( jEll(S) > jE2j{S))

lEl + E2j{S) = {jElj{S) + jE2l(S)) (subject to overflow)

jEl - E2j{S) = ( lElj{S) -jE2l(S)) (subject to overflow)

lEl * E2j{S) = ( jEll(S) X jE2l(S)) (subject to overflow)

396 ORDINAL DATA TYPES


IEl DIV E2j{S) = ( IEli(S) 7 (g(S))
§:}i~~(S) = ( §)(S)- ( §)(S) 7(g(S) X [g(S)))
I+E](S) = @](S)

8)(8) =- ~(S)
!]ill (S) = ~ (S)
The definition gives the meaning of each operator. For an expression with more than one operator,
·he meaning is built up term by term, according to Pascal's syntax rules for precedence and
associativity.

14.3.6 Converting Characters to Numbers


Consider a string of digits of length N:
c1c2···CN-1CN
The correspondence between the string and the number it represents is given by the formula:
N
~ ( ci X BN-i) where B stands for the base and c is the numeric value of the i-th character in the
i -1
:> r ing. Thus the number 0101 stands for the following base-ten values in the indicated bases:
0101 2 = OX23 + 1X22 + OX2 1 + 1X2° = 0 + 4 + 0 + 1 = 5 10
0101 8 = oxsa + 1xs2 + oxs1 + 1xs0 = o + 64 + o + 1 = 65 10
0101 10 = OX103 + 1X102 + OX101 + 1X10o = 0 + 100 +0 + 1 = 101 10
0101 16 = OX163 + 1X162 + OX161 + 1X16o = 0 + 256 + 0 + 1 = 257 10
e "digits" of bases greater than ten are often represented by letters, e.g., the digits of base sixteen
are:
0, 1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
The best strategy to find the base-ten value of the base-B expression:
c 1 X BN- 1 + c2 X BN- 2 + ... + eN X B0
!s to factor it using Horner's rule:
( ... (c 1 X B + c 2 ) X B + c3 ) ... X B + eN ).
The program StringToint converts strings of characters in the base specified in Base to numeric
alues in Result using Horner's rule.
Design Part 1
PROCEDURE StringToint(VAR F : TEXT; VAR Base,Result: INTEGER);
{F=<,x#y,R> where x is a string of digits and
Base is an integer, 2<=Base<=l6, indicating
their base --> F,Result : = <x#,y,R>,N(x)
where N(x) is the integer value of the character
representation x}
VAR
Ch: CHAR;
Digit: INTEGER;
BEGIN {StringToint}
Result : = 0;
READ(F,Ch);
WRITE(Ch) ;
WHILE (Ch <> ' #') AND (Ch <> I ' )
DO

14.3.6 Converting Characters to Numbers 397


BEGIN
{Digit := number corresponding to Ch in Base};
Result := Result*Base + Digit;
READ(F,Ch);
WRITE(Ch)
END
END; {StringToint}
The code to find the integer corresponding to Ch is straightforward but long, so it is packaged in a
procedure.
Design Part 1.1
{Digit := number corresponding to Ch in Base}
CharToDigit(Ch,Digit);
In this problem, only Str ingToint references CharToDigi t so the latter's declaration can be
placed inside the former's declaration.
PROCEDURE CharToDigit(VAR Ch: CHAR;
VAR Result: INTEGER);
{( 10 1<=Ch<= 19 1 OR 1A 1<=Ch<= 1F' -->
Result : = number corresponding to Ch)
(NOT(0 1<=Ch<= 1 9 1 OR 1A 1<=Ch<= 1F') -->
Result := 0)}
BEGIN {CharToDigit}
IF Ch = 1 0' THEN Result := 0 ELSE
IF Ch = 1 1 1 THEN Result : = 1 ELSE
I 2 I
IF Ch THEN Result := 2 ELSE
I 3 I
IF Ch = THEN Result := 3 ELSE
IF Ch = '4' THEN Result .- 4 ELSE
IF Ch = 151 THEN Result := 5 ELSE
IF Ch = 16 I THEN Result := 6 ELSE
IF Ch = '7' THEN Result := 7 ELSE
IF Ch = I 8 I THEN Result := 8 ELSE
IF Ch 191 THEN Result : = 9 ELSE
IF Ch = 'A' THEN Result := 10 ELSE
IF Ch = 'BI THEN Result := 11 ELSE
IF Ch = IC I THEN Result .- 12 ELSE
IF Ch = IDI THEN Result := 13 ELSE
IF Ch = lEI THEN Result := 14 ELSE
IF Ch = IFI THEN Result := 15
ELSE
BEGIN
Result := 0
END
END; {CharToDigit}
To test this design, it is placed in a program that first reads a base-ten representation of the base ·
which the following string is to be interpreted.

398 ORDINAL DATA TYPES


PROGRAM Convert(INPUT,OUTPUT);
VAR
Base, Number: INTEGER;
{Include PROCEDURE StringToint
(VAR F: TEXT; VAR Base, Result: INTEGER);
( F=<,iDx#y,R> where i is a base-ten integer
between 2 and 16 and x is a st-ring of digits
--> F,Number := <iDx#,y,R>,N(x) )
where N(x) is the base-l value of x }
BEGIN {Convert}
READ(Base);
StringToint(INPUT, Base, Number);
WRITELN;
WRITELN('The base-ten value is:', Number)
END. {Convert}
Executions
INPUT :8 0101#
OUTPUT:8 0101#
The base-ten value is: 65
INPUT : 16 1F#
OUTPUT:16 1F#
The base-ten value is: 31

14.3.7 Exercises
14.3.1 What is the value of MAX/NT in your Pascal machine? What happens when you try to read
a. value larger than MAX/NT from · the input? What happens when you try to write a constant
arger than MAX/NT as a part of a Pascal program? What happens when you try to add two
· t egers whose sum exceeds MAX/NT?
14.3.2 Suppose that the expression
(X + Y*Z) DIV W
ing INTEGER variables X, Y, Z, W occurs in a program, and the programmer wishes to be
ertain that no overflow ever occurs when it is calculated. Given that the machine being used has a
alue of MAX/NT held in the INTEGER variable MI, write appropriate IF statements to protect
-his expression.
4.3.3 In Section 14.3.2 a program Readlnts2 was written to read a series of integers from the
· put, being careful about line markers. That program cannot handle the situation in which the final
!nteger is followed by extraneous characters.
a) Explain exactly what Readints2 does when it fails.
b) Write Readlnts3 that does not fail no matter what characters follow the final integer.
c) Write Readlnts4 to permit arbitrary extraneous characters to follow any of the integers in
the file . (Warning-the most difficult part of this exercise is in stating the requirements for
Readlnts4. Spend as much time as you can thinking about what the program is supposed to
do before starting a. design.)
4.3.4 Which (if any) of the following expressions are legal? Justify your answers by drawing syntax
-rees.
a) - -X

b)-( - X)

14.3.7 Exercises 399


c) A * B < C

d) X * -Y
14.3.5 Given execution state {A·5, B·25, C·IO}, work out the meanings of the following expressions:
B * C MOD (A + 3)
B * C MOD A + 3
B + C DIY A + 3
B + C DIY (A + 3)
B MOD (A DIY B)
14.3.6 Given an integer N in the input, design and test a program to find the sum of integers up to
N.
14.3.7 Given an integer N in the input, design and test a program to find the sum
1 + 2 + · · · + NN.
1 2

14.3.8 Design and test a procedure to print the value of an INTEGER variable, with no preceding
blanks. (Hint: compute and use a format size to print the integer.)
14.3.9 Given an integer N in the input, design and test a program to find the sum of all the factors
(except 1) of N.
14.3.10 Given an integer N in the input, design and test a program to find the maximum difference
between a number and the sum of its factors (except I) for all numbers between 2 and N. Can you
find an analytic solution to this problem?
14.3.11 Given a file of integers, determine the minimum, maximum, and average (including
fractional part to two decimal places).
14.3.12 Given a file of integers, determine the median.
14.3.13 Design and test a module for doing extra-precision addition and multiplication for numbers
stored in representations of your choice on a file.

14.4 Subrange Types

Preview

When a variable should assume values in a limited range, it may be restricted to a subrange of
another ordinal type. The restriction helps to catch mistakes that would send the values out of
range.

New Pascal types can also be formed by specifying their values to lie within a subrange of t _
values o( an existing ordinal type. The subrange is defined by listing the minimum and maxim u~
constants that are part of the subrange.
<subrange type> ::=<constant> .. <constant>
<constant> ::=<sign> <unsigned number> I <unsigned number>
I <constant identifier> I <character string>
Consider the following declarations:

400 ORDINAL DATA TYPES


TYPE
Letter = 'A' I z I;

Smallint = 0 100;
Smallerint = 1 .. 10:
VAR
L: Letter;
X: - Smallint;
Y: Smallerint;
The type from which the constants defining the subrange type are drawn is called the host type. All
operations of the host type may be applied to operands of the subrange type. In the example above,
he host type of Letter is CHAR, and that of Smallint and Smallerint is INTEGER. Thus
CHAR operations can be applied to L, and INTEGER operations to X or Y.
BEGIN

IF L ='A' THEN ... {compare Letter and CHAR}


WRITE(X + Y); {add INTEGER values}

END
Once an enumerated type has been declared, its constants may be used to define subrange
7 pes whose host type is the enumerated type. The declaration of new subrange types (like
· eekDay and WeekEndDay in the following example) are not duplicate declarations of the
"dentifiers used to define the subrange type.
TYPE
DayOfWeek = (Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday, Sunday);
WeekDay= Monday .. Friday;
WeekEndDay =Saturday .. Sunday;
(Since character strings are constants, the syntax rules seem to permit defining subranges of
haracter strings; however, this is not legal.)

14.4.1 Subrange Assignments


is not always possible to determine in advance whether a statement containing subrange values
o.n violate the subrange boundaries. Consider the following assignment statement involving
Smallint X and Smallerint Y declared above:
y := y + X
"""ince the host type is INTEGER, it is legal to apply the addition operator to these operands. The
add ition yields an INTEGER value that may be assigned to Y because its host type is INTEGER.
However, if the result of addition is outside the declared range of values for Y (i.e., 0 .. 10 ),
rogram execution will terminate with an error message.
Subrange types require redefinition of the meaning of the assignment statement so that the
unction of the assignment statement (but not that of the expression) is undefined if the value of the
expression is outside the declared range of the variable on the left side of the assignment.
IV := El= {<s, t>: @j(s) E Vals(V), t = (s- {<V, c>: c E Vals(V)}) U {<V, @j(s)>}},
where Vals( V) is the set of all values for the type of V.

14.4.2 Temperature Conversion


The following example converts temperatures below the boiling point of water from Farenheit to
Celsius. Both variables could be declared as integers; however, knowledge of the problem domain
provides a meaningful restriction of the values.

14.4.2 Temperature Conversion 401


PROGRAM Temperature(INPUT,OUTPUT);
VAR
Farenheit: -459 .. 212;
· Celsius: -273 .. 100;
BEGIN
READ(Farenheit);
Celsius := (Farenheit- 32)*5 DIV 9;
WRITELN(Farenheit:4 , ' _degrees Farenheit or',
Celsius:4,' degrees Celsius')
END.
Execution
INPUT :212
OUTPUT: 2~2 degrees Farenheit or 100 degrees Celsius
INPUT :32:
OUTPUT: 32 degrees Farenheit or 0 degrees Celsius
INPUT :-459
OUTPUT:-459 degrees Farenheit or-272 degrees Celsius
INPUT : 10.0 0
OUTPUT:Value 1000 is out of range
The last output is not produced by the program but by the Pascal machine which detects that 1
is outside the range of allowable values for Farenheit.
Subrange types help document a program and shift the burden of checking the legality
assignments from the programmer to the Pascal machine. This often r esults in a smaller, m
easily understood program. In addition, values with subrange types may require less storage tha=.
identical host-type values.

14.4.3 Exercises
14.4.1 Which of the following are correct subrange types:
a)Letter = 0 .. 100
b)Reverse = 'Z' 'A'
c)Temp = 32 ... 212
d) One = 1 .. 1
14.4.2 The use of subrange types in assignment statements slows down a Pascal program whe
checks are made.
a) Explain why the speed suffers only when the subrange variable appears on the left side of t
assignment.
b) Make timing comparisons on your computer to determine how bad the slowdown is.
14.4.3 Suppose that for a particular computer, MAX/NT is 1,000,000. Explain why a programm
might want to declare:
VAR
X: -1000000 .. 1000000
instead of
VAR
X: INTEGER

402 ORDINAL DATA TYPES


14.5 Design and Analysis with Ordinal Types

Preview

The design rules for OF Pascal extend to ordinal types with little change. Analysis of programs
is also similar, but the operators of the INTEGER type require some new techniques. The
accumulating iteration is particularly easy to analyze.

14.5.1 Design Rules


The design rules for BEGIN, IF, and WHILE statements in OF Pascal can be extended for D Pascal
and its ordinal types. The single change required is in the preservation rule for BEGIN statements.
For OF Pascal it is:
At each step, preserve all original values of the right side of the intended concurrent
assignment.
The word "preserve" can be replaced by the phrase "preserve the ability to compute," that is:
At each step, preserve the ability to compute all original values of the right side of the
intended concurrent assignment.
Since arithmetic operations have inverses, satisfying the new preservation rule will not always
require introducing temporary variables.
Consider the problem of designing a BEGIN statement to exchange two integer values without
using a temporary variable.
X, Y := Y, X
Although a temporary variable would be required to preserve either the value of X or Y before it is
changed by the first assignment, one value and the sum of the two values preserves the ability to
compute them both, since the value of one variable can be subtracted from the sum to obtain the
other. That is, the following design is acceptable:
BEGIN
X .-
X+Y;
y .- X-Y;
X := X-Y
END
The values of X and Y must of course lie in the interval [-MAX/NT, MAX/NT], but the
interchange will not operate as desired for all such values. The results of each operation must also
fall within this interval. The conditions of correct execution are calculated in the following symbolic
execution. The abbreviation inrange(X) means that the value of X is within the interval [-MAX/NT,
MAX/NT].
Part Condition X y

X y
X := X + y inrange(X + Y) X+Y
y .- X - y inrange(X + Y- Y) X+Y-Y=X
X := X - y inrange(X + Y- X) X+Y-X=Y

The conditions evidently all hold if the first one does, and this also forces inrange(X) and inrange(Y),
so the function computed by this exchange code is actually:
(z'nrange (X+Y) -+ X, Y : = Y, X)
Overflow is not often treated this formally, but the problem always exists.
As another example of design using the INTEGER type, consider simulating addition by
repeatedly arlding one. That is, to add the values of X and Y, design a WHILE statement to add
one to X, Y ~imes. Overflow will henceforth be ignored, so the desired function is:

14.5.1 Design Rules 403


f = ( 0 <= Y - X : = X+ Y)
The existence condition of the WHILE design rule reqmres that range(!) C domain(}), which is
satisfied; and, for each s E range(!), f(s) = s. The latter condition fails. Consider a state s 1
range(}) for which the value of Y is nonzero. f maps this state to one in which the value of X is
replaced by the value of X+Y, which is necessarily different, hence the new state cannot be s as
required by the existence condition.
Perhaps the WHILE statement could be designed if the value of Y is allowed to change, bu-
the same argument shows that the second part of the existence condition will fail unless the ne
value of Y is zero. Thus consider
g = (0 <= Y - X,Y := X+Y,O)
as the desired function. The range(g) is those states in which the value of Y is zero, and g is define
on all such states, so
range(g) C domain(g)
is satisfied. So is the second part of the existence condition,
g(s) = s for all s E range(g),
because any state s E range(g) has Y value zero, and so g adds zero to the X value, leaving t-
unchanged.
With the existence condition satisfied, the WHILE condition B must be chosen such that:
@] (s) is TRUE for all s E domain(g)- range(g),
@](s) is FALSE for all s E range(g).
In domain(g), the value of Y is nonnegative, and since the Y value is zero in range(g), the simple_
expression forB is Y>O.
Finally, a BEGIN statement D must be chosen such that
WHILE Y>O DO D
terminates for every state in domain(g), and
jiF Y>O THEN Dl
preserves the values needed to reach the final state, that is, X+ Y.
To make progress toward termination Y can be decremented. In order to preserve X+Y, _
must be incremented by the same amount that Y is decremented. Thus the WHILE statemen-
designed for g is:
WHILE Y > 0
DO
BEGIN
y := y 1;
X := X + 1
END

14.5.2 Program Analysis


Programming tasks do not always begin with design--often an existing program must be understood
in order to modify it, for example. Because the program analysis techniques developed for CF Pasc~
depend only on the definition of the box functions, they can be applied to additional data types lik
INTEGER. Consider the program fragment:

404 ORDINAL DATA TYFES


BEGIN
X := 3;
y := 5;
{(Y>=O ~-> X,Y := X+Y,O) (Y<O --> )}
WHILE Y > 0
DO
BEGIN
y := y - 1;
X .- X + 1
END
END
The intended function F for the WHILE statement is given by the comment:
F = (Y>=O --> X,Y := X+Y,O) (Y<O --> )
e WHILE verification rule requires the following three conditions:
1. domain(F) =domain( !WHILE Y>O DO BEGIN Y:=Y-1; X:=X+1 ENDj)
2. (Y<=O -+F) = (Y<=O -+ )
3. F =I IF Y>O THEN BEGIN Y:=Y-1; X:=X+l ENDio F
The domain of F is all states, so termination is required for all values of Y. If the value of Y
.:_ negative or zero, the WHILE statement is skipped so termination is assured. If the value of Y is
:;: itive, the WHILE statement is executed and Y is decremented, so eventual termination is assured
-~cause the value of Y approaches 0. Thus the first condition is satisfied.
We can rewrite the left side of the second condition so that it is identical to the right side.
Y<=O -+ F
= (Y<=O-+ (Y>=O-+ X,Y : =X+Y ,O)) I (Y<=O -+ (Y<O -+ ) )
= (Y<=O AND Y>=O-+ X,Y : = X+Y,O) I (Y<=O AND Y<O -+ )
= (Y=O-+ X,Y := X+Y,O) I (Y<O -+ )
= (Y<=O -+ )
Finally, consider the right side of the third condition:
IIF Y>O THEN BEGIN Y:=Y-1; X:=X+1 ENDioF
-:he function corresponding to the IF statement is:
I IF Y>O THEN .. ·I = (Y>O -+ X' y : = X+ 1' y -1 ) (Y<=O -+ )
- I IF Y>O THEN •. . 1o F is:
( (Y>O -+ X, Y := X+1, Y-1) I (Y<=O -+ ) ) o
(Y>=O -+ X, y : = X+Y' 0) I (Y<O -+ )
There are four cases to consider, corresponding to the first and second conditions of the two
_ nd itional assignments. Name these cases b which conditions are selected; for example, case 1-2
:::1eans the first condition of IF Y>O THEN ... and the second condition of F.

Case 1-1
Part Condition X y

IF Y>O X + 1 Y - 1
F Y-1>=0 X+1+Y-1 = X+Y 0

e condition simplifies to Y>=1, so the function is:


(Y>=1 -+ X,Y := X+Y,O)

14.5.2 Program Analysis 405


Case 1-2
Part Condition I X I .Y
IF Y>O I X + 1 I y - 1
F Y-1<0

The condition is:


Y>O AND Y-1<0 = Y>O AND Y<1
which cannot be satisfied, so this case does not occur.

Case 2-1
Part Condition I X I Y
IF Y<=O
F Y>=O I X+Y I 0

The condition simplifies to Y=O, so the function is


(Y=O --+ )
because when Y is zero, setting it to zero and adding it to X makes no change.

Case 2-2
Part I Condition I I X Y
IF Y<=O
F Y<O

Thus the function is:


(Y<O --+ )
Putting the four part functions together:
(Y>=1 --+ X, y := X+Y, 0) I (Y=O - ) I (Y<O - )
= (Y>O --+ X, y := X+Y, 0) I (Y<=O --+ )
Thus the right side of the third condition is identical to F, as was to be shown.
Since the conditions of the WHILE verification rule are satisfied, F is the function computed b.
the WHILE statement.
Composing F with the function of the first assignment statements:
(X, Y : = 3 , 5) o F
gives the function for the entire program fragment:
(5>0--+ X,Y := 3+5,0) I (5<=0--+ X,Y := 3,5)
= (X,Y := 8,0)
The intended function for a WHILE statement is not always given as a conditional assignmen
often the intended function must be obtained from the program. In the previous example, it woul
have been easy to determine the function for the WHILE statement because both the assignmen~
statements in it are accumulating assignment statements. In an accumulating assignment a
variable's value is altered by adding or subtracting a fixed value. The mathematical concept of
summation (i.e., repeated addition) is a natural choice for a function to describe the repeated effecL
on X and Y. Since the loop is executed as long as Y>O, and the value of Y is decreased by 1 during
each execution, the summation will contain Y terms. On each iteration, since both X and Y are
altered by the constant 1, this is the term to be summed. The sum is added to the initial value of X
and subtracted from the initial value of Y. Thus the WHILE-statement function is:

406 ORDINAL DATA TYPES


y y
X,Y :=X+ E 1, y - E 1
k = 1 k = 1
ese values can be simplified:
y
X + E 1 = X+ 1(Y-1+1) = X+Y-1+1 = X+Y
k =1
y
y - E 1 = Y- 1(Y-1+1) = Y-Y-1+1 = 0
k =1
he function is:
(X,Y :=X+Y,O)
before.
This technique can be used to determine the function of another WHILE statement . The
~asc al fragment below was written to simulate the operators DIV and MOD using only addit ion an
-·~bt raction. Its function is intended to be:
f = (Numerator>=O AND Denominator>O -+
Quotient, Remainder :=
Numerator DIV Denominator, Numerator MOD Denominator)
·.nile the function of the entire fragment is given, the function computed by the WHILE stat eme......
ust be determined in order to verify that the fragment computes f.
Quotient := 0;
Remainder := Numerator;
WHILE Remainder >= Denominator
DO
BEGIN
Quotient := Quotient + 1 ;
Remainder := Remainder - Denominator
END
Let the values of Quotient, Remainder, and Denominator be Q, R, and D, respectively.
Th e test for termination is R > D , or R - D > 0, so the loop will be executed k times, where
k = (R-D) 7 D =R 7 D- 1
Th us, Q and R will be changed to:
R.:,.D
Q + E 1
"= 1
R.:,.D
R- ED
k = 1
respectively. These expressions reduce to:
R-7-D
Q + E 1 = Q + 1( R 7 D)
k = 1
R"':-D
R- E D = R- D(R 7 D).
k = 1
T he function for the WHILE statement is thus:

14.5.2 Program Analysis 407


(Remainder>=Denominator AND Denominator>O -+
Remainder,Quotient :=
Remainder - (Remainder DIV Denominator) * Denominator ,
Quotient + (Remainder DIV Denominator))
(Remainder<Denominator -+ )
The verification that this is the WHILE-statement function, and that the program fragment =-
correct for the given intended function J, are similar to those in the previous example .

14.5.3 Exercises
14.5.1 Given the problem of designing a BEGIN statement to exchange two integer values witho··-
using a temporary variable, i.e., to achieve
X,Y := Y,X
the first step
BEGIN
X := X - Y;

END
satisfies the preservation rule . Complete the design of this BEGIN statement and determine t il?
actual condition for its validity because INTEGER values are restricted to the range [-MAXINT
MAXINT].
14.5.2 Use the design rule for BEGIN statements to achieve
X,Y,Z := X-Y,x+Y,X
14.5.3 Show that the function :
y
(Y>O -+ X, Y : = X - :E Y - i, 0 ) (Y<o -+
i =1
is computed by the WHILE statement:
WHILE Y > 0
DO
BEGIN
Y := -Y - 1;
X := X - Y
END
14.5.4 Show that the function:
(X>=O -+ X := X MOD 3)
is computed by the WHILE statement:
WHILE (X>2) OR (X<O)
DO
X := X - 3

14.5.5 Show that the function:


(X>Y-+ X,Y := X-2((X-Y+3) DIV 4), Y+2((X-Y+3) DIV 4))
(X<=Y -+ )
is computed by the WHILE statement:

408 ORDINAL DATA TYPES


WHILE X > Y
DO
BEGIN
X := X - 2;
y := y + 2
END
A.5.6 The Fibonacci numbers are defined recursively as:

Fib(n-1) + Fib(n-2) if n>2


Fib(n) = {1 if n=l.
0 if n=O
Consider the following WHILE statement:
WHILE (K<N) AND (N>O)
DO
BEGIN
K := K + 1;
F : = F + G;
G := F - G
END
Tnstead of determining the function for all the variables in the WHILE statement, restrict atten -
;o F and demonstrate

!wHILE (K<N) ... I=


(K<N AND N>O -+ F := Fib (N-K+1) *F + Fib (N-K) *G)
(K>=N OR N<=O -+ )
14. 5.7 Consider the following procedure:
PROCEDURE Fact(VAR M,A: INTEGER);
BEGIN {Fact}
IF A > 1
THEN
BEGIN
A := A - 1;
Fact(M,A);
A := A + 1;
M := M * A
END
ELSE
M := 1
END {Fact}
Show that
IFact (M,A) j({M·?, A•a}) = {M·a!, A·a}
if a > 0. What is the result if a < 0?
14.5.8 Is the following recursive procedure:

14.5.3 Exercises 409


PROCEDURE Sum(VAR L,H,I,R: INTEGER);
BEGIN {Sum}
IE' L <= H
THEN
IE' L = H
THEN
R := I + R
ELSE
BEGIN
R := R + I;
L := L + 1;
Sum(L,H,I,R);
L := L - 1
END
ELSE
R := 29
END {Sum}
correct with respect to the specification:
(L<=H-+ R := R+I*(H-L+1))
14.5.9 What functions are computed by the following Pascal fragments?
a)WHILE T <> Y
DO
BEGIN
W := W * X; T := T + 1
END
b)WHILE X<> N
DO
BEGIN
X := X + 3; Y := Y + X
END
c)S := 0;
WHILE X <> 0
DO
BEGIN
T := 0;
WHILE T <> Y
DO
BEGIN
s := s + 1;
T := T + 1
END;
X := X - 1
END
14.5.10What function is computed by the procedure statement:
Sum(X,Y,Z)
where Sum is declared as follows:

410 ORDINAL DATA TYPES


PROCEDURE Sum(VAR X,Y,Z: INTEGER);
BEGIN {Sum}
IF X <= Y
THEN
BEGIN
X := X + 1;
Z := Z + X
END
ELSE
BEGIN
y := y + 1;
Z := Z + Y;
Sum (X, Y, Z)
END
END {Sum}
- 5.11 Write a Pascal fragment to implement the function:
Arb,Extra := Arb+Extra,O
--erify your answer.
5. 12 Write a Pascal fragment to implement the function:
(X<>O AND Y>O -+ X, Y := X2Y, 0 )
- rify your answer.
· 5.13 For each of the following functions, either design a WHILE statement to compute
ction or explain why it cannot be done.
a) /(X) =X mod 10
b) !(X)= _x2
X if X< 100
c) !(X)= { 100 if X> 100
0 if X is odd
d) /(X)= { 1 if X is even

0 if X= 0
1 if X= 1
e) f(X) = 2 if X is prime
3 if X is not prime and X >2
(X,X+1) if X> 100
f) /(X, Y) = { (X, Y) if X< 100

(X, Y +1) if X> 100


g) f(X, Y) = { (X, Y) if X< 100

4.6 Summary
t his chapter, Pascal's ordinal data types have been examined. The operations that can be applied
values of each type are summarized below.

14.6 Summary 411


Operator Operation Functionality
NOT negation Boolean -+ Boolean

AND conjunction Boolean X Boolean -+ Boolean


OR disjunction

= equality ordinal X ordinal -+ Boolean


<> inequality (ordinal types must match,
< less subranges are considered to
<= less or equal have the host type)
> greater
>= greater or equal

+ unary+ Integer -+Integer


unary-

+ binary addition Integer X Integer -+ Integer


binary subtraction
* multiplication
DIV integer division
MOD modulus

Each ordinal type has its use. BOOLEAN variables can be used to record complex conditions
for later testing. INTEGER variables make counting easy, so long as the restriction to [-MAXINT,
MAXINT] is observed. Enumerated types are good fo r capturing a small set of values, each with a
mnemonic name. Subrange types allow the programmer to declare value bounds, that are then
checked automatically.
The analysis methods used for CF Pascal extend to the the ordinal data types almost without
change.

412 ORDINAL DATA TYPES


CHAPTER 15

THE PROGRAM STRUCTURE OF D PASCAL

Chapter Preview

D Pascal programs may include sections defining constants and types for use within the
program. They may also define and use functions in much the same way as procedures. They
can handle files that exist outside the program, perhaps created by some other means, or to be
passed on for another use. These features are a convenience, but they also contribute to good
programming style by documenting and localizing program meaning.

OF Pascal programs consist of a heading, variable declarations, procedure declarations, and a


body. D Pascal is a richer language than OF Pascal, containing additional features such as external
files, constants, types, and functions, and its program structure reflects this. The <program
heading> and <block> of a D Pascal program may contain more features than in a OF Pascal
program. The syntax for D Pascal is:
<program> ::= <program heading> ; <block>
<program heading> ::= PROGRAM <identifier> ( <identifier list> )
<identifier list> ::= <identifier list> , <identifier>
<block> ::= <constant definition part> <type definition part>
<variable declaration part> <procedure and function declaration part>
<BEGIN statement>
<variable declaration part> ::= VAR <variable declarations> ; I
<variable declarations> ::=<variable declarations> <variable declaration>
I <variable declaration>
<variable declaration> ::= <identifier list> : <type denoter >
The order of these parts is very rigid, but is easy to remember from Pascal's requirement that
identifiers be declared before use . Constant identifiers, which are declared in the constant definition
part and may be used in subrange declarations in the type definition part, are defined before any
types. Type identifiers, which are declared in the type definition part and used in variable
declarations in the variable declaration part, are defined before any variables. Similarly, variable
declarations must appear before the uses of variables in procedures and procedures must be declared
before they are called from the program's BEGIN statement. External files are an exception because
they appear in the program heading, yet are later declared in the program's block .

15.1 External Files

Preview

Files whose existence goes beyond the execution time of programs can be used to create program
systems, and to communicate between Pascal programs and other parts of a computer system.

Files may have lifetimes (or extents) like those of other Pascal values (limited to the duration of
procedure or program execution), but they may also continue to exist outside program execution. If
a file variable appears in a program heading (with INPUT and OUTPUT), the file is called external.
The contents of an external file may be read or written by a Pascal program, but the file may exist
before execution begins, and it remains after program execution terminates. For example, in the

15.1 External Flies 413


program
PROGRAM FileProcess(INPUT,OUTPUT,ExtFile);
VAR
IntFile, ExtFile: TEXT;
BEGIN

END.
the contents of ExtF i le may exist before F i leProcess executes, and remains after execution ,
but the contents of IntFile must be created within FileProcess, and will be destroyed at the
end of execution. Each Pascal machine has different ways of associating Pascal external files with
names in its file system. Often the Pascal identifier is the name used, but may be subject to
restrictions outside Pascal.
External files can have three different uses: input (supplying data to programs from outside),
output (holding data supplied by this program for other programs), and update (bringing data to the
program to be updated for later use). In illustration, an external file could be used to maintain a
cumulative record of students preregistered in a course. Suppose the standard input file INPUT
contains the names of all students who registered today, and Students contains the names of
previously registered students in sorted order. The following design part shows the processing
required.
PROGRAM Register(INPUT, OUTPUT, Students);
{add names in INPUT to those in Students and
report any duplicate names in OUTPUT}
VAR
Students, Templ, Temp2: TEXT;
BEGIN {Register}
{copy INPUT to Templ};
{sort names in Templ};
{merge Templ and Students into Temp2--
if a name occurs in both, send to OUTPUT
and don't duplicate in Temp2};
{copy Temp2 to Students}
END. {Register}
Templ and Temp2 are internal files that disappear at the end of execution. Students has been
updated and will be used in the same program tomorrow (with tomorrow's INPUT).
With external files D Pascal provides the capability of creating program systems, not simply
single programs, in which programs exchange information through external files. For example, in the
illustration above, another program might print the file Students, say as in the design part:
PROGRAM PrintRegistered(INPUT, OUTPUT, Students);
{print the names in Students to OUTPUT}
VAR
Students: TEXT;
BEGIN {PrintRegistered}
{copy and format names from Student to OUTPUT}
END. {PrintRegistered}
Program systems require careful design just as do programs, but now the data structures are the
external files, and the elementary operations are the actions of the programs within the system. In
data processing applications, the person who designs program systems, but not necessarily the
programs within the system, is called a system analyst.

414 THE PROGRAM STRUCTURE OF D PASCAL


15.1.1 Exercises
15.1.1 Find out what name is actually entered in the system directory when a Pascal program
wr ites an external file named MyF i 1 e.
~5. 1.2 Find out what happens on your Pascal machine if you try to read an external file that does
_ot in fact exist before your program begins execution. Can this information be used to write a test
:or a file that does not yet exist (so that, for example, the program could then create it)?
5.1.3 Write a trivial program system consisting of three programs as follows:
Program A creates an empty external file.
Program B updates the external file by adding to it a line copied from the input.
Program C prints out the contents of the external file .
-rite a "users' manual" for this program system that tells how to use it as a one-line-per-day diary.

5.2 Constants

Preview

By naming constant values, the programmer can change them throughout a program without
having to search for each occurrence.

nstant values can be given names using declarations like the following:
CONST
LastCh = '#' ;
FieldWidth = 4;
Max = 9999;
Min = -Max;
Name= 'Blaise Pascal';
- ce a constant identifier introduced by a constant declaration names a value, it has the same type
.- t he value and can appear anywhere the value could. In the following example based on the above
ST declarations, Min and Max are used to declare a subrange type; LastCh is compared to a
a racter value; F ieldWidth controls the size of the field in which the Smallint values are
-~in ted; and Name is used in a WRITE statement.

TYPE
Smallint =Min .. Max;
VAR
Value: Smallint;
Ch: CHAR;
BEGIN

WHILE Ch <> LastCh


DO

READ(Value);
WRITELN(Value:FieldWidth);
WRITELN(Name);

The syntax for constant declarations appears below.


<constant defin ition part> ::= CONST <constant definitions> :

15.2 Constants 415


<constant definitions> ::= <constant definitions> <constant definition>
I <constant definition>
<constant definition> ::= <identifier> = <constant> ;
<constant>::= <sign> <unsigned number> I <unsigned number>
I <sign> <constant identifier> I <constant identifier>
I <character string>
<constant identifier> ::= <identifier>
The context rules that accompany this syntax state that only the <identifier >s that occur in
<constant definition>s are <constant identifier >s. These special identifiers may not be used as if
they were variables, because their values are fixed. For example, a constant identifier cannot appear
on the left side of an assignment statement or as an actual parameter corresponding to a form al
VAR parameter. ,
Constant identifiers help document programs and make program changes easier. Declaring the
constant LastCn with fixed value # , and using LastCh instead of the literal '#' improves a
program. Consider the following fragment:
CONST
LastCh = '#';
VAR
Ch: CHAR;
BEGIN

WHILE Ch <> LastCh


DO
• • • I

WRITELN ('Inventory # is: ', ... )


END
If we decided to end files with another character (say $) in order to process a file that contained the
character # , only the constant declaration in the program fragment need be changed:
CONST
LastCh = '$';
Searching through the entire program changing uses of # to $ is both tedious and error-prone. Fo
example, the # in the final WRITE statement might be changed by mistake .

15.2.1 Exercises
15.2.1 Which of the following are legal constant declarations in Pascal?
CONST
CharsPerWord : 4;
StringLength = 15;
WordsPerString = (StrLength+CharPerWord-1) DIV CharPerWord;
Lowint = -MAXINT;
Hello= 'Hello';
15.2.2 Explain why it is reasonable to forbid passing a constant identifier as an actual VAR
parameter to a procedure.
15.2.3 Give an example in which the same constant identifier is used in both the <variable
declaration part> and the <BEGIN statement> of a program so as to prevent a pote ntia
inconsistency between declaration and usage.

416 THE PROGRAM STRUCTURE OF D PASCAL


5.3 Types

Preview

Because types are an important feature of Pascal, it is advantageous to be able to name them,
and use the name instead of the full type description when needed. By defining a complex type
only once, the programmer can prevent accidental transcription errors and make it easy to alter
very occurrence of that type.

·hough data types describe sets of values and the operations that can be applied to the values,
- - al type declarations only describe the representation of the values. New types are declared in
"' <type definition part> section of a block by associating an identifier (the name of the new type)
.-h a description of its values (e.g., listing the constants of an enumerated type or the bounds of a
r ange type). Some examples of new types appear below.
TYPE
CType = CHAR;
Ct = CType;
Month = (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
Summer = Jun . . Aug;
DayNum = 1 .. 31;
Letter= 'A' .. 'Z';
identifiers like Letter can occur anywhere primitive types occur in declarations and formal
m eter lists. Type identifiers are a convenient shorthand- by naming a list of constant
- ifiers like Month it can be easily repeated in a program without the danger that it might be
:ed incorrectly.
The syntax of type declarations appears below:
<type definition part> ::=TYPE <type definitions> l
<type definitions> ::= <type definitions> <type definition>
: <type definition>
<type definition> ::= <identifier> = <type denoter >
<type denoter> ::=<type identifier> l <new type>
<new type> ::= <new ordinal type>
<new ordinal type> ::= <enumerated type> l <subrange type>

=- most important use of type identifiers is in formal parameter lists.


<formal parameter list> ::= ( <formal parameters> ) :
<formal parameters> ::= <formal parameters> ; <formal parameter>
: <formal parameter>
<formal parameter>::= <variable parameter>
<variable parameter> ::= VAR <identifier list> : <type identifier>
..1 formal parameter must be declared with a type identifier. Thus none of the formal parameters
- e following procedure heading are legally declared:

15.3 Types 417


PROCEDURE Proc
(VAR Mo: (NoMonth, Jan, Feb, Mar, Apr, May,
Jun, Jul, Aug, Sep, Oct, Nov, Dec);
VAR Day: 1 .. 31;
VAR Finitial, Linitial: 'A' .. 'Z');
because they are not declared with type identifiers. By introducing type declarations, the procedure
heading can be correctly written as follows:
TYPE
Month = (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
DayNum = 1 .. 31;
Letter= 'A' .. 'Z';

PROCEDURE Proc(VAR Mo: Month; VAR Day: DayNum;


VAR Finitial, Linitial: Letter);
The ability to define new types raises the question of type equality: when are two types the
same? Many of the operations in programs depend on their operands having matching types, and
Pascal has three degrees of type compatibility to handle different situations: same, assignment
compatible, and compatible. The context rules governing type agreement are:
1. Actual parameters must have the same types as their corresponding variable formal
parameters.
2. The type of the variable on the left side of an assignment statement must be assignment
compatible with the type of expression on the right side.
3. The operands of relational operators must have compatible types.
In Pascal, two types are the same if they have been defined as being equivalent in a type
definition. Variable~ have the same type if they are declared with type identifiers that are the same
or if they are declared together in a variable declaration. For example, consider the declarations:
TYPE
Tl = INTEGER;
T2 = T1;
Smallint = 0 .. 9;
Age = 0 .. 9;
VAR
W: Smal1Int;
X: 0 .. 9;
Y, Z: 0 .. 9;
The types INTEGER, T1, and T2 are all the same in the above example. Small Int and Age
are not the same types. Y and Z have the same type, but technically X has a different type. In
the following Pascal fragment, using the above declarations, only W may be passed to P since it is
the only variable with the same type as Farm.
PROCEDURE P(VAR Farm: Smallint);

P(W); {legal-- Farm and W have type Smallint}


P(X); {not legal-- Farm and X have different types}
P(Y); {not legal-- Farm andY have different types}

Two types, T1 and T2, are compatible if either of the following is true:
T1 and T2 are the same type.
T1 is a subrange of T2, or T2 is a subrange of T1, or both T1 and T2 are subranges of the
same host type.

418 THE PROGRAM STRUCTURE OF D PASCAL


In the above example, the type of X is compatible with Y because both are subranges of INTEGER.
The variables Letter and Digit in the following example have types that are compatible because
they are both subranges of the host type CHAR.
VAR
Letter: 'A' . . 'z' ;
Digit: '0' .. '9';
BEGIN

IF (Digit <= '0') OR (Digit = Letter)

Thus Digit can be compared to the constant '0' (which has type CHAR) and to Letter.
A value of type T2 is assignment-compatible with a type T1 if either of the following is true:
T1 and T2 are the same type (but not a file type).
T1 and T2 are compatible ordinal types and the value of type T2 is in the closed interval
specified by the values of type Tl.
In the following example, only the first two assignment statements are legal.
TYPE
Smallint = 0 .. 99;
Smallerint = 0 .. 9;
VAR
X: Smallint;
Y: Smallerint;
BEGIN
X := 0; {The type of X (Smallint) is compatible with
the type of 0 (INTEGER) and 0 is in
Smallint's closed interval}
Y := X; {The type of Y (Smallerint) is compatible
with the type of X (Smallint) and the value
of X (0) is in Smallerint's
closed interval}
Y := X-1 {The type of Y (Smallerint) is compatible
with the type of X-1 (INTEGER) but the
value of X-1 (-1) is outside Smallerint's
closed interval}
END

15.3.1 Exercises
15.3.1 In the following Pascal fragment, identify the errors (if any).
TYPE
Smallint = 0 .. 99;
Smallerint = 0 .. 9;
VAR
W: 0 •• 9;
X: Smallint;
Y: Smallerint;
Z: 0 .. 9;
Letter : 'A' 1 z I;

Digi t : '0 ' 1 9 I;

15.3.1 Exercises 419


PROCEDURE P(VAR Parml: Smalllnt; VAR Parm2: Smallerlnt);
BEGIN . {P}
Parml := Parm2
END; {P}
BEGIN
Digit := · 1 0 1 ;
Letter : = 1 A 1 ;

z : = 0;
X := 0;
W := Z;
IF (Digit <= 1 0 1 ) OR (Digit =Letter)
THEN
WRITELN( 1 hello 1 ) ;
y :=: X;
P(X,Z);
P (X, X-1) ·;
P (W, Y)
END
15.3.2 The technical rules about Pascal type agreement for variable usage range from obviously
necessary to rather arbitrary. Give an example of a rule that is obviously necessary and explain
why. Give an example of a rule that seems merely arbitrary.

15.4 Value and Variable Parameters

Preview

Parameters passed by value are an isolation mechanism between the ·point of call and the
statements of the called routine. No actions in the routine can transmit information back to
the caller through the value parameter. Thus these parameters serve as a safe way to provide
read-only input values.

There are two kinds of formal parameters in D Pascal: variable parameters and value parameters.
In a procedure declaration variable parameters look like declaration statements. They begin with
the reserved word VAR, followed by a list of identifiers separated by commas that are the names of
the formal parameters, a colon, and an identifier giving the parameter type. Value parameters are
declared like variable parameters except that the leading VAR is omitted. In the following example,
VarParm is a variable formal parameter of type T and ValParm is a value formal parameter of
type T.
PROCEDURE P(VAR VarParm: T; ValParm: T);
The syntax rules describing parameters are:
<formal parameter> ::= <variable parameter> I <value parameter>
<variable parameter>::= VAR <identifier list> : <type identifier>
<value parameter> ::= <identifier list> : <type identifier>
When an actual parameter is bound to a variable formal parameter, an alias is set up between
the name of the actual parameter (which must be a variable rather than a constant or expression)
and the name of . the formal parameter. The word alias is used because when the procedure
statement is replaced by the body of its procedure declaration, variable formal parameters are
replaced by their corresponding actual parameters. Thus, each time a value is assigned to a variable
formal parameter the value of the aliased actual parameter is changed. The type of the actual
parameter must be the same as t he type of the formal parameter.

420 THE PROGRAM STRUCTURE OF D PASCAL


Many parameters are used as inputs only; no value is returned through these parameters.
Input-only parameters should be declared as value formal parameters to distinguish them from
variable formal parameters, which are used as output or input-output parameters. When an actual
parameter is bound to a value formal parameter, the value of the actual parameter is assigned to
the formal parameter but no alias is established between the formal parameter and the actual
parameter. Thus, any assignment to a value formal parameter in a procedure's block changes the
value of the formal parameter but not that of the actual parameter. Since the value of the actual
parameter is assigned to the value parameter rather than an alias established between the actual
and the formal parameters, any expression (including constants, variables, and more complex
expressions) may be used as the actual parameter as long as the type of the actual parameter value
is assignment-compatible with the type of the formal. The difference between variable and value
parameters can be seen in VarVsValue.
PROGRAM VarVsValue(OUTPUT);
VAA
A, B: INTEGER;
PROCEDURE Bump(VAA VarF: INTEGER; ValF: INTEGER);
BEGIN
VarF := VarF + 1;
ValF := ValF + 1;
WRITELN(VarF, ValF)
END;
BEGIN {VarVsValue}
A := 0;
B := 0;
Bump(A, B);
WRITELN(A, B)
END. {VarVsValue}
Execution
OUTPUT: 1 1
1 0
Bump adds 1 to each of its formal parameters so that both VarF and ValF have the value 1 at the
end of the procedure. However, the changes made to the formal parameters in Bump appear only in
the actual parameter that was bound to the variable formal parameter (A has the value 1 at the end
of the program's block.) and not in the actual parameter that was bound to the value formal
parameter. (B retained the value it had before the execution of the procedure.)
The meaning for a procedure invocation with variable parameters presented in Chapter 9 can
be extended to work for value parameters. Formal value parameters are added and removed from
the state just as the procedure's local variables are. After a formal value parameter is added to the
state, the new variable is assigned the current value of the actual parameter. With the declaration:
PROCEDURE P(Y: Typ);
T;
the body T is added to the state as the value of identifier P. Then the meaning of a procedure
statement
P ( E )
lS

IP( E )j{s)= lvAA Y: TyploiY := Elols(P)IoiVAA Y: Typ1T.


If there are VAA par ameters, or confusion between local and global identifiers, these must be
modified in s(P) as described in Sections 9.1.4 and 9.2.5, respectively .
This definition can be used to work out the meaning of the call on Bump in VarVsValue .
The beginning state is

15.4 Value and Variable Parameters 421


{OUTPUT·<tt,tt,w> }.
Then the value of the program function is
jvAR A, B: INTEGERio
!BEGIN A:=O;B:=O;Bump(A,B);WRITELN(A,B) ENDio
jvAR A, B: INTEGERIT o[)({OUTPUT·<tt,tt,w>})
=!BEGIN A:=O;B:=O;Bump(A,B);WRITELN(A,B) ENDio
jvAR A, B: INTEGERIT o[)({OUTPUT·<tt,tt,w>, A·?, B·?})
=I Bump (A, B) lojWRITELN (A, B) lojVAR A, B: INTEGERIT o[)
( {OUTPUT·<tt,tt,w>, A·O, B·O}).
The procedure statement is expanded into a series of function compositions that apply the function
corresponding to the declaration of Valf, the assignment of the value of B to Valf, the body of
Bump with A substituted for Varf, and the transpose of the declaration of Valf. Thus the above
becomes:
jVAR Valf: INTEGERioiValf : = BiolA := A+lloiValf := Valf+llo
jWRITELN (A, Valf) lojVAR Valf: INTEGERIT ojWRITELN (A, B) lo
lvAR A, B: INTEGERIT o[)({OUTPUT·<tt,tt,w>, A·O, B·O})
=IValf := BiolA := A+llojValf := Valf+llojWRITELN (A, Valf) lo
jvAR Valf: INTEGERITojWRITELN(A,B)IojVAR A,B: INTEGERITo[)
({OUTPUT·<tt,tt,w>, A·O, B·O, Valf ·?})
=lA := A+llojValf : = Valf+llo.-jWR_I_T-EL-N-(A-,-V-a_l_F__,)lo

jvAR Va1F: INTEGERIT o jWRITELN (A, B) lo


jVAR A, B: INTEGERjT o[)({OUTPUT·<tt,tt,w>, A•O, B·O, Valf·O})
=IValf := Valf·rllojWRITELN (A, Valf) lojVAR Valf: INTEGERjT o

jWRITELN (A, B) lojVAR A, B: INTEGERIT o[)


({OUTPUT·<tt,tt,w>, A·l, B·O, Valf·?})
= jWRITELN (A , Valf) lo jvAR Valf: INTEGER ITo jWRITELN (A, B) I o
jvAR A, B: INTEGERIT o[)({OUTPUT·<tt,tt,w>, A· l, B·O, Valf·l})
=jVAR Valf: INTEGERIT oiWRITELN (A, B) joiVAR A, B: INTEGERIT oQ
({OUTPUT·<t 1 1/t,tt,w>, A·l, B·O, Valf · l})
=IWRITELN(A,B)joiVAR A,B: INTEGERIToO
({OUTPUT·<t 1. 1/t,tt,w>, A·l, B·O})
=jVAR A,B: INTEGERIT o[)
({OUTPUT·<t 1 1/ 1 0/t,tt,w>, A·l, B·O}}
=Q({oUTPUT·<t 1 1/ 1 O/t,tt,w> })
=<t 1 1t, t 1 ot>

422 THE PROGRAM STRUCTURE OF D PASCAL


15.4.1 Exercises
15.4.1 Value parameters have been called "initialized local variables." Explain why .
15.4.2 Explain why the restriction that a constant identifier cannot be passed as a parameter
applies only to variable parameters.
15.4.3 ln the meaning given to procedure statements with parameters, explain why passing variable
X as actual parameter to a procedure in a variable parameter requires renaming a local variable X
in the procedure, but passing X in a value parameter does not.

15.5 Functions

Preview

Like procedures, functions encapsulate extensive computations so that they may be used easily,
and may be modified by transmitting different arguments when used. However, functions return
values, and are called by naming them within expressions.

Procedure declarations encapsulate sequences of operations, often to compute and return a value ·
t hrough a variable parameter. Function declarations and calls in Pascal provide another way of
returning the computed value. Function declarations look much like procedure declarations, but the
header ends with a colon and a type identifier, giving the type of the result . A procedure
communicates its results by assigning values to its parameters. Within the block of a function
declaration, the result of the function is indicated by assigning it to the identifier that is the name of
t he function. If no assignment is made to this identifier in the block, the result of the function
invocation is undefined. The mechanism for returning results is illustrated by Max2 below:
FUNCTION Max2(Pl, P2: INTEGER): INTEGER;
{ (Pl > P2 --> Max2 := Pl) I
(P2 >= Pl --> Max2 := P2)}
BEGIN {Max2}
IF Pl > P2
THEN
Max2 := Pl {return Pl as Max2's value}
ELSE
Max2 . - P2 {return P2 as Max2 ' s value}
END; {Max2}
The syntax rules for function definitions appear below:
<procedure and function declaration part> ::= <proc/func declarations> l
<proc/func declarations> ::= <proc/func declaration>
l <proc/func declarations> <proc/func declaration>
<proc/func declaration> ::=<heading> ; <block> ;
<heading> ::= PROCEDURE <identifier> <formal parameter list>
l FUNCTION <identifier> <formal parameter list> : <result type>
Function calls look like procedure calls: the name of the function is followed by a list of actual
parameters, which are expressions, separated by commas. However, there is an important difference
between function and procedure calls. Since functions deliver values, function calls are expressions
which m ay appe ar within larger expressions or alone wherever an expression could appear in a
program. Procedure calls, wh ich do not deliver values, are st atements. To find the maximum of
t hree values, Ma x3 could be declared as follows, using two calls to Max2 as right sides of
assignment statements.

15.5 Functions 423


FUNCTION Max3(Pl, P2, P3: INTEGER): INTEGER;
{Max3 is assigned the largest value from among
Pl, P2, and P3}
VAR
T: INTEGER;
BEGIN {Max3}
T := Max2(P2,P3);
Max3 := Max2(Pl,T)
END; {Max3}
When Max2 is invoked the first time it returns the larger of the values of P 2 and P 3, which is then
assigned to T. T is subsequently used as an actual parameter in the the second invocation of
Max2. Since function invocations can appear anywhere expressions can and Max2 declares its
formal parameters to be value parameters, Max3 could be rewritten by making the first call to
Max2 a parameter of the second call to Max2.
FUNCTION Max3(Pl, P2, P3: INTEGER): INTEGER;
{Max3 is assigned the largest value from among
Pl, P2, and P3}
BEGIN {Max3}
Max3 := Max2(Pl, Max2(P2, P3))
END; {Max3}
The expression in the assignment statement would be evaluated by first calling Max2 to obtain the
maximum of P2 and P3, and then calling Max2 again with Pl and the result of-the earlier call.
The following rules of syntax incorporate function calls into expressions by adding them to
<factor>.
<factor>::= <variable access> I <function designator>
I <unsigned constant> I ( <expression> ) I NOT <factor>
<function designator> ::= <function identifier> <actual parameter list>
<function identifier> ::= <identifier>
<actual parameter list> ::= ( <actual parameter list> ) I
<actual parameter list> ::= <actual parameter list> , <actual parameter>
I <actual parameter>
<actual parameter> ::= <expression>
Notice that the <actual parameter list> may be empty-a function may have zero parameters.
A function declaration adds the text of the function's <block> to the state under the identifier
that names the function, just as for a procedure declaration. When the function is later called, this
text is invoked to obtain a value. If there are variable parameters, or conflicts between local
declarations and global identifiers in the state, the text must be modified as described in Sections
9.1.4 and 9.2.5, respectively. If there are value parameters, these must be treated as described in
Section 15.4. Suppose a function F is declared as
FUNCTION F . . . : Typ;
T ;
with body T. Ignoring all modifications required for parameters and local variables, the meaning of
a call on F in state s is:
II](s) =[£]{is (T') l{!vAR F': Typl(s))) =
(I v AR F, : Typ I Is ( T,) I [I] )( s).
0 0

Where F' is a new identifier, and T' is the same as T except that every occurrence ofF on the left
side of an assignment in T-is replaced by F' in T'. That is, a temporary st a t e is created by adding
a new identifier F' to s; the modified body of the function acts on the te mporary st ate; then the

424 THE PROGRAM STRUCTURE OF D PASCAL


value attached to F ' is extracted from it. The value returned by a function is the most recent
assignment to its identifier in the body; the declaration of a special iden t ifier to receive this value is
what makes the definition work.
This de fi nit ion does not properly account for any modifications the function body may make in
a state, because it happens to use identifiers that are VAR parameters or global variables. Such
modifications are called side effects, and are treated in the next section.
As an example of a side-effect-free function call, consider a call on Max2 above:
WRITE ( 1 Larger is 1
, Max2 ( 3, 7))
The parameters may be INTEGER constants, since Max2 has value parameters, and since a call on
Max2 is an expression, it can appear in a WRITE statement. Suppose that the state at the time of
t his call is
s = {OUTPUT·<tt,tt,w>, Max2·BEGIN IF Pl > P2 ... END} .
Then
jwRITE( 1 Larger is 1
, Max2(3, 7))1(s)
={OUTPUT·< jMax2 (3, 7) l(s),tt,w>, Max2·BEGIN ... END}.
F rom the definition above, with the modification required for value parameters,
IMax2(3, 7)1(s) =(lvAR Pl,P2 : INTEGER ; Pl : =3; P2:=7lo
\VAR Max2N: INTEGER ; B\oiMax2Ni)(s),
where Max2N is the new identifier, and B is s(Max2) modified to:
BEGIN
IF Pl > P2
THEN
Max2N . - Pl
. ELSE
Max2N .- P2
END
by th e insertion of Max2N. This is:
1Max2(3, 7),(s)=\Max2NI(jvAR Pl,P2: INTEGER; Pl:=3; P2 : =71
o lvAR Max2N: INTEGER; Bj)(s)
=1Max2NI( @"j({Pl·3, P2 ·7, Max2N·?,
OUTPUT·<tt,tt,w>, Max2·BEGIN ... END}
=1Max2N\({Pl ·3, P2·7, Max2N·7, OUTPUT·<tt,tt,w>, Max2•BEGIN ... END}
=7.
So the result is
· !wRITE ( 1 Larger is Max2 (3, 7)) l(s)
= {OUTPUT·<tLarger is 7 t, tt ,w>, Max2 · BEGIN ... END}.

15.5.2 Side Effects in Functions


When a statement in a function body alters the value of a nonlocal variable or formal variable
parameter , a call on the function (or the function itself) is said to have a side effect. The name
a rises because a function's main effect is to return a value . Side effec ts often come as a nasty
su rprise to the programmer, who may not be expecting any action from a function call other than
t he value returned. Furthermore, side effects in functions can make the results of expression
ev'aluation unpredictable. The order in which operators are applied is defined by the Pascal syntax
rules, but the language definition does not specify the order in which operands are fetched . Consider

15.5.2 Side Effects in Functions 425


the following definition, whose intention is given by the comment:
FUNCTION Identity(VAR In: INTEGER): INTEGER;
{<Identity,In,Glob> : = <In,In+l,Glob+3>}
BEGIN {Identity}
Identity := In;
In := In + 1;
Glob := Glob + 3
END; {Identity}
Its main effect is to return the value of the parameter In; it has side effects of altering this
parameter and also altering the global variable Glob. The following values then can result from
the expression
Glob + Identity(In) + In
depending on the way the operands are fetched:

Possible Values of Glob + Identity (In) + In


Value Order of Evaluation
Glob+In+In+l {Glob and In fetched left to right:
Tl := Glob;
T2 := Identity(In);
T3 := In;
Result := Tl + T2 + T3 }
Glob+3+In+In I {Glob and In fetched right to left:
Tl := In;
T2 := Identity(In);
T3 := Glob;
Result : = T3 + T2 + Tl }
Glob+In+In I {Glob and In fetched before the call:
Tl := Glob;
T2 := In;
T3 := Identity(In);
Result := Tl + T3 + T2 }
Glob+3+In+In+l I {Glob and In fetched after the call:
Tl := Identity(In);
T2 := Glob;
T3 := In;
Result := T2 + Tl + T3 }

There are many cases in which the fetch order does not alter the value of an expression, but
because of the potential surprise that any side effect may cause, it is good programming practice to
avoid side effects in functions. When it is necessary to modify a variable parameter, a procedure is a
better choice, and modification of global variables within functions or procedures should be avoided.

15.5.2 Predefined Functions


Several functions are predefined in the Pascal machine; these functions can be used without declaring
them.

SUCC a.nd PRED


Two predefined functions are available for all the ordinal types: successor (succ) and
predecessor (FRED). SUCC and FRED do not have a standard definition for character expressions
because of the differences in character sets on different machines. For the . characters which
correspond to digits, these functions work much as expected:

426 THE PROGRAM STRUCTURE OF D PASCAL


CEx
0 undefined
1 2 0
2 3 1
etc.
9 undefined 8

However, SUCC ('A') may not have value B on some Pascal machines, because the values A and
B are not represented by adjacent codes in the underlying machine. Thus these built-in functions
cannot sensibly be used with alphabetic characters on all machines, and the careful programmer
avoids using them even if they work properly on today's_machine.
Since only a subset of the integers can be represented in a Pascal program, the functions SUCC
and PRED are defined for INTEGER expressions in the obvious way:
jsucc( E ) j(s) =IE + 1j(s),
jPRED ( E ) j{s) =IE - lj{s),
where these equations include the possibility that the value of E is ±MAX/NT, and the
corresponding addition or subtraction is undefined.
SUCC and PRED can be applied to enumerated-type operands to produce values in the
sequence defined by the declaration. For example, consider the declaration:
TYPE
Month = (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
The order of appearance of the identifiers in the type declaration is from the constant with the least
value to that with the greatest value. For example:

Ex ression Value
SUCC(Jan) Feb
PRED(Oct) Sept
PRED(NoMonth) undefined
SUCC(Dec) undefined

Enumerated types can be used to eliminate "mystery" numbers from programs. Mystery
numbers are those that arise from arbitrary mappings of real-world values to numerical values. If
the programmer had made up integer values for the months instead of declaring TYPE Month, 0 for
January, 1 for February, etc., a program to list the number of days in each month might be:

SUCC and PRED 427


PROGRAM PrintDays(INPUT, OUTPUT);
VAR
Mo: INTEGER;
BEGIN {PrintDays}
Mo := 0;
WHILE Mo <= 11
DO
BEGIN
IF (Mo=3) OR {Mo=S) OR (Mo=8) OR (Mo=10)
THEN
WRITELN{30)
ELSE
IF (Mo=O) OR (Mo=2) OR (Mo=4) OR (Mo=6)
OR (Mo=7) OR (Mo=9) OR (Mo=11)
THEN
WRITELN(31)
ELSE
WRITELN(28);
Mo := Mo + 1
END
END. {PrintDays}
Execution
OUTPUT: 31
28
31
30
31
30
31
31
30
31
30
31
Mystery numbers make programs hard to read, and when (say) 1 appears it may represent
February, or perhaps a page number of some report , or the first day of some month, etc.
A program using an enumerated type for the same purpose is better:
PROGRAM PrintDays(INPUT, OUTPUT);
VAR
Mo: (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
BEGIN {PrintDays}
WRITELN{31);
Mo := Jan;
WHILE Mo < Dec
DO
BEGIN
Mo := SUCC(Mo);
IF (Mo=Sep) OR (Mo=Ap~) OR (Mo=Jun) OR (Mo=Nov)
THEN
WRITELN(30)
ELSE
IF Mo=Feb

428 THE PROGRAM STRUCTURE OF D PASCAL


THEN
WRITELN(28)
ELSE
WRITELN (31)
END
END. {PrintDays}
The program using an enumerated type is easier to read because the constant identifiers are more
meaningful than the integers in the previous version of the program. Instead of having to count
down to discover that September is 8, Sep comes immediately to mind. It is easier to use the
mnemonic rhyme "Thirty days hath September, April, ... "in the program with an enumerated type.
The WHILE statement also exhibits the use of a relational operator and SUCC with values
from an enumerated type. The WHILE statement body must not be executed when Mo has the
value Dec because SUCC is undefined on the final value of an enumerated type. Thus, the following
shorter incorrect program fragment must be avoided in favor of the longer, correct version.

{Incorrect Version} {Correct Version}

Mo := Jan; Mo := Jan;
WHILE Mo <= Dec {statement list};
DO WHILE Mo < Dec
BEGIN DO
{statement list}; BEGIN
Mo := SUCC(Mo) Mo := SUCC(Mo);
END {statement list}
END

The WHILE statement was "unrolled" by repeating its body to handle the case that Mo has the
value Jan, and incrementing Mo before executing the body in the BEGIN statement. Thus, on the
last execution of the BEGIN statement, Mo has the value Dec and the next test of the WHILE
condition fails. (Another way to solve this problem would be to move NoMonth to the end of the
enumeration, providing a phony month after Dec.)
SUCC and PRED are not often used with the BOOLEAN type, but the results can be predicted
from the information that this type acts as if it had been declared:
TYPE
BOOLEAN = (FALSE, TRUE)

ODD, ABS, and SQR


These three functions take INTEGER arguments. ODD returns value TRUE if the argument is
odd (i.e., a member of { ... , -3, -1, 1, 3, 5, ... }), FALSE if the argument is even. ABS returns an
INTEGER value that is the absolute value of the argument, and SQR produces the INTEGER square
of the argument value. For example:

Expression Value
ODD(-5) TRUE
ODD (10) FALSE
ABS ( - 5) 5
ABS (12) 12
SQR(-5) 25
SQR (10) 100

ORO and CHR 429


ORD and CHR
ORD and CHR are built-in functions that give access to the numerical pos1t10n of values in
finite enumerations. They are most often used to manipulate characters as if they were integers.
ORD converts the first value of an enumerated type to 0, the second to 1, etc. Thus for example in
the Month type
(NoMonth, Jan, feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec)
ORD (Jun) has value 6.
vVhen ORD is applied to a CHAR argument it returns the pos1t10n of the character in the
collating sequence of the Pascal machine . CHR is the inverse : on argument a collating-sequence
number , it returns the corresponding character. For example, CHR (ORD ( 1 F 1 ) ) has value F. It is
not particularly useful to work with the numerical value of a character unless its position relative to
other characters is known . Since the digit characters are guarantee d to fall in sequence,
ORD ( I 7 I ) - ORD ( I 0 I )
has INTEGER value 7-that is, this expression can be used to convert a digit character to its
corresponding integer value. (Subtracting the position of 0 in the sequence would not be necessary if
the character 0 occupied position zero, but it does not on most Pascal machines.) To go the other
way requires both ORD and CHR, for example to conver t the integer value 7 to 7:
1
CHR(7 + ORD( 1 0 ))

Programmers who are more comfortable wi t h the INTEGER type than with enumerated types
often use ORD to excess, and thereby lose the advantages of the mnemonic constants.

15.0.1 Exercises
15.0.0 A function call and a procedure call have the same syntactic form-an identifier followed by
an optional list of actual parameters. Can the two forms be used interchangeably? How is your
answer reflect ed in the syntax rules?
15.0.0 Suppose that Max2 (Section 15.5) had been written with variable parameters instead of
value parameters. Explain why such a Max2 could not be used as an argument to itself in the
second version of Max3.
15.0.0 Consider a Pascal FUNCTION that does digit input. Its header is
FUNCTION DigitR: INTEGER;
and each time it is called, the next character is read from INPUT, converted from CHAR to
INTEGER, and returned . If the next character is not a digit , -1 is returned. For example , if INPUT
contains the string f123Xt then the value of
DigitR + DigitR + DigitR + DigitR
is 5.
a) Write Digi tR using ORD .
b) Write Digi tR without using ORD.
c) Use Digi tR to write another function that reads strings for base-ten numbers in a fixed
range, returning the number if in range, or -1 if out of range:
FUNCTION BaselO(MaxVal: INTEGER): INTEGER;
{Interpret a string of digits followed by a nondigit
as a representation of a base-ten integer N.
If 0 <= N <= MaxVal, return the value; otherwise,
return -1.}
d) Are there side effects in Digi tR? In BaselO? How could side effects cause a programmer
trouble in this case?

430 THE PROGRAM STRUCTURE OF D PASCAL


• ~· ..

15.5.4 Each Pascal fragment below contains the identifier Qu. You are to deduce a possible type of
Qu from the context in each separate case. Express the answer as a type specification for the
declaration:
VAR
Qu: ?
Replace the question mark with the type required of Qu. For example, if you decide that one answer
is a subrange of CHAR between 3 and 6, fill in:
VAR
QU: I 3 I • • 16 I

If there are several possible types, explain briefly.


a)Qu MOD PRED(Qu)
1 1
b) SUCC(Qu > 8 )

c)CHR(ORD(SUCC(Qu)))
15.5.5 What values are printed by the following programs?
a)PROGRAM Alias1(0UTPUT);
VAR
X: INTEGER;
PROCEDURE P(VAR A,B: INTEGER; C: INTEGER);
BEGIN {P}
A := A + 1;
B : = B * A;
C : = C + X;
WRITELN (A, B, C)
END; {P}
BEGIN {Alias1}
·X := 1;
P(X,X , X);
WRITELN(X)
END. {Alias1}
b)PROGRAM Alias2{0UTPUT);
VAR
X, Y: INTEGER;
PROCEDURE P(VAR A,B: INTEGER; C : INTEGER);
BEGIN {P}
A := A + 1;
B := B * C;
C := X * Y;
WRITELN(A,B,C)
END; {P}
BEGIN {Alias2}
X := 1; Y : = 2;
P(X,Y,X);
WRITELN(X,Y)
END. {Alias2}
15.5.6 Write a program to print out the absolute sequence positions of the characters
0 ... 9a .. .zA.. . Z in your Pascal m ac hine. Explain why it is or is not all right to use SUCC to pass from
characte r to character in the progra m .
15.5.7 Explain why the formal definition of function-call meaning in Section 15.5 is not the proper
in tuitive meaning when there are side effects. Give an example of a call where the meaning is wrong,
showing both what the definition provides, and what it should provide.

15.5.3 Exercises 431


15.5.8 Formulate a fo rm:~\ definition f~._"r tht' mc:~ nin~ c'f :\ flllh'th'll c:dl w1th ~td(' c!kcts (ll111t ..
make the function call into an equivalent procedure c:tll.)
15.5.9 Consider the following declaration:
FUNCTION Func(VAR Formal: INTEGER) ;
BEGIN {Func}
Formal : = Formal + 1;
Func : = Formal
END; {Func}
a) Rewrite it as a procedure, retaining the nonfunctional behavior of modifying the parameter.
b) Rewrite the function, to obtain the effect of computing the mathematical "add one" function
without side effects. Can this be done without changing the variable parameter to a value
parameter?
15.5.10 The detrimental aspect of side effects of function and procedure calls is that the effect is
hidden: it literally happens "on the side" and the programmer may not expect it . Listed below are
several locations in Pascal programs. Mark each one SAFE or DANGER as to whether a side effect
could occur there and cause trouble for an unsuspecting programmer. The first one is done for you:
a procedure statement is obviously a dangerous position because procedures have only side effects. If
you are in doubt about an answer, explain briefly.
DANGER In a procedure statement
In the test part of an IF statement
In the right side of an assignment statement
In a WRITE statement
In an actual parameter being passed by value
15.5.11 Consider a function that is part of a data abstraction module, intended to be called from
outside the module. It is possible for the function to have a side effect, yet not violate the rules of
separation between module and users. Explain how this can be .
15.5.12 Given the specification
R = {(0,0), (0,1), (1,0), (1,1), (2,4), (2,1)}
determine whether each of the functions of X below is correct with respect to R.
a) (X>=O -+ X : = X*X)

b) (X E {1, 2, ..• , 255}-+ X : = ORD(CHR(X)) - 1)


(where CHR is defined in the range [1, 255])
c) (X E INTEGER -+ X : = SUCC (X))
15.5.13 Ignoring the effects on K and Z, show the consistency of the specification:
(N>=O AND X<>O -+ Y : = XN)
and the program fragment:

432 THE PROGRAM STRUCTURE OF 0 PASCAL


K := N; Y := 1; Z := X;
WHILE K <> 0
DO
IF ODD (K)
THEN
BEGIN
K : = K - 1; Y := Y * Z
END
ELSE
BEGIN
K := K DIV 2; Z := Z * Z
END

15.6 Forward Declarations

Preview

Pascal's rule that identifiers must be declared before use is saved in the case of mutual recursion
by the FORWARD declaration.

With mutually recursive procedures (e.g., two procedures each calling the other) Pascal's rule to
declare identifierS' before using them cannot be observed. For example, the call of Second precedes
he declaration of Second in the following example, but moving Second before First will only
result in the call of First preceding its declaration.
PROCEDURE First(IVar: INTEGER);
BEGIN {First}
... ,
Second(CHR(IVar));

END; {First}
PROCEDURE Second(CVar: CHAR);
BEGIN {Second}
... ,
First(ORD(CVar));

END {Second}
Pascal provides a way to preserve its rule of declaration before use in the FORWARD declaration.
One of the mutually recursive procedures has its heading moved up, but its body remains in place. A
FORWARD declaration includes the entire procedure h_eading (name, formal parameter list, return
ty pe), but replaces the block with the reserved word FORWARD. The procedure's block appears later
in the program, but only the reserved word PROCEDURE or FUNCTION and the name of the
procedure may appear with the block. The parameters and return type are not repeated. For the
example above:

15.6 Forward Declarations 433


PROCEDURE Second(CVar: CHAR); FORWARD;
PROCEDURE First(IVar: INTEGER);
BEGIN {First}

Second(CHR(IVar));

END; {First}
PROCEDURE Second; {don't repeat parameters}
BEGIN {Second}
• • • I

First(ORD(CVar));

END {Second}

15.7 Summary
Several declarations were introduced in this chapter: external files, constants, types, value
parameters, and functions. External files provide a way to create program systems. Constant and
type declarations help organize programs by cen,tralizing information, making a program easier to
change. For example, consider the following:
TYPE
T = ... ; {any type with> and:= operations}
FUNCTION Max2(VAR Pl, P2: T): T;
{ (Pl > P2 --> Max2 := Pl) I
(P2 >= Pl --> Max2 := P2)}
BEGIN {Max2}
IF Pl > P2
THEN
Max2 := Pl
ELSE
Max2 := P2
END; {Max2}
By simply changing the definition of T (say from CHAR to INTEGER), Max2 could be applied to
integers rather than characters.
Pascal procedures may have value parameters. Value parameters are more secure than
variable parameters because inadvertent assignments to them do not change the execution state
where the call was made . Thus, input values are passed as value parameters, and output variables
or variables to be updated are passed as variable parameters.
Like procedures, functions help structure programs by abstracting behavior . However, function
calls are expressions that re turn values, while procedure calls are statements that act by changing
either their variable formal parameters or some nonlocal variables. Also, since functions can only
return a single value, procedures must be used to return multiple values through assignments to
variable parameters.

434 THE PROGRAM STRUCTURE OF D PASCAL


CHAPTER 16

AGGREGATE DATA TYPES IN D PASCAL

Chapter Preview

This chapter describes three mechanisms for creating composite values in Pascal: sets, files and
records. A limited version of set objects is provided. Files extend the TEXT files of CF Pascal
t o types other than CHAR. Records providing a grouping mechanism for any finite collection of
t ypes.

Each ordinal type contains values that are atomic- the values are not composite. Aggregate
types, on the other hand, combine previously defined types, which may be ordinal or other aggregate
.ypes, into new types that contain components of the previously defined types.
<new type> ::= <new ordinal type> I <aggregate type>
<aggregate type> ::= <unpacked structured type>
<unpacked structured type> ::= SET OF <base type>
I FILE OF <component type>
I RECORD <field list> END
<base type> ::=<ordinal type>
<ordinal type> ::= <new ordinal type> I <ordinal type identifier>
<ordinal type identifier> ::= <type identifier>
<component type> ::= <type denoter >
" ets, files, and records combine component types in different ways. Set and file types group identical
components, while record types group components that differ. The set and record types group
components without regard to order, while file types form sequences of components. Any component
of a record may be selected; the components of a file may only be accessed sequentially; and no
selection operation at all is defined for set components.

16.1 Sets

Preview

Pascal's set data type allows the programmer to declare and use variables that are sets whose
members are drawn from (small) finite collections. A full range of set operations is available.

A set type is declared as follows:


TYPE
SetType = SET OF ComponentType;
where ComponentType is an ordinal type . Pascal sets are further restricted to take their members
from a limited collection, whose maximum size is not part of the definition of Pascal but varies from
one machine to another . (The limit is typically small, often only a few hundred.) Thus, it is illegal
to declare:
TYPE
IntSet = SET OF INTEGER;
but legal to declare:

16.1 Sets 435


CONST
Max = 20;
TYPE
IntSet =SET OF 2 .. Max;
if Max is chosen small enough to be within the limit. Declaration statements do not provide an
initial value for a set variable. Thus, the value of Sieve after
VAR
Sieve: SET OF 2 •. Max;
is undefined.
Set constants are constructed by enclosing lists or subranges of expressions in square brackets.
A set constant containing an empty list of values (i.e., [ ] ) is used to represent the empty set.
The possible values for a set are the members of the powerset (i.e., set of all subsets) of the
collection of values used to define the type . For example, the powerset of the type:
TYPE
SmallintSet =SET OF 1 .. 3
lS

{{}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}},
so any of these sets may be values of type Small IntSet. The Pascal set constant for the value set
that has three elements would be written [1, 2, 3] or [1 .. 3].
Any of the following assignment statements initialize Sieve to the set value {2,3, ... 19,20}:
Sieve : = [2 .. 20];
Sieve : = [2,3,4,5,6,7,8,9,10,11,12,13,
14,15,16,17,18,19,20];
Sieve:= [2 .. 9,10,11 .. 15,16,17,18 .. 20]
The operations on set values are + (set union), - (set difference), * (set intersection); =
(equality), <> (inequality), <= (nonstrict subset), >= (nonstrict superset); and IN (set
membership).

Set Operator I Operands-+- Result


+ * - Set X Set -+- Set
= <> <= >= Set X Set -+- Boolean
IN ComponentType X Set-+- Boolean
·--...:~-- _-._- ·- - ·- ~ ..J [ ] ComponentType X ... X ComponentType -+- Set

The union, difference, and intersection operations are defined only for pairs of sets, not set
components. Thus if Sieve has integer members it is illegal to write:
Sieve + 2
Instead, the element must be made into a 1-set value:
Sieve + [2]
Some sample relational expressions and their values are:

Expression Value
[3, 5] <= [3 . . 5] TRUE
[3 5] <= [3 .. 6] TRUE
[3 5] >= [3 .. 5] TRUE
[3 5] >= [3 .. 6] FALSE

The binary infix operator IN tests set membership:

436 AGGREGATE DATA TYPES IN 0 PASCAL


Expression Value
4 IN [3 . . 5] TRUE
4 IN [] FALSE
4 IN [3 5] 1 FALSE

:fie square brackets are not only used for set constants, but to form sets from expressions whose
alues are the member elements. For example, if X is of type INTEGER with value 3:

Expression Value
[2 X]
1 {2, 3}
[3 .• X] {3}
[X .. X+2 1 SUCC(X+7)] {3, 4, 5, 11}
[X .. 1] {}
-::>roperties of the set operators are shown below:

Operator Associativity Precedence


[ ] none highest
* left tI
+ left I
relationals none lowest

Ev aluations of some sample set values are shown below in the Pascal constants notation.
[1] + [7 o o 9] * [7 1 9] = [1] + [71 9] = [11 71 9]
[4 .. 6] - [41 6] + [1] = [5] + [1] = [51 1]

16.1.1 Counting Characters


Sets can simplify some of the tests in programs. For example, the following program counts the
number of letter and digit characters in the input.
PROGRAM AlphaCount(INPUT, OUTPUT);
VAR
Ch: CHAR;
Count: INTEGER;
BEGIN {AlphaCount}
Count := 0;
WHILE NOT EOF
DO
BEGIN
READ(Ch);
IF ((Ch >= I a I) AND (Ch <= 'z ')) OR
((Ch >= 'A') AND (Ch <= 'Z')) OR
( (Ch >= 'O I) AND (Ch <= I 9 I))
THEN
Count := Count + 1
END;
WRITELN(Count alphanumeric characters read')
1 '

END. {AlphaCount}

16.1.1 Counting Characters 437


Executions
INPUT :abc U de01 23f
OUTPUT: 11 alphanumeric characters read
INPUT :
OUTPUT: 0 alphanumeric characters read
The test for alphabetic characters needs no logical operations if a set is used, as in the fragment:
WHILE NOT EOF
DO
BEGIN
READ(Ch);
IF Ch IN [ I A I • • I Z I , 'a I • • I z I , I 0 I • • I 9 I ]
THEN
Count := Count + 1
END;

16.1.2 Rules of Syntax and Meaning


The syntax rules for expressions including set operations are shown below.
<expression> ::= <simple expression>
I <simple expression> <relational operator> <simple expression>
<simple expression>::= <simple expression> <add operator> <term>
I <term> I <sign> <term>
<term> ::= <term> <mult operator> <factor> I <factor>
<factor> ::= <identifier> I <function designator>
I <unsigned constant> I ( <expression> )
I NOT <factor> I <set constructor>
<relational operator>::= =I <> I < I <=I > I >=I IN
<sign>::= + I-
<add operator> ::= + I - I OR
<mult operator>::= * I DIV I MOD I AND
<set constructor> ::= [ <member designators> ]
<member designators> ::= <member designators> , <member designator>
I
I
<member designator> ::= <expression> I <expression> .. <expression>
The meaning of each operation between set operands El and E2 and set-member operand Me can
·be given in terms of the usual mathematical operators as follows:
IEl = E2j{s) = ( IElj{s) =IE21(s))
IEl <> E2j{s) = ( IElj{s) ~~E2j{s))
IEl <= E2j{s) = {jElj{s) C IE2j{s))
IEl >= E2j{s) = ( jElj{s)::) IE2j{s))
jMe IN E2j{s) = {jMej{s) E jE2j{s))
IEl + E2j{s) = ( jElj{s) ujE21(s))
IEl - E21(s) = ( jElj{s) -jE21(s))

438 AGGREGATE DATA TYPES IN D PASCAL


IEl * E21(s) = (jElj{s)nlE2j{s))
I [Me] I(s) = {IMe I(s)}
[lill(s) =~(s)
for any state s. For more complex expressions the rules of syntax, associativity, and precedence
specify the order in which operations are to be performed .

16.1.3 Prime Numbers


A method known as the sieve of Eratosthenes can be used to print the prime numbers between 2 and
a n arbitrary upper bound, say 16. Initially all the numbers between 2 and 16 are placed in the sieve.
The smallest number in the sieve is prime; and all its multiples are removed from the sieve. The new
smallest number in the sieve is the next prime and its multiples are also removed . This process is
repeated until the maximum value in the sieve is reached. Initially, let
s= {2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}.
Removing multiples of 2:
s- {2, 4, 6, 8, 10, 12, 14, 16} = {3, 5, 7, 9, 11, 13, 15}.
:\-fultiples of 3:
{3, 5, 7, 9, 11, 13, 15}- {3, 6, 9, 12, 15} = {5, 7, 11, 13}.
(Some multiples of 3 have already been removed, i.e., those that are also multiples of 2.)
The series of set differences performed would be :

Contents of sieve removed multiples of


{2,3,4,5,6,7 ,8,9,10,11,12,13,14,15,16}
{3,5,7 ,9,11,13,15} {2,4,6,8, 10,12,14,16} 2
{5,7,11,13} {3,6,9,12,15} 3
{7,11,13} {5,10,15} 5
{11,13} {7,14} ,, 7
{13} {11} 11
{} {13} 13

The sieve is now empty and the primes up to 16 are 2, 3, 5, 7, 11, 13.
The set type is exactly the right data type to implement this algorithm. In the design of a
solution for the prime number problem, the sieve is represented as a set of integers in order to use
t he set difference operator to remove elements from the set.
Design part 1
PROGRAM Prime(OUTPUT);
{Print the prime numbers between 2 and Max}
CONST
Max = 16;
VAR
Sieve: SET OF 2 .. Max;
Next, NextMult: INTEGER;
BEGIN {Prime}
Sieve := [2 Max];
Next := 2;
WHILE S i e v e < > []
DO
BEGIN
{print smallest member Next of Sieve};
{remove multiples NextMult of Next from Sieve}

16.1.3 Prime Numoers 439


END
END. {Prime}
To find the smallest member of Sieve, since sets are unordered collections of values, requues
testing successive integers.
Design part 1.1
BEGIN {print smallest member Next of Sieve};
WHILE NOT(Next IN Sieve)
DO
Next := Next + 1;
WRITE(Next:5)
END
Design part 1.2
BEGIN {remove multiples of Next from Sieve}
NextMult := Next;
WHILE NextMult <= Max
DO
BEGIN
Sieve:= Sieve- [NextMult];
NextMult := NextMult + Next
END
END
Assembling the program:
PROGRAM Prime(OUTPUT);
{Print the prime numbers between 2 and Max}
CONST
Max = 16;
VAR
Sieve: SET OF 2 .. Max;
Next, NextMult: INTEGER;
BEGIN {Prime}
Sieve:= [2 .. Max];
Next := 2;
WHILE Sieve <> []
DO
BEGIN
BEGIN {print smallest member Next of Sieve}
WHILE NOT(Next IN Sieve)
DO
Next := Next + 1;
WRITE(Next:S)
END;
BEGIN {remove multiples of Next from Sieve}
NextMult := Next;
WHILE NextMult <= Max
DO
BEGIN
Sieve:= Sieve- [NextMult];
NextMult := NextMult + Next
END
END
END
END. {Prime}

440 AGGREGATE DATA TYPES IN D PASCAL


Execution
OUTPUT: 2 3 5 7 11 13
Notice the use of the constant identifier Max in Prime. To change the upper bound on the
range of numbers considered, only the constant definition need be changed.

16.1.4 Exercises
16.1.1 Explain why sets would not be very useful on a Pascal machine that limited the underlying
-ype to have less than 26 elements.
16 .1.2 How many set values of the type SET of BOOLEAN are there?· How many members has
he set of this type with the most members?
16 .1.3 Suppose that for the declaration
VAR
SetSized: SET of Typ
-he underlying type Typ has exactly five possible values.
a) How many possible different values are there for SetSized?
b) Explain why no value of SetSized can have more than five members.
6.1.4 Rewrite the program Pr intDays of Section 15.5.2 so that it tests for the 30-day months
sing a constant of type SET of Month .
16.1.5 In the context of the following program fragment:
VAR
A: SET OF 1 . • 20;
B,C,D: INTEGER;
BEGIN
A : = [1. . 20];
B : = 5·'
c : = 8·'
D : = 10;
ev aluate the following expressions:
a) NOT ( [B, C, D] <= A)
b) [B .. C] + [D] * [2 .. 8]
c) [B .. C] - A
d) B IN A

e)B IN (A- [B])


f) [B .. C] * [C .. D]
16.1.6 Each Pascal fragment below contains the identifier Qu . You are to deduce a possible type of
u from the context in each separate case. Express the answer as a type specification for the
declaration:
VAR
Qu: ?
Re place the question mark with the type required of Qu . For example, if you decide that one answer
is a subrange of CHAR between 3 and 6, fill in:
VAR
QU : I 3 I • • 16 I

If there are several possible types, explain briefly.

16.1.4 Exercises 441

,. .
a) 'A' IN Qu
b) Qu + [3]
16.1.7 Show that the following program fragment:
S := []; I := 0; N := 4;
WHILE I <= N
DO
BEGIN
I := I + 1;
S : =
S + [I]
END
computes the function:
_ S,I,N := [1. .5] ,5,4
You may assume that the function of the WHILE statement is:
(I<=N ~ S, I : = {I+k I 1 <k<N-I+1}, N+1)
(I>N ~ )

16.1.8 Determine the function of the following WHILE statement.


WHILE B < 50
DO
BEGIN
A:= A+ [2*B];
B := B + 1
END

16.2 Files

Preview

Files are ordered sequences of values. WRITE statements append a value to an existing
sequence (always at the end), and READ statements can obtain the values in the same order
they were earlier written. The values are not limited to CHAR as in TEXT files, but may be of
almost any Pascal type. Buffer variables and PUT /GET statements provide an additional way
to manipulate files.

In addition to character files, Pascal lets us define new file types with components of any previously
defined type other than file or another aggregate type containing a file as a component. A typical
type declarationfor a file is:
VAR
IntFile: FILE OF INTEGER;
An IntF i le value is a sequence of integer values-only INTEGER quantities may be read from, or
written to, the file. File variables are the only Pascal variables that cannot be the target of
assignment statements. All file values must be created using predefined operations.
A file declaration implicitly declares a buffer variable written as the file identifier followed by
an up-arrow (e.g., IntF i let) with the same type as the components of the file. When we are
reading a file, IntFilet contains the value currently available for reading. New file components
are appended to a file by filling the buffer variable and writing its contents. Buffer variables can
appear anywhere other variables can appear, e.g., in expressions:
3 + IntFilet * 7
However, buffer variables are not identifiers since they end in a character which is neither a letter
nor a digit. Thus, we must update our syntax rules, replacing <identifier> on the right side of the

442 AGGREGATE DATA TYPES IN D PASCAL


<factor> rule with <variable access> which derives both identifiers and buffer variables.
<factor>::= <variable access> l <function designator>
l <unsigned constant> l ( <expression> )
I NOT <factor> l <set constructor>
<variable access> ::=<variable identifier> l <buffer variable>
<buffer variable> ::= <file variable> t
<file variable> ::=<variable access>
(Although the syntax rules indicate that a file identifier followed by an arbitrary number of t
characters is a file variable, only a single t may appear after the identifier.)
TEXT file values are 3-lists whose first two elements are strings for the past and future values
of the file. For arbitrary files, the past and future values are lists of elements, elements of the type
defined for the file. The third element of a file value is the mode indicator telling whether the file is
open for reading (R) or writing (w).
Since the declaration of a file variable introduces a buffer variable in addition to the file, the
semantics of the declaration statement for files must be altered to add a buffer variable with an
undefined value to the state:
jvAR F: FILE OF TypeTj(s) = s U {F·<?,?,?>, Ft·?}.

16.2.1 Non-TEXT Files


Files are sequences of elements all of the same type. The operations on files are listed below.

Operation Operand type - Result type


REWRITE File- void
PUT File- void
RESET File- void
GET File- void
EOF File - Boolean
The term "void" is used to indicate that these operations do not return values; the operations are
similar to Pascal procedure statements and can appear anywhere a statement can. The effects of
each of these operations on the state of a file are indicated by the finite-state automaton below. Its
states are the two file modes R and W.

RESET REWRITE

REWRITE

RESET

An initial RESET or REWRITE puts a file in the R or W mode.


REWRITE and PUT are used to build file values. REWRITE empties a file and prepares it for
writing.
jREWRI TE (F ) I={(
u, v): F is not INPUT or OUTPUT and v = u except that
v(F) = << >, < >,w>}.
If a file is open for writing, PUT appends the value of its buffer variable to the file and leaves the
buffer variable undefined.

16.2.1 Non-TEXT Files 443


lPUT (F) I= {( u, v): u(F).3=W and v = u except that
v(F).1 = u(F).1 \7( u(Ft)) and v(Ft) is undefined}.
Thus PUT is undefined when a file is not open for writing.
For example, to write a file of integers, the file has to be declared and rewritten:
VAR
IntList: FILE OF INTEGER;
BEGIN
REWRITE(IntList);

END
Then values are assigned to IntListt so that subsequent PUT statements will append the values
to IntList:
{equivalent of WRITE(IntList, 1)}
IntListt := 1;
PUT(IntList); {appends 1 to IntList, IntListf undefined}
{equivalent of WRITE(IntList, 2)}
IntListf : = 2;
PUT(IntList) {appends 2 to IntList, IntListf undefined}
At this point, IntList contains the values 1 and 2 in its past list, the file is writable, and the
contents of IntListf are not defined.
lntList = < <1,2>,<>,W > and IntListt =undefined
RESET and GET are the operations that retrieve values from files. RESET returns its file to
its starting position so that reading can begin. The file's buffer variable is assigned the value of the
first component of the file's future list {if one exists).
!RESET (F) I= {( u, v): F is not INPUT or OUTPUT and v = u except that
v{F) = <<>, u{F).1 & u(F).2, R> and v(Ft) = e(u(F).1 & u(F).2}.
Since the head function 9 is undefined for empty lists, the buffer variable is undefined when RESET
is applied to empty files.
If a file is open for reading and its future list is not empty, GET transfers the first element
from the future list to the past list, and assigns the value of the new first element of the future list
to the file's buffer variable.
lGET (F) I= {(u,v): u{F) .3 = Rand u(F) .2 =I=<> and v = u except that
v(F).1 = u(F).1 \7(8( u(F).2)), v(F).2 = A{ u(F).2), and
v(Ft) = e(A{ u(F).2))}.
If a file is not open for reading, the statement aborts and program execution will halt. The statement
will also abort if there is no data left to read (i.e ., the future list is empty).
The file IntList constructed above has two integer values in its past list and is open for
writing; that is, it's value is <<1,2>, <>, W > . A RESET operation changes the values of both
IntList and IntListt to <<>, <1,2>, R> and 1, respectively . Now a GET on IntList
moves 1 to the past list of the file so the value of IntList becomes:
<V(e(<1,2>)), A{<1,2>), R> = <<1>, <2>, R>,
and the buffer pointer IntListt has value
e(A{<I,2>)) = 8(<2>) = 2.
The end-of-file function EOF returns a Boolean value: TRUE if no more components remain in
the file's future list and FALSE otherwise.
jEOF (F) l(u) = (u(F).2 = <>).
If EOF is true, the value in the file buffer may not be accessed beca use it i::: undefi ned.

444 AGGREGATE DATA TYPES IN D PASCAL


. . - - - -- - - - -- - - - - -- - - - -..........~----=
--=
- =-
=~~~~~
-~~~~------- ---

'
EOF (IntList) is FALSE after the first GET described above, since IntList has a future list
containing 2. A second GET makes the file value
<<1> V'(e(<2>)), A(<2>), R> = <<1,2>, <>, R>
and the buffer-variable value
e(A(<2>)) = 9(<>)
which is undefined . The value of EOF (IntList) is now TRUE. To avoid an attempt to access a
b uffer variable when it is undefined we adopt programming paradigms in which EOF is always tested
before the buffer is accessed. For example:
RESET(F);
WHILE NOT(EOF(F)) DO
BEGIN
Fl ... {access the buffer variable}

GET (F)
END

16.2.2 OddBeforeEven Program System


Pascal TEXT files can usually be created without using a PASCAL program, for example by typing
characters using an editor. But a FILE OF INTEGER (say) cannot be created by typing what look
like integer values using an editor. Such a file is still a TEXT file, in which characters like 3 appear.
T o create non-TEXT files Pascal must be used. For example, the following program reads the TEXT
in put file and creates a file with integer values.
PROGRAM GenintFile(INPUT, Intin, OUTPUT);
{write the integers in INPUT to Intin,
echo INPUT to OUTPUT}
VAR
Intin: FILE OF INTEGER;
Int: INTEGER;
BEGIN {GenintFile}
REWRITE(Intin);
READ(Int); {converts INPUT chars to integer}
WHILE Int <> - 1
DO
BEGIN
{WRITE(Intin, Int)}
Intinl : = Int;
PUT(Intin);
WRITE(Int); {write characters representing
integer to OUTPUT}
READ(Int) {converts INPUT chars to integer}
END;
WRITELN
END. {GenintFile}
Execution
INPUT :1 2 3 4 5-1
OUTPUT : 1 2 3 4 5
The execution does not show the re al purpose of GenintF i le, however, which ts to create the
external file Intin, whose value following the last WRITE statement is

16.2.2 OddBeforeEven Program System 445


<<1,2,3,4,5>, <>, w>.
The second part of the program system takes its input from Intin, and sends output to Out
with a copy to the regular output for observation. OddBeforeEven is intended to separate the
integers of its input into odds and evens, and list the odd values first, followed by the even values,
otherwise preserving the sequence in Intin .
Design Part 1
PROGRAM OddBeforeEven(Intin, Out, OUTPUT);
VAR
Intin, Out: FILE OF INTEGER;
BEGIN {OddBeforeEven}
REWRITE(Out);
{copy odd members of Intin to Out};
{copy even members of Intin to Out};
RESET(Out);
{copy Out to OUTPUT}
END. {OddBeforeEven}
Since RESET (Intln) will fill Intint with the first value in Intin, this value is copied to Outt
so that the subsequent PUT (Out) copies the value to Out .
Design Part 1.1
{copy odd members of Intin to Out}
RESET(Intin);
WHILE NOT EOF(Intin)
DO
BEGIN
IF ODD (Intint)
THEN
BEGIN
{WRITE(Out, Intint)}
Outt : = Intint;
PUT(Out)
END;
GET (Intin)
END;
Design Part 1.2
{copy even members of Intin to Out}
RESET(Intin);
WHILE NOT EOF(Intin)
DO
BEGIN
IF NOT ODD (Intint)
THEN
BEGIN
{WRITE(Out, Intint)}
Outt : = Intint;
PUT(Out)
END;
GET (Intin)
END;

446 AGGREGATE DATA TYPES IN D PASCAL


Design Part 1.3
{copy Out to OUTPUT}
WHILE NOT EOF(Out)
DO
BEGIN
WRITELN{Outt);
GET {Out)
END
Combining these design parts yields the following sample results, usmg the file Intin
_ roduced by the execution of Genintf i le shown above.
Execution
INPUT :
OUTPUT: 1
3
5
2
4
Many Pascal machines support two other operations on non-TEXT files , allowing programmers
-o avoid the use of buffer variables. However, these operations are not officially part of the Pascal
nguage , so they may not be available .
READ{FileVar, Varl, . .. , Varn)
here Varl, ... , Varn are variables of the appropriate type, may be defined in terms of GET as
_ollows:
READ{FileVar, Varl, • . . , Varn)
as the effect of:
BEGIN
Varl : = FileVarf;
GET(FileVar);

Varn := FileVart;
GET{FileVar)
END
Similarly,
WRITE{FileVar, Expl, ... , Expn)
where Expl, ... , Expn are expressions of the appropriate type, has the effect of:
BEGIN
FileVarf := Expl;
PUT{FileVar);

FileVarf := Expn;
PUT{FileVar)
END
If a Pascal machine supports READ and WRITE on non-TEXT files, OddBe foreEven can be
rewritten without using buffer variables:

16.2.2 OddBeforeEven Program System 447


PROGRAM OddBeL:-n~Even (Intin, Out , OUTPUT);
VAR
Intin, Out: FILE OF INTEGER;
Val: INTEGER;
BEGIN
REWRITE(Out);
RESET(Intin);
WHILE NOT EOF(Intin)
DO
BEGIN {copy odd members of Intin to Out}
READ(Intin, Val);
IF Odd (Val)
THEN
WRITE(Out, Val)
END;
RESET(Intin);
WHILE NOT EOF(Intin)
DO
BEGIN {copy even members of Intin to Out}
READ(Intin, Val);
IF NOT Odd (Val)
THEN
WRITE(Out, Val)
END;
RESET(Out);
{copy Out to OUTPUT}
WHILE NOT EOF{Out)
DO
BEGIN
READ(Out, Val);
WRI~ELN(Val)
END
END.

16.2.3 TEXT Files


The meaning of TEXT file operations was given in Sections 6.2.4 and 6.2.5 . These definitions agre e
with those given for non-TEXT files by replacing the lists of elements in the latter with strings, in
which the special character / represents a line marker. GET and PUT and buffer variables may
thus be used with TEXT files as well. There is one difference involving RESET: when a TEXT file is
being written, its final line is incomplete until the line marker is added. If the last operation is
WRITELN the marker is present; if not, a RESET adds it. (Many Pascal machines do not properly
implement this feature of RESET, which is why in this text we have been careful to always use a
final WRITELN .) TEXT files permit the READLN, WRITELN, and EOLN operations that are not
available for other files. In addition there is a statement that causes the output to be adjusted to a
page boundary if the printing device has such a feature:
PAGE(F)
causes the next WRITE statement for F to appear at the top of the page.

448 AGGREGATE DATA TYPES IN D PASCAL


16.2.4 Justifying Text with lnsertExtraBlanks
T he existence of INTEGER variables and the use of buffer variables makes complex text processing
easier in D Pascal than in CF Pascal. .Af3 an extended example, the problem of justifying text will be
onsidered. In this problem, words (defined as sequences of nonblank characters) and blanks
lternate in the input lines. A line length L is given as the available output space. The words from
input are to be printed in this space, and furthermore, the right and left margins are to be aligned.
T he most straightforward algorithm for solving the problem is to remove any extra blanks between
ords, and if the words fit the available space, pad some of the spaces between words to make the
::nargms even.
The procedure RemoveExtraBlanks of Section 10.1.3 will be useful in implementing this
·ext-justifying algorithm. Another useful component will be a routine that begins with a t~short
ine, and pads it to fill the space L. Suppose there are N words with no extra blanks between them,
ontaining with the blank separators a total of C characters. Then L - C blanks must be fitted into
_ - 1 slots between words. If there is more than a single word, N > 1,
(L- C) 7 (N -1)
blanks can be put in each of the slots. But if the division does not come out even, the remaining R
blanks must be distributed. They can be distributed among the rightmost R slots of the line, making
•hose spaces one blank wider than the ones at the beginning of the line. The design based on these
·de as is:
Design Part 2
PROCEDURE InsertExtraBlanks(VAR Filein, FileOut: TEXT;
LineLength: INTEGER);
{assumes Filein contains no extra blanks
between words;
copies Filein to FileOut, uniformly inserting extra
blanks so that each line in FileOut is LineLength
. characters long}
VAR
TFile: TEXT;
XBlanks: INTEGER;
Words, Chars: INTEGER;
BEGIN {InsertExtraBlanks}
RESET(Filein);
REWRITE(FileOut) ;
WHILE NOT EOF(Filein)
DO
BEGIN
{copy one line from Filein to TFile,
counting Words and Chars in line};
XBlanks := LineLength - Chars ;
IF (Words > 1) AND (XBlanks > 0)
THEN {multiple words on line and
blanks to insert}
BEGIN
{copy line from TFile to FileOut
inserting blanks}
END
ELSE {single word on line, line too long,
or no blanks t o a d d}
BEGIN
{copy entire line from TFile to FileOut,
inserting any extra blanks at end}

16.2.4 Justifying Text with lnsertExtraBianks 449


END
END;
RESET(Filein);
RESET(FileOut)
END; {InsertExtraBlanks}
Since F i lein does not contain extra blanks, each blank or line marker signifies the end of a word.
Design Part 2.1
{copy one line from Filein to TFile,
counting Words and Chars in line}
REWRITE(TFile);
Chars := 0;
Words := 0;
WHILE NOT EOLN(Filein)
DO
BEGIN
Chars := Chars + 1;
IF Fileinf - 1 1
THEN
Words := Words + 1;
TFilef := Fileinf;
PUT(TFile);
GET(Filein)
END;
Words := Words + 1;
READLN(Filein);
WRITELN(TFile);
RESET(TFile);
In the next design part, Blanks is the number of extra blanks to insert between two words on
the line. All slots between words beyond number Pes get an additional blank.
Design Part 2.2
{copy line from TFile to FileOut inserting blanks}
Blanks:= XBlanks DIV (Words -1 );
Pes :=Words- (XBlanks MOD (Words-1));
Word := 0;
WHILE NOT EOF(TFile)
DO
BEGIN
{copy word from TFile to FileOut};
Word := Word + 1;
IF Word < Words
THEN {not last word}
BEGIN
WRITE (FileOut, 1 1 ) ;
{insert extra blanks:
Blanks if Word < Pes , Blanks+1 if Word >= Pes}
END
ELSE {last word}
WRITELN(FileOut)
END

450 AGGREGATE DATA TYPES IN D PASCAL


De8ign Part 2.2.1
{copy word from TFile to FileOut}
WHILE TF ilet <> 1 1
DO
BEGIN
FileOutt := TFilet;
PUT(FileOut);
GET(TFile)
END;
GET(TFile);
Design Part 2.2.2
{insert extra blanks:
Blanks if Word < Pos, Blanks+l if Word >= Pos}
Bindex := 1;
WHILE Bindex <= Blanks
DO
BEGIN
WRITE(FileOut, 1 1 ) ;
Bindex := Bindex + 1
END;
IF Word >= Pos
THEN
WRITE (FileOut, 1 1 )
If there is only a single word on the line, all the extra blanks should be inserted to the right of
-he word . If the value of XBl anks is less than or equal to zero, the length of the text line is greater
-han or equal to LineLength, and the entire text is printed.
Design Part 2.3
{copy entire line from TFile to FileOut,
inserting any extra blanks at end}
WHILE NOT EOLN(TFile)
DO
BEGIN
FileOutt := TFilet;
PUT(FileOut);
GET(TFile)
END;
WHILE XBlanks > 0
DO
BEGIN
WRITE (FileOut, 1 1 ) ;
XBlanks := XBlanks - 1
END;
WRITELN(FileOut)
These design parts can be assembled into two development programs for testing. The first
reports counts of words and char acters, and contents of TFile to OUTPUT and prints the value of
Words and Chars.

16.2.4 Justifying Text with lnsertExtraBianks 451


Development Program 2A
PROGRAM Testinsert(INPUT, OUTPUT);
VAR
TempFile1, TempFile2: TEXT;
LineLength: 1 .. 65;
Ch: CHAR;
{Include
PROCEDURE RemoveExtraBlanks
(VAR Filein, FileOut: TEXT);
copy Filein to FileOut removing leading, trailing,
and extra blanks between words in Filein}
PROCEDURE InsertExtraBlanks
(VAR Filein, FileOut: TEXT;
LineLength: INTEGER);
{assumes Filein contains no extra blanks
between words;
copies Filein to FileOut, uniformly inserting extra
blanks so that each line in FileOut is LineLength
characters long}
VAR
TFile: TEXT;
XBlanks: INTEGER;
Words, Chars: INTEGER;
BEGIN {InsertExtraBlanks}
RESET(Filein);
REWRITE(FileOut);
WHILE NOT EOF(Filein)
DO
BEGIN
{copy one line from Filein to TFile,
counting Words and Chars in line}
REWRITE(TFile);
Chars := 0;
Words := 0;
WHILE NOT EOLN(Filein)
DO
BEGIN
Chars := Chars + 1;
IF Fileint = ' '
THEN
Words := Words + 1;
TFilef := Fileinf;
PUT(TFile);
GET (F ilein)
END;
Words := Words + 1;
READLN(Filein);
WRITELN(TFile);
RESET(TFile);

452 AGGREGATE DATA TYPES IN D PASCAL


{copy TFile to OUTPUT; write Words and Chars}
WHILE NOT EOF(TFile)
DO
BEGIN
WHILE NOT EOLN(TFile)
DO
BEGIN
OUTPUTf := TFile f;
PUT(OUTPUT);
GET (TFile)
END;
READLN(TFile);
WRITELN;
WRITELN('Words=' ,Words,', Chars=' ,Chars)
END;
XBlanks := LineLength - Chars;
IF (Words > 1) AND (XBlanks > 0)
THEN {multiple words on line and
blanks to insert}
BEGIN
{copy line from TFile to FileOut
inserting blanks}
END
ELSE {single ~o•d on line, line tao lang,
or no blanks to add}
BEGIN
{copy entire line from TFile to FileOut,
inserting any extra blanks at end}
END
END;
RESET(Filein);
RESET(FileOut)
END; {InsertExtraBlanks}
BEGIN {Testinsert}
REWRITE(TempFilel);
READLN(LineLength);
WHILE NOT EOF
DO
BEGIN
WHILE NOT EOLN
DO
BEGIN
READ(Ch);
WRITE(TempFilel,Ch)
END;
READLN;
WRITELN(TempFilel)
END;
RemoveExtraBlanks(TempFilel,TempFile2);
Inser t Ext r a Blanks(TempFile2,TempFilel,LineLength)
END. {Tes t insert}

16.2.4 Justifying Text with lnsertExtraBianks 453


Execution
INPUT :15
Now is the time
for all good
men
to come to the
aid of their party.
OUTPUT:Now is the time
Words 4, Chars 15
for all good
Words 3, Chars 12
men
Words 1, Chars 3
to come to the
Words 4, Chars 14
aid of their party.
Words 4, Chars 19
The second development program is formed by removing the added code in the last skeleton and
adding the remaining design parts and code to copy TempF i lel to OUTPUT. If the same input is
presented to this program, the result is:
Execution
OUTPUT:Now is the time
for all good
men
to come to the
aid of their party.
Even this small amount of test data covers many of the cases in the program. The first line is
exactly the right length; no blanks are added to it. The second and fourth lines each need blanks
added, and additional blanks are added before the rightmost words. The third line contains a single
word, and the last line is too long.

16.2.51nteractive Input/Output
To this point, all the programs executed by the Pascal machine have been batch-processed, i.e., the
programs and all the data are presented to the machine at the same time, the program is executed,
and the results are printed. Many programs are designed to be run interactively, prompting the user
to enter more data after execution has begun and displaying some results before all the data has
been entered. MinMax below is a typical interactive program. It reads a single character
command that guides the program to print either the higher (requested by the command H) or lowe r
(requested by L) of the two characters following the command. The program continues comparing
characters as long as the first character on an input line is a valid command. When an invalid
command is encountered, the program terminates.

454 AGGREGATE DATA TYPES IN D PASCAL


---------- - - -- -·-- - - - - - - - - - - - - - - - - - - - - - -

PROGRAM MinMax(INPUT,OUTPUT);
VAR
Command,Chl, Ch2: CHAR;
PROCEDURE SortTwo(VAR Chl, Ch2: CHAR);
{ <Chl ,Ch2> := <min(Chl,Ch2),max(Chl,Ch2)>}
VAR
Temp: CHAR;
BEGIN {SortTwo}
IF Chl > Ch2
THEN
BEGIN
Temp := Chl;
Chl := Ch2;
Ch2 := Temp
END
END; {SortTwo}
BEGIN {MinMax}
WRITELN( 1 Enter <H or L> <chl> <ch2> 1 ) ;
READ(Command);
WHILE (Command = 1 H 1 ) OR (Command = 1 L 1 )
DO
BEGIN
READLN(Chl,Ch2);
SortTwo(Chl,Ch2);
IF Command = 1 H 1
THEN
WRITELN( 1 The greater character is I I I

Ch2, I I I I )
ELSE
WRITELN( 1 The lesser character is I I I
Chl, I I I I ) ;
READ(Command)
END
END. {MinMax}
Execution
OUTPUT:Enter <H or L> <ch1> <ch2>
INPUT :HFG
OUTPUT:The greater character is 'G'
INPUT :LFG
OUTPUT:The lesser character is ' F'
INPUT :J
OUTPUT:
As is typical of interactive program, MinMax prompts its user for input with the line:
Enter <H or L> <chl> <ch2>
a nd only a user unacquainted with BNF would think that the angle brackets should be entered in
r esponse. To be useful this line must appear immediately , but there is a peculiarity of Pascal that
m ay hold it until some input has been entered. Program execution begins with an implicit
RESET (INPUT) ;REWRITE (OUTPUT). The RESET does a GET (INPUT) to place the first
charac te r of t he fi le in I NPUTt, bu t no data has been entered because the user is waiting for the
prom p t! F ur thermore, when no d ata has bee n entered what is the value of EOF or EOLN? Pascal
machines usually get around these problems by defining default values for INPUTf, EOF, and
EOLN, or using lazy evaluation strategies. The programs in this text assume lazy evaluation.

16.2.5 Interactive lnput'Output 455


The default values that are typically used are:

Quantity Initial Value


EOF (INPUT) FALSE
INPUTf 0
EOLN(INPUT) TRUE

With these values, a program's first READ statement obtains a blank, and INPUTf assumes the
value of the first character actually in the file. Thus when a Pascal machine adopts this method, the
programmer must do an initial GET or READLN to discard the first character.
Lazy evaluation is a strategy by which values are not computed until they are needed. Since
no value associated with INPUT is needed in MinMax until the first READ statement
(READ (Command)) is encountered, execution of the implicit RESET (INPUT) is postponed until
after the first WRITELN statement.
In either case, an initial WRITE statement can provide a prompt for an interactive program .
However, if lazy evaluation is used, no extra READ statement is required, .and using one will discard
the first real character of input. So experimentation with the Pascal machine is required before
interactive programs can be written.

16.2.6 Exercises
16.2.1 TEXT files may be examined using either READ statements or through the buffer variable .
Give a typical program loop that uses a READ statement to obtain each element in a TEXT file ,
ignoring the line structure . Then write an equivalent program using the buffer-variable method of
access.
16.2.2 The OddBeforeEven program makes two passes over the file Intin. Redo the entire
design so that only one pass is required, using a temporary file if required. Instead of using Out ,
send the results directly to OUTPUT.
16.2.3 Design and test a programming system using two external files, one a FILE OF Month and
the other a FILE OF INTEGER. One program in the system writes these files, putting in the
month and number of inches of rainfall for that month . (Simply assume that each month a separate
program is made up to write that month's data, and give the program for a February where the
rainfall was 3 inches.) The second program summarizes a year's data and prints out a table; this
program is to be run after the December data is entered. Is a third program needed to initialize the
files?
16.2.4 The following declarations are in force for this question:
TYPE
Drink= (Coffee, Tea, Milk);
VAR.
Ch: CHAR;
Itg: 3 .. 33;
Enm: Drink;
EFile: FILE OF Drink;
FUNCTION Ff(VAR. Prl: CHAR; Pr2: CHAR): BOOLEAN;
In each part below there is at least one mistake. Locate and explain each one.
a)IF Ff(Ch, Ch) = 1
THEN
EFilef := SUCC(Tea)
b) (within the body of Ff):
F f : = Ch IN [ 1 A 1 , Enm]

456 AGGREGATE DATA TYPES IN D PASCAL

. ~
c)IF (Coffee< Tea) AND (Ff('A', 'B'))
THEN
Itg : = Itg + 32
d)IF [3, 44 , Itg] +[Tea, PRED(Tea)] []
THEN
RESET (EFile)
16.2.5 Extend the text-justifying problem of Section 16.2.4 to include rearranging the words on lines
so that no line is too long. That is, the words are to be taken from INPUT, and justified lines are to
be sent to OUTPUT, with enough words to fill, but not overfill each line. The most straightforward
a lgorithm is to read words until the sum of their lengths (including one-blank separators) just
exceeds the output length available, then justify the line using all but the final word, which is taken
to start the next line. Give a program design that solves the extended problem, using as many of the
routines from Section 16.2.4 as possible.
16.2.6 The simple algorithm suggested in the previous exercise does not always produce the best
looking text.
a) Describe how simple hyphenation could be added to the algorithm, where only the following
suffixes are broken off by hyphen:
-tion
-ing
-ed
b) Describe how the algorithm could be modified to hold three lines, and minimize the number of
spaces that are inserted by moving words between lines. Give an example in which the three-
line algorithm would produce text with a better appearance.
16.2.7 Investigate the use of prompt lines in interactive programs on your Pascal machine.
a) Is an initial discarded READ statement needed?
b) Explain how a program expecting free-form input in which all leading blanks are of no
significance, can be written with a prompt line so that it works on Pascal machines, whether
they use a default value for the buffer variable or lazy evaluation.

16.3 Records

Preview

When a complex data object is to be used in a program, it is convenient to have a single name
for it, and to declare it without explicitly repeating all the names of its parts. The Pascal
i"ecord types allow the object's description to be given once, then used either by its composite
name, or by naming its separate parts.

Records permit a fixed number of related data items of (possibly) different types to be grouped
together in a single object . Each data item, called a field, consists of a name, a colon, and a type.
In the following example, there are two record types, Date and Workerinfo, each containing
t hree fields. Some of the fields of the Workerinfo type are themselves records. Thus, record types
can be nested to create descriptions of arbitrarily complex objects.

16.3 Records 457

. t
'
'
"
TYPE
Month =
(NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
DayNum = 1 .. 31;
ValidYear =
1850 .. 2001;
Date = RECORD
Mo: Month;
Day: DayNum;
Year: ValidYear
END;
Workerlnfo =
RECORD
SSNumber: INTEGER;
BirthDay: Date;
FirstHired: Date
END;
When a variable has been declared to be of a record type, its name refers to the entire record .
As a summary name, the variable is useful, for example when the entire record is to be passed as an
argument to a procedure. However, to manipulate the parts of a record, other names are needed.
The fields are named by joining their identifiers (from the type declar ation) to the variable name
with a period . For example, the variable
VAR
Worker: Workerlnfo;
declares Worker to be a record with the three fields named in the type declaration above as
SSNumber, BirthDay, and FirstHired. The names of the parts of this particular
Workerlnfo are
Worker.SSNumber
Worker.BirthDay
Worker.FirstHired
where the first is an elementary item, and could be given a value:
Worker.SSNumber := 123456789
The other two parts of Worker are themselves records, and their parts must be further identified.
For example:
Worker.BirthDay.Mo := Feb;
Worker.FirstHired.Day := 22;
The values of record variables encompass all the possible values of their components, in a ll
possible combinations. Some of these combinations may not correspond to the idea that the
programmer had in mind for the type . For example, the author of the type Date above has made
an attempt to restrict each field to sensible values, but nothing prevents the construction of dates
like February 29, 1961, or September 31, 1901 in which there is a mismatch between the components.
Within a record, field names must be unique . However, since references to fields must contain
the names of all records of which the field is a component, fields in inner records can be named by
the same identifiers as fields in outer records or other variables. No ambiguity arises from this reuse
of identifiers. Thus the following Pascal fragment is legal:

458 AGGREGATE DATA TYPES IN D PASCAL


VAR
X: INTEGER;
Y: RECORD
X: RECORD
X: INTEGER
END
END;
BEGIN
... ,
WRITELN(X, Y.X.X)
END
The innermost integer field named X is named by prefixing the names of all records that contain it
(that is, Y and X).
A composite record name, the record variable (as distinguished from its fields), may not be
assigned anything but another record variable of the same type, and that only if none of the fields
are files. However, a record variable may be passed as an actual VAR parameter. The procedure to
which it is passed may then alter its fields, so that the effect in the calling program is to alter the
record through its composite name.

16.3.1 Record Syntax and Meaning


The syntax rules for records appear below:
<unpacked structured type> ::= SET OF <base type>
l FILE OF <component type> l RECORD <field list> END
<field list> ::= <fixed part>
<fixed part> ::=<record sections>
<record sections> ::=<record sections> <record section>
l <record section>
<record section> ::= <identifier list> <type denoter >
The syntax rules for <variable access> must also be changed since sequences of identifiers separated
by periods are now permitted.
<variable access> ::= <variable identifier> l <buffer variable>
l <component variable>
<buffer variable> ::= <file variable> t
<file variable> ::=<variable access>
<component variable> ::= <field designator>
<field designator> ::= <record variable> . <field specifier>
l <field designator identifier>
<field designator identifier> ::= <identifier>
<record variable> ::=<variable access>
<field specifier> ::= <identifier>
Because the components of record variables are objects of previously described types, and
because each component has a name that is a series of identifiers separated by periods, records can
be incorporated into the program calculus by simply placing these identifiers and their associated
values in execution states.

16.3.2 Sorting Dates 459


.Jr ..,,w,,
··~· ~

16.3.2 Sorting Dates


When reading or writing several collections of related values to files, it is possible to use a separate
file for each collection. For example, to record a collection of dates, the Month of each could be
written to one file, the Day to another, and the Year to a third. These files would then be,
respectively:
FILE OF Month
FILE OF DayNum
FILE OF ValidYear
The dates would be kept together by position in the three files: the first items written would be one
date, the second another, etc. A much easier way to handle composite items is to group the related
values in a single record and transmit the record value with a single read or write.
The following program reads a list of dates from INPUT (one date, month and day, per line ),
sorts the dates into ascending order, and prints the list. Each date has the following form:
<date> ::= <month> <day integer>
<month> ::= JAN l FEB l MAR l APR l MAY l JUN
1 1 1 1 1 1
I JUL I AUG I SEP I 0CT I NOV I DEC

where <day integer> lies between 1 and 31.


Instead of developing the sorting program directly, it is wise to first define some data types to
make the sorting task easier. Routines will be needed to read and write dates, and to compare one
date to another. In Section 14.1.2, procedures were developed to create a type Month whose values
could be read from INPUT, written to OUTPUT, and compared to one another, as follows:
TYPE
Month = (NoMonth, Jan , Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);

PROCEDURE ReadMonth(VAR Fin: TEXT; VAR Mo: Month);


{Fin.3=R and length(Fin.2)>=3 -->
read three characters from Fin.2 and assign the
corresponding Month value to Mo if possible;
otherwise assign NoMonth to Mo}

PROCEDURE WriteMonth(VAR FOut: TEXT; VAR Mo: Month);


{F0ut.3=W and Mo<>NoMonth -->
write the three characters corresponding to Mo's
value to F0ut.1}
Since objects of type Month are represented as constants from an enumerated type, no comparison
function needs to be written to determine if one Month value is less than another.
A similar type could be developed for day of the month, starting with the type:
TYPE
DayNum = 1 .. 31
However, no routines need be written to read, write, or compare DayNum values, since Pascal
permits the direct operations on INTEGER, of which DayNum is a subrange type.
Since a date is just a month and a day, it can be represented as a record and its operations can
be built with the more primitive operations for Month and DayNum.

460 AGGREGATE DATA TYPES IN D PASCAL

""'
TYPE
Date = RECORD
Mo: Month;
Day: DayNum
END;
{Include PROCEDURE ReadMonth(VAR Fin: TEXT; VAR Mo: Month)}
PROCEDURE ReadDate (VAR Fin: TEXT; VAR Result: Date);
{Fin.3=R and length(Fin.2)>=5 -->
read three characters representing a Month and
an integer representing a day from Fin . 2, and
assign the corresponding Month and DayNum values
to Result.Mo and Result.Day}
BEGIN {ReadDate}
ReadMonth(Fin,Result.Mo);
READ(Fin,Result.Day)
END; {ReadDate}
{Include PROCEDURE WriteMonth(VAR FOut: TEXT; VAR Mo: Month)}
PROCEDURE WriteDate(VAR FOut: TEXT; VAR D: Date);
{FOut.3=W and D.Mo<>NoMonth -->
write the character representation corresponding
to the values of D.Mo and D.Day to FOut.l}
BEGIN {WriteDate}
WriteMonth(FOut,D.Mo);
WRITE(F0ut,D.Day:3)
END; {WriteDate}
FUNCTION Less(VAR Dl, D2: Date): BOOLEAN;
{ Less := Dl < D2 }
BEGIN {Less}
IF Dl.Mo < D2.Mo
THEN
Less := TRUE
ELSE
IF Dl.Mo > D2.Mo
THEN
Less := FALSE
ELSE {Dl.Mo = D2.Mo}
Less := (Dl.Day < D2.Day)
END; {Less}
Although Less is a function that does not modify its parameters, they are declared as variable
parameters rather than value parameters. Value parameters require a copy operation (hidden by
t he Pascal machine) to ensure that changes within the function cannot reach the outside; this
overhead is not present with variable parameters. Some Pascal machines do not permit the use of
r ecord types in value parameters for this reason.
In these data types, the type and procedure declarations have been kept in the same unit of
scope, to emphasize their connection. This method of organization produces a "flat" procedure
structure-all the procedures are declared in the same unit of scope and (avoiding the restriction of
d eclaration before use) any procedure can be called by any other procedure. A different, more nested
program structure could also be adopted based on procedure-call relationships. For example, since
ReadMonth is called only by ReadDate, the declaration for the former could be nested within the
declaration of the latter. A similar relationship holds between Wr i teMonth and Wr i teDate .
Thus, the following program structure could be used:

16.3.2 Sorting Dates 461


PROCEDURE ReadDate (VAR Fin: TEXT; VAR Result: Date);
PROCEDURE ReadMonth(VAR Fin: TEXT; VAR Mo: Month);
... ,
BEGIN {ReadDate}

END; {ReadDate}

PROCEDURE WriteDate(VAR FOut: TEXT; VAR D: Date);


PROCEDURE WriteMonth(VAR FOut: TEXT; VAR Mo: Month);
... ,
BEGIN {WriteDate}

END; {WriteDate}

FUNCTION Less(VAR Dl, D2: Date): BOOLEAN;


BEGIN {Less}

END; {Less}
With this organization, only ReadDate can call ReadMonth; only Wr i teDate can call
Wr i teMonth. Restricting the availability of procedures hides information. Whether the fia t
structure emphasizing the relationship between type definitions and operations is better than th e
nested structure hiding names is a matter of taste.
The program to sort dates can be developed with either of the organizations above, and most
of the work is already accomplished by the existence of the right data types. A single date is read
and inserted in a file of dates (DateFile). Subsequent dates are inserted in the file so that it
remains in ascending order.
Design Part 3
PROGRAM SortDates(INPUT,OUTPUT);
TYPE
Month= (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
DayNum = 1 .. 31;
Date = RECORD
Mo: Month;
Day: DayNum
END;
FileOfDate = FILE OF Date;
VAR
DateFile: FileOfDate;
{Include ReadDate, WriteDate, Less, ReadMonth, WriteMonth}
BEGIN {SortDates}
REWRITE(DateFile);
ReadDate(INPUT,DateFilet);
READLN; {Skip over line marker between dates}
PUT(DateFile);
WHILE NOT EOF
DO
{insert a new date in ascending order
in DateFile};
{copy DateFile to OUTPUT}
END. {SortDates}
To keep the values in DateFile in ascending order, all those dates smaller than a new date Dare
copied from DateFile to a temporary file TFile, D is written to TFile followed by the

462 AGGREGATE DATA TYPES IN 0 PASCAL

.
"

,

remaining dates in DateFile, and TFile is copied back into DateFile. (Declarations for D and
TFile must be added to Design Part 3.)
Design Part 3.1
{insert a new date in ascending order in DateFile}
BEGIN
ReadDate(INPUT,D);
READLN; {Skip over line marker between dates}
{copy elements smaller than D
from DateFile to TFile};
TFilet := D;
PUT(TFile);
{copy remainder of DateFile to TFile};
{copy TFile to DateFile}
END
Dates are copied from DateFile to TFile until either no more dates remain in the former or
until a date is greater than the value of D (i.e., Less (DateFilet,D) has the value FALSE). A
declaration for the Boolean variable Copying must be added to Design Part 1.
Design Part 3.1.1
{copy elements smaller than D from DateFile to TFile}
BEGIN
RESET(DateFile);
REWRITE(TFile);
Copying := TRUE;
WHILE NOT EOF(DateFile) AND Copying
DO
IF Less(DateFilet,D)
.THEN
BEGIN
TFilet := DateFilet;
PUT(TFile);
GET (DateFile)
END
ELSE
Copying := FALSE
END
The three copying operations are all straightforward. When DateFile is copied to OUTPUT,
only one date is written on a line.
Design Part 3.1.2
{copy remainder of DateFile to TFile}
WHILE NOT EOF(DateFile)
DO
BEGIN
TFilet := DateFilet;
PUT(TFile);
GET(DateFile)
END;

16.3.2 Sorting Dates 463


Design Part 3.1.3
{copy TFile to DateFile}
RESET(TFile);
REWRITE(DateFile);
WHILE NOT EOF(TFile)
DO
BEGIN
DateFilef := TFilef;
PUT(DateFi l e);
GET(TFile)
END
Design Part 3.2
{copy DateFile to OUTPUT}
RESET(DateFile);
WHILE NOT EOF(DateFile)
DO
BEGIN
WriteDate(OUTPUT,DateFilef);
WRITELN;
GET(DateFile)
END
A sample execution of the assembled program is shown below.
Execution
INPUT :FEB 1
JAN29
JAN30
DEC25
OUTPUT:JAN 29
JAN 30
FEB 1
DEC 25

16.3.3 Exercises
16.3.1 Suppose that a file has record components, and one component of the record is a file. c·
such a declaration, and give the form of the buffer variable of the inner file .
16.3.2 Consider the following declarations:
CONST
Upper=19;
TYPE
Material=(Brick, Block, Stone, Lumber) ;
VAR
St: SET OF Material;
Rd: RECORD
A: INTEGER;
B: Material;
C : Upper . . Upper
END
a) Give an execution state whose contents goes with these declarations, including an appropria.t""
value for each of the objects declared (not "?" but some value that might legally be assigned
For the remaining par ts of this problem, call you r st a t e S.

464 AGGREGATE DATA TYPES IN 0 PASCAL

"
1111'~--

b) Calculate the value


~,-------~~------------------------------~
c) Calculate [Block .. Lumber] + [] + (Rd. B] (S), showing
each step.

16.4 WITH Statement

Preview

Pascal provides a convenient way to avoid writing the outer fields of a nested record description.

Records can be composed to achieve complex types. Consider the following declarations:
TYPE
Month= (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
Date = RECORD
Mo: Month;
Day: 1 .. 31;
Year: INTEGER
END;
PRecord = RECORD
... ,
BirthDay: Date;

END;
VAR
Smith: PRecord;
Each Date has three selectors: Mo, Day, and Year. PRecords associate dates with personnel.
If a BirthDay were to be assigned to Smith, the sequence of assignments might be:
Smith.BirthDay.Mo := Feb;
Smith.BirthDay.Day := 28;
Smith.BirthDay.Year := 1960
Repeating the names of the fields in each statement is tedious and error-prone . The WITH
statement allows references like this to be factored into a statement heading. For example, the
previous assignments can be written:
WITH Smith.BirthDay
DO
BEGIN
Mo : = Feb;
Day := 28;
Year := 1960
END
The syntax rules for WITH statements appear below.
<with statement> ::= WITH <record variable list> DO <statement>
<record variable list> ::= <record variable list> , <record variable>
I <record variable>
<record variable> ::= <variable access>

16.4.1 Exercises 465


16.4.1 Exercises
16.4.1 Suppose the example above had begun:
WITH Smith
DO
Draw a syntax tree to show that this is legal, and write the appropriate assignment statements.
16.4.2 Give a context rule to accompany the WITH statement syntax, that captures the idea t hat
the header must "go with" the declarations and the statement following, and which would forbi d
(among others):
WITH Smith.Day
DO
Birthday := 29

16.5 Summary
Declarations for the aggregate data types SET, FILE, and RECORD take the form:
SET OF T
FILE OF T
RECORD
Fl: Tl;
... ,
Fn: Tn
END
Where T, Tv ... , Tn are types and F 1, ... , Fn are selector identifiers. Sets and files have components
of a single type; records may have components with different types. Set components must have
ordinal types other than INTEGER, although limitations on the number of components are imposed
by each Pascal machine. File components may be any type except another file or a record
containing a file component. Records have a fixed number of components specified by the
declaration. Set values may consist of zero or more components, but a maximum number is fixed by
the declaration. Files may grow to arbitrary lengths.
The members of a set cannot be explicitly selected, but the IN operator can test for the
presence of a given member. Only a single component of a file F can be accessed at any time during
execution by referring to the file's buffer variable Ft. Any component of a record can be selected for
reference or assignment by appending a period and the name of the component to a variable of the
appropriate type.
When choosing between the value and variable transmission mechanism for parameters with
aggregate types, considerations of security must be balanced against those of efficiency. Value
parameters are more secure because inadvertent assignments t o them do not change the execution
state where the call was made. However, value parameters require that separate copies of their
values are made in the program state of the called procedure each time the procedure is called. This
can be quite expensive for aggregate types. Thus, ordinal-type values are passed as value
parameters unless the called procedure uses them to return one of several results, but aggregate
values are often passed as variable parameters even if they are not updated by the procedure.

466 AGGREGATE DATA TYPES IN D PASCAL

--

OT -
.....
CHAPTER 17

DESIGNING SEQUENTIAL CONTROL

Chapter Preview

The control statements described in this chapter are not essential for programming. The effect
of each can be obtained using a combination of statements already described. However, each
statement is convenient in common programming situations, and the programmer who has them
handy often finds them a natural choice.

The CASE, FOR, and REPEAT statements of this chapter permit programmers to express
heir intentions more precisely and more conveniently than with the control s\atements of CF
Pascal. Where the IF statement selects one of two statements for execution, the CASE statement
selects one of what may be many alternatives. Like the WHILE statement, FOR and REPEAT
statements control iteration. In a FOR statement, the control is by a count, so FOR statements are
natural when the necessary number of repeated executions is known . In contrast, the WHILE
statement is appropriate when the number of iterations is not known, but a test condition for
stopping the repetition is available . WHILE iterations may be unbounded, but FOR iterations may
not. The REPEAT statement also controls an unbounded iteration, but where the WHILE test for
ermination occurs before each iteration, and is cast in the form of a condition required to continue,
t he REPEAT test occurs after each iteration, and is in the form of a condition required to stop.

17.1 The CASE Statement

Preview

It is common to use nested IF statements to select a single statement for execution from many.
The CASE statement can often replace such a nested IF with a gain in clarity and efficiency.

A CASE statement is formed from an ordinal-type selection expression and a list of statements, each
statement labeled by one or more constants of the same ordinal type . For example, in
CASE A > B OF
TRUE: Max :=A;
FALSE: Max := B
END
the selection expression is A > B of type BOOLEAN and the labels are the BOOLEAN constants
TRUE and FALSE . The selection expression is evaluated and the statement labeled with that value
(if any) is selected for execution. After the selected statement has finished execution, control is
transferred to the statement following the CASE statement. No constant label may be repeated, so
at most one statement can be executed. If no statement carries a label with the value of the
selection expression, the result of executing the CASE statement is undefined.
A CASE statement can be nearly simulated with nested IF statements. Consider the CASE
statement:

17.1 The CASE Statement 467


.,
---~,··
..·.'···.,· · .
~ ."(.. . l : ' .-

CASE Exp OF
Lla, Llb: Sl;
L2: S2;
• • e I

LN: SN
END
The IF statement:
IF (Exp = Lla) OR (Exp = Llb)
THEN
Sl
ELSE
IF Exp = L2
THEN
S2
ELSE

ELSE
IF Exp = LN
THEN
SN
has the same effect when the value of Exp is one of Lla, Llb, L2, ..., LN . However, when th e
value of Exp is not listed as a label, this IF statement acts as a null statement, but the CASE
statement is undefined. (Many Pascal machines implement the CASE statement incorrectly, making
it act like the IF statement for a missing label.)
The use of the CASE statement reduces the depth of the statement nesting and the need for
logical operators, making a program easier to read. The CASE statement is also more efficient than
the IF statement because the CASE selection expression is evaluated only once instead of for each
companson.

17.1.1 CASE Statement Examples


CASE statements are particularly useful with enumerated types. The procedure Wr i teMonth
writes a string value corresponding to an enumerated-type value of the type Month.
PROCEDURE WriteMonth(VAR FOut: TEXT; Mo: Month);
{FOut.3=W and Mo<>NoMonth -->
write the three characters c o r responding to Mo's
value to FOut . l}
BEGIN {WriteMonth}
CASE Mo OF
Jan: WRITE(FOut, 'Jan');
Feb: WRITE(FOut , ' Feb ' );
Mar: WRITE(FOut , 'Mar');
Apr: WRITE(FOut, 'Apr ' );
May: WRITE(FOut, 'May');
Jun: WRITE(FOut, 'Jun');
Jul: WRITE(FOut, 'Jul');
Aug: WRITE(FOut, 'Aug');
Sep: WRITE(FOut, 'Sep ' );
Oct: WRITE(FOut, 'Oct');
Nov: WRITE(FOut, 'Nov');
Dec: WRITE(FOut, 'Dec')
END
END; {WriteMonth}
Wr i teMonth assumes that Mo has been set to one of the constant s of type Mcrrth, and explicitly

468 DESIGNING SEQUENTIAL CONTROL


excludes NoMonth from its domain. A more robust version of Wri teMonth could check that Mo
had one of the twelve Month values before executing the CASE statement to print the string
corresponding to the value .
IF Mo IN [Jan .. Dec]
THEN
CASE Mo OF
Jan: WRITE(FOut, 'Jan');
Feb: WRITE(FOut, 'Feb');
Mar: WRITE(FOut, 'Mar');
Apr: WRITE(FOut , 'Apr');
May: WRITE(FOut, 'May');
Jun: WRITE(FOut, 'Jun');
Jul: WRITE(FOut, 'Jul');
Aug: WRITE(FOut, 'Aug');
Sep: WRITE(FOut, 'Sep');
Oct: WRITE(FOut, 'Oct');
Nov: WRITE(FOut, 'Nov');
Dec: WRITE(FOut, 'Dec')
END
By protecting the CASE statement in this way, the programmer can be sure that it will never abort.
CASE statements are also useful with data types containing many values, if the cases to be
processed are only a small collection. For example, a routine that reads characters, and takes
special action for certain characters, can be written in the pattern:
WHILE NOT EOF
DO
BEGIN
READ (Ch);
IF Ch IN [I+ I, I* I
I/']
THEN
CASE Ch OF
I+ I ' I_ I •

BEGIN
WRITELN('Adding operator encountered . ');
CASE Ch OF
'+': NumPlus := NumPlus + 1;
'-': NumMinus := NumMinus + 1
END
END;
I* I
' / ' : WRITELN('Multiply/Divide encountered.')
END
ELSE
NumOther := NumOther + 1
END
Notice the nested CASE statement in which actions are grouped and then split again.
Similarly, a few cases in an infinite type can be handled conveniently:

17.1.1 CASE Statement Examples 469


IF AnswerCount <= 1
THEN
CASE AnswerCount OF
0: WRITE('There are none.');
1: WRITE ( 'There is 1. ')
END
ELSE
WRITE('There are ', AnswerCount:3)

17 .1.2 CASE Statement Definition


The syntax rules for the CASE statement appear below.
<case statement> ::= CASE <expression> OF <case list> END
<case list> ::= <case list> ; <case> l <case>
<case> ::= <constant list> : <statement>
<constant list> ::= <constant list> , <constant> l <constant>
The CASE statement, like the IF statement, can be given a meaning as a union of functions.
Let E be an expression yielding an ordinal-type value, and C11 C2 , and C3 distinct constants of the
same type, and 8i and 8 2 statements. Then the CASE statement 8:
CASE E OF
cl'· c2: 81:
Ca: 82
END
has meaning
@]= {<u, @J(u)>: @)(u) =[SJ(u) OR @)(u) =[S](u)}
u {<u, ~(u)>: @J(u) =I caHun.
The generalization to more labels and more statements is evident. Note that if none of the selectors
has the value of E, the function @]is undefined .

17.1.3 Exercises
17 .1.1 Explain how to construct a CASE statement that exactly simulates the effect of any given IF
statement.
17.1.2 The following declarations are in force for this question :
TYPE
Drink= (Coffee, Tea, Milk);
VAR
Ch: CHAR; Itg: 3 .. 33;
Enm: Drink;
EFile: FILE OF Drink;
In each part below there is at least one mistake . Locate and explain each one.
a)CASE SUCC(Tea) OF
Enm: Ch := 'X';
Coffee: Ch := 'Y'
END

470 DESIGNING SEQUENTIAL CONTROL


,...
••••••
b) CASE Itg Of
'Y': Ch :='X';
X: Ch := 'Y'
END
c)CASE SUCC(Itg) Of
Tea : Ch : = 'X ' ;
Coffee: Tea: Ch := 'Y'
END
7.1.3 In the skeleton CASE statements below, either give as much information about the type of
ariable Sel as you can, or explain why the statement is syntactically incorrect:
a)CASE Sel Of
I XI : ••• ,

I z I : • 0 0

END
b)CASE PRED(Sel) Of
X: •.• ,
Z:
END
7.1.4 Explain why the following pattern is not a useful one for the situation in which two cases
ave a common action, and one has an additional action:
CASE Ch Of
'A', 'B':
BEGIN
{Common action for 'A' and 'B'};
CASE 'A' Of
'A': {Additional action for 'A'}
END
END;
'C': {Other cases ... }
END
17.1.5 It has been proposed that the constants used in CASE statements to label each component
statement should be generalized to allow any expression as a label. Explain informally what the
meaning of such a generalized CASE statement should be, and as a part of your explanation,
criticize the proposal.

17.2 The FOR Statement

Preview

Iteration control in a FOR statement is by stepping through the values of some ordinal type,
and the current step value is available for calculation within the statement.

Let V be a variable of some ordinal type, and A and Z be expressions of the same type. The FOR
statement:
FOR V := A TO Z
DO
s
executes the statement S a number of times defined by the values of A and Z before the iteration
begins. Suppose that the value of A is p and the value of B is q at the outset. Then the first time S
is executed the value of V is p, the second time, V takes value SUCC (p), the third time,

17.2 The FOR Statement 471


SUCC (SUCC (p)), and so on until the value for V reaches q in succession, which is the last execution
of S. Thus the number of executions is q- p + 1, so long as this value is at least 1; if not, that is if p
> q, S is not executed at all. A FOR statement may also be written to iterate the same number of
times, but in the reverse order:
FOR V := Z DOWNTO A
DO
s
where now the values taken by V are successively
q, PRED (q), PRED (PRED (q)), ... , p.
Vis called the control variable of the FOR statement. At the end of the iteration, the value of
V is undefined.

17.2.1 Examples of FOR Statements


Because the control variable takes on successive values of its ordinal type on successive iterations,
the FOR statement is ideal for controlling an action that must be repeated over a collection. For
example, the enumerated type Month can be stepped through using
FOR Mo := Jan TO Dec
DO

more naturally than with the WHILE statement of Pr intDays in Section 15.5.2.
The INTEGER type is also a natural one to control with a FOR statement. The following
program might be the skeleton for producing student average grades; it reads a set of scores and
averages them.
PROGRAM AverageScore(INPUT,OUTPUT);
CONST
NumberOfScores = 5;
ClassSize = 3;
TYPE
Score= 0 .. 100;
VAR
WhichScore: 1 .• Number0fScores;
Student: 1 •. ClassSize;
NextScore: Score:
Ave, TotalScore, ClassTotal: INTEGER;
BEGIN {AverageScore}
ClassTotal := 0;
WRITELN ('Student averages: ') ;
FOR Student := 1 TO ClassSize
DO
BEGIN
TotalScore := 0;
FOR WhichScore := 1 TO NumberOfScores
DO
BEGIN
READ(NextScore);
TotalScore := TotalScore + NextScore
END;
READLN;
TotalScore := TotalScore*lO;
Ave := TotalScore DIV NumberOfScores;
IF Ave MOD 10 >= 5
THEN

472 DESIGNING SEQUENTIAL CONTROL


WRITE (Ave DIV 10 + 1)
ELSE
WRITE(Ave DIV 10);
ClassTotal : = ClassTotal + TotalScore;
END;
WRITELN;
WRITELN('Class average:');
ClassTotal := ClassTotal DIV (ClassSize*NumberOfScores);
WRITELN(ClassTotal DIV 10, '. ', ClassTotal MOD 10:1)
END.
Execution
INPUT :90 100 85 85 80
90 80 75 90 90
0 so 80 85 80
OUTPUT:Student averages:
88 85. 59
Class average:
77.3
his program expects individual scores on a line, and a line for each person. The number of
scores/line, and the number of lines, are given by the constant definitions. Individual averages are
rounded to the nearest integer; the overall average is given to one decimal place (truncated).

17.2.2 FOR Statement Definition


The syntax rules for the FOR statement are:
<for statement> ::=FOR <variable identifier> :=
<for list> DO <statement>
<for list> ::= <expression> TO <expression>
: <expression> DOWNTO <expression>
The control variable and the expressions in the <for list> may be any ordinal type. The
control variable must be an identifier and may not appear on the left side of an assignment
statement within the loop. The control variable's value is undefined at the end of execution of the
FOR statement. On a particular Pascal machine the control variable may have a value when the
F OR statement is finished, but it is poor programming practice to use this value in further
computations because it may or may not be the same for other Pascal machines.
The meaning of a FOR statement can be given by an equivalent WHILE statement, but care
must to taken to account for several differences between the two statements. The most
straightforward correspondence is wrong. The FOR statement:
FOR Index := E1 TO E2
DO
s
is not equivalent to:
Index := E1;
WHILE Index <= E2
DO
BEGIN
S;
Ind ex . - SUCC ( Index)
END
The control variable in the WHILE statement is not undefined as it should be for the FOR
statement. The body of the WHILE statement will be executed for the last time when Index

17.2.2 FOR Statement Definition 473


-------

already has the same value as E2. If this value were the last in an enumerated type, the final
evaluation of SUCC in the body would abort, where the FOR statement would not abort. Also,
assignment statements in the collection of statement S whose targets are variables in E2 would
change the number of iterations in the WHILE statement, but not in the FOR statement. The FOR
statement below iterates five times, even though First and Last change after execution begins:
First := 1;
Last := 5;
FOR Index := First TO Last
DO
BEGIN
First := First + 1;
Last := Last - 1;
WRITELN(Index, First, Last)
END
Execution
OUTPUT: 1 2 4
2 3 3
3 4 2
4 5 1
5 6 0
Some of these differences can be removed by using control variables that occur nowhere else in the
program (say T1 and T2), and by unrolling the first execution of the loop:
T1 := E1;
T2 := E2;
IF T1 <= T2
THEN
BEGIN
Index := T1;
S;
WHILE Index <> T2
DO
BEGIN
Index:= SUCC(Index);
s
END
END
Although this sequence of statements is closer to the meaning of the FOR statement, it still leav~
Index defined at the end of execution.
A direct definition of the FOR statement can be given as follows:
FOR V := E1 TO E2 DO S
where V is a variable of ordinal type T, has the meaning:
IF oR v : = E 1 To E 2 Do sj( t) =
(lvAR V: TIT oiVAR V: Tj)(t 0 ) iflsuccln- 1 {jE1j) (t) =IE21(t), where
t 0 =@] ( lv : = SUCC (V) I(t 0 _1))

t 1 =@](jv := E1j{t));
(jvAR V: TIT olvAR V: Tj)(t) if~(t) >IEzl(t).
By composing lVAR V : T IT a.nd lvAR V: Tj, the control variable is removed from the state, the;.
reinserted as i' 1t had been newly declared , with the effect that upon exit from the FOR statemen

474 DESIGNING SEQUENTIAL CONTROL


is undefined.
A FOR statement using DOWNTO is defined in a similar fashion.

17 .2.3 Blo ck L etters


As an extended example of the use of FOR statements, consider the problem of printing large block
etters without explicitly locating each character that forms them. In an area that contains each
nlock letter, say five column_s wide by five lines high, number the positions as follows:

Coil Col2 Col3 Col4 Col5


Line 1 1 2 3 4 5
Line 2 6 7 8 9 10
Line 3 11 12 13 14 15
Line 4 16 17 18 19 20
Line 5 21 22 23 24 25

In this area, a block letter may be represented as the set of positions in which a character is to be
printed, the other positions to r emain blank . For example, a block letter M might be represented by
the set constant:
[1,5,6,7,9,10,11,13,15,16,20,21,25]
t o indicate printing as follows:
M M
MM MM
MMM
M M
M M
Thus, the type SET OF 1 .. 25 can be used for the values that describe a block letter.
Two nested FOR statements (one to print each line and within it another to print each
column) can be used to print the letters.
Design Part 1
PROCEDURE PrintLetter(Ch : CHAR);
CONST
Height = 5;
Width = 5;
TYPE
PrintPos =SET OF 1 .. 25;
VAR
BLetters: PrintPos;
BEGIN {PrintLetter}
{BLetters assigned set of print positions};
{for each element of BLetters, print Ch}
END; {PrintLetter}
The value of Ch is used to select a n assignment statement that assigns the appropriate set of print
positions to BLetters .

17.2.3 Block Letters 475

- - -~ -

~ •
. ·--· . '
Design Part 1.1
{BLetters assigned set of print positions}
CASE Ch OF
'A': BLetters := [3,7,9,11 .. 15,16,20,21,25];

'M': B~etters := [1,5,6,7,9,10,11,13,15,16,20,21,25];

'Z': BEetters := [1 .. 5,9,13,17,21 .. 25]


END
Two variables (HCount and WCount) are declared to keep track of which line and column are being
printed.
Design Part 1.2
VAR
HCount: 1 .. Height;
WCount: 1 .. Width;
{for each element of BLetters, print Ch}
FOR HCount := 1 TO Height
DO
BEGIN
FOR WCount := 1 TO Width
DO
{if current position is in BLetters print Ch;
otherwise print blank};
WRITELN
END
To determine whether to print the value of Ch or a blank, the values of the control variables (which
both range from 1 to 5) must be mapped into the range 1 to 25 corresponding to the proper position
for each row and column. Each increase in HCount must increase the value of the expression
enough to skip the remaining print positions on a line. Multiplying the value of HCount by Width
in the following expression accomplishes this.
(HCount-1) * Width + WCount
The next design part completes the procedure.
Design Part 1.2.1
{if current position is in BLetters print Ch;
otherwise print blank}
IF (HCount-l)*Height+WCount IN BLetters
THEN
WRITE(Ch)
ELSE
WRITE (I I)
Assembling these design parts in a small driver program permits the design to be tested.
PROGRAM BlockLetter(OUTPUT);
{Include PROCEDURE PrintLetter(Ch: CHAR);}
BEGIN {BlockLetter}
PrintLetter('A');
WRITELN;
PrintLetter ( 'M')
END. {BlockLetter}

476 DESIGNING SEQUENTIAL CONTROL


Execution
OUTPUT: A
AA
AAAAA
A A
A A

M M
MM MM
MM M
M M
M M

17 .2.4 Analysis of FOR Statements


nlike the WHILE statement, the FOR statement is guaranteed to terminate. Thus no separate
argument about loop termination is needed. To verify that a FOR statement computes a particular
.unction, the FOR statement is expanded according to its definition and the function of the sequence
of statements is worked out explicitly. As an example, consider the statement:
FOR I := 1 TO N
DO
8 := 8 + I
ts meaning is the following function (for 8, omitting the control variable I):
(8 := 8 + (N* (N+1)) DIV 2)
First the function of the FOR statement can be rewritten using its definition.
!FOR I := 1 TO N DO 8 := 8 + II=
lr := 1lol8 := 8 + rlolr := 8UCC(I)Iol8 := 8 +rio

lr := Nlol8 := 8 + IloiVAR I: INTEGERjT ojVAR I: INTEGER!


T he compositions can be worked out using a trace table :

Function I 8
I := 1 1
8 := 8+I 8+1
I ·-
.- 8UCC (I) 8UCC(1)=2
8 := 8+I 8+1+2

I := N N
8 := 8+I 8+1+2+ ... +(N-1)+N

The table stops just before I is removed from the state then reinserted .) Thus the result is:
(8 := 8+1+2+ ... +(N-1)+N)
T he sum of the first N positive integers can be grouped from the beginning and end as follows:
1+N + 2+ (N-1) + 3+ (N-2) + . ..
where there are N72 pairs, since the terms are being removed two at a time from the original sum .
Each pair sums to N+1, so the result is
(8 := 8 + (N* (N+ l) ) DI V 2)
a.s required.

17.2.5 Exercises 477


17 .2.5 Exercises
17 .2.1 The following declarations are in force for this question:
TYPE
Drink= (Coffee, Tea, Milk);
VAR
Ch: CHAR; Itg: 3 .. 33;
Enm: Drink;
EFile: FILE OF Drink;
FUNCTION Ff(VAR Prl: CHAR; Pr2: CHAR): BOOLEAN;
In each part below there is at least one mistake. Locate and explain each one.
a)FOR Enm := PRED(Enm) DOWNTO SUCC(Enm)
DO
WRITELN ('HELLO I)
b)FOR Itg := 22 TO 33
DO
Itg : = SUCC (Itg)
17.2.2 In the following FOR statement what is the type of the variable Qu?
FOR Qu :=FALSE TO TRUE DO WRITELN('+')
How many lines are printed?
17 .2.3 Explain why FOR statements are not allowed to have control variables of SET types.
17.2.4 Give a general method for stepping through the contents of a SET using a FOR statemen
Does the type of the set members matter?
17.2.5 Explain why each legal FOR statement has to terminate (or abort), no matter wh a·
statements its body contains.
17 .2.6 Modify AverageScore (Section 17.2.1) so that:
a) it handles 7 scores per line instead of 5.
b) it handles a 10-character name at the beginning of each line of scores, which is printed with t he
individual average for that line.
17.2.7 What is printed by the following fragment in which Ending, Count, and RealCoun
have been declared INTEGER?
Ending := 2;
RealCount := 0;
FOR Count := 0 TO Ending
DO
BEGIN
WRITELN(RealCount, Count , Ending);
Ending := -2
END
17.2.8 The box functions of both FOR and WHILE statements are defined as a k-fold composition of
the box functions of statements that a re repeated by these constructions, where k is the number of
repetitions. A guess-and-verify method is required for calculating !WHILE . .. j. Is a similar
I
method needed for calculating FOR .. . j? Explain.
17 .2.9 Consider the declarations:

478 DESIGNING SEQUENTIAL CONTROL


TYPE
Roman= (I, II, III, IV, V);
FUNCTION AddRoman(A, B: Roman): Roman;
{Adds Roman numerals up to five}
BEGIN {AddRoman}
FOR A := I TO A
DO
BEGIN
WRITELN ( I - I ) ;
B := SUCC(B)
END;
AddRoman:= B
END {AddRoman}
a) If this function is used by
IF AddRoman(II, II) = IV
THEN
WRITELN (I OK I)
what will be added to OUTPUT?
b) Explain exactly the sense in which the function "adds Roman numerals up to five", by giving its
. .
precise meamng.
c) Suppose the parameters of the function were both variable parameters, and a Roman variable
Tmp was declared. Discuss the code
Tmp := II;
IF AddRoman(Tmp, Tmp) = IV
THEN
WRITELN (I OK I)
17.2.10 Prove that the following program:
PROGRAM Sums(OUTPUT);
CONST
N 10;=
VAR
I,Odds,Evens: INTEGER;
BEGIN {Sums}
Odds := 0;
Evens := 0;
FOR I := 1 to N
DO
IF ODD(I)
THEN
Odds := Odds + I
ELSE
Evens := Evens + I;
WRITELN(Odds,Evens)
END.
produces the output:
25 30
Using the method of Section 17 .2.4.

17.3 The REPEAT Statement 479


-.-.-- : .•. ,~,: :.
.

17.3 The REPEAT Statement

Preview

REPEAT statements control iteration with a test at the end of the loop body, a test that
terminates the iteration when TRUE.

The REPEAT statement provides for repetitive execution of a list of statements so long as a
BOOLEAN expression controlling execution evaluates to FALSE. The form is:
REPEAT
{Body statements}
UNTIL {controlling expression}
No BEGIN-END is required to bracket the statements of the loop body. Loop termination does no
occur as soon as the controlling expression becomes TRUE, but when it evaluates to true at the end
of the list of statements. Thus a REPEAT statement always has an iteration count of at least one.
The fragment:
Exp := TRUE;
REPEAT
WRITELN('This line is written to OUTPUT')
UNTIL Exp
appends a string to OUTPUT in spite of an expression controlling loop execution that is initially true.

17.3.1 REPEAT Statement Definition


The syntax rules for the REPEAT statement are shown below.
<repeat statement> ::= REPEAT <statement list> UNTIL <expression>
<statement list> ::= <statement list> ; <statement> : <statement>
A REPEAT statement:
REPEAT S UNTIL B
has exactly the same effect as a VVHILE statement with the test reversed and the body performed
initially:
BEGIN S END; WHILE NOT B DO BEGIN S END
The equivalence can be seen by examining cases. First, the REPEAT statement body Sis executed
exactly once if condition B is TRUE after S is executed. In the VVHILE statement sequence, S is
executed and the VVHILE statement prevents further execution of S because NOT B is FALSE. Thus
the two programs agree in this case. Second, suppose the REPEAT statement executed its body
exactly twice. Then B is FALSE after the first execution of S, but something in S causes B to
become TRUE when evaluated after a second execution of S, terminating the loop. In the VVHILE
sequence exactly the same behavior is observed. S is executed; NOT B is TRUE so the copy of S
within the VVHILE statement is executed, making B TRUE; then NOT B evaluates to FALSE,
terminating the loop. The remaining cases in which the REPEAT statement is executed more than
twice are similar: the copy of S preceding the VVHILE statement takes the first execution, and the
repeated S picks up the remainder.
The meaning of the REPEAT statement can be derived as was that for the VVHILE statement.
Consider
REPEAT S 1 ; 5 2 ; ••• Sn UNTIL B
where B is a BOOLEAN expression and each of the Si, 1<i<n, are statements. Let S be an
equivalent BEGIN statement:

480 DESIGNING SEQUENTIAL CONTROL


BEGIN 8 1 ; 8 2 ; • • • Sn END
nd thus consider the REPEAT statement R:
REPEAT S UNTIL B
or any initial execution state s, R will either either have k>1 iterat ions, or fail to terminate at all.
the statement fails to terminate, s is not a member of domain( ~) and so of no concern. If R
-erminates, B must evaluate to FALSE after each of the first k-1 executions of Sand to TRUE after
"ts kth execution.
The function for R can thus be defined as
~= {<s, t>: 3 k integer, k>1 Vi integer, 1<i<k ~i o @](s) =FALSE,
~ ko @](s) =TRUE, @] k(s) = t}.
A verification rule similar to that for a WHILE statement can be derived for a REPEAT
atement. However, since each REPEAT statement has an equivalent statement sequence involving
a. WHILE statement, the former can be analyzed by analyzing the latter, as in Section 8.2.3.

17 .3.2 Exercises
17.3.1 Consider the programs:
PROCRAM RepeatTest(INPUT, OUTPUT);
VAR
Color: (Red, Green, Blue);
BEGIN {RepeatTest}
Color := Red;
REPEAT
Color := SUCC(Color)
UNTIL Color = Blue;
WRITELN('Color is now blue')
END. {RepeatTest}
PROGRAM WhileTest(INPUT, OUTPUT);
VAR
Color: (Red, Green, Blue);
BEGIN {WhileTest}
Color := Red;
WHILE Color <> Blue
DO
Color := SUCC(Color);
WRITELN('Color is now blue')
END. {WhileTest}
a) Are they equivalent?
b) Suppose that the initial assignment is changed to
Color := Blue
in each program. Are they equivalent now?
17 .3 .2 By writing an equivalent WHILE program and analyzing it usmg the WHILE verification
rule, show that the fragment R:
REPEAT
y := y - 1;
X := X + 2
UNTIL Y <= 0
computes the function:

17.3.2 Exercises 481


I= (Y>O-+ X,Y := X+2*Y,O) (Y<=O -+ X+2, Y-1)
17.3.3 Verify that the following program:
PROGRAM Divide(OUTPUT);
CONST
z = 20;
D = 3;
VAR
Q,R: INTEGER;
BEGIN {Divide}
Q := 0;
R := Z;
REPEAT
Q := Q + 1;
R := R - D
UNTIL R<D;
WRITELN(Q,R)
END. {Divide}
produces the output:
6 2
by writing an equivalent WHILE program and using the WHILE verification rule to analyze it .
17.3.4 Work out a REPEAT verification rule similar to the WHILE verification rule of Section 8.2.1

17.4 Chapter Summary


A CASE statement is a convenient replacement for nested IF statements when an expression must
compared to several constant values, and a statement executed for each possibility. FOR statemen--
control iteration whose limits can be established before the iteration begins. They provide a con tr<
variable whose value shows the position through the complete iteration , The REPEAT statemen t ~
a variant of the WHILE statement in which the test is reversed and placed at the end of eae
iteration. Each of these statements is sometimes more natural to use than the equivalent IF -
WHILE statement.

482 DESIGNING SEQUENTIAL CONTROL


CHAPTER 18

ABSTRACT DATA TYPES

Chapter Preview

The program modules described in Chapter 13 are important because they divide the complexity
of a program. In this chapter it is shown that they are also useful in dividing the calculation of
program meaning. The meaning obtained for a module may be used in an application, and the
calculation outside the module need not be repeated if hidden changes are made at the concrete
level. A method is given for verifying that a module correctly implements an abstract meaning.

When it was helpful to count in CF Pascal, lacking the natural data type for counting
I NTEGER), Chapter 13 created a module containing a collection of CHAR variables and the
procedures to use them for counting. All the cumbersome aspects of using characters to count were
hidden within this counter module, so that module users could ignore them . When the INTEGER
ype was introduced in Chapter 14, the counter module was no longer required. But from the user's
point of view there is little difference: · the counting operations using INTEGER are essentially the
same as those using the module . In this sense INTEGER is not a necessary feature of Pascal. (The
limitations of size imposed by the counter module were seen in Chapter 14 to be inherent in the
NTEGER type as well; they are limitations inherent in mechanical computation.)
The situation with a counter and INTEGER is not typical for Pascal or any other language.
The complicated data objects needed for difficult problems usually do not appear magically in a later
chapter. Thus programmers must construct the objects they need, but by hiding them in modules
t hey can be made so easy to use that they might as well be built in. Pascal features that were not
av ailable in Chapter 13 make module construction far easier and more powerful:
1. A TYPE declaration can be used for objects within the module, to hide their detailed
construction behind the type identifier.
2. A RECORD can be used in the TYPE to bind several objects into a new aggregate representing
an abstract object.
The module makes the TYPE construction details its secret, exporting the name only. Users of the
module can declare objects of this type, but are not supposed to know how they are constructed.
The rule of module construction is that for a TYPE secret, the TYPE identifier is available outside
the module, but the details of the TYPE definition are not available.
As an example, recall the counter example of Chapter 13, with some changes to take advantage
of TYPE declarations. The changes will allow users to have as many distinct counters as needed,
and to start a counter off at values other than zero. Users think of Counter objects as integer
values in the range 0-999, but within the module they are characters, the module's secret. By
declaring:
TYPE
Counter = RECORD
Hundreds, Tens, Ones: CHAR
END
users can create the needed characters without ever mentioning more than the TYPE identifier. For
example,
VAR
Cl, C2: Counter
would create two counters. To preserve the secret, the user must use only the names Cl and C2,
not (for example) C2. Tens, etc . To get the characters to the module, its procedures must be given
a Counter parameter:

ABSTRACT DATA TYPES 483


PROCEDURE StartAt (VAR Ctr : Counter; DlOO, DlO, Dl : CHAR);
PROCEDURE Bump(VAR Ctr : Coun ter ) ;
PROCEDURE Value (Ctr : Coun ter ; VAR DlOO, DlO, Dl: CHAR);
where the Start of Chapter 13 has been replaced by StartAt which allows the count er to be
initialized from t he hundreds, tens, and units digit values in the corresponding CHAR parameters.
Then a user declar ing
VAR
Cl, C2: Counter
might call
StartAt (C2, '1 ' , ' O' , ' 3') ;
Bump (C2 );

Bump (C2 );
Va lue (C2 , Hd, Td , Od );
WRITELN('The value is ', Hd, Td, Od )
t o use C2 beginning at 103, and could use Cl for some different counting task .
The illustrated use of a Counter is the general case for modules. The module contains two
special procedures that get things started (here, StartAt) and report results (here, Value), an d
one or more procedures (here just one, Bump) that modify things in between. The pattern is to
begin with regular Pascal objects (here, characters) that are supplied to the starting procedure, the n
perform intermediate manipulations (without any detailed knowledge of the objects being
manipulated), and finally to obtain the result, again as regular Pascal objects. Of course, the initial
values might have been obtained with a READ statement or in some other way, and the final values
might have been used in some other way than with a WRITE statement . But to make use of them
outside the module requires that they be converted from/to standard Pascal data objects.
The use of a TYPE declaration also protects the module interface against changes in the data
structures used within the module . If the components of the TYPE were shown explicitly, the
declaration for Bump would have been:
PROCEDURE Bump(VAR Hundreds, Tens, Ones : CHAR)
but if the module were to switch to using INTEGER it would be:
PROCEDURE Bump(VAR Count: INTEGER)
such a change would require the user to alter not only the calls, but declarations of quantities to be
passed as actual parameters. On the other hand, if the TYPE declaration alone is changed:
TYPE
Counter = INTEGER
then changes are confined to the module; the user program declares and uses Counter objects
without caring tha t they have changed form.

18.1 Concrete and Abstract Worlds

Preview

The implementors of a module and its users have very different views of the module meanmg,
which are related but distinct.

It is important to keep the two wor lds, the one within a mod ule, and the one outside for users
of the module, distinct. We call the module interior the concrete world, and the user one outside the
abstract world . It is difficult to keep these apart, since t hey are intimately connected by a certain
idea that the user wants the module to realize. But if modules are to be useful in hiding the details
of an abstraction, the separation must be observed .

484 ABSTRACT DATA TYPES


The user's view of a module is called an abstract data type. It consists of the set of abstraCt
objects that the user imagines to be available, and this set is named by the TYPE identifier from the
module. The user imagines that the abstract objects have a standard description consisting of other
Pascal objects. Finally, the user has in mind a collection of abstract operations that manipulate
abstract objects. Two of these operations are special: one must map the standard objects that
describe an abstract object to the latter; the other maps in the reverse direction, from an abstract
object to its corresponding standard objects.
In the counting example, the abstract objects are integers in the range 0-999, and the TYPE
identifier is Counter. The standard description of a counter comprises three characters fot its
digits. The three operations in the example are:
StartAt, the special operation carrying a standard description of three characters to a
Counter;
Value, the other special operation, carrying a Counter back to three characters; and
Bump, which maps a Counter to a Counter .
This view provides a user with everything needed to create objects of the abstract data type (using
the special mapping from known objects), manipulate these abstract objects (using the operations),
and then pass back from the abstract results to known forms (using the other special mapping).
From the viewpoint of the module itself things are quite different. We say that the module
implements an abstract data type T when it contains a TYPE definition for the identifier of T, and
procedures for each of the abstract mappings.
In the counting example the TYPE definition is
TYPE
Counter: RECORD
Hundreds, Tens, Ones: CHAR
END
and the procedures are StartAt, Value, and Bump.
In the concrete world of the module, there are only ordinary Pascal data objects and
procedures that manipulate them. When a parameter of the abstract TYPE is passed into this
world, it is really made up of component objects according to the TYPE definition. The procedures
manipulate these components using regular Pascal operations. In the abstract world, the user
imagines that abstract objects exist, those that have been declared to have the abstract TYPE, and
that the abstract operations map them one to another. In the counting example, if the user declares
VAR
N: Counter;
Manyl, Many2, Many3: CHAR
then the abstract world has data states containing four identifiers and their values, while the
concrete world lacks identifier N and its imagined integer value, having instead the variables
N. Hundreds, N. Tens, and N. Ones, with their character values. When the user performs a
sequence of operations like
StartAt (N, 1 0 1 , 1 0 1 , 1 0 1 ) ;
Bump(N);
Value(N, Manyl, Many2, Many3)
the abstract picture is that the Counter object 0 is created from character constants, then
increased to 1, and transformed into the character triple (0, 0, 1). In the concrete world, three
CHAR variables (N. Hundreds, N. Tens, and N. Ones) are initially set to 0, the last of these is
altered to 1, and the final values transferred to other CHAR variables (Manyl, Many2, Many3).
The situation in the two worlds can be diagramed as follows:

18.1 Concrete and Abstract Worlds 485


Abstract World Concrete World

{N·?, Manyl·?, {N .Hundreds·?, N. Tens·?, N. Ones·?


Many2·?, Many3•?} Manyl·?, Many2·?, Many3·?}

! Star tAt !I StartAt I


{N·O, Manyl·?, {N.Hundreds·O, N.Tens·O, N.Ones·O
Many2·?, Many3·?} Manyl·?, Many2·?, Many3·?}

! Bump !I Bump I
{N·1, Manyl·?, {N .Hundreds·O, N. Tens·O, N. Ones•1
Many2·?, Many3·?} Manyl·?, Many2·?, Many3·?}

l
{N·1, Manyl·O,
Value !1 Value

{N.Hundreds·O, N.Tens·O, N.Ones·1


I
Many2·0, Many3·1} Manyl·O, Many2·0, Many3·1

If the procedures are doing the right things, the actions in the concrete world properly reflect those
in the abstract world step by step. The correspondence is the subject of Section 18.3.

18.2 Rational Numbers

Preview

Rational numbers (fractions) are a data abstraction not available in most computer languages.
They can be defined in a module, and used to solve a problem involving a fractional series.

The harmonic series has the partial sum


H(n) = 1 + 1/2 + 1/3 + 1/4 + ... + i/n.
The terms are fractions, so H(n) is a fraction. To calculate H(n) exactly requires operating in the
abstract world on objects that are rational numbers; these will be implemented in the concrete world
as pairs of INTEGER values.
The first design part for calculating H(n) shows as comments parts that cannot be executed by
the D Pascal machine. This design part reads a single integer from the input and prints the sum of
that many terms from the series.

486 ABSTRACT DATA TYPES


Design Part 1
PROGRAM HarmonicSeries (INPUT, OUTPUT);
{There is an integer n in INPUT,
(n>O -->Sum:= 1 + 1/2 + 1/3 + ..• + 1/n)
(n<=O --> Sum := 0) }
VAR
Terms: INTEGER;
Ctr: INTEGER;
{Sum: Rational}
DSum, NSum: INTEGER;
BEGIN {HarmonicSeries}
READ(Terms);
Ctr := 0;
{Sum := 0/1};
{Get numerator and denominator of Sum
in NSum and DSum};
WRITELN( 1 Term 0, Sum 1 , NSum, 1 / 1 , DSum);
WHILE Ctr < Terms
DO {Sum:= 1 + 1/2 + •.. + 1/Ctr}
BEGIN
Ctr := Ctr + 1;
{Sum := Sum + 1/Ctr};
{Get numerator and denominator of Sum
in NSum and DSum};
WRITELN( 1 Term 1
, Ctr:2,
1
, Sum 1 , NSum, 1 / 1 , DSum)
END
END. {HarmonicSeries}

18.2.1 Representing Rationals


A rational number can be conveniently implemented as a pair of integers, which are grouped m a
record so they can be passed as a single parameter.
TYPE
Rational = RECORD
Num, Den: INTEGER
END;
Declaring Rational as a type identifier permits the declarations of Design Part 1 to be completed.
Design Part 1.1
{Sum: Rational}
Sum: Rational;

18.2.2 Operations on Rationals


The operations needed for the harmonic series problem are implemented in procedures MakeRat,
AddRat, and BackRat. MakeRat converts two integers into a rational number; AddRat adds
t wo rationals and produces a rational value; and BackRat converts a rational back into a pair of
integers. Since MakeRat and AddRat return rational values, they would be best implemented as
functions. Then Design part 1.2 could be refined i~to a single Pascal statement:

18.2.2 Operations on Rationals 487


Design Part 1.2
{Sum := Sum + 1/Ctr}
Sum := AddRat(Sum,MakeRat(1,Ctr)) {Not allowed in Pascal}
As the comment indicates, Pascal does not permit functions to return any type but an ordinal, so t h
Rational RECORD does not qualify. Thus, MakeRat and AddRat must be procedures whic
return their values through variable parameters. A temporary variable R to hold the result from ~
procedure simulating a function must be added to the declarations and the following sequence o!.
instructions must be used:
Design Part 1.2
{Sum := Sum + 1/Ctr}
BEGIN
MakeRat(R,1,Ctr); {R := 1/Ctr}
AddRat(Sum,Sum,R) {Sum := Sum + R}
END
Having made these decisions, the remaining design parts are easy to complete.
Design Part 1.3
{Sum := 0/1}
MakeRat(Sum,0,1)
Values returned by BackRat can be written out directly.
Design Part 1.4
{Get numerator and denominator of Sum
in NSum and DSum}
BackRat(Sum, NSum, DSum)
The module for rational numbers is an example of the general definitions given in Section 18.1.
The abstract data type consists of
1) the TYPE identifier Rational;
2) the set of rational numbers (fractions), with the standard description a pair of INTEGE
values for numerator and denominator;
3) the operations of constructing a Rational from two INTEGER values, adding t wo
Rational values to get a third, and converting back to two INTEGER values.
This is the abstract point of view.
The implementation consists of the RECORD with fields Num and Den for Rational and t he
procedures MakeRat, AddRat, and BackRat now to be developed. This is the concrete point of
vtew.
The concrete world is re lated to the abstract in that concrete states containing X. Num and
X. Den (but not X itself) correspond to abstract states containing X (but not X. Num and X. Den}
for all identifiers X declared Rational in the abstract world. The rational value associated wit
such an abstract identifier X is the one whose numerator and denominator are the values
respectively associated with X . Num and X. Den in the concrete world.
In writing an implementation of the Rational data type, it is essential to keep in mind wha
the user is expecting, and correspondence between the two worlds. In this simple case t he
declaration of TYPE Rational is reminder enough of the representation mapping, but in more
complex cases it may help to first write the special conversion routines for the implementation, which
usually explore all the ins and outs of the connection. The expectations of the user can be captured
by writing comments about the procedure part functions in the abstract state. These comments are
labeled "abs:" in the code. Of course, the concrete procedures themselves are concrete-state
mappings, and have part functions in that world as usual; these are described by comments labeled
"con:".

488 ABSTRACT DATA TYPES


Although HarmonicSer ies needs only nonnegative rational numbers, we will implement a
module to handle negative ones as well.
BackRat can simply return its parameter's Num and Den components for numerator and
denominator as long as both these values are positive. However, when the Num or Den values are
negative there is the possibility of multiple forms for the same rational number. Most people would
consider (-1)/4 and 1/(-4) to be the same fraction, but if BackRat simply returns the Num and Den
values both could appear. A canonz"cal form (or standard notation) for rational numbers will be
adopted to avoid this duplication. Positive rational numbers will be represented with positive
numerators and denominators and negative rational numbers will be represented with negative
numerators and positive denominators. If the operations that produce rational values (MakeRat
and AddRat) adhere to this convention, BackRat can just return the existing values.
PROCEDURE BackRat(VAR Rat: Rational;
VAR NumRat, DenRat: INTEGER);
{abs: there are n,d such that Rat = n/d -->
NumRat, DenRat .- n, d }
{con: NumRat, DenRat : = Rat.Num, Rat.Den }
BEGIN {BackRat}
NumRat : = Rat.Num;
DenRat : = Rat.Den
END; {BackRat}
MakeRat creates the internal form for a Rational from its INTEGER components. The
concrete comment is more complex than the abstract comment because it must describe the
canonical form used in the implementation.
PROCEDURE MakeRat(VAR Result: Rational;
Num, Den: INTEGER);
{abs: (Den<>O --> Result := Num/Den) I
(Den=O --> Result := 0/1) }
{con:
(Den<O -->
Result.Num,Result.Den := -Num,-Den)
(Den=O -->
Result.Num,Result.Den := 0,1)
(Den>O -->
Result.Num,Result.Den : = Num,Den) }
BEGIN {MakeRat}
IF Den < 0
THEN
BEGIN
Result.Num := -Num;
Result.Den := -Den
END
ELSE {Den >= 0}
IF Den > 0
THEN
BEGIN
Result.Num := Num;
Result.Den : = Den
END
ELSE {Den = 0}
BEGIN
WRITELN;
WRITELN('*** zero Den for MakeRat ***');
Result.Num := 0;

18.2.2 Operations on Rationals 489


Result.Den := 1
END
END; {MakeRat}
Notice the action taken for the case of a zero denominator: MakeRat issues an error message, br·
creates a(n incorrect) concrete value anyway. This is described in both the abstract and concre ,~
comments, and is appropriate when software is being debugged . Experimentation can continue aft e.
the error occurs. However, when everything is working correctly, it might be better to ca
MakeRat to abort after the error message instead of creating a value . That way a program cannc
continue after such an error occurs.
The following formula can be used to add two Rational values, contained in Opl and Op 2:
Opl.Num * Op2 . Den + Opl . Den * Op2 . Num
Opl . Den * Op2.Den
AddRat must be written very carefully because it can be used with the same actual parameter fo-
two of its formal pa rameters. That happens in HarmonicSeries, where Sum is both one of til-
values to be added, and the place to store the result. The following ill-considered code:
PROCEDURE AddRat(VAR Result, Opl, Op2: Rational);
BEGIN {AddRat}
{Ill-considered because of parameter aliasing}
Result.Den := Opl.Den * Op2 . Den;
Result . Num := Opl.Num * Op2.Den +
Opl.Den * Op2.Num
END; {AddRat}
goes wrong when Result and (say) Opl are given the same identifier as actual parameter, becaUSE
then the assignment to Result. Den is really changing Opl, whose value is used in the secon~
assignment. In that case the value for Result is actually:
Opl.Num * Op2.Den + Opl.Den * Op2.Den * Op2.Num
Opl.Den * Op2.Den
However, since the calculation for Result. Den uses no . Num values, the statements can bE
r eversed to avoid this difficulty:
PROCEDURE AddRat(VAR Result , Opl, Op2: Rational);
{abs: Result := Opl + Op2 }
{con: Result . Den , Result . Num :=
Opl.Den * Op2.Den,
Opl . Num * Op2 . Den + Opl . Den * Op2.Num }
BEGIN {AddRat}
Result.Num : = Opl .Num * Op2 . Den +
Opl . Den * Op2 . Num;
Result . Den : = Opl . Den * Op 2. Den
END ; {AddRat}
T his impleme ntation of AddRat m a kes no explicit provision for producing results in canonical for m
but a n inductive a rgument proves th at t his happens automatically. Assuming that Rationa ::.
input parameters are in canonical fo rm (i.e., have positive de nominators), it can be shown th a-
Rational outputs are also in canonica l for m . Only MakeRat and AddRat produce rationa.
outputs so the results of these procedures must be shown to be in canonical form . Since MakeRa
has no rational inputs, it is the base case. MakeRat contains explicit statements to guarantee th a-
the resulting Den is always positive. The inductive hypothesis lets us assume that the inpm
parameters for AddRat, Opl and Op2, have positive denominators. Since Opl. Den a n ·
Op2.Den are both positive, Opl.Den*Op2 . Den is also positive. Thus the Rational result 01.
AddRat always has a positive denominator .

490 ABSTRACT DATA TYPES


Harmonic Series
assembled solution for HarmonicSeries is shown below.
PROGRAM HarmonicSeries (INPUT, OUTPUT);
{There is an integer n in INPUT,
(n > 0 -->Sum:= 1 + 1/2 + 1/3 + •.• + 1/n)
(n <= 0 --> Sum := 0) }
TYPE
Rational = RECORD
Num, Den: INTEGER
END;
VAR
Terms: INTEGER;
Ctr: INTEGER;
Sum, R: Rational;
DSum, NSum: INTEGER;
{Include PROCEDURE BackRat
(VAR Rat: Rational, VAR NumRat, DenRat: INTEGER);
abs: there are n,d such that Rat = n/d -->
NumRat, DenRat := n, d }
{Include PROCEDURE MakeRat
(VAR Result: Rational; Num, Den: INTEGER);
abs: (Den<>O --> Result := Num/Den) 1
(Den=O --> Result := 0/1) }
{Include PROCEDURE AddRat
(VAR Result, Op1, Op2: Rational);
abs: Result := Op1 + Op2 }
BEGIN {HarmonicSeries}
READ(Terms);
Ctr := 0; ·
{Sum := 0/1}
MakeRat(Sum, Q, 1);
{Get numerator and denominator of Sum
in NSum and DSum}
BackRat(Sum, NSum, DSum);
WRITELN('Term 0, Sum', NSum, ' / ' , DSum);
WHILE Ctr < Terms
DO {Sum:= 1 + 1/2 + ••• + 1/Ctr}
BEGIN
Ctr := Ctr + 1;
{Sum := Sum + 1/Ctr}
BEGIN
MakeRat(R,1,Ctr); {R := 1/Ctr}
AddRat(Sum,Sum,R) {Sum := Sum + R}
END;
{Get numerator and denominator of Sum
in NSum and DSum}
BackRat(Sum, NSum, DSum);
WRITELN ('Term ' , Ctr: 2,
',Sum', NSum, ' / ' , DSum)
END
END. {HarmonicSeries}

18.2.3 Harmonic Series 491


Execution
I NPUT : 13
OUTPUT :Term 0, Sum 0 / 1
Term 1, Sum 1 / 1
Term 2, Sum 3 / 2
Term 3, Sum 11 / 6
Term 4, Sum so / 24
Term 5, Sum 274 / 120
Term 6, Sum 1764 / 720
Term 7, Sum 13068 / 5040
Term 8, Sum 109584 / 40320
Term 9, Sum 1026576 / 362880
Term 10, Sum 10628640 / 3628800
Term 11, Sum 120543840 / 39916800
Term 12, Sum 1486442880 / 479001600
Term 13, Sum -1672077440 /1932053504
The 13-term sum above is obviously incorrect since overflow occurred in the AddRat formulas for
this case (and perhaps for earlier ones, less obviously). (This Pascal machine exceeds MAXINT
silently- it just begins delivering the wrong result.) If it were necessary to sum more terms, th e
canonical form of rational numbers could be changed so their numerators and denominators had no
common divisors. For example, by removing the common divisor 2, term 4 above can be represented
as 25/12.

18.2.4 An Alternate Representation for Rationals


In order to remove common divisors from rational numbers, we first compute the greatest common
divisor of two integers with a function Gcd.
FUNCTION Gcd(I1, I2: INTEGER): INTEGER;
{ I1>0 AND I2>0 --> Gcd := GCD(I1,I2)
I1<=0 AND I2>0 --> Gcd := I2 I
I2<=0 --> Gcd := I1 }
VAR
Rem: INTEGER;
BEGI N {Gcd};
WHILE I2 > 0
DO
BEGIN
Rem := I1 MOD I2;
I1 := I2;
I2 : = Rem
END;
Gcd := I1
END; {Gcd}
Positive values of I 2 are the only ones allowed to enter the WHILE statement because MOD is
undefined if its second operand is not positive. If I 1 is not positive, the result of I 1 MOD I 2 will
not be positive either. Then the WHILE statement will execute exactly once since the result of the
MOD operation is assigned to I 2 each time the body is executed, and I 1 will be assigned the value
of I2 .
To check the correctness of Gcd, determine the function of the body:
jWHILE I2 > 0 . . . loiGcd := Ill
and compare it to that given in the comment:

492 ABSTRACT DATA TYPES


(I1>0 AND I2>0 -+ Gcd := GOD (I1, I2))
(I1<=0 AND I2>0 -+ Gcd := I2) I
(I2<=0 -+ Gcd := I1)
ere GOD is the mathematical function for the greatest common divisor. The meaning F of the
liiLE statement is obtained from the comment and the program itself:
F = (I1>0 AND I2>0 -+ I1, I2 := GOD (I1, I2), 0) I
(I1<=0 AND I2>0-+ I1,I2 := I2, I1 MOD I2)
(I 2<=0 -+ )
be temporary variable Rem has been omitted, since it is only used to preserve a value in the
:alculation of I1 and I2.) The WHILE statement verification rule requires establishing the three
_ nditions:
I
1. domain( F) = domain( WHILE I 2 > 0 ... D
2. (I2<=0 -+F) = (I2<=0 -+ )
3. F=I IF I2>0 THEN D~o F
here D is the BEGIN statement within the WHILE statement.
For the first condition, it is clear that the conditions on I 1 and I 2 in F cover all possibilities,
so F is defined for all states in which these variables are defined. The WHILE statement terminates
.or all values of I 1 and I 2. There is no iteration for values of I 2 that are not positive. If I 2 is
positive, consider the possibilities for Il. If I1 < 0, (I1 MOD I2) < 0 and since this value is
assigned to I 2 the WHILE statement terminates after a single execution of its body. If I 1 > 0,
I 2 is always decreasing in the loop because it is set to I 1 MOD I 2 each time through the loop and
he value of I 1 MOD I 2 is strictly less than the value of I 2. Thus the domain of the WHILE
statement function and the domain ofF are the same.
For the second condition, the guard I 2<=0 selects the identity case, so the result is
immediate.
Finally, in the third condition, the function corresponding to the IF statement in the right side
lS:

(I2>0 -+ I1, I·2 := I2, I1 MOD I2) (I 2<=0 -+ )


To work out the right side:
( (12>0 -+ I1, 12 := I2, I1 MOD I2) I (I2<=0 -+ ) ) o
( (I1>0 AND I2>0 -+ I1, I2 := GOD (11, I2), 0) I
(I1<=0 AND I2>0-+ I1,I2 := I2, I1 MOD I2) I
(I2<=0 -+ ) )
six cases must be considered.

Part Condition I1 I2
IF I2>0 I2>0 I2 I1 MOD I2
F I2>0 AND I1 MOD I2>0 GOD(I2,I1 MOD I2) 0

Thus the function is:


I2>0 AND I1 MOD I2>0-+ I1,I2 := GOD(I2,I1 MOD I2),0

Part Condition I1 I2
IF I2>0 I2>0 I2 I1 MOD I2
F I2<=0 AND I1 MOD I2>0 Il MOD I2 I2 MOD (Il MOD I2)

The composite condition includes I2>0 AND I2<=0,which cannot be satisfied. Thus this case
contributes no pairs to the resulting function.

18.2.4 An Alternate Representation for Rationals 493


Part Condition I I1 I I2
IF I2>0 I2>0 I I2 I I1 MOD I2
F I1 MOD I2<=0

Thus the function is:


I2>0 AND I1 MOD I2<=0-+ I1,I2 := I2, I1 MOD I2

Part Condition I I1 I I2
IF I2>0 ... I2<=0
F I1>0 AND I2>0 I GCD(I1,I2) I 0

Again the composite condition requires the impossible of I 2, so there is no contribution to the
function.

Part Condition I I1 I I2
IF I2>0 ... I2<=0
F I1<=0 AND I2>0 I I2 I I1 MOD I2
This case also contributes no pairs to the function.

Part I Condition I I 1 I I 2
IF I2>0 I2<=0
F I2<=0
Thus the function is:
(I2<=0 -+ )
The union of these six functions yields the following:
(I2>0 AND I1 MOD I2>0-+ I1,I2 := GCD(I2,I1 MOD I2),0)
(I2>0 AND I1 MOD I2<=0-+ Il,I2 := I2, Il MOD I2)
(I 2<=0 -+ )
Some properties of greatest common divisors are needed to continue.
Property 1: GGD(X,Y) = GCD(Y,X)
Property 2: GCD(X,Y) = GCD(X MOD Y, Y)
Property 3: If X MOD Y = 0, then Y = GCD(X,Y).
Applying Property 1 to the first part function gives:
(I2>0 AND I1 MOD I2>0 -+ Il, I2 := GCD (I1 MOD I2, I2), 0)
(I2>0 AND I1 MOD I2<=0-+ I1,I2 := I2, I1 MOD I2)
(I2<=0 -+ )
Applying Property 2 next results in:
(I2>0 AND I1 MOD I2>0-+ I1,I2 := GCD(I1,I2) ,0) I
(I2>0 AND I1 MOD I2<=0-+ I1,I2 := I2, I1 MOD I2)
(I 2<=0 -+ )
Splitting the second function into two cases corresponding to I 1 MOD I 2=0 and I 1 MOD I 2<0
yields:
(I2>0 AND I1 MOD I2>0 -+ I1,I2 : = GCD (I 1, I 2) , 0)
(I2>0 AND I1 MOD I2=0 -+ I1,I2 := I2,0) I
(I2>0 AND I1 MOD I2<0 -+ I1 , I2 := I2, I1 MOD I2)
(I 2<=0 -+ )
By Property 3:

494 ABSTRACT DATA TYPES


(I2>0 AND I1 MOD I2>0 -+ I1,I2 := GOD (I1, I2), 0) I
(I2>0 AND I1 MOD I2=0 -+ I1,I2 := GOD (I1, I2), 0) I
(I2>0 AND I1 MOD I2<0 -+ I1,I2 := I2, I1 MOD I2) I
( I2<=0 -+ )
_ ow the first two cases can be combined:
(I2>0 AND I1 MOD I2>=0 -+ I1, I2 := GOD (I1, I2), 0) I
(I2>0 AND I1 MOD I2<0 -+ I1,I2 : = I2, I1 MOD I2) I
(I 2<=0 -+ )
This function is identical to F except for the guards. However we observe:
(I2>0 AND I1 MOD I2>=0)
(I2>0 AND I1 MOD I2<0) == (I2>0 AND I1>0)
(I2>0 AND I1<0)
so the two functions are really identical.
With F established as the function of the WHILE statement, the entire procedure has the
meaning F o Iced : = I 1j:
(I1>0 AND I2>0-+ Gcd,I1,I2 := GOD(I1,I2),GOD(I1,I2),0)
(I1<=0 AND I2>0 -+ Gcd,I1,I2 := I2,I2,I1 MOD I2) I
(I2<=0 -+ Gcd := Il)
This function is the same as that of the comment on Gcd, except that it includes changes to the
value parameters I 1 and I 2. These disappear outside of the procedure. Thus Gcd does calculate
t he greatest common divisor in the case of positive input parameters.

18.2.5 Re-implemented Operations for Rationals


Only the procedures MakeRat and AddRat need to use Gcd. Notice that neither procedure calls
Gcd unless both actual parameters to the function are strictly positive, thus confining the procedure
to the case for which it has been shown to compute GOD. (The code to arrange for positive values
occurs three times, so it probably should have been packaged with the Gcd function rather than
repeated on each call.)
PROCEDURE MakeRat(VAR Result: Rational;
Num, Den: INTEGER);
{abs: (Den<>O --> Result := Num/Den) 1
(Den=O --> Result := 0/1) }
{con: Let x = gcd(Num,Den) then
(Den<O -->
Result.Num,Result.Den := -Num/x,-Den/x)
(Den=O -->
Result.Num,Result.Den := 0,1)
(Den>O -->
Result.Num,Result.Den := Num/x,Den/x) }
VAR
Cf: INTEGER;
BEGIN {MakeRat}
{find the gcd of Num and Den}
IF Num * Den <> 0
THEN
Cf := Gcd(ABS(Num),ABS(Den))
ELSE
Cf := 1;
IF Den < 0
THEN
BEGIN
Result .Num := -Num DIV Cf;
Result.Den := -Den DIV Cf

18.2.5 Re-lmplemented Operations for Rationals 495


END
ELSE
IF Den > 0
THEN
BEGIN
Result.Num := Num DIV Cf;
Result.Den := Den DIV Cf
END
ELSE
BEGIN
WRITELN;
WRITELN('*** zero Den for MakeRat ***');
Result.Num := 0;
Result.Den := 1
END
END; {MakeRat}
Gcd is called a second time in AddRat after Sum is computed because the addition of two reduce
rational terms does not necessarily result in a reduced rational result. For example, consider th=
case in which the sixth term is added to the sum of the first five terms: 137 + ..!.. . T h-
60 6
denominators have a common factor of 6 which is removed and used to multiply their product:
137 * (6 DIV 6) + 1 * (60 DIV 6) 147
10 * 1 * 6
=- -
60
A common factor of 3 can be removed from the result to obtain the sixth term in the series: ~ .
20
PROCEDURE AddRat(VAR Result, Op1, Op2: Rational);
{abs: Result := Opl + Op2 }
{con: Let x =
gcd(Op1.Den*Op2.Den,
Op1.Num*Op2.Den+Op2.Num*Opl.Den),
Result.Den,Result.Num :=
(Op1.Den * Op2.Den) I x,
(Opl.Num*Op2.Den + Opl.Den*Op2.Num) I x }
VAR
Cf: INTEGER;
Den1, Den2: INTEGER;
BEGIN {AddRat}
{find the gcd of Op1.Den and Op2.Den}
IF Op1.Den*Op2.Den <> 0
THEN
Cf := Gcd(ABS(Op1.Den),ABS(Op2.Den))
ELSE
Cf := 1;
Den1 := Opl.Den DIV Cf;
Den2 := Op2.Den_DIV Cf;
Result.Num : = Op1.Num * Den2 + Denl * Op2 . Num;
Result.Den := Denl * Den2 * Cf;
{find the gcd of Result.Num and Result.Den}
IF Result.Num*Result.Den <> 0
THEN
Cf := Gcd(ABS(Result.Num),ABS(Result.Den))
ELSE
Cf : = 1;
Result.Num := Result.Num DIV Cf;
Result.Den := Result.Den DIV Cf

496 ABSTRACT DATA TYPES


END; {AddRat}
~ e user need not be concerned with any of the Gcd transformations- all the user notices is that
=:1ore terms in the series can be computed before overflow occurs. Since rational numbers were
:.:nplemented as a module, the code for the BEGIN statement in HarmonicSeries does not
ange.
Execution
INPUT :25
OUTPUT: Term 0, Sum 0 / 1
Term 1' Sum 1 / 1
Term 2, Sum 3 / 2
Term 3, Sum 11 / 6
Term 4, Sum 25 / 12
Term 5, Sum 137 / 60
Term 6, Sum 49 / 20
Term 7, Sum 363 / 140
Term 8, Sum 761 / 280
Term 9, Sum 7129 / 2520
Term 10, Sum 7381 / 2520
Term 11' Sum 83711 / 27720
Term 12, Sum 86021 / 27720
Term 13, Sum 1145993 / 360360
Term 14, Sum 1171733 / 360360
Term 15, Sum 1195757 / 360360
Term 16, Sum 2436559 / 720720
Term 17, Sum 42142223 / 12252240
Term 18, Sum 14274301 / 4084080
Term 19, Sum 275295799 / 77597520
Term 20, Sum 55835135 / 15519504
Term 21, Sum 18858053 / 5173168
Term 22, Sum 19093197 / 5173168
Term 23, Sum 444316699 / 118982864
Term 24, Sum 1347822955 / 356948592
Term 25, Sum 307215901 /-333780208

18.2.6 Exercises
8.2.1 Change the abstract comment for MakeRat to include the changes made to OUTPUT in the
ase Den=O.
~8 .2.2 Alter the action of MakeR at in the case of zero denominator to assign no values in this
!mpossible case; that is, to abort. Change both abstract and concrete comments to reflect what you
· ave done.
18.2.3 Aliasing of parameters had to be considered in AddRat. Is there a similar problem with
:>arameters Rat and NumRat in the operation BackRat? Explain.
18.2.4 Suppose that for some Pascal machine the value of MAX/NT is defined by CONST Mxi.
a) Explain how to alter the Rational module so that erroneous results like those shown in the
executions of HarmonicSeries never occur. [Hint: use a subrange type.]
b) Will any changes be required in HarmonicSer ies itself? Explain.
c) Change the abstract comments to reflect this change.
18.2.5 Consider a ltering the Rational module so that rational values may be externally described
as an integer part plus a fraction. For example, instead of passing the pair (5, 3) to the module
representing five thirds, a triple (1, 2, 3) could be passed representing one and two thirds.

18.2.6 Exercises 497

• , . ~ ,'' I
a) Design an appropriate input-conversion procedure for this format, making no assumptions
about the fractional part. Do not change the internal form for Rational values, and use
MakeRat in the implementation if you can.
b) Discuss the question of whether or not to alter the internal form for Rational values to have
an integer, numerator, and denominator part. Describe a situation in which this form would be
the most efficient, and another in which it would not be efficient. Consider also the question of
making the internal form unique-when would that be wise, and when not wise?
c) Design an appropriate output-conversion procedure for the new format, assuming that th e
internal form for Rational values is not changed. Return the largest possible integer par
(that is, with a fractional part less than 1).
-
d) Show the changes in HarmonicSer ies that would be required to print the result in the for m
of an integer plus fraction, using your designs from a) and c) . .

18.3 The Calculus of Abstract Data Types

Preview

The user of an abstract data type can employ its operations correctly if the "abs:" comments
can be trusted. It is shown how to verify that these do indeed reflect the actual actions taken
by an implementation. Furthermore, the abstract comments can be used to calculate the
meaning of user programs. Thus these comments are all that users need to know about an
abstract data type.

To illustrate the calculus of an abstract data type, we will use a particularly simp}
abstraction. The program below determines if the input contains an even or an odd number of
characters (counting line markers as single characters). It uses an abstract Switch object Sw
keep track of the characters read. Sw has abstract values Off and On. The abstract operations fo
a Switch are !nit, Flip, and Test. Init(x, y) initializes the value of Switch x from the CHA?..
value Y (On) or N ( Oj]) in y. Flip(x) changes x from Off to On and vice versa. Test(x, y) queri ~
the value of Switch x and sets y to the corresponding CHAR value.
PROGRAM OddOrEven (INPUT,OUTPUT);
{ (INPUT= <,w,R> and ODD(length(w)) -->
OUTPUT:= <'Input has odd length',,W>)
(INPUT= <,w,R> and EVEN(Length(w)) ~->
OUTPUT:= <'Input has even length',,W>) }
TYPE
Switch = RECORD
S: CHAR
END;
VAR.
Ch, SwitchOn: CHAR;
Sw: Switch;

498 ABSTRACT DATA TYPES


PROCEDURE Init(VAR Sw : Switch; Ival : CHAR);
{abs: (Ival='Y' --> Sw := On) I
(Ival<>'Y' --> Sw :=Off }
{con: (Ival= ' Y' --> Sw . S := 'U') I
(Ival<> ' Y' --> Sw . S := 'D') }
BEGIN {Init}
IF Ival = 'Y'
.THEN
Sw.S : = 'U'
ELSE
Sw . S . - 'D'
END ; {Ini t}
PROCEDURE Flip(VAR Sw: Switch) ;
{abs : (Sw=On --> Sw : = Off) I
(Sw=O ff --> Sw : = On) }
{con : (Sw.S='U' --> Sw.S : = 'D')
(Sw.S<>'U' --> Sw . S := 'U')}
BEGIN {Flip}
IF Sw . S = 'U'
THEN
Sw . S . - 'D'
ELSE
Sw.S .- 'U'
END; {Flip}
PROCEDURE Test(VAR Sw: Switch; VAR Result : CHAR);
{abs : (Sw=On -->Result : = 'Y') I
(Sw=Off -->Result : = 'N')}
{con : (Sw.S='U' -->Result : = 'Y' ) I
(Sw.S<>'U' -->Result : = 'N') }
BEGIN {Test}
IF Sw . S = ' U '
THEN
Result . - 'Y'
ELSE
Result . - 'N'
END ; {Test}
BEGIN {OddOrEven}
Init(Sw, ' N ');
WHILE NOT EOF
DO
\ BEGIN
READ (Ch) ;
Flip (Sw)
END;
Test(Sw ,Swi tchOn );
IF SwitchOn = 'Y'
THEN
WRITELN('Input has odd length ')
ELSE
WR ITELN( ' Inpu t has even length')
END. {OddOrEven}
By the rules of module isolation, no program part except the operat ions of the abstract type may
a ccess or a lter the variables that represent an abstract object . Thus, the variable Sw. S is not used

1B.3 The Calculus of Abstract Data Types 499


in the BEGIN statement of OddOrEven. Two concurrent assignment comments describe the affec·
of each procedure. The assignment comment labeled "abs:" deals with the abstract objects known t-:
the user (i.e., Sw), and the assignment labeled "con:" uses concrete objects (i.e., Sw. s) that a""'"
hidden from the user.
The definition of Section 18.1 applied to this module gives the following:
1) The abstract data type consists of the TYPE identifier Switch, and the set of abstrac-
objects {On, Off}. The standard description of the value On is the character Y, and of Off L.
the character N. (An enumerated type (Off, On) might have been a better choice for t hE
standard description, but we used characters to emphasize the distinction between abstrac·
values and their standard descriptions.) For abstract operations, the [nit operation is L _
special one that creates abstract values from their standard descriptions; the Test operation ·
the inverse operation; Fl£p maps the abstract values into themselves by reversing the value
its parameter.
2) The implementation consists of the TYPE definition for
RECORD S: CHAR END, and the procedures Ini t, F 1 ip, and Test, which would
'
Switch, namel~

functions returning Switch values but for the Pascal restriction to return only ordinal types.
3) The representation mapping onto the abstract value for identifier Sw is defined for two valuE
of the concrete identifier Sw. S: U (carried to On), and D (carried to Off). (Again, a~
enumerated type might have been a better choice, but we wanted to clearly distinguish t h
concrete values both from the abstract ones, and from the standard description.)
This example is too small to offer much abstraction, but OddOrEven is isolated from changes in t h
implementation. If an Australian had written the implementation, the values D and U wou l
probably be reversed, since Australian light switches are down when on, up when off. Such chang~
in the values for the . S component of the implementation do not alter the BEGIN statement of
OddOrEven at all.

18.3.1 Verifying an Implementation


The "abs:" comments are added to modules so that users, those in the abstract world, need no
examine the code (or even the "con:" comments that document it). But abstract comments are very
different than others placed with code: there is no code to back them up! There are no "abstrac
procedures," and all the ideas about the abstract data type are intuitive, in the minds of human
users, not in the implementation. The question that then arises is: how can a user know that the
abstract comment is trustworthy? A user with this question is in the hands of the person who
implemented the module. The implementor knew what the user was expecting, and in fact wrote the
abstract comment. If the implementation has been done properly, the abstract comment can be
believed, and used in proofs at the abstract level. The formal definitions of Section 18.1 allow us to
prove that this is the case .
An abstract data type can only be used as follows: starting with standard descriptions of
abstract objects, the user calls the input conversion function, which returns the corresponding
abstr act objects for further manipulation; finally, abstract values are given to the output conversion
function and standard descriptions returned to the user. That is, the user views the data
abstraction as a composite transformation from an input standard description to an output standard
description. Since only the standard-description objects exist outside the module, the user cannot
manipulate any abstract objects without first converting; and, the results of abstract operation
cannot be observed without converting back. This limited pattern of use is the basis for the
definition of module correctness.
Consider a typical such application of the Switch module in which a Switch X is initialized
from a standard description, then flipped, and finally returned as a standard description. That is,
the abstract operations !nit, Flip, Test are applied in sequence:
lnit(X, 'N') o Flip(X) o Test(X, R).
X is started with a value whose standard description is N. !nit then gives X t he a bstract value Off,
Flip transforms this to On, a nd Test returns the standard description Y in variable R. From the

500 ABSTRACT DATA TYPES


abstract point of view, the module has transformed N to Y, which the user thinks of as Off to On.
This same sequence of operations can be viewed from the concrete side, within the module.
The sequence of operations is
IIni t (X , 1
N 1
) lo ,.....,F-1-ip_(_X__,) o Test (X, R)II I
which results in an assignment to X. S of U, changing this to D, and finally returning Yin variable
R. Thus from the concrete point of view, the module has transformed N to Y, but done so through
the hidden values D to U of X. S.
The sequence of concrete functions I nit (X, 1 N 1 ) I, IF 1 ip (X) I
Test (X, R) correctly I, I I
1 1
implements the sequence of abstract operations Init(X, N ), Flip(X), Test(X, R) if and only if, for
any (concrete) state T,
(Init(X, 1
N1 ) o Flip(X) o Test(X, R))( T)
C (jinit (X, 1
N1 ) loiFlip (X) lo..-,T-es_t_(_X_,-R--.)j)(T).
The definition does not insist that these compositions be exactly the same, only that when the
abstract functions are defined, the concrete ones mimic them. It is not unusual for the concrete
functions to be defined when the abstract are not. For example, instead of aborting in some error
case, a module may issue an error message; this 'w as done in the Rational module for zero
denominators.
The sequence of operations in each of the two worlds can be shown in a single implementation
diagram beginning and ending with concrete states:

{abstract states} Flip (X) ... {abstract states}

!nit (X, 1 N 1 )
I \Test (X, R)

{concrete states}
I \
{concrete states}

\ I
jrnit(X, N
1 1
) I ITest(X,R) I
\
{concrete states} IFlip(X) I __,.
I
{concrete states}

This implementation diagram commutes: starting at the left with any standard description, the
standard description obtained at the right by following the upper (abstract) path is the same as the
one obtained at the right by following the lower (concrete) path. Within the diagram the two worlds
differ, but they begin and end in the same place. The statement that the diagram commutes is
exactly the same as the statement that the sequence of concrete operations correctly implement the
abstract sequence.
Commuting implementation diagrams can be used to reason about the relationship between the
concret e an d abstra ct worlds. No matter what initial standard description is chosen, and no matter
what intermediate opera t ions are used, the path th rough the abstract world should lead to the same
final standard description as the path through the concrete world. For the Switch module this
means considering all values of the parameter Ival passed to Ini t, and all sequences of
operations beginning with Ini t, continuing with any number of F 1 ip applications, and ending

18.3.1 Verifying an Implementation 501

; ',o - I •
.
with Test. For any such sequence the text of these procedures defines the resulting value for the
parameter Result in Test. The concrete comments summarize what should happen. There is no
"abstract code", but the abstract comments can be used to calculate Result from Ival. The
implementation is correct if and only if these two sequences of operations agree . /
It is a simple matter to check the particular implementation diagram above, which represents
one initial value for Ival and the simplest sequence of operations consisting of just one Flip .
Tracking the assignment statements in the concrete and abstract worlds, it can be easily seen that
the diagram does commute. However, to handle all diagrams in a complex module would be a
formidable task. Fortunately, the proofs can be decomposed by considering a correspondence
between the two worlds within each implementation diagram.
An implementation diagram must necessarily commute if the abstract and concrete worlds
"stay in step." That is, they begin together with a common (concrete) state. If each function in turn
maps to a state where there is still agreement, when they come back together in a common fin al
state, that agreement will still exist . To make a connection between the worlds within the
implementation diagram requires a new mapping called the representation mapping. This mapping is
in the mind of the person doing the implementation, and reflects the intuitive correspondence
between the two kinds of objects. It maps from the concrete to the abstract rather than the other
way around, because while it is essential that every abstract object have at least one corresponding
concrete object, it can happen that some concrete objects have no abstract counterparts, or th at
more than one concrete object corresponds to the same abstract one. If the representation mapping
were to go from abstract to concrete, under these circumstances it would not be a function. For
objects that lie in both worlds, that is, concrete objects that are not involved in t he module's special
TYPE declaration, the representation mapping is identity.
In the case of the Switch module, the representation mapping that the programmer had in
mind is Aswitc:h' which carries any concrete identifier X. S and its value to the abstract identifier X
and its value. Value U corresponds to On, and D to Off. That is,
Aswitc:h = {<s, t>: t = s except that X in t replaces X. S in s,
with t(X) = Off if s(X. s) = D, t(X) = On if s(X. s) = u}.
An implementation diagram can be decomposed using a representation mapping. For example, for
Switch, the diagram

{abstract states}

/
/nit (X, 'N ' )

/
{concrete states} Aswitc:h

~
IInit(X, 'N') I
~{concrete states}

concerns only the input-conversion mapping, while

502 ABSTRACT DATA TYPES


{abstract states} - - - - Flip(X) {abstract states}

Aswitch Aswitch

{concrete states} IFlip(X) {concrete states}

deals with the Flip operation alone, and

{abstract states}

Test (X, R)

Aswitch {concrete states}

I
Test (X, R) I
{concrete states}

is for the output-conversion mapping. If each of these diagrams commutes starting at the (lower)
left and ending at the (upper) right, then the composite implementation diagram must also commute.
All possible implementation diagrams can be handled by being slightly more general in the
proofs of the individual-operation diagrams. If instead of using the particular parameters X and
1
N 1 for Ini t, we use its formal parameters Sw and !val, then proving that the diagram
commutes proves that Ini t is a correct implementation for all possible parameter values. If the
diagrams for Flip and Test are similarly verified, then any implementation diagram beginning
with Ini t, continuing with any number of applications of Flip, and ending with Test, must
commute. That is, checking just three separate diagrams proves that the module is correctly
implemented.

18.3.1 Verifying an Implementation 503

·... . ! .J
This discussion leads to the following method for verifying the correctness of a module:
1) Find a representation mapping A that is identity on concrete objects not in the module '~
TYPE, that maps the implementation RECORD identifiers to a single abstract identifier, wi t
intuitively correct value correspondence.
2) Check that the diagram for each separate operation of the module commutes using A . This
can be done by verifying that the concrete comments correctly describes the implemente
functions, and then comparing with the function defined by the abstract comment.
The representation mapping is not part of the module implementation, but it is the most importan-
piece of information the person doing the implementation possesses. Correctness turns on th
existence of this mapping, making the individual diagrams commute.

18.3.2 Sample Verification


As an example,, the abstract data type Switch used in OddOrEven will be verified. A
representation mapping was worked out above. For the identifier Sw that is used as parameter i::
all three procedures it is:
Aswitch = {<s, t>: t = s except that Sw in t replaces Sw. S in s,
with t(sw) = Off if s(sw. s) = D, t(sw) = On if s(Sw. s) = u},
which is identity on all parts of a state not involving the Switch Sw, as required. It remains
show that each operation has a commuting diagram using Aswitch· In the proofs it will be assume
that the concrete comment is correct, but of course this must be checked using the program calcu l~
in the usual way within the module.
Consider Ini t, whose diagram is:

{abstract states}

~
Init(Sw, Ival)

~
{concrete states} Aswitch

~..---:-----,
I Ini t (Sw, !val) I
~
{concrete states}

The statement that the diagram commutes is:


Init(Sw, Ival)C!Init(Sw, Ival)loAswitch·
The abstract and concrete comments give these functions. From the abstract comment,
/nit (Sw, !val) =
(Ival='Y' -. Sw := On) 1 (Ival<>'Y' -. Sw := Off)
On the other hand, Ini t (Sw, Ival) is not defined by the concrete comment, but by the progra
calculus for the proce ure co e assume that it has been so calculated, and that it agrees wit.:
the concrete comment:

504 ABSTRACT DATA TYPES


lrnit(Sw, Ival)l=
(Ival= 1 Y 1 -+ Sw.S := 1
U') (Ival<>'Y 1 -+ Sw.S := 'D 1 )
The right side of the commutativity equation is:
lrnit (Sw, !val) lo Aswitch =
((Ival='Y' -+ Sw.S := 'U') (Ival<> 1 Y' -+ Sw.S := 1
D1 ) o ((Sw.S= 1 U 1 -+
Sw :=On) I (Sw.S='D 1 -+ Sw :=Off))
The following execution tables help to compute this composition.

part condition Sw Sw.S !val

lrnit (Sw, Ival) I Ival='Y' u


A switch Sw.S='U 1 On

part condition Sw Sw.S I val

lrnit (Sw , !val) I Ival= 1 Y 1 u


A switch Sw.S='D 1 Off

part condition Sw Sw.S !val

lrnit (Sw, !val) I Ival<> 1 Y' D


A switch Sw.S= 1 U 1 On

part condition Sw Sw.S I val

IIni t (Sw, !val) I Ival<> 1 Y' D


A switch Sw . S='D 1 Off

The second part of the composite condition is TRUE (U = U and D D) m the first and third
tables, and FALSE in the second and fourth tables, so the result is:
(Iva l = 1 Y 1 -+ S w : = On) (Iva l < > 1 Y 1 -+ S w : = 0 ff)
This is the same as lnit(Sw, Ival), and hence the diagram for Ini t commutes.
The diagram for Flip is:

18.3.2 Sample Verification 505


{abstract states} Flip(Sw) {abstract states}

l
Aswitch
l
Aswitch

{concrete states} Flip (Sw) ~ {concrete states}

and the statement that it commutes is:


Aswitch oFlip(Sw) C !Flip (Sw) jo Aswitch·
Inserting the definition and abstract and concrete comments, this is:
((Sw.S='U' -+ Sw :=On) 1 (Sw.S='D' -+ Sw :=Off)) o
( (Sw=On -+ Sw : = Off) I (Sw=O.ff -+ Sw : = On))
C ((Sw.S='U' -+ Sw.S := 'D') I (Sw . S<>'U' -+ Sw.S := 'U')) o
((Sw.S='U' -+ Sw :=On) I (Sw.S= ' D' -+ Sw :=Off))
The trace tables for these compositions are similar to those for Ini t; only two cases survive in each
case. The left side is:
(Sw.S='U' -+ Sw := Off) (Sw.S='D ' -+ Sw := On)
and the right side is:
(Sw.S='U' -+ Sw := Off) (Sw.S<>'U' -+ Sw := On)
The former is a subset of the latter as required, so the Flip diagram commutes.
The diagram for Test is:

{abstract states}

~Test (Sw, Result)

~{concrete states}
• Aswitch

~
Test(Sw,Result)
D
~
{concrete states}

The required equation is

506 ABSTRACT DATA TYPES


Aswitch o Test(Sw, Result) C ITest (Sw, Result) I,
or with the comments inserted:
( (Sw. S= 1 U 1 - Sw := On) (Sw.S= 1 D 1 - Sw := Off)) o
((Sw=On- Result := Y ) 1 (Sw=Off- Result := 1 N 1 ) )
1 1

C (Sw.S= 1 U 1 -Result := 1 Y 1 ) 1 (Sw.S<> 1 U 1 -Result := 1 N 1 )


The left side is:
(Sw.S= 1 U 1 -Result := 1 Y 1 ) (Sw.S= 1 D 1 -Result := 1 N 1 )
w_hich is identical to ITest (Sw, Result) I, except for having a smaller domain. Thus the Test
d1agram commutes.
Verification of the Switch module is complete: a representation function was found that
makes all of the operation diagrams commute.

18.3.3 Verifying a Module Use


Verifying an implementation establishes the correctness of its abstract comments as a description of
the abstract functions. Thus !nit, Flip, and Test and the values On and Off can be employed in
calculations about the program OddOrEven using the Switch module. Once the implementation
is verified, none of its Pascal code, not even its concrete comments, need be examined again.
Ignoring line breaks, suppose the input to OddOrEven is a string w. The comment indicates
t hat the function computed should be
{<w, tinput has even lengtht>: length(w) is even} U
{<w, tinput has odd lengtht>: length( w) is odd}.
The calculation of the program function amounts to working out the following:
!BEGIN . . . ENDioiVAR Ch, SwitchOn: CHAR; Sw: SwitchiT oO
({INPUT·<tt,w,R>, OUTPUT·(tt,tt,w), Ch·?, Swi tchOn·?, Sw·?}).
Within the BEGIN statement,
I Ini t (Sw, 1 N 1 ) I = {(r, s): s = r except I swj{s) = Off}.
The function computed by the WHILE statement is:
{<s, t>: where t = s except that I INPUTj{t)=<w,tt,R>,
I
IChi ( t)=D, Iswl ( t)=Off if length( w) is even and swl ( s )=Off} U
{<s, t>: where t = s except that IINPUTj{t)=<w,tt,R>,
IChi ( t)=D, Iswl ( t)=Off if length( w) is odd and Iswl ( s )=On} U
{<s, t>: where t = s except that IINPUTj{t)=<w,tt,R>,
I
IChi ( t)=D, Iswj( t)=On if length( w) is odd and swj{ s )=Off} U
{<s, t>: where t = s except that IINPUT I(t)=<w,tt,R>,
lchj(t)=D, lswj{t)=On if length(w) is even and ~(s)=On}.
(It must be shown that this is indeed WHILE NOT EOF DO . . . using the WHILE verification rule.)
I
Because Ini t (Sw, 1 N 1 ) I produces a sta e m w 1c w as the value Off, only the first and
third parts of the WHILE statement meaning appear in the composition of the two functions:
{<r, t>: where t = r except that I INPUTj{t)=<w,tt,R>,
lchj{t)=D, lswj{t)=On if length(w) is odd} U
{<r, t>: where t = r except thatiiNPUTj{ t)=<w,tt,R>,
IChi ( t)=D, Iswl ( t)=Off if length( w) is even}.
Because there are no identifier conflicts and no aliasing, I
Test (Sw, Swi tchOn) can be I
calculated by substituting Swi tchOn for Result in the abstract comment for Test:

18.3.3 Verifying a Module Use 507


lTest(Sw,SwitchOn)l=
(Sw=On -+ SwitchOn := 'Y') (Sw=O.ff -+ Swi tchOn : = 'N')
Combining these three functions:
IIni t (Sw, 'N') jo,....jWH_I_L_E-NO_T_E_O_F_D_O_.-
. .....,.lo!Test (Sw, Swi tchOn) I
I
= { <r, s>: where s = r except that INPUTj(s)=<w,tt,R>,
lchj(s)=D, lswl(s)=On, !swi tchonj{s)=Y if length(w) is odd} U
{<r, s>: where s = r except that lrNPUTj{s)=<w,tt,R>,
lchj(s)=D, !swl(s)=O.ff, lswitchonj{s)-N if length(w) is even}.
For the final statement in the program,
lrF SwitchOn ... j=
(SwitchOn = 'Y' and OUTPUT=(x,tt,W) -+OUTPUT :=
<x & tinput has odd lengtht,tt,W>) I
(SwitchOn <> 'Y' and OUTPUT=(x,tt,W) -+ OUTPUT :=
<x & tinput has even lengtht,tt,W>)
None of the previous functions has altered the value of OUTPUT, so the already-written portion =
must be empty. Composing lrF SwitchOn ... jwith the meaning of the rest of the BEG!: '
statement yields:
I
{<r, t>: when~ INPUTj(t)=<w,tt,R>, lch!(t)=D, swj (t)=On, I
!ouTPUTj(t)=<tinput has odd lengtht,tt,w>,
lswitchOnj{t)=Y if length(w) is odd} U
{<r, t>: where lrNPUTl(t)=<w,tt,R>, !chj(t)=D, !swj(t)=O.ff,
!ouTPUTj{t)=<tinput has even lengtht,tt,w>,
ISwi tchonl ( t) N if length( w) is even} .
Applying:
.-1V_AR
___Ch_,_S_w_i_t_c_h--On_:_C_HAR
__,-._S_w_:_S_w_i_t_c_h-,1 T o Q
gives exactly the same function as the comment for OddOrEven indicates.

18.3.4 Exercises
18.3.1
a) Give a formal representation function A for the Rational module of Section 18.2.1.
Show that the abstract comments for the following operations are correct, using the given concr e t~
comment and the representation of a).
b)PROCEDURE AddRat(VAR Result, Opl, Op2: Rational);
{abs: Result := Opl + Op2 }
{con: Result.Den,Result . Num .-
Opl.Den * Op2.Den,
Opl.Num * Op2.Den + Opl.Den * Op2.Num }

508 ABSTRACT DATA TYPES


c)PROCEDURE Square(VAR R: Rational);
{abs: (R<>O --> R := R*R) 1 (R=O --> )
con: (R.Num*R.Den <> 0 --> R.Num,R.Den :=
R . Num*R.Num, R.Den*R.Den ) I
(R.Num*R.Den = 0 -->
R.Num,R.Den := 0,1) }
d) If the module is changed to the alternate representation of Section 18.2.4, would it also alter a)
-c) above? Explain.
18.3.2 Objects of type Switch (Section 18.3) could have been implemented as an integer rather
t han a character:
TYPE
Switch = RECORD
C: INTEGER
END;
F lip could then have been written:
PROCEDURE Flip(VAR Sw : Switch);
{abs: (Sw=On --> Sw : = Off)
(Sw=Ofr --> Sw : = On)
con: (Sw. C=O -- > Sw. C . - 1) I
(Sw . C=1 --> Sw.C .- 0) }
BEGIN {Flip}
Sw.C := (Sw.C+1) MOD 2
END; {Flip}
a) Show that the concrete comment is not a description of the procedure's actual statement, and
replace it with a concrete comment that is correct.
b) Prove that the abstract comment is correct.
18 .3.3 In the ver ification of Switch of Section 18.3.2:
a) Prove that the concrete comment correctly describes the action of the actual procedure
statements for Flip.
b) Give the details of the proof that Test is correctly implemented.
c) Prove that the suggested function for the WHILE statement is correct.
18 .3.4 Given the following heading:
PROCEDURE Push(VAR S: Stack; C: CHAR);
{ abs: S := S & <C>
con: S.T=<,x,R> --> S.T : = <,cx,R> }
and type declaration:
TYPE
Stack = RECORD
T : TEXT
END;
give a representation function for Stack objects and show that the abstract comment is correct.
18.3.5 A portion of a module for the data abstraction String is given below.

18.3.4 Exercises 509


CONST
MaxL = 5;
TYPE
String = RECORD
C1, C2 , C3, C4, C5: CHAR;
Len: 0 .. MaxL
END;
{Strings are stored in the Ci variables beginning
with C1 and continuing to the proper values for
the length stored in Len.
No string may be more than MaxL characters long.}
PROCEDURE CatString
(VAR Result: String; First, Second: String);
{abs: (length(First) + length(Second) <= MaxL -->
Result : = First & Second) I
(length(First) + length(Second) > MaxL -- > )}
VAR
Ctr: INTEGER; Tmp: CHAR;
BEGIN
IF First . Len + Second.Len <= MaxL
THEN
BEGIN
Result := First;
Result.Len := First.Len + Second.Len;
FOR Ctr := 1 TO Second.Len
DO
BEGIN
CASE Ctr
OF
1: Tmp := Second.C1;
2: Tmp : = Second.C2;
3 : Tmp := Second.C3;
4: Tmp : = Second.C4 ;
5: Tmp := Second.C5
END;
CASE First.Len + Ctr
OF
1: Result.C1 : = Tmp;
2 : Result.C2 : = Tmp;
3: Result.C3 : = Tmp;
4: Result.C4 : = Tmp;
5: Result . C5 := Tmp
END
END
END
END

a) Write a concrete comment describing the implementation of the operation CatStr ing.
b) Give a representation function for this implementation of strings.
c) Verify the implementation.
18.3.6 Consider a procedure P without parameters and without any conflicts involving its loc a.
identifiers . P contains a commen t t hat cl a ims to give the box function of t h e body of th e proced ure_

510 ABSTRACT DATA TYPES


a) Explain how it could be useful in calculating the meaning of the whole program, to first check
that the comment in Pis accurate in its claim.
b) Suppose that P is part of a data abstraction module, and has two comments, one for its
abstract function and one for its concrete function. Explain how the verification of P (showing
that the abstract comment is accurate) could be useful in calculating the meaning of a
program using P . Distinguish this case from the one (a) that doesn't involve abstraction.

18.4 Chapter Summary


The abstract comments written for the operations of a module are all the external user need know
about its meaning. If these comments correctly describe the actual module operations, they can be
used in meaning calculations for any module use. The verification of a module therefore consists of a
demonstration that each abstract comment is correctly implemented by the implementation
procedure. Central to this proof is a formal representation mapping that establishes a
correspondence between the concrete and abstract worlds. All possible implementation diagrams for
t he module commute if and only if the diagrams for each operation commute.

18.4 Chapter Summary 511

. .f ~. -
PART V: PROGRAM DESIGN IN 0 PASCAL

The advanced facilities of 0 (Optimization) Pascal permit random data references and executicx
control. Two new data types in 0 Pascal permit random data references, arrays and pointers. _c.
variable of type :
ARRAY [ J ] OF T
is a bounded list of elements of type T, whose elements are indexed by values of an ordinal type •
For any type T, there is also a predefined, unbounded list of elements of type T indexed by values o
an unordered type called "pointer."
Program parts with arrays or pointers have part functions, but the formal derivation of th eR
part functions from the program text is beyond the scope of this book. Arrays and pointers ca.=
provide large speed improvements in a program, but they must be used with great care. They shou.~
be used only when their program part functions can be determined and verified at least informall.
We have already seen a similar situation with iteration statements. Any terminating WHIE
statement can be replaced by a group of nested IF statements, but the WHILE statement is
efficient (in terms of program text required), that the loops are worth the extra effort they requ ir=
for the determination of their part functions . Indeed, there is no mechanical way to prove t h:c
termination of loops, and therefore no mechanical way to verify the part function of a WHILE
statement. Nevertheless, with discipline, we can restrict ourselves to writing WHILE statemenT-
whose termination can be proved by ad hoc proofs. Similarly, even though a mechanical derivatior:
of part functions may not be possible when arrays and pointers are used, we can restrict ourselves
uses in which ad hoc derivations and verifications are possible.
GOTO statements permit transfer of execution control between almost any statements of ~
program; their program part functions are also beyond the scope of this book. However, GOT
statements are only introduced for efficiency reasons. They may be systematically removed tc
obtain an equivalent GOTO-free program that can be more easily analyzed.
0 Pascal contains a new data type, REAL, which approximates real numbers. Its advantage E
that, up to wide bounds, numbers of any size can be treated uniformly. Its disadvantage is th a·
numbers are rounded off in computation and round-off errors can accumulate to invalidate t h
results.

512 PART V: PROGRAM DESIGN IN 0 PASCAL


CHAPTER 19

AGGREGATE DATA TYPES IN 0 PASCAL

Chapter Preview

The array data type provides a collection of objects in which each member may be accessed as
quickly as any other. The size of the collection is limited and inflexible, but when size
restrictions are not important, arrays can provide a dramatic speed increase. Arrays are useful
for implementing abstract data types and for operations involving searching and sorting.

Arrays are fixed-size collections of objects that can be used to replace either functions (when
heir domains are small) or files (when they contain only a few components). Like functions, arrays
map elements from a domain of one type to a range of a possibly different type. With arrays
simulating functions, the value of a function can be "looked up" instead of computed each time it is
needed, often with a large saving in time. When an array simulates a file, access to any element
t akes a fixed amount of time, providing a large speedup when elements are needed in an
unpredictable order.

19.1 Arrays

Preview

An array is formed from two types: its components and its indices. The index values are used
to select component values. The power of arrays is in the ability to compute an index value,
then use it to obtain a component value, in a time that does not depend on either value or the
past history of array use.

Pascal array types are declared with a statement of the form:


TYPE
ArrType = ARRAY [IndexType] OF ComponentType;
The domain of this array is IndexType and its range is a subset of ComponentType. In Pascal,
IndexType is limited to ordinal types other than INTEGER so that the number of values in the
domain is fixed by the declaration. An array variable, say ArrVarl or ArrVar2, can be declared
to have this type, and a variable of the index type will be useful:
VAR
Index: IndexType;
ArrVarl, ArrVar2: ArrType;
Array variables map values of type IndexType to values of type ComponentType. This mapping
is obtained by subscripting the array variable with an index value. The subscripting operation in
Pascal is indicated by square brackets, e.g.:
ArrVarl[Index]
The mapping represented by an array is finite and explicit; each domain element is given its
associated range element by an assignment . The assignment is usually done one index element at a
time, but also an entire array can be assigned to another array. The statement:
ArrVarl[Index] := CTypeValue
changes the value of the mapping represented by ArrVarl at the point in the domain specified by
Index to the value of CTypeValue, but leaves the remainder of the mapping unchanged . The
statement:

19.1 Arrays 513


ArrVar1 := ArrVar2
changes the entire mapping by assigning all the components of ArrVar2 to the corresponding
components of ArrVarl.
In the following example, Arr has both of its types a subrange of INTEGER, and the FOR
statement assigns each element in the array a value equal to its position. That is, this array is
assigned the identity function on the domain {1, 2, 3, 4, 5}.
TYPE
Both = 1 .. 5;
VAR
I: Both;
Arr: ARRAY [Both] OF Both;
BEGIN
FOR I := 1 TO 5
DO
Arr [I] := I
END
Code ciphers are an application ideally suited to the use of an array. The simplest way to
encrypt a message is to use a substitution cipher that replaces the characters of a message with
characters from a code. A message can be represented as an array of characters, with the index
values integers that are the position of each character in the message. Suppose that messages of
length up to Len are formed only of capital letters:
CONST
Len = 20;
TYPE
Str =ARRAY [1 .. Len] OF 'A' .. 'Z';
VAR
Msg: Str;
Another array of characters can hold the cipher characters (not limited to letters), with index values
the letters that occur in a message.
TYPE
Cipher =ARRAY ['A' .. 'Z'] OF CHAR;
VAR
Code: Cipher;
Characters in a message that are not capital letters are reproduced without substitution.
The following design part reads and encrypts an arbitrary number of lines of input according to
the scheme described above. Since each line of input is stored in Msg prior to encryption, Msg is
padded with blanks after each line is read so that characters read on a previous, longer line do not
appear in the encrypted output of a subsequent, shorter line.

514 AGGREGATE DATA TYPES IN 0 PASCAL


Design Part 1
PROGRAM Encryption(INPUT,OUTPUT);
{translate the characters in INPUT according
to the code in Cipher and print the new
characters in OUTPUT}
CONST
Len = 20;
TYPE
Str =ARRAY [l .. Len] OF 'A' .. 'Z';
Cipher =ARRAY ['A' .. 'Z'] OF CHAR;
VAR
Msg: Str;
Code: Cipher;
BEGIN {Encryption}
{initialize Code};
WHILE NOT EOF
DO
BEGIN
{read a line into Msg and echo it};
{pad Msg with blanks};
{print coded Msg}
END
END. {Encryption}
Initializing the cipher array ts a long senes of assignment statements so it ts packaged m a
procedure.
Design Part 1.1
{initialize Code}
Initialize(Code);
Design Part 1.1.1
PROCEDURE Initialize(VAR Code: Cipher);
{assign substitution cipher to Code}
BEGIN {Initialize}
Code ['A'] : = 'z';
Code [ ' B ' ] : = ' Y ' ;
Code [ ' C ' ] : = 'X' ;
Code [ ' D ' ] : = ' # ' ;
Code [ ' E ' ] : = 'V' ;
Code [ ' F ' ] : = ' U' ;
Code [ 'G'] : = 'T';
Code [ 'H' ] : = ' S ' ;
Code [ ' I ' ] : = ' I ' ;
Code [ ' J ' ] : = ' Q ' ;
Code [ 'K'] : = 'P ' ;
Code [ ' L ' ] : = ' ! ' ;
Code [ 'M ' ] : = ' N ' ;
Code [ 'N ' ] : = 'M' ;
Code [ ' 0 ' ] : = ' 2 ' ;
Code [ 'P ' ] : = ' K ' ;
Code [ 'Q'] : = - '$' ;
Code [ ' R ' ] . - ' D' ;
Code [ ' S ' ] . - 'H' ;
Code [ ' T ' ] : = ' * ' ;
Code [ ' U ' ] : = ' F ' ;

19.1 Arrays 515


Code [ 1 V 1 ] : = 1 E 1 ;
Code [ I WI ] : = I T I ;
Code [ 1 X 1 ] : = 1 C 1 ;
Code [ 1 Y 1 ] : = 1 B 1 ;
Code [ 1 Z 1 ] : = 1 A 1
END; {Initialize}
Characters are read into consecutive elements of Msg until a line mark is encountered.
Design Part 1.2
{read a line into Msg and echo it}
I := 0;
WHILE NOT EOLN AND (I < Len)
DO
BEGIN
I := I + 1;
READ(Msg[I]);
WRITE(Msg[I])
END;
READLN;
WRITELN;
The index I is now positioned at the last character of the message (up to Len), and blanks shout
be added to pad out the line:
Design Part 1.3
{pad Msg with blanks}
FOR I := I+1 TO Len
DO
BEGIN
Msg[I] := I I
END;
Since translating Msg takes several lines of code, they are packaged in a procedure.
Design Part 1.4
{print coded Msg}
Encode (Msg)
Msg elements A to Z are used as subscripts into Code to obtain their translations. Othe:
characters are printed without translation.
Design Part 1.4.1
PROCEDURE Encode(VAR S: Str);
{write the characters in Code corresponding to
the alphabetic characters in S}
VAR
Index: 1 .. Len;
BEGIN {Encode}
FOR Index := 1 TO Len
DO
IF S [Index] IN [ 1 A 1 • • 1 z 1 ]
THEN
WRITE(Code[S[Index]])
ELSE
WRITE(S[Index]);
WRITELN
END; {Encode}

516 AGGREGATE DATA TYPES IN 0 PASCAL


. ..
The complete program and some sample input and output appear below.
PROGRAM Encryption(INPUT,OUTPUT);
CONST
Len = 20;
TYPE
Str =ARRAY [1 .. Len] OF 1 A 1 • • 1 Z 1 ;
Cipher = ARRAY [ 1 A 1 • • 1 Z 1 ] OF CHAR;
VAR
I: 1 .. Len;
Msg: Str;
Code: Cipher;
{Include PROCEDURE Initialize(VAR Code: Cipher);}
{Include PROCEDURE Encode(VAR S: Str);}
BEGIN {Encryption}
Initialize(Code);
WHILE NOT EOF
DO
BEGIN
I := 0;
WHILE NOT EOLN AND (I < Len)
DO
BEGIN
I := I + 1;
READ(Msg[I]);
WRITE (Msg [I])
END;
READLN;
WRITELN;
FOR I := I+1 TO Len
DO
BEGIN
Msg[I] := I I
END;
Encode (Msg)
END
END. {Encryption}
Execution
INPUT :HOW IS THE TIME.
END
OUTPUT:HOW IS THE TIME.
K2T IH •SV •IHV .
END
VM#

19.1.1 Syntax and Semantics for Arrays


The following syntax rules describe array types.
<aggregate type> ::= <unpacked structured type>

19.1.1 Syntax and Semantics for Arrays 517


<unpacked structured type> ::= SET OF <base type>
: FILE OF <component type> : RECORD <field list> END
: ARRAY [ <index type list>] OF <component type>
<index type list> ::= <index type list> , <ordinal type> l <ordinal type>
Since the values of array components can be referenced and defined like those of other variables, L~
definition of <variable> must be expanded to include them.
<variable access> ::=<variable identifier> l <buffer variable>
: <component variable>
<component variable> ::= <field designator> l <indexed variable>
<indexed variable> ::= <array variable> [ <expression list> ]
<array variable> ::=<variable access>
<expression list> ::= <expression list> , <expression> l <expression>
Formally, the values that correspond to array variables in a state are relations. The pairs 0:
an array-value relation have their first members in the (finite) set of values of the index type, anC:
their second members in the set of values of the component type. Thus the number of possible val u ~
of an array variable is large. Each index could be paired with any of the component values, so -
there are N indices and M components possible, the array can have any of MN values, each one a
mapping from the indices to corresponding components. For example, the variable:
VAR
CArray: ARRAY [1 .. 3] OF CHAR;
has 1283 ,....., 2,100,000 possible values for a Pascal machine with 128 distinct character values, one
which is {<1,X>, <2,B>, <3,3>}. This array value would result from the assignments:
CArray[1] :='X';
CArray[2] := 'B';
CArray[3] := '3'
Once each index of an array has an assigned component, the array value must be a function
However, so long as any index remains unassigned, that part of the array acts like an uninitialize
variable. For example, if only , the first and third assignments above have been applied to CArray
its value is
{<1,X>, <3,3>} U {<2,c>: cis a character},
which we abbreviate as usual to
{<1,x>, <3,3>, <2,?>}.
These values may occur in a state with the array identifier, e.g.,
S = {CArray·{<1,X>, <3,3>, <2,?>}, ... }.
In the special case that the entire array is uninitialized, the further abbreviation CArray·? will b~
used.
The meaning of an <indexed variable> can be obtained from the value of the corresponding
array . For example, for the state S above,
lcArray [1] j(S) = X.
In general, for array identifier A and index I, where I is of type J:
lA [ I ] I= {<s, v>: s(I) is a value of type J, v = (s(A))(s(I))}.
The definition of assignment (Section 6.2.1) applies to arrays without change, but does not cover the
case where an array element stands on the left. In that case the assignment aborts unless t he
subscript has a proper value for its type. Formally, for a su bscript I of type J:

518 AGGREGATE DATA TYPES IN 0 PASCAL


lA [ I ] := Ej= {<s, t>: Q](s) is a value of type J,
t=sexceptthatiA [ I ]j(t)=~(s)}.
In all other cases, the value of the expression becomes attached to the left-side identifier in the state
following the assignment. When the assignment assigns one bare array identifier to another, this
value is the relation that is the whole array value.

19.1.2 Array parameters


Entire arrays or components of arrays can appear as actual parameters in programs. AB with
Pascal's other aggregate types, arrays are usually bound to variable parameters for reasons of
efficiency. If an array were passed to a value parameter, the implicit copying operation would have
to duplicate every component. However, array components can be efficiently bound to either value
or variable parameters. The definitions of Section 9.1.4 are not adequate to handle the case of
aliasing between a subscript identifier and an array component with that subscript, passed to
variable parameters. The following program indicates some of the complications that arise.
Index is passed to First and . Arr [Index] is passed to Second. Thus Second is aliased
(through the subscript Index) to First. Firs:t (or Index) is incremented before Second (or
Arr (Index]) is referenced. However, it is the value of Arr [2 ] rather than the value of Arr (3]
that is altered because the binding of the name of the actual parameter to the formal parameter
occurs when a procedure is called rather then when a variable parameter is referenced.
PROGRAM Bind(OUTPUT);
VAR
Arr: ARRAY [1 .. 5] OF INTEGER;
Index: INTEGER;
PROCEDURE IncreaseParms(VAR First, Second: INTEGER);
BEGIN {IncreaseParms}
First := First + 1;
Second := Second + 1
END; {IncreaseParms}
BEGIN {Bind}
FOR Index := 1 TO 5
DO
BEGIN
Arr[Index] := 1
END;
Index := 2;
IncreaseParms(Index, Arr[Index]);
WRITELN('Index is', Index:2);
FOR Index := 1 TO 5
DO
BEGIN
WRITELN ( 'Arr (', Index: 1, 1 ] is 1 , Arr (Index] : 2)
END
END. {Bind}
Execution
OUTPUT:Index is 3
Arr[1] is 1
Arr[2] is 2
Arr[3] is 1
Arr[4] is 1
Arr[S] is 1
In this special case, when an array subscript and a component with that same subscript are passed
as variable parameters, the actual parameter copied into the procedure body must have its subscript

19.1.2 Array parameters 519


evaluated in the state of the call, not a subsequent state within the body.

19.1.3 Exercises
19.1.1 Modify the program Encryption of Section 19.1.1 so that
a) the blanks between words are encoded, so a code breaker would not have this clue to go by.
b) instead of padding with blanks, the program keeps track of which characters in Msg are a
valid part of the input, using a variable of type 1 .. Len to record the subscript "high water
mark."
19.1.2 Discuss the potential difficulty in modifying Encryption (Section 19.1.1) so that it can
handle arbitrarily long lines. Is this a practical difficulty?
19.1.3 In the Encryption program of Section 19.1.1:
a) Assume that the contents of the array Code defines a one-to-one mapping; that is, no range
element is duplicated. Modify Encryption so that using the same Code array (not one with
the code inverted) it can decode. That is, the input is a coded message, and the output is to be
the "clear" text .
b) Suppose that the contents of Code is not necessarily one-to-one. Write a procedure to check
for this property and print the duplications that exist.
c) Write a decoding program using Code as it exists, to handle the case where there may be
duplications in the range of Code. For each encrypted input, print all possible clear texts.
19.1.4 Which declarations and statements in the following Pascal fragment contain errors?
TYPE
AType = ARRAY (0 .. 4] OF BOOLEAN;
VAR
I, J: INTEGER;
A: ARRAY [I .. J] OF INTEGER;
B,C: ARRAY (0 •. 4] OF BOOLEAN;
D: AType;
E: ARRAY [0 .. 4] OF BOOLEAN;
F: AType;
G: ARRAY [1 .. 5] OF BOOLEAN;
BEGIN
IF B(I+J] = D[I*J]
THEN
WRITELN(C[PRED(O)]);
B := C;
B := D;
B := E;
D := F;
E := G
END
19.1.5 Each Pascal fragment below contains the identifier Qu. You are to deduce a possible type of
Qu from the context in each separate case. Express the answer as a type specification for the
declaration:
VAR
Qu: ?
Replace the question mark with the type required of Qu . For example, if you decide that one answer
is a subrange of CHAR between 3 and 6, fill in:

520 AGGREGATE DATA TYPES IN 0 PASCAL


VAR
QU: I 3 I • • 16 I

:.. t here are several possible types, explain briefly.


a) 2 * Qu [ 1 X 1 ]
b)Qu[Red] AND (SUCC(Red) =Green)
c)Qu[ 1 X 1 ].Component = 7
1
d) (Qu [Qu [C]] + C > 0) OR (Qu [C] = X1 )

9.1.6 Consider the Pascal fragment


VAR
B, C: ARRAY [0 •• 4] OF BOOLEAN;
I: 0 .. 4;
BEGIN
B[O] := TRUE;
FOR I := 1 to 4
DO
B[I] :=NOT B[I-1];
C[2] :=FALSE
END
¥h at execution state exists after the final assignment?
19.1.7 Suppose that the abbreviation
Arr·?
a ppears in an execution state. Write out a complete formal definition of its meaning.
19.1.8 Write out a special case of the definition of procedure-parameter meaning in Section 9.1.4
hat applies to aliasing like that found in Bind (19.1.3).

19.2 Implementing Abstract Types with Arrays

Preview

A data abstraction in which the concrete objects are finite can be implemented very efficiently
using arrays.

The array data type implements finite lists with efficient, random access to any list member.
The file type provides access to any member of a file, of course , but only, in sequential order. If the
desired member is not near the head of the file's future list when it is needed, many READ
statements will be required to obtain it.

19.2.1 Finite TEXT Files


Arrays can be used directly to implement a finite version of CF Pascal text files . The abstract type
SmallF i le consists of the declaration:

19.2.1 Finite TEXT Files 521


CONST
MaxFileLen = 100;
TYPE
SmallFile = RECORD
Str: ARRAY[O .. MaxFileLen] OF CHAR;
Cursor: O .. MaxFileLen;
Mode: (Reading, Writing)
END;
and procedures with the headings:
PROCEDURE InitializeSF(VAR F: SmallFile);
PROCEDURE ResetSF(VAR F: SmallFile);
PROCEDURE RewriteSF(VAR F: SmallFile);
PROCEDURE ReadSF(VAR F: SmallFile; VAR Ch: CHAR);
PROCEDURE WriteSF(VAR F: SmallFile; Ch: CHAR);
Each procedure can be used in place of the corresponding file operation. For example, the sequence
of statements
RESET(F);
READ (F, Ch)
for a new TEXT file F and CHAR Ch could be replaced by
InitializeSF(Sf);
ResetSF(Sf);
ReadSF (Sf, Ch)
using a SmallFile Sf. So long as the MaxFileLen limit is observed, there should be no
difference except that the SmallFile operations will be considerably faster on most Pascal
machines.
The ResetSF and ReadSF procedures are:
PROCEDURE ResetSF(VAR F: SmallFile);
BEGIN {ResetSF}
IF F.Mode=Writing
THEN {fill with blanks}
FOR F.Cursor := F.Cursor TO MaxFileLen
DO
F.Str[F.Cursor] :=' ';
F.Cursor := 0;
F.Mode :=Reading
END; {ResetSF}
PROCEDURE ReadSF(VAR F: SmallFile; VAR Ch: CHAR);
BEGIN {ReadSF}
IF (F.Mode=Reading) AND (F.Cursor<MaxFileLen)
THEN {file still readable}
BEGIN
Ch := F.Str[F.Cursor];
F.Cursor := F.Cursor + 1
END
ELSE {undefined}
WRITELN('Undefined operation')
END; {ReadSF}
The treatment of end file is not the same as for TEXT files, but the difference will not matter so long
as a program observes the discipline of reading no more tha n was prn ic:.rsly wr itten in a
SmallFile.

522 AGGREGATE DATA TYPES IN 0 PASCAL


19.2.2 Finite Stacks
_ bounded stack can be implemented with an array and an integer. The integer is zero if the stack
empty; otherwise it is the subscript of the top element of the stack, the others being stored below
~ down to subscript 1. Since there are only a finite number of array components, only a finite
=umber of values can be pushed on a stack implemented in this way.
In the following example, the first Depth characters in the input are reversed by pushing their
alues on a stack, then printing and popping the top stack value.
PROGRAM Reverse(INPUT,OUTPUT);
{reverse the first Depth characters in INPUT}
CONST
Depth = 20;
TYPE
EltType = CHAR;
Stack = RECORD
Val: ARRAY [l .. Depth] OF EltType;
StackTop: O.. Depth
END;
VAR
S: Stack;
Elt: EltType;
PROCEDURE NewStack(VAR S: Stack);
{S := <>}
BEGIN {InitStack}
S.StackTop := 0
END; {InitStack}
PROCEDURE Push(VAR S: Stack; E: EltType);
{S := S &. <E>}
BEGIN {Push}
IF S.StackTop >= Depth
THEN
WRITELN('** OVERFLOW**')
ELSE
BEGIN
S . StackTop := S.StackTop + 1;
S.Val[S.StackTop] := E
END
END; {Push}
PROCEDURE Pop(VAR S: Stack);
{There are Stack X, EltType E such that
S = X &. <E> --> S := X}
BEGIN {Pop}
IF S.StackTop <= 0
THEN
WRITELN('** UNDERFLOW **')
ELSE
S.StackTop := S.StackTop - 1
END; {Pop}

19.2.2 Finite Stacks 523


FUNCTION Top{VAR S: Stack): EltType;
{There is a Stack X, EltType E such that
S = X & <E> --> Top := E}
BEGIN {Top}
IF S.StackTop <= 0
THEN
BEGIN
WRITELN{'** READING EMPTY STACK**');
Top := '0'
END
ELSE
Top := S.Val[S.StackTop]
END; {Top}
FUNCTION Empty{VAR S: Stack): BOOLEAN;
{Empty := {S = <>)}
BEGIN {Empty}
Empty : = S.StackTop <= 0
END; {Empty}
FUNCTION Full{VAR S: Stack): BOOLEAN;
{Full := (Length{S) =Depth)}
BEGIN {Full}
Full := S.StackTop >= Depth
END; {Full}
BEGIN {Reverse}
NewStack(S);
WHILE NOT EOF AND NOT Full(S)
DO
BEGIN
READ(Elt);
Push(S,Elt)
END;
WHILE NOT Empty{S)
DO
BEGIN
WRITE(Top{S));
Pop (S)
END;
WRITELN
END. {Reverse}
Execution
INPUT :abcdefghijklmnopqrstuvwxyz
OUTPUT:tsrqponmlkjihgfedcba
Finite TEXT files and bounded stacks are two abstract types that could become the basis for a
library of types.

19.2.3 Exercises
19.2.1 Explain why the Ini tializeSF operation is necessary, in addition to ResetSF and
RewriteSF, for the SmallFile data abstraction of Section 19.2.1.
19.2.2 Add abstract comments to the procedure headers of the SmallF i le data abstraction of
Section 19.2.1.

524 AGGREGATE DATA TYPES IN 0 PASCAL


19.2.3 Th.e implementation of SmallF i le is limited in the number of items that may be placed in
t he data structure.
a) Write an exact description of how this limitation shows up in the given ReadSF, and how it
should enter in the procedure Wr i teSF. What happens when the limits are exceeded?
b) Do the implementation for Rewri teSF and Wri teSF.
19.2.4 Suppose that a SmallFile has had exactly MaxFileLen WriteSF operations performed
on it. Explain why later reading this file with ReadSF does not behave as would a combination of
WRITE and READ statements on an actual TEXT file.
19.2.5 Implement an improved SmallFile data abstraction by adding an analog of the TEXT file
EOF function, and an EOM (for End Of Medium) function that can be used before Wr i teSF to see if
in fact a write operation is possible, or if the implementation limits have been exceeded.
19.2.6
a) Isolate the Stack data abstraction within the program Reverse of Section 19.2.2.
b) Can the Stack components be changed to any desired type by just replacing the TYPE
declaration for El tType? Explain.
19.2.7
a) Are the comments for Stack (Section 19.2.2) abstract or concrete? Explain.
b) Give a representation function for Stack.
c) Verify the Push operation of Stack.
19.2.8 Solve the Towers of Hanoi problem described in Chapter 6 of the text but without using
recursion. Your solution should contain the data types Move and StackOfMoves, Each Move
should be represented by a record containing information about the number of disks to be moved, the
origin of the move, etc. The StackOfMoves should be represented as an array and a top of stack
index. Moves of more than a single disk must be broken into smaller moves and pushed on the stack
until they can be attempted. For example, to move n disks from peg A to peg B using peg C as
intermediate storage, first move n-1 disks from A to C, moving a single disk from A to B, and moving
n-1 disks from C to B. As a sample, show the behavior of the program when moving 3 disks from peg
A to peg B.

19.3 Searching and Sorting Arrays

Preview

Because array elements may be accessed in any order with no time penalty, new methods of
sorting and searching become available.

Sequences of values can be stored in arrays as well as in files. The major differences between
files and arrays are the number of components and the ability to access components. The number of
components in a file is not specified by the programmer, but grows during execution time to a limit
imposed by the Pascal machine, while the number of components of an array is defined in the
declaration of the array and cannot be changed during execution. Each component of an array is
immediately accessible, but only a single component of a file (the first from the future list) is. A file
has a built-in mechanism (EOF) for detecting its end, but only part of an array may contain valid
values, and this must be explicitly provided for by the programmer.

19.3.1 Searching an Array 525

' ,, \
19.3.1 Searching an Array
An array can be searched as a file is: by examining each component in order. However, when not all
components of the array should participate, a way must be found to mark the end of valid data in
the array. A clever way is to use the element to be found as a sentinel, placing it in the array at the
end to stop the search.
Design Part 2
PROGRAM SentinelSearch (INPUT, OUTPUT);
{INPUT contains integer values:
Key, i1, ... , iN, N=ArrSize-1 -->
((There is a J, 1<=J<ArrSize, iJ=Key -->
OUTPUT :=Key & 'found at position J')
(For all J, 1<=J<ArrSize, iJ<>Key -->
OUTPUT :=Key & 'not found')) }
CONST
ArrSize = 11;
VAR
Arr: ARRAY [1 .. ArrSize] OF INTEGER;
Index: INTEGER;
BEGIN {SentinelSearch}
{get values for Arr and Key};
Index := 1;
WHILE (Index <= ArrSize) AND
(Arr[ArrSize] <> Arr[Index])
DO
Index := Index + ~;
IF Index < ArrSize
THEN
WRITELN(Arr[ArrSize], ' found at position', Index)
ELSE
WRITELN(Arr[ArrSize], 'not found')
END.
The value to be found is read directly into the last position of Arr:
Design Part 2.1
{get values for Arr and Key}
READ(Arr(ArrSize]);
FOR Index := 1 TO ArrSize-1
DO
READ(Arr[Index]);
Assembling these two parts:
Execution
INPUT: 9 0 1 2 3 4 5 9 8 7 6
OUTPUT: 9 found at position 7
SentinelSearch takes no advantage of an array's ability to access components out of order .
A technique called binary search can improve the execution speed of the search if the data list is
sorted. The idea is to probe into the list at the halfway point, discover on which side the sought
value lies, then probe that side at its halfway point, and so on until only one possible position
remains. The desired value must lie there or it is not in the array . For example, consider searching
the following array for the value 70:

526 AGGREGATE DATA TYPES IN 0 PASCAL


Pos 1 2 3 4 5 6 7
Arr rPosl 10 20 30 40 50 60 70
Probes p1 p£ p8

The first probe is made at position 4, the midpoint of the entire array. Since Arr [ 4] is less than
t he value sought, the lower portion of the array is eliminated and the next probe is made at 6,
halfway through the upper half of the array. Again the lower portion is eliminated, leaving only
Arr [7] to be examined. Thus three comparisons were necessary to search an array of seven values.
In geqeral for an array with 2n-1 elements, the elements can be split in half at most n times before
reaching a subsequence of the array containing only a single element. Thus instead of having to
make at most 2n-1 comparisons to locate a value, no more than log 2(2n) = n comparisons are
required.
Design Part 3
PROGRAM BinarySearch (INPUT, OUTPUT);
{INPUT contains integer values:
Key, i1, ... , iN, N=ArrSize;
AND (For all K, 1<=K<ArrSize, iK <= i(K+1) ) AND
(i1<=Key<=iArrSize) -->
((There is a J, 1<=J<=ArrSize, iJ=Key -->
OUTPUT :=Key & 'found at position J')
(For all J, 1<=J<=ArrSize, lJ<>Key -->
OUTPUT :=Key & 'not found')) }
CONST
ArrSize = 7;
VAR
Arr: ARRAY [1 ArrSize] OF INTEGER;
Low, High, Key: INTEGER;
BEGIN {BinarySearch}
{get values for Arr and Key};
Low := 1;
High := ArrSize;
IF (Key >= Arr[Low]) AND (Key <= Arr[High])
THEN
{Search Arr[Low] to Arr[High] for Key}
ELSE
WRITELN(Key, ' not in range of values')
END. {BinarySearch}
Design Part 3.1
{get values for Arr and Key}
READ (Key);
FOR Index := 1 TO ArrSize
DO
READ(Arr[Index]);
The binary search portion repeatedly divides the interval between Low and High in half until at
most a single element remains. The value of this element is compared to that of Key.

19.3.1 Searching an Array 527


Design Part 3.2
{Search Arr[Low] to Arr[High] for Key}
BEGIN
WHILE High > Low
DO
{Divide interval until at most one
element remains};
IF Arr[Low] =Key
THEN
WRITELN(Key, ' found at position', Low)
ELSE
WRITELN(Key, ' not found')
END
The integer variable Mid is introduced to calculate the interval's midpoint . If the element at Mi -
has the same value as Key, the search is terminated by reducing the length of the interval to one_
Otherwise, the search continues in the half of the interval that could possibly contain the Ke'"
value.
Design Part 3.2.1
{Divide interval until at most one element remains}
BEGIN
Mid := (High + Low) DIV 2;
IF Key= Arr[Mid]
THEN {found - reduce interval to length one}
BEGIN
Low := Mid;
High := Mid
END
ELSE {still looking - divide interval in half}
IF Key> Arr[Mid]
THEN
Low := Mid + 1
ELSE { Key< Arr[Mid] }
High := Mid - 1
END;
Some sample executions are shown below:
Execution
INPUT: 70 10 20 30 40 SO 60 70
OUTPUT: 70 found at position 7
INPUT: S 10 20 30 40 SO 60 70
OUTPUT: S not in range of values
INPUT: 33 10 20 30 40 SO 60 70
OUTPUT: 33 not found

19.3.2 Sorting Arrays


In previous chapters characters in TEXT files were sorted. The algorithms were constrained by the
sequential access and retrieval operations available for files. The sorting algorithms presented below
take advantage of the random access operations supported by arrays.
The simplest method of sorting an array into ascending order is called an exchange sort. The
smallest value is found and exchanged with the value in the first position. Now the array is sorted
through the first element , and the sa me technique is applied to the rem ainde r of t he array.

528 AGGREGATE DATA TYPES IN 0 PASCAL


Design Part 4
PROCEDURE ExchangeSort(VAR Vals: IntArray; Start, Size: INTEGER);
VAR
Min, Index, Save, Last: INTEGER;
BEGIN {ExchangeSort}
Last := Start+Size-1;
FOR Start := Start TO Last
DO
BEGIN
{Vals(Min] is the smallest value in
(Vals(Start], ... ,Vals(Last]]};
{exchange Vals(Start] and Vals(Min]}
END
END; {ExchangeSort}
Design Part 4.1
{Vals[Min] is the smallest value in
[Vals[Start], ... ,Vals[Last]]};
Min := Start;
FOR Index := Start+l TO Last
DO
IF Vals[Index] < Vals[Min]
THEN
Min := Index;
Design Part 4.2
{exchange Vals[Start] and Vals[Min]}
Save := Vals[Start];
Vals[Start] := Vals[Min];
Vals[Min] :=Save
While this technique is easy to implement, it is not very fast. Suppose the value of Size is N.
he first execution of the inner FOR statement requires N-1 comparisons to find the smallest
lement. Since the first element is not reexamined, the next execution of the FOR statement requires
_ '-2 comparisons to find the smallest remaining element. The last execution of the FOR statement
involves only a single element and requires N-N or zero comparisons since it must be the smallest
-emaining element. Thus, the total number of comparisons is:
N-l + N-2 + ... + 0 = N2- (1+2+... +N) = N2- N(N+l)/2 = N(N-1)/2.
The exchange-sort algorithm can be thought of as dividing the array into a sorted part
containing just the first element) and a further unsorted part. N divisions are required for an array
or size N. An algorithm called merge sorting divides the array at the midpoint, sorts each piece,
J..D.d merges the sorted pieces. For an array of size N = 2k for some k, the array can be divided at
:::10st log 2 N = k times before reaching a piece of size one which is obviously sorted. This algorithm
"nds itself to recursive implementation:

19.3.2 Sorting Arrays 529


Design Part 5
PROCEDURE MergeSort(VAR Vals: IntArray;
Start, Size: INTEGER);
BEGIN {MergeSort}
If Size > 1
THEN
BEGIN
{sort the values in the first part of Vals}
MergeSort(Vals,Start,Size DIV 2);
{sort the values in the second part of Vals}
MergeSort(Vals,Start+Size DIV 2,Size DIV 2);
{merge the sorted halves}
Merge(Vals,Start,Size)
END
END; {MergeSort}
To merge two sorted subarrays, their first elements are compared and the smaller copied into a
temporary array. The process is then repeated with the copied element removed. When the
elements of one array are exhausted, remaining values from the other are added to the temporar_
array. Finally, the values of the temporary array are copied into the space originally occupied b_
both subarrays.
Design Part 5.1
PROCEDURE Merge(VAR Vals: IntArray;
Start, Size: INTEGER);
VAR
firstStart, firstEnd,
SecondStart, SecondEnd: INTEGER;
Index: INTEGER;
Temp: IntArray;
BEGIN {Merge}
{Divide Vals in half: firstStart .. firstEnd
and SecondStart .. SeccondEnd}
FirstStart := Start;
SecondStart := Start + Size DIV 2;
FirstEnd := SecondStart - 1;
SecondEnd := Start + Size - 1;
Index := 1;
WHILE (firstStart <= firstEnd) AND
(SecondStart <= SecondEnd)
DO
BEGIN
{(Vals[firstStart] < Vals[SecondStart] -->
Temp[Index], firstStart :=
Vals[firstStart], firstStart+l)
(Vals[firstStart] >= Vals[SecondStart] -->
Temp[Index], SecondStart :=
Vals[SecondStart], SecondStart+l) }
Index : = Index + 1
END;
{copy Vals[firstStart] through Vals[firstEnd] to Temp};
{copy Vals[SecondStart] through Vals[SecondEnd] to Temp};
{copy Temp to Vals[Start] through Vals[StartTSize-1]}
END; {Merge}

530 AGGREGATE DATA TYPES IN 0 PASCAL


Design Part 5.1.1
t(Vals[FirstStart] < Vals[SecondStart] - - >
Temp[Index],FirstStart := Vals[FirstStart],FirstStart+l)
(Vals[FirstStart] >= Vals[SecondStart] -->
Temp[Index],SecondStart : = Vals[SecondStart],SecondStart+1)}
IF Vals[FirstStart] < Vals[SecondStart]
THEN
BEGIN
Temp[Index] := Vals[FirstStart];
FirstStart := FirstStart + 1
END
ELSE
BEGIN
Temp[Index] := Vals[SecondStart];
SecondStart := SecondStart + 1
END;
Design Part 5.1.2
{copy Vals[FirstStart] through Vals[FirstEnd] to Temp}
FOR FirstStart := FirstStart TO FirstEnd
DO
BEGIN
Temp[Index] := Vals[FirstStart];
Index := Index + 1
END;
Design Part 5.1.3
{copy Vals[SecondStart] through Vals[SecondEnd] to Temp}
FOR SecondStart : = SecondStart TO SecondEnd
DO
BEGIN
Temp[Index] := Vals[SecondStart];
Index : = Index + 1
END;
Design Part 5.1.4
{copy Temp to Vals[Start] through Vals[Start+Size -1]}
FirstStart := Start;
FOR Index := 1 TO Size
DO
BEGIN
Vals[FirstStart] := Temp[Index];
FirstStart := FirstStart + 1
END
Suppose that the array to be sorted is of size N = 2k. Merging two sorted arrays of size m/2
each requires at most m/2+m/2-1 = m-1 comparisons since the last value is the largest and does not
need to be compared to any other value. The following table shows the ope rations at each step,
beginning with the deepest level of recursion:

19.3.2 Sorting Arrays 531


Step Merges Array Size Total Comparisons
1 N/2 1 (N/2)(1)
2 N/4 2 (N/4)(3)
3 N/8 4 (N/8)(7)
.. ...
k-1
k=log2(N)
I 2
1 I N/4
N/2
2(N/2-1)
1(N-1)=N(N-1)/N
Summing the total comparisons at each step gives the total number of comparisons for the entire
sort:

N( .!.+.!+1.+. .. + NN1 ) .
2 4 8
_ .!. .! l_ N-1 _ .! l_
Let T - ( + + +. .. + N ) . Then 2T - (1+ + +. .. +
2(N-1)
,.., ). Performing the subtractio
2 4 8 2 4
2 T- T in the following peculiar way:

2T= 1 + 3/2 + 7/4 + + 0


-T= 0 + 1/2 + 3/4 + + (N-1)/N
T = 1 + 1 .+ 1 + (N-1)/N
gives
T = k- (N-1)/N = log 2 N- (N-1)/N.
Thus the number of comparisons for the sort is
NT = N log2 N- N + 1 = N( k-1) + 1.
The following table shows the superiority of MergeSort to ExchangeSort:
Comparisons
Size N k ExchangeSort MergeSort
4 2 6 5
8 3 28 17
64 6 2,016 321
1024 10 523,776 9,213

19.3.3 Exercises
19.3.1 Write a recursive version of BinarySearch (Section 19.3.1}, using the fact that once the
search array is reduced in size, searching it is an instance of the same problem.
19.3.2 An insertion sort is a good technique to use when data values must be placed in order as they
arrive . An array holds the values received so far, in sorted order. When a new value arrives, its,
proper position in the array is found and it is inserted, moving the existing values out of the way.
Write an intera~tive program that prompts for an integer as input, keeps the integers in an arra_
sorted by insertion sort, and can print the array on demand .
19.3.3 A pseudo-key called a hash code can be computed from a search key, and used as an index to
build or search a table . As long as all search keys hash to unique codes, a single comparison (to see
if a table entry exists) will determine if the table contains a value equal to the search key. If a table
is being built, the search key can be inserted at the position selected by its hash code. Man.
different algorithms exist for computing hash codes, e.g., for arrays of characters we might add the
ordinal representation of the first several characters and use the sum modulo the table size as the
hash code index.

532 AGGREGATE DATA TYPES IN 0 PASCAL


Unfortunately different search keys can have identical hash codes, resulting in collisions. Keys
whose hash codes collide may be stored elsewhere in the table. Common collision resolution
techniques are storing the key in the next table location, or some fixed number of locations past the
index location. For large, sparse tables hashing provides the most effective retrieval.
Write a program that builds and searches a hash table containing integers. Some care is
required to determine when to stop searching for entries when collisions occur.
19.3.4 · The sorting techniques in this section are based upon arbitrary values. Knowledge of the
range of values can be used to decrease the time needed to sort them. To bucket sort a set of k
possible values, an array with k index values (the "buckets") is created. A new value is inserted by
increasing the count in its bucket. A sorted list (including duplicates) can be printed using the index
values of the buckets. Write a program that bucket sorts examination scores, where the only
possible scores are multiples of 5 between 50 and 100.

19.4 Array Extensions and Applications

Preview

PACKED arrays are stored so that they occupy less space m the ·Pascal machine.
Multidimensional arrays are useful for storing tables and matrices.

19.4.1 The PACKED Attribute


The PACKED attribute can be applied to any aggregate type in Pascal.
<new structured type> ::= <unpacked structured type>
: PACKED <unpacked structured type>
Objects declared PACKED are stored in the Pascal machine so as to occupy minimum space,.
even if this results in longer processing time. Arrays of CHAR are the most frequently PACKED.
Often, the programmer can use both kinds of arrays:
VAR
PWord: PACKED ARRAY[PLb .. PUb] OF CHAR;
Word: ARRAY[Lb .. Ub] OF CHAR;
With two arrays containing the same data, the programmer can exploit the tradeoff between
execution time and storage space. Accessing packed array elements is relatively expensive
(compared to accessing unpacked array elements) and assigning one packed array to another is
relatively cheap (compared to assigning one unpacked array to another).
The operations PACK and UNPACK permit data to be moved from a variable of one
representation to a variable of the other representation. For either of these procedures to be defined
the size of the unpacked array must be at least as great as the size of the packed array. The PACK
procedure moves values from an unpacked array to a packed array. With the declarations above,
the statement
PACK(Word,Exp,PWord)
acts like:
FOR J := PLb TO PUb
DO
PWord[J] := Word[J-PLb+Exp]
That is, the number of elements moved is the size of the packed array; the move begins at subscript
Exp of the unpacked array. The components moved are:

19.4.1 The PACKED Attribute 533


PWord[PLb] PWord[SUCC(PLb)] PWord[PUb]

l
Word[Exp]
l
Word[SUCC(Exp)]
t
Word[SUCC( ... Exp ... )]

The number of SUCC applications in the last subscript is the size of the unpacked array.
Similarly, the UNPACK procedure moves values from a packed array to an unpacked array.
For the declarations above,
UNPACK(PWord,Word,Exp)
acts like:
FOR J := PLb TO PUb
DO
Word[J-PLb+Exp) := PWord[J)
The number of components unpacked is the size of the packed array. The first component of the
packed array is moved to the unpacked array at subscript Exp, and so on.

PWord[PLb) PWord[SUCC(PLb)) PWord[PUb]

!
Word[Exp]
!
Word[SUCC(Exp)]
!
Word [SUCC ( ... Exp. ·.. ) ]

A special form of packed data has already been seen in CF Pascal: strings. A string constant
of length Str ingLength is treated as if it has a type:
PACKED ARRAY[l •. StringLength] OF CHAR
Thus the type of 'HELLO' is
PACKED ARRAY[l .. S] OF CHAR
while that of 'GOODBYE ' is:
PACKED ARRAY[1 .. 7] OF CHAR
Just as these string constants can be written to TEXT files, so can any PACKED ARRAY .. .
OF CHAR. However, although it might seem sensible, strings may not be read from TEXT files. The
relational operators can also be applied to strings, but only if the string lengths are the same. Thus,
'HELLO' and 'GOODBYE ' cannot be compared because their lengths are not the same, but string,
'HELLO ' can be compared with 'GOODBYE' since both have length 7. The comparison is the
usual lexicographic one: characters are compared from the left, and the first different pair
determines which string precedes the other, using the collating sequence for the Pascal machine. For
example,

Expression has value


'HELLO I < 'GOODBYE' FALSE
I HELLO I <> 'HELLO ,- TRUE

534 AGGREGATE DATA TYPES IN 0 PASCAL


Components of packed arrays can only be bound to value parameters. Thus, for example, a
single-character element from a packed array of characters may not be passed to a procedure with
variable parameter of type character.
As an illustration of the use of packed and unpacked arrays, a program will be designed to
print in lexicographic order Interrupt The program stores information in packed form (as an array of
strings), but for processing unpacks the data one string at a time. When it comes time to print the
results, the packed form can be used directly.
Design Part 6
PROGRAM Index(INPUT,OUTPUT);
{create an index of the words in INPUT}
CONST
TableSize = 100;
MaxWordLength = 20;
TYPE
WordType =PACKED ARRAY[1 .. MaxWordLength] OF CHAR;
TableType =
RECORD
Word: ARRAY[1 .. TableSize] OF WordType; {word array}
Entries: O .. TableSize {count of words in table}
END;
VAR
Table: TableType;
WordCount: 1 .. TableSize;
BEGIN {Index}
Table.Entries := 0; {empty table}
{read and insert words in Table};
{print words in Table};
FOR WordCount := 1 TO Table.Entries
DO
WRITELN(Table.Word[WordCount])
END {Index}.
Reading and inserting words in the table are both complex enough operations that they should be
encapsulated in procedures. ReadWord will have two output parameters: Word (a string
containing the text of the next word (if any) in INPUT, and Found (a BOOLEAN indicating whether
or not a word was present).
Design Part 6.1
{read and insert words in Table};
ReadWord(INPUT,Word,Found);
WHILE Found
DO
BEGIN
InsertWord(Word,Table);
ReadWord(INPUT,Word,Found)
END;
ReadWord stores characters in an unpacked array, then packs them into the Result
parameter. Short words are padded with blanks; excess characters in the input are discarded.

19.4.1 The PACKED Attribute 535

·· ·· . . _ . ---_, ~ \ .
Design Part 6.1.1
PROCEDURE ReadWord(VAR Fin: TEXT; VAR Result: WordType;
VAR Success: BOOLEAN);
{ReadWord reads the next word from Fin, places the
result in Result, and sets Success to indicate
whether or not a word was read}
VAR
WideWord: ARRAY[l .. MaxWordLength] OF CHAR;
Ch: CHAR;
BEGIN {ReadWord}
Ch := I I ;
WHILE NOT (EOF(Fin} OR (ChIN ['A' .. 'Z', 'a' . . 'z']))
DO {skip characters not in words}
READ(Fin,Ch);
IF NOT EOF (Fin}
THEN
BEGIN
{read word, putting first MaxWordLength chars
in WideWord};
{pad WideWord with blanks};
PACK(WideWord,l,Result);
Success := TRUE
END
ELSE
Success := FALSE
END; {ReadWord}
Care must be taken in the next design part because end of file may be encountered while reading
characters into WideWord.
Design Part 6.1.1.1
{read word, putting first MaxWordLength chars
in WideWord}
ChCount := 0;
WHILE NOT EOF(Fin) AND (ChIN ['A' .. 'Z', 'a' .. 'z'])
DO
BEGIN
IF ChCount < MaxWordLength
THEN
BEGIN
ChCount := ChCount + 1;
WideWord[ChCount] := Ch
END;
READ(Fin,Ch)
END;
WideWord is padded with blanks before packing it into Result.
Design Part 6.1.1.2
{pad WideWord with blanks}
FOR ChCount := ChCount+l TO MaxWordLength
DO
WideWord[ChCount] := I
If Word is not in Table, InsertWord installs it in lexicographic order, using an insertion
sort. Initially Word is pla ced at the end of Table so a sentinel search can be used.

536 AGGREGATE DATA TYPES IN 0 PASCAL


Design Part 6.1.2
PROCEDURE InsertWord(VAR Word: WordType;
VAR Table: TableType);
{Place Word in lexicographic order (no duplicates)
in Table}
VAR
Index: 1 .. TableSize;
BEGIN {InsertWord}
. IF Table.Entries < TableSize
THEN
BEGIN
Table.Word[Table.Entries+1] :=Word;
WordCount : = 1.;
WHILE Table.Word[WordCount] < Word
DO
WordCount := WordCount+1;
{install Word}
END
END {InsertWord};
'Vhen the search terminates (as it must smce Word was placed in the last entry of Table) ,
-::'able. Word [WordCount] is lexicographically greater than or equal to Word. If WordCount
indicates that Word matched the last Table entry, Word is right where it should be
al phabetically and the number of table entri"es should be updated to indicate that a new word was
:ound. If WordCount indicates that an earlier table entry is greater than or equal to Word , then
either Word is already in the table (and no processing needs be done) or · Word belongs within the
t able and the following entries have to be shifted down to make room to install it.
Design Part 6 .1.2.1
{install word}
IF WordCount = Table.Entries+1
THEN {new word, greater than all other words}
Table.Entries := WordCount
ELSE {Table.Word[WordCount] >= Word}
IF Table.Word[WordCount] <> Word
THEN {new word}
BEGIN
Index := WordCount;
Table.Entries : = Table . Entries + 1;
WordCount := Table . Entries;
WHILE WordCount >= Index+l
DO {shift existing entries lower in table}
BEGIN
Table.Word[WordCount] .-
Table.Word[WordCount-1];
WordCount := WordCount - 1
END;
{install new word}
Table.Word[Index] . - Word
END
After assembling the program and adding the decl ar ations required by the variables introduced
during development , the following sample execution occurs.

19.4.1 The PACKED Attribute 537


Execution
INPUT: WHILE ChIN ['A' •• 'z','a' •• 'z']
oo·
BEGIN
IF ChCount < MaxWordLength
THEN
BEGIN
ChCount := SUCC(ChCount);
WideWord[ChCount] := Ch
END;
IF EOF
THEN
Ch :=
, ,
ELSE
READ(Ch)
END;
OUTPUT:A
BEGIN
Ch
ChCount
DO
ELSE
END
EOF
IF
IN
MaxWordLength
READ
SU.CC
THEN
WHILE
WideWord
z
a
z

19.4.2 Multidimensional Arrays


The range of an array can be any type, including another array type. In Index of the previous
section, the table contained an array of packed array, and the range elements were accessed as
complete arrays. The more common case is to use each component separately. Consider the
declaration:
TYPE
Matrix= ARRAY [1 .. 3] OF ARRAY [1 .. 5] OF INTEGER
A component of a Matrix variable MArr is selected by using two subscripts:
VAR
MArr: Matrix;
Index1: 1 .. 3;
Index2 :· 1 .. 5;
BEGIN
MArr [Index1] [Index2] , ...
END
The first subscript (Index1) selects one of three arrays, and the second subscript (Index2) selects

538 AGGREGATE DATA TYPES IN 0 PASCAL


integer from among the five positions of the selected array. The syntax of multidimensional
. . rrays may be abbreviated in Pascal. The abbreviations for the example above would be:
TYPE
Matrix= ARRAY [1 . . 3, 1 .. 5] OF INTEGER
VAR
MArr: Matrix;
Index1: 1 3;
Index2: 1 .. 5;
BEGIN
MArr[Index1,Index2]
END
T he components of MArr can be thought of as being arranged in a two-dimensional table:
MA.rr [1] [1] MArr [1] [2] MArr [1] [3] MArr [1] [4] MArr [1] [5]
~rr [2] [1] MArr [2] [2] MArr [2] [3] MArr [2] [4] MArr [2] [5]
MA.rr [3] [1] MArr [3] [2] MArr [3] [3] MArr [3] [4] MArr [3] [5]

where the first subscript indicates a row and the second subscript a column. Thus the number of
subscripts is often referred to as the dimension of an array. Arrays can have any number of
dimensions. For example, a three-dimensional array with
TYPE
ThreeD =ARRAY [0 .. 3, 0 .. 3, 0 .. 3] OF Chip
could be used to represent a three-dimensional tic-tac-toe board of 4 planes of 4 X 4 boards.
Multidimensional arrays are often used to represent matrices of numerical values. Suppose
that three matrices M1, M2, and M3 are declared as follows:
CONST
Sub1 = 1;
Sub2 = 3·,
Sub3 = 2·,
TYPE
Matrix1 = ARRAY [1 .• Sub1, 1. :sub2J OF INTEGER;
Matrix2 = ARRAY [1 .. Sub2, 1 . . Sub3] OF INTEGER;
Matrix3 = ARRAY [1 .. Sub1, 1 .. Sub3] OF INTEGER;
VAR
M1: Matrix1;
M2: Matrix2;
M3: Matrix3;
The product of M1 and M2 is defined to be a matrix whose type is Matrix3 and whose element
[I ,K] has the value
Sub2
I; (M1 [I , J] * M2 [ J, K])
J =1
for all I in the range 1 . . Sub1 and all K in the r ange 1 .. Sub3. The second dimension of Ml
·must be the same as the first dimension of M2 for the product to. be defined . Suppose that M3 is to
be computed as the product of Ml and M2. Element M3 [I, K] is formed by multiplying the
corresponding elements of row I of M1 by those of column K of M2. If Ml and M2 have the values
shown below:

then the product is:

19.4.2 MultldlmenslonaiArrays 539


M3=[4o 46]
smce
M3 (1, 1] = 1*4 + 2*6 + 3*8 = 40
M3 [1, 2] = 1*5 + 2*7 + 3*9 = 46
The following program multiplies two matrices whose values are read from the input:
PROGRAM MatrixMultiply(INPUT, OUTPUT);
{Include Matrix declarations}
PROCEDURE Mult(VAR In1: Matrix1; VAR In2: Matrix2;
VAR Out: Matrix3);
{for all I,K (1<=I<=Sub1) AND (1<=K<=Sub3)
Out(I,K] := Sum(J=l..Sub2) (In1[I,J]*In2[J ,K])}
VAR
I: 1. .Sub1;
J: 1 .. Sub2;
K: 1 .. Sub3;
Value: INTEGER;
BEGIN {Mult}
FOR . I := 1 TO Sub1
DO
FOR K := 1 TO Sub3
DO
BEGIN
Value : = 0;
FOR J := 1 TO Sub2
DO
Value:= Value+ In1[I,J] * In2[J,K];
Out[I,K] := Value
END
END; {Mult}
BEGIN {MatrixMultiply}
{fill M1}
FOR I := 1 TO Sub1
DO
FOR J : = 1 TO Sub2
DO
READ(M1[I,J]);
{fill M2}
FOR J := 1 TO Sub2
DO
FOR K := 1 TO Sub3
DO
READ(M2[J,K]);
Mult(M1,M2,M3); {M3 := M1 * M2}
FOR I := 1 TO Subl
DO
BEGIN
FOR K := 1 TO Sub3
DO
WRITE (M3 [I, K]) ;
WRITELN
END;
END {MatrixMultiply}.

540 AGGREGATE DATA TYPES IN 0 PASCAL

'
~ -.
y... , ' "'
Execution
INPUT :1 2 3 4 56 7 8 9
OUTPUT: 40 46

19 .4.3 Exercises
19.4.1 In many computers the basic unit of data is larger than a character, so several characters
an be placed within one unit. The machine operations mostly move whole units, but special
operations are available for extracting and replacing part of a unit. For such a machine, PACKED
arrays of CHAR are stored with several characters in each unit, and unpacked arrays with one
haracter in each unit. In terms of this implementation, explain each of the following:
a) why more data can be stored in the machine if packed arrays are used than if unpacked arrays
are used.
b) why moving a single character from one position to another in a packed array takes more time
, than if the array were unpacked.
c) why the PACK operation always uses the entire packed array, but only part of the unpacked
array may be used.
d) why packed-array elements may not be bound to variable parameters.
19.4.2 It is difficult in Pascal to write a procedure declaration that will handle as actual
parameters constant strings. As an example, one might like to pass:
PROC ('HELLO I);
PROC ( I GOODBYE I )
but no such procedure PROC can be written.
a) Explain why.
b) Describe the best way around the problem that Pascal allows.
c) Does this difficulty come up with variables as well as constants? Explain.
19.4.3 Modify the program Index (19.4.1) so that each printed word is followed by a line number,
of the line on which it first appeared. Number lines from 1 as they are input.
19.4.4 Write a mod~le for sparse table, a collection of character strings positioned in rows and
columns, where most positions in the table are blank. Declare a TYPE Table in which all the
strings are stored end-to-end in a CHAR vector in arrival order, and a list of INTEGER values
locates the starting and ending positions of each string, and its position in the table. This locator
list should be kept in an order so that the table can be printed by going through it sequentially.
Each Table will require a maximum of 500 characters total string storage. The following
types and operations are required:
CONST
SMax = 500;
TYPE
String = RECORD
Data: PACKED ARRAY[l .. SMaxJ OF CHAR;
{The characters of the string in order}
EndPoint: O .. SMax {The subscript of the
final character; 0 for empty string}
END;
Loc = RECORD
Row: INTEGER;
Col: INTEGER
END; {Row and Column values begin at 1}

19.4.3 Exercises 541

.
, ... . # .......-..·
PROCEDURE ClearTable(VAR AllNew: Table);
{This routine must be called first for each table
to be used. It can be used to initialize the data.}
FUNCTION TableEntryOK(It: String; Pos: Loc;
VAR Tab: Table): BOOLEAN;
{Enter It into Tab at Pos, returning TRUE if no entry
was there, FALSE if the entry there was overlaid.}
FUNCTION PrintTable(Tab: Table): BOOLEAN;
{Print the existing table without disturbing it.
Use as many columns and rows as needed.
Make the columns equal-sized in an 60-space width.
Left justify the entries, and where entries are
adjacent, there must be at least one space between
them. If entries would collide or go outside the
60 spaces, truncate them as necessary, and in
this case return FALSE. Otherwise, TRUE.}
Write an application of this module in which strings are input with their row and column subscripts,
placed in the table, then printed. For this application, the input data (in order: row, column,
string):
3 2 Corner
1 3 #
5 1 Jumped Over
4 1 This string is not too long to fit
5 3 The Quick Brown Fox Jumped Over the Lazy Dog
7 2 THE END
should print as:
#

Corner
This string is not too long to fit properly
Jumped Over The Quick Brown Fox

THE END
19.4.5 Matrix addition is defined component by component. That is, two matrices can be added
only if they are the same size, and each component of the sum (which is also of this size) is the sum
of the corresponding components of the two. Write a procedure that adds two matrices.

19.5 Analyzing Programs with Arrays

Preview

Most program analysis is made more difficult by the presence of arrays. Subscripts that may be
calculated make each array access a potential reference to any part of the whole array.

Array programs in which the subscript values are fixed can be analyzed using each array element as
a variable. This is just the approach used for RECORD types, in which the variables named using
the field selectors are treated like any other variable. But array elements are unlike fields of records
in that subscripts need not be constant, and when a subscript value is calculated in a program, the
element to , which it refers may range over the whole array. The program meaning may well be
different for different subscript values, and the only known way to perform the analysis is to treat
the whole array as a variable, with its composite val ue.

542 AGGREGATE DATA TYPES IN 0 PASCAL


Array values are relations of finite domain, that is, ordered pairs in which the first member of
each pair is one of the possible subscript values. Assignment of array elements is described in terms
·of the entire array by introducing a special relation . Let Arr be an array and Sub a subscript,
Exp an element value. Then the effect of the assignment:
Arr[Sub] := Exp
is to alter the old relation value for Arr to a new one symbolized as
(Arr; Sub: Exp)
That is, (Arr; Sub: Exp) represents an array value that is the same as Arr but with one
element changed. This notation allows concurrent assignments to be written for entire array values.
For example, the statement
A[S] := Ch
has the concurrent-assignment meaning:
A : = (A; S: Ch)
The definition of array-element assignment (Section 19.1.2) shows that the array value (A; S: Ch)
is the same as that of A except in the pair(s) with first member S, now paired with Ch. Although
t his notation is not part of Pascal, it is convenient to treat it as if it were an array name, and speak
(say) of the value of
(Arr; Sub:Exp) [I]
in a state s . This value is either the value of Arr [I] in s, if I and Sub do not have the same
value in s, or it is the value of Exp in s, if I and Sub are the same in s. This may be written
I (Arr; Sub:Exp) [IJI=
{<s, IArr [I] j{s)>: [j(s) ~lsubl(s)} U {<s , IExpl(s)>: [j(s) =lsubj{s)}.
This formula expresses the fundamental information required for analysis of array programs.

19.5.1 Exchange
The following fragment was used m ExchangeSort to switch the values of two elements in the
array Vals:
{exchange Vals[Start] and Vals[Min]}
Save: = Vals[Start];
Vals [Start] := Vals[Min];
Vals[Min] :=Save
The trace table for this fragment is shown below:

Statement Save Vals


Save := Vals[Start] Vals [Start]
Vals[Start] := Vals[Min] (Vals; Start:Vals[Min])
Vals[Min] . -Save ((Vals; Start:Vals[Min]);
Min:Vals[Start])

That is, the effect of these three statements is given by the concurrent assignment:
Save,Vals : = Vals[Start],
((Vals; Start:Vals[Min]); Min:Vals[Start])
To prove that these statements do what they were designed to do requires showing that the values
a t subscript positions Min and Start are switched. But because an array is involved, that is no.t
all that must be shown. The values at all other subscript positions must be unchanged. Without
t his additional demonstration, the statements could do arbitrary damage to the array and still be
considered correct. In terms of the initial state, the statement that the values are switched can be
given using the array value from the trace table. The value v1 of

19.5.1 Exchange 543


~-

( (Vals; Start:Vals [Min]); Min:Vals [Start]) (Start]


must be the same as the value of Vals (Min], and
((Vals; Start:Vals(Min]); Min:Vals[Start]) [Min]
must have the same value ( v 2) as Vals [Start]. Consider the first of these. There are two cases.
Suppose that Start and Min have different values. Then using the fundamental array property, v1
is the value of
(Vals; Start :Vals [Min]) [Start]
and using the array property again, because the subscript values are the same, v1 is the value of
Vals [Min] as required. In the second case, Start and Min are the same. Then v 1 is the value of
Vals [Start], which is Vals [Min] as required, since the subscripts are the same . A similar
analysis establishes that the value v2 is correct.
Finally, it must be shown that all other array elements are unchanged, that is, for any J value
within the bounds (but not the value of Min or Start), the value
( (Vals; Start:Vals [Min]); Min:Vals [Start]) [J]
is the same as that of Vals [J]. Applying the array property twice gives
(Vals; Start: Vals [Min]) [J]
and then Vals [J] as required.
It is instructive to compare the effort required to verify an exchange between two array
elements and two scalar variables, as done in Chapter 6. The proof for array-element exchange is
much more difficult, indicating that arrays should be treated with respect, and avoided where
possible.

19.5.2 A WHTI..E Statement Example


Suppose we want. to verify that the WHILE-statement:
WHILE I <= Size
DO
BEGIN
Arr [I] : = I;
I := I + 1
END
computes the function f:
(I<Size --+
I,Arr := Size+l, (( . . . ((Arr;I:I);I+l:I+l) ... ) :Size;Size))
(!>Size --+ )
Since the WHILE statement terminates for any values of I and Size, and f is defined for all such
values, the domains a ree as required by the first condition of the WHILE verification rule. For any
states such that I <= Size (s) is FALSE, the definition gives f(s) = s, as required by condition 2
of the rule. It remams on y to emonstrate condition 3:
lrF !<=Size THEN BEGIN Arr[I] :=I; I : =I+l ENDjoJ=f.
The following execution tables are helpful:

Statement Condition I Arr


IF I<Size I+l (Arr; I: I)
f I+1<Size Size+1 ( ( . . . ( ( Ar r ; I : I ) ; I + 1 : I + 1 ) . . . ) ; S i z e : S i z e)

The final line in this table is tricky to calculate, since it involves replacing I by I+ 1 and Arr by
(Arr; I : I) in :

544 AGGREGATE DATA TYPES IN 0 PASCAL


(( •.. ((Arr;I:I);I+1:I+1) .•. );Size:Size)
_:nee the new value of Arr depends on the value of I, the latter is substituted first:
( ( . . . ( (Ar r ; I + 1 : I + 1) ; I + 2 : I + 2 ) . . . ) ; Size : Size)
- en substitute for Arr:
(( ... (((Arr;I:I) ;I+1:I+1) ;!+2:!+2) ... ) ;Size:Size)
- the table shows. In this case the the condition is
(!<=Size) AND (I+1<=Size)
hich is the same as !<Size. The assignment is:
I , Ar r : = Size+ 1 , (( . . . (( Ar r ; I : I ) ; I+ 1 : I + 1) . . . ) ; Size : Size)
-:De next table:

Statement Condition I Arr


IF I<Size !+1 (Arr; I: I)
I I+1>Size

~esults in the condition I=Size and the assignment


I, Arr := !+1, (Arr; I:I)
nd in this assignment Size may replace I since they are the same in this case.
The next table contributes nothing to the meaning of the composition being calculated, because
:ts condition is contradictory:

Statement Condition I Arr


IF !>Size
I I<=Size Size+1 ( ( . . . ( (Ar r ; I : I ) ; I + 1 : I + 1 ) . . . ) ; S i z e : S i z e)

The final table has the condition !>Size and an identity assignment:

Statement Condition I Arr


IF ... 3 I>Size3 3 3
I !>Size

The combined results are identical to I, with the first case broken into two:
(I <Size -+
I,Arr := Size+1, (( ... ((Arr;I:I);I+1:I+1) ... );Size:Size) )
(I=Size-+ I,Arr := Size+1, (Arr; Size:Size) ) I
(I>Size -+ )
Although the meaning of the WHILE statement has been demonstrated to be I, the form of the
final array value:
( ( . . . ( (Ar r ; I : I ) ; I + 1 : I + 1 ) . . . ) ; Size : S i z e)
does not give much insight into the values of the individual array elements. What is needed is to
calculate the value
( ( . . . ( (Ar r ; I : I ) ; I + 1 : I + 1 ) . . . ) ; S i z e : S i z e) [ J]
for an arbitrary value of J that lies between the values of I and Size. Applying the fundamental
array property until the term with subscript J is reached, the value is J. Thus the WHILE
statement has set the array value to an identity mapping in which each subscript contains its own
value.
Again it is evident that verifications with arrays require much effort, corresponding to the
flexibility and power they provide for optimizing execution . When an array is used in a controlled
way, for example as part of the implementation of a module, then the power may be worth the cost,
because the complications of analysis are confined to the proof that the module is correct. But

19.5.2 A WHILE Statement Example 545


uncontrolled use of arrays is to be avoided.

19.5.3 Exercises
19.5.1 Simplify the value:
(((X;I:X [ I-1]);J: (X;K:Y) [K]);I-1:Z) [I]
using the fundamental property of arrays.
19.5.2 For the fragment that exchanges two values from array Vals (Section 19.5.1), try to
construct a trace table using
Vals [1] Vals [2] Vals[10]
as the column headings. Explain what goes wrong.
19.5.3 Suppose a programmer means to set one array element to zero, and writes
A[I] := 0
In subsequent debugging it is discovered that the subscript should be I+ 2, but the first statement is
erroneously left in the program:
A[I] := 0;
A[I+2] := 0
The programmer now wishes to prove that the program is correct.
a) In terms of the complete value for array A, write the statement that subscript position I+ 2
has been zeroed, and prove that it is so.
b) Explain why the proof of a) is insufficient.
c) Give a statement of what should be proved, and show that it does not hold for the the
erroneous program.
19.5.4 Show that the Pascal fragment
A [K] : =
A [K] + K;
K := K + 1;
A [K] : = A [K] - K;
K := K - 1
computes the function
A[KJ, A[K+1] := A[K] +K, A[K+1] -K-1
(Don't forget to show that the other array elements are unchanged by the fragment.)
19.5.5 Show that the Pascal fragment
K := 0;
X [0] : = 1;
WHILE K <= N
DO
BEGIN
K := K + 1;
X[K] := X[K-1] * K
END
computes the function
For all I O<=I<=N, X[I],K := I!,N
(Hint: do the analysis using the whole array value, and go to the given form only at the end.)
19.5.6 Show that the Pascal fragment

546 AGGREGATE DATA TYPES IN 0 PASCAL


· ' - -
K := A;
WHILE K <= B
DO
BEGIN
K := K + 1;
X[K-1] := X[K]
END
computes the function
For all I A<=I<B, X[I],K := XO[I+1],B
where XO stands for the initial value of the array X. (Hint: express the function m terms of the
whole array value.)

19.6 Chapter Summary


Arrays allow efficient implementations of finite versions of abstract data types based on lists such as
files, stacks, and queues. They also permit efficient implementations of functions, particularly
functions for which no simple computational rule is available. In fact the subscript notation is
suggestive of that for function values.
However, there is another side to this power. Used in an undisciplined way, arrays represent
design liabilities because of their generality and power. The data types of D Pascal provide not only
disciplined access to data in a list, but also the intellectual tools to help assure success in program
design. For example, the use of a file can ensure that every record of interest has been examined,
whereas a solution employing random access to the same set of records in an array can skip some
inadvertently. As a rule, it is easier to improvise solutions with arrays than with more disciplined,
list-based data types, but much more difficult to achieve correct solutions.
These differences lead to a program design strategy that is fundamental for the rest of this
book. In order to get the best of both random access and disciplined access data types, it is a good
idea to observe the following guidelines:
Use arrays primarily for the construction of abstract data types which access data in
disciplined ways, but in ways best suited to the solution being implemented.
Never use arrays in ways that jeopardize your practical ability to carry out correctness
arguments about your programs.
Recognize, in early design thinking about any program , that arrays are seldom suitable for
high-level design. That is, design in D Pascal and optimize in 0 Pascal.
Design for function first and performance second. Get your solution right with a preponderance
of disciplined data types, and optimize in a stepwise reorganization of your solution.

19.6 Chapter Summary 547


-:::

CHAPTER20

LINKED STRUCTURES

Chapter Preview

Many important data structures grow and change as they are used by a program during
execution. These linked structures can be kept in Pascal array storage, but they are more
naturally implemented using Pascal pointer types.

All Pascal data types except files are static: the size of stored data is fixed by the declaration.
Ordinal and set data types have trivial data structures of a single value. Records and arrays
comprise multiple values, but the structure and number of components is fixed. Files are of one
generic structure, the sequence, and can vary dynamically only in length.
However, many problems are best solved with data structures that vary in size and shape as
the solution progresses. For example, an arithmetic expression such as:
(3+9) 1 (12- (3* 2))
can be represented as the tree:

/~
+

/\
3 9
~* 12

/~
3 2

The evaluation of the expression, by repeatedly replacing innermost parenthesized expressions by


their values, can be viewed as tree pruning, replacing a lowest subtree representing a binary
operation by the result of that operation. In this process the tree is transformed successively into
new trees until only one node remains, containing the result of evaluation.

I I
/~ /~
+ 1: ~ -

3
/~~ 9 12 6
/~
12 6
(First the multiplication, then the addition has been performed.)

548 LINKED STRUCTURES


I 2

12
/\ 6
(The subtraction, then the division, yield the final result 2.)
Each tree in the sequence can be viewed as an instance of a single data structure varying
dynamically during the solution.

20.1 Implementing Linked Structures With Arrays

Preview

An array of records can be used to hold a link~d structure by adding a "pointer" field to the
record, to record the subscript values of other records in the structure.

Static array structures can be viewed as dynamic if their components move relative to each other.
For example, suppose a sequenc~ of records is stored in an array, and a sorted version of the data is
required. ExchangeSort or MergeSort (Section 19.3.2) can be used to rearrange the records,
moving the entire record based on all or part of its contents. (The portion of a record that actually
participates in the ordering is called the sort key.) But a better way to sort the data is to add a new
field to each record and place in it the subscript of the alphabetically following record. The records
are chained together by these subscript pointers, and by following the chain, they can be obtained in
sorted order. The advantage over physically sorting the re cords is that only the chain pointers need
be changed. When the records are large, particularly when the sort key is a small part of a large
record, chaining is much faster than moving the records.

20.1.1 A Simple Linked List


The array PRecs might be designed to contain personnel information for a group of people:
CONST
NameLen = 7;
AddrLen = 25;
Max = 4;
TYPE
Month = (Jan, Feb, Mar, Apr, May, June,
July, Aug, Sept, Oct, Nov, Dec);
Sex= {Female, Male);
Date = RECORD
Mo: Month;
Day: 1 .. 31;
Year: INTEGER
END;
Person = RECORD
Name: PACKED ARRAY[l .. NameLen] OF CHAR;
SocialSecurity: INTEGER;
Address: PACKED ARRAY[l .. AddrLen] OF CHAR;
BirthDate: Date
END;
VAR
PRecs: ARRAY [1 .. Max] OF Person;

20.1.1 A Simple Linked List 549

-----~-
Suppose that the following data are stored in this structure:

Index I PRecs[Index] .Name rest of record


1 MillerD
2 SmithDD
3 PlaneDD
4 JonesDD

Consider the Name field of each record as the key. The most straightforward way to get a sortec.
version of the data is to move the records about, to eventually produce the array:

Index I PRecs[Index] .Name rest of record


1 JonesDD
2 MillerD
3 PlaneDD
4 SmithDD

The second way to get a sorted version is to add a 0 .. Max field Next to each record and declare a
new variable of type 0 .. Max, say First. Then, leaving the records in place, set the Next field of
each record to the subscript of the one following in alphabetical order, and First to the subscript
of the initial record in that order {4):

Index I PRecs[Index] .Next PRecs[Index] .Name rest of record


1 3 MillerD
2 0 SmithDD
3 2 PlaneDD
4 1 Jones DO

The last record is flagged with the sentinel 0, an impossible subscript for this array indicating that
nothing follows. Now to obtain the record subscripts in alphabetical order of their contents, begin
with First and follow the chain:
Index :=· First;
WHILE Index <> 0
DO
BEGIN
{Index is the subscript of the current
item in alphabetical sequenpe. Use
it to access whatever fields are needed.}
Index. : = FRees [Index] . Next
END
The series of values taken on by Index inside the WHILE statement is thus 4,1,3,2-the sorted
order.
The chain pointers can be drawn in to give a graphic representation of the linked structure.
The subscript value in each record is replaced with an arrow pointing to the record with that
subscript:

550 LINKED STRUCTURES


t
I I Next

First Name
Miller Smith Plane Jones

(rest of
000 000 00 0 000
record)

PRecs [1] PRecs [2] PRecs [3] PRecs[4]

However, a simpler diagram results if the array sequence is abandoned in favor of the sort sequence.

Fir st
I ... Next

Name
Jones Miller Plane Smith

(rest of
0 00 0 00 000 000
record)

PRecs [4] PRecs [1] PRecs [3] PRecs[2]

A structure in which records contain pointers to other records is called a linked list. The links,
stored in each record's Next component, provide a sort sequence by specifying, for each record, the
location of its alphabetic successor. When there is no link (indicated here by a 0 value), the record is
the last in the linked list.
Records may be added to a linked list without moving even the links for most of the existing
records. For example, if a new record with the Name RushDDD should be added to the alphabetic
sequence, the array can be expanded to place it without regard for the sequence, and the links
rearranged.

20.1.1 A Simple Linked List 551


~ I
Fir st
Jones Miller Plane Rush Smith

... ... ... ... ...

PRecs [4] PRecs [1] PRecs [3] PRecs[S] PRecs [2 =


The linking requires just two assignments no matter how large the list may be:
PRecs[S] .Next := PRecs[3] .Next;
PRecs[3] .Next := 5
Similarly, any record in the list may be deleted with a single assignment.

20.1.2 Insertion Sort


A linked list is the ideal data structure for creating a sorted list of large records as they are read one
by one. Beginning with an array large enough to contain all the records expected, the records can be
placed in the array in sequential order, and links created to maintain sorted access as each record is
added. In the example below the records consist of just a single-character key and all the input
characters are on a single line. Since the design does not depend on the form of input record, it can
be used as a model for sorting records of any type, modifying only the TYPE declaration.
Design Part 1
PROGRAM InsertionSort(INPUT,OUTPUT);
{sort the characters in INPUT into ascending order}
CONST
Max = 16;
ListEnd = 0;
TYPE
RecArray = ARRAY[l .. Max] OF
RECORD
Next: 0 .. Max;
Key: CHAR
END;
VAR
Arr: RecArray;
First, Index: O .. Max;
Extra.: CHAR;

552 LINKED STRUCTURES


BEGIN {InsertionSort}
First := 0;
Index := 0;
{Place record in linked list if space permits,
otherwise ignore with error message}
WHILE NOT EOLN
DO
BEGIN
Index .- Index + 1;
IF Index > Max
THEN
BEGIN
READ(Extra);
WRITELN( 1 Record containing 1
Extra, 1
ignored.');
END
ELSE
BEGIN
READ(Arr[Index].Key);
{link Arr[Index] into existing list}
END
END;
{print list starting with Arr[First] .Key}
END. {InsertionSort}
To find the point at which to insert the newly obtained key Arr [Index] . Key, a new variable
Curr is assigned successive values of the link field until:
Arr[Index] .Key <= Arr[Curr] .Key
is TRUE or the end of the list is reached (Curr takes value 0). Assigning Arr [Index] .Next the
value of Curr links the new value to the values that should follow it in the list, but does not link
the beginning of the list to the new value. Another variable Prev can be made to point at the
element before that of Curr, which allows the second part of linking to be completed. For example,
consider the situation in which a new item has been read into Arr [8] (Index is 8), and the item
should be inserted between subscripts 4 (Prev) and 7 (Curr ). Before the insertion, this portion of
the linked list appears as:

M Q p

Arr [4] Arr[7] Arr [8]

Arr[Prev] Arr[Curr] Arr [Index]


Assigning Arr [Index] . Next the value of Curr (that 1s, linking the end of the list to the new
record) results in:

20.1.2 Insertion Sort 553


M Q p

I ------

Arr (4] Arr [7] Arr (8]

Arr[Prev] Arr[Curr] Arr(Index]


Assigning Arr (Prev] . Next the value of Index links the beginning of the list to the new record:

t
M Q p

Arr [4] Arr [7] Arr [8]

Arr [Prev] Arr[Curr] Arr [Index]

Some care must be taken when linking the first part of the list to the new value since this value rn a_
become the new first element of the list. If Prev begins with value 0 and does not change durin
the search, the new element is at the beginning of the list, and must be pointed to by First r ath e~
than by another list element . No similar caution is required should the new element fall at the en
of the list, because the 0 value in the old final record would be transferred to the new record as if i
were an onward pointer.
Design Part 1.1
{link Arr[Index] into existing list}
Prev := 0;
Curr := First;
{find values for Prev and Curr (if they exist)
such that
Arr[Prev] .Key <= Arr[Index] . Key <= Arr[Curr] .Key};
Arr[Index] .Next := Curr;
IF Prev = 0
THEN
First := Index
ELSE
Arr[Prev] .Next := Index
A Boolean vari~ble Found is introduced to stop the search as soon as Prev and Curr have
acquired their values.

554 LINKED STRUCTURES


Design Part 1.1.1
{find values for Prev and Curr (if they exist)
such that
Arr[Prev] .Key<= Arr[Index] .Key<= Arr[Curr] .Key}
Found := FALSE;
WHILE (Curr <> 0) AND NOT Found
DO
IF Arr[Index] .Key > Arr[Curr] .Key
THEN
BEGIN
Prev := Curr;
Curr .- Arr[Curr] .Next
END
ELSE
Found := TRUE;
The final design part prints the values in sorted order.
Design Part 1.2
{print list starting with Arr[First] .Key}
Index := First;
WHILE Index <> ListEnd
DO
BEGIN
WRITE(Arr[Index].Key);
Index := Arr[Index] .Next
END;
WRITELN

20.1.3 Evaluating Arithmetic Expressions


An expression tree like:

/~
+

/\
3 9
\---_•
12

/~
3 2

. can be stored as an array of records of the form:

20.1.3 Evaluating Arithmetic Expressions 555


CONST
ESize = 9;
TYPE
Node = RECORD
Left, Right: O .. ESize;
Op: CHAR;
Val: INTEGER
END;
VAR
Exp: ARRAY [l .. ESize] OF Node;
where ESize is the maximum number of nodes in the expression tree. Each record (node) can
contain two pointers to the information below it in the tree (Left, Right), a character for the
arithmetic operation (op), and an integer operand (Val). A particular node will contain either an
operation or an operand, but not both. Operation nodes will have pointers, but operand nodes will
not. In the structure, the expression above appears as:

Node 1 2 3 4 5 6 7 8 g
Left 2 4 6 0 0 0 8 0 0
Riqht 3 5 7 0 0 0 g 0 0
Oo3 I + - *
Val 3 g 12 3 2

where node N is stored in position N of the array Exp. An operation can be performed if an
operator node is linked to two operand nodes. The nodes numbered 2 and 7 have this property .
When an operation is performed, the resulting value can replace the operation node. This is
accomplished by erasing the node fields corresponding to the Left and Right links and the
operation, and assigning the result of the operation to the Val field. For example, if the operation
at node 2 is carried out, the representation of the expression would change to:

Node 1 2 3 4 5 6 7 8 g

Left 2 0 6 0 0 0 8 0 0
Riaht 3 0 7 0 0 0 9 0 0
Op3 i - *
Val 12 3 9 12 3 2

If the operation at node 7 is carried out as well, the resulting data structure is:

Node 1 2 3 4 5 6 7 8 g

Left 2 0 6 0 0 0 0 0 0
Rioht 3 0 7 0 0 0 0 0 0
Oo3 I -
Val 12 3 g 12 6 3 2

Now the operation at node 3 can be carried out:

556 LINKED STRUCTURES

..;..;:· '


Node 1 2 3 4 5 6 7 8 9
Left 2 0 0 0 0 0 0 0 0
Riqht 3 0 0 0 0 0 0 0 0
Op3 I
Val 12 6 3 9 12 6 3 2

Finally the completed evaluation is obtained by performing the node-1 operation:

Node 1 2 3 4 5 6 7 8 9
Left 0 0 0 0 0 0 0 0 0
Riqht 0 0 0 0 0 0 0 0 0
Op
Val 2 12 6 3 9 12 6 3 2

A program could be designed for evaluating arithmetic expressions stored in the structure
above. To recognize operator nodes, a character (say I) could be placed in the Op field of each
operand node. The recognition of operator nodes that can be immediately evaluated can then be
accomplished by searching for an Op value that is not I, but whose Left and Right links go to
Op values that are I. Some efficient ways to construct and use expression trees. are suggested in the
Exercises.

20.1.4 Tree Structures


The preceding examples of linked data structures illustrate two fundamental principles:
1. Every node is described by a single data type, which contains at least one link component.
2. The values of the links determine the data structure.
At least one link into a linked data structure exists outside the structure. Sometimes this link IS
explicit, as First is in the list of personnel records; sometimes it is implicit, as in the expression
trees, where the root of the tree is stored in the first array position.
While the values of the links determine a data structure at each instant, unless those values
correspond to some recognizable pattern such as a list or tree, the semantics of the data structure
itself (not of the array that holds it) can become very complex. It is therefore valuable to have
standardized linked structures that can be employed as required, and the k-trees are such a family .
A k-tree, for integer k > 0, is a tree in which every interior node or crotch has exactly k branches,
while every end node or leaf has no branches. For example, a 1-tree is a simple linked list like that
used for the personnel records, a 2-tree is a binary branching tree like that used to represent
arithmetic expressions. The nodes of a k-tree each have k links. Since each node has only one
pointer to it, the tree is said to be singly-linked.
Recursion is a valuable technique in processing k-trees. For example, suppose that an
arithmetic expression is stored in a 2-tree with each crotch an operator node and each leaf an
operand. Evaluation can be accomplished as follows :
If the root is a leaf, the value is the value of the leaf.
Otherwise (the root is a crotch), evaluate the left and right sub 2-trees and compute the value
using their values with the operation at the root.
The evaluation is recursive because evaluation of an operation node requires evaluation of the nodes
to which it is linked, which may in turn be operation nodes.
When information is stored in a k-tree, it is often necessary to traverse the tree in some
fashion, following the links in some particular way, for example, to examine each leaf. Navigating
around in a k-tree is easy so long as the links are followed, but often it is convenient to go
backwards. (For example, when information is found at a leaf, to obtain the node immediately
. above it.) This can be done in a single-linked tree only by storing extraneous information during the
traversal, or by starting again from the root. In a doubly-linked k-tree, one link is added to each

20.1.4 Tree Structures 557


---

node N (making k+1 total links), which points to the node that points to N. Doubly-linked tree5
permit easier navigation , but are larger and more difficult to construct and modify, since more links
must be maintained.

20.1.5 Exercises
20.1.1 In the example of the linked list containing five names at the end of Section 20.1.1:
a) give a single assignment statement that will delete Miller from the list.
b) Will an assignment similar to a) do as well for any other record in the list? Explain.
20.1.2 Suppose that names are stored in an array of PACKED arrays, as in PRecs in Section
20.1.1.
a) Modify ExchangeSort (Section 19.3.2) to sort the names.
b) How can a name consisting of the usual first, last, and middle parts be stored in an array of
PACKED arrays so that names may be easily sorted and searched in the ways that might be
required by a personnel office?
20.1.3
a) Devise a single line of test data for InsertionSort (Section 20.1.2) that will exercise all of
the following situations:
The inserted item goes at the beginning of an existing list.
The inserted item goes at the end of an existing list, and the following inserted item goes at the
end of that list.
The inserted item goes somewhere between the beginning and end of the list.
b) This kind of testing is called special-values or boundary-values testing because it exercises
special cases in the data structure. Explain why for InsertionSort, an item falling at the
end of the list isn't really a boundary value, but one falling at the beginning is.
20.1.4 Modify InsertionSort (Section 20.1.2) so that after it has constructed the list, it reads a
second line of input containing an integer P, and prints record number P in the list. Be sure to
handle error cases.
20.1.5 Program animation consists of adding graphic capabilities to a program so that as it
executes, a pictorial record of its actions are produced. Animation is valuable in understanding how
algorithms work, and in debugging programs. Think about animating InsertionSort (Section
20.1.2). What should be printed at which points in execution? Give an example of what you would
like the output to look like for a typical addition of one record.
20.1.6 Consider the sample expression tree at the start of Section 20.1.3.
a) What is the value of Exp[Exp[Exp[l] .Left] .Right] . Val?
b) What array expression will access the character * ?
20.1.7 Using an array whose index type is
TYPE Color = (Violet, Indigo, Blue, Green, Yellow, Orange, Red)
create a data structure that can be used to read in a list of colors and sort them into "rainbow
order" (the order of the enumerated t ype above) using a linked list. The color names are in the inpu-t
as initial letters on a single line, but the full names should be printed. For example, the response to
the input tYIGt should be
Indigo
Green
Yellow
20.1.8 Write a program to do expression evaluation by searching the structure of Section 20.1.3 for
an operation that can be performed, performing it, and repeating the search.

558 LINKED STRUCTURES


20.1.9 Replace the expression structure of Section 20.1.3 with a doubly-linked one in which each
operation node contains a back pointer to the operation node before it, and write a program to do
expression evaluation in this structure by following the back pointer after each operation is
completed.
20.1.10 Write a program to do recursive expression evaluation using the structure of Section 20.1.3.
20.1.11 In a 2-tree, all the possible node positions can be numbered starting with the root as 1, as
follows:

2
/~3

4
~5
/~7
6

8 9
~/\
10 12 13 11
~
14 15

If these position numbers are used as the subscripts in which records are stored, no pointers need be
stored; instead, the links can be computed. For a rf!cord stored at position N, the location of its left
subtree is position 2N, and its right subtree is position 2N+l. Furthermore, the backpointer from
node M is to node M+2.
a) Give the declaration of the suitable 2-tree data structure for arithmetic expressions.
b) Write a recursive evaluation program for the structure of a).
c) Write a nonrecursive depth-first search procedure that examines a 2-tree stored as in a) by first
going all the way down the left subtree, then back to the first node with an unsearched subtree
and down its left subtree, etc.
20.1.12 Polynomials can be represented by linked structures. Declare a type and variable of that
type to represent a polynomial in the variable x with integer coefficients. Give the values that
represent x 2 - 2x + 1.

20.2 Pointers

Preview

Pascal pointer objects make precise the idea of linking between parts of a structure. In
addition, they allow programs to create and use structures whose size truly vanes as the
program executes.

Pascal has a collection of data types to support the construction and manipulation of linked
structures, called pointers. Pointers are unusual data objects, because they have associated with
their identifiers two values. One value is a Pascal data item of the usual kind, an example of a
Pascal TYPE. The other value is something new, a kind of "locator" for the data value. The locator
value is really what the pointer is all about. The reason for introducing this new locator between
the identifier and the value is that in a linked structure operations are often not on the values stored
in the structure, but only on objects that allow the values to be found, later. For example, in
building the list of values for an insertion sort, most of the processing involves the links between the
values, not the values themselves. In fact, the links are called "pointers" because their values are not
the data, but only help to find the data . We will call the "locator" value the pointer value (or

20.2 Pointers 559

..
--

'
. .,..
sometimes, for clarity, the value of the pointer itself). The value pointed at will be called the target
value. A pointer value for identifier Ptr is thus represented by the diagram:

Locator Target

-+---~1 I
One pointer type is declared by prefixing a type identifier with an upward arrow, "t". The
rules of type syntax are extended as follows:
<new type> ::= <new ordinal type> I <new structured type> I
<new pointer type>
<new pointer type> ::= t <type identifier>
For example:
TYPE
Refint = fiNTEGER;
Refint is the type of a pointer that locates an integer value. A pointer variable is declared using a
pointer type, e.g.:
VAR
Pint: Refint;
The operations for any pointer type are the relationals = and <>, and assignment. When
pointers are assigned, it is the value of the pointer itself that gets copied, not the target value. Two
pointers are equal if their pointer parts are the same; otherwise, even though they may point to
target values that are the same, the pointers are not equal. There is one constant value for a
pointer itself: NIL. When a pointer variable is assigned this constant, e.g.,
Pint := NIL
there is no target value to which Pint points. Pointers can be given values only by assignment (to
NIL, or to another pointer with a value), or with the NEW statement, which creates both the object
and the locator values. The built-in procedure NEW is given a pointer variable as actual parameter.
For example, the statement
NEW(Pint)
creates a new pointer-itself value for Pint, which locates (points to) an INTEGER target value (as
yet unknown). The target of pointer Pint is accessed by suffixing Pint with an upward arrow:
Pintf
This is often called dereferencing a pointer value. Thus the target of a pointer has the same form as
a file buffer. The rules of syntax are extended as follows to include targets:
<variable access> ::= <variable identifier> I <buffer variable> I
<component variable> I <identified variable>
<identified variable> ::=<pointer variable> t
<pointer variable> ::=<variable access>
The DISPOSE built-in procedure takes as parameter a pointer expression.
DISPOSE (Pint)
releases the storage for the target pointed at by Pint. The pointer itself is not released, nor is it
necessarily set to NIL. Thus, NEW and DISPOSE are used in pairs separated by the computation
that uses a target object:

560 LINKED STRUCTURES


NEW(Pint);
{Make use of the target po inted to by Pint};
DISPOSE (Pint)
Pointers and structures built from point ers are best descr ibed by pictures. It is conventional to
represent pointer variables and targe ts by boxes, wit h an arrow between them representing the value
of the pointer itself. The diagram below summarizes the sequences of operations above.
VAR Pint: tiNTEGER NEW (Pint) DISPOSE(Pint)

Pint Pintt Pint Pintt Pint Pintf

G-D
Some Pascal machines set the value of a pointer to NIL as a part of the DISPOSE statement, but if
this is not done (as in the diagram above), the pointer is said to be a dangling reference. It points to
a target that has ceased to exist. When a pointer value itself is NIL there is no target, but the
programmer can test for this condition:
IF Pint <> NIL
THEN
{Use Pintt .. . }
However, only careful programming, keeping track of which pointer values are valid, prevents the use
of dangling references.

·20.2.1 Pointer and Target Values in the Execution State


Pointers are unique in Pascal in allowing the creation of data objects whose size varies as the
program executes. The NEW statement creates two objects that did not exist before it was
executed: the pointer itself and the target . Since NEW statements can be executed over and over,
the number of objects is potentially unlimited . However, there is a naming problem associated with
the creation of many objects: they cannot all have identifiers. Any program declares a finite
number of identifiers, of which say N are pointer variables. Then N NEW statentents can create N
pointer values for these variables, and N targets. There is no reason why another NEW statement
cannot be executed, but which pointer variable should be used? Suppose one of the N is reused.
Then a new pointer value and object are created, and the former values are lost. Thus there are still
only N pointer values and targets that can be used. The lost values have no connection with any
identifier, and so cannot be accessed. The answer to this riddle lies in making a place within a target
object for a pointer value. Then each time a target is created by a NEW statement, a place is
created for another pointer value, and there is no limit to the number of objects the program may
create and use. A linked list is a structure of exactly this kind: there is one identifier that points to
the beginning of the list, then each record of the list contains a pointer to the next record, but there
are no identifiers associated with these records, so a single variable effectively controls an object of
arbitrary size.
The existence of pointer values without identifiers poses a problem in showing them within an
execution state. Names are needed for the pointer values when no pointer variable is used, and we
take these names from the collection {tl, t2, t3, ... }, that is, positive integers preceded by an up
arrow. In any given state some of these values may already appear; when a NEW statement creates
another pointer value, the unused name with the smallest integer is selected. The NEW statement
creates not only (say) t3 as a pointer value itself, but a target, which we write with the arrow on the
other side : 3t. For example, suppose that beginning with a state containing no pointer values, say
{Ch·X}
(containing one CHAR identifier and its value), the declaration

20.2.1 Pointer and Target Values In the Execution State 561

.
' J". .......~. · .·
. -
.··i
VAR.
Pint: fiNTEGER
is encountered. The state then becomes
. {Ch·X, Pint·?}
in the usual way. Following the statement
NEW(Pint).
it becomes
{Ch•X, Pint·f1, 1 f·?}
because the first pointer name is used for the value of Pint itself, and the target to which it points
is as yet _unknown. Finally,
Pintf := 99
yields
{Ch•X, Pint·f1, 1 t·99}.
In simple cases like this, Pintf·99 might just as well have been put in the state instead of 1 t·99,
and Pint left out altogether. But two complications make our notation essential: pointers may be
duplicated, and there may be no identifier for the target value.
First, consider a duplicate pointer. If the declaration is
Pint, Qint: fiNTEGER
then the code
NEW(Pint);
NEW(Qint);
Pintt := 99;
Qintf := Pintf
leads to the state
{Pint·fl, Qint·f2, 1f·99, 2f·99},
which has the diagram:

Pint Qint

lt
~
But with the same declarations,
2t
0
NEW(Pint);
Pintt := 99;
Qlnt := Pint
yields the rather different state
{Pint·fl, Qint·fl, 1 f·99},
with diagram:

562 LINKED STRUCTURES


Pint

It~
In the second case, the pointer values themselves, that is, the values of Pint and Qint, are equal,
and so are the object values (Pintt and Qintt); in the first case only the target values are equal,
and the notation shows this.
Second, consider a target value without an identifier.
TYPE
ContP = RECORD
Int: INTEGER;
Pint: fiNTEGER
END;
VAR
TopP: tcontP;
BEGIN
NEW(TopP);
TopPf.Int := 93;
NEW(TopPf.Pint);
TopPf.Pintf := 0
END
leads to the state
{TopP=f1, 1 f.Int=93, 1 f.Pint=f2, 2f==O}
in which the target 2f with value 0 has no declared identifier. The diagram is:

TopP 1t

If a linked list is being constructed, this situation is carried to the extreme that no target but the
first item in the list has an identifier; the rest have only pointed-at places.

20.2.2 Implementing Linked Structures with Pointers


Pointers are not very useful if they only name a single value as in the type Re fint above. Trying
to simulate an array of 20 integers by performing that number of NEW statements:
FOR Index := 1 TO 20
DO
NEW(Pint)
creates 20 targets, but overlays the pointers to the first 19. There is no way to recover access to
these 19 targets, or even to delete them. A target to which no pointer points is called garbage.
When pointer values are created by NEW they must be saved, if not in the NEW parameter, then by
assigning them to other storage.

20.2.2 Implementing Linked Structures with Pointers 563

. I·
' . ' '
One way to create enough places to put the pointer values created by NEW statements is t
situate the pointer within the target object that is created. For example,
TYPE
Node = RECORD
Value: INTEGER;
Next: fNode
END;
is a record containing a pointer to its own type. The identifier Node is used within its owr:
definition, one of the rare occurrences in Pascal where definition need not precede use. In fact, a
type identifier could be associated with fNode before Node was defined:
TYPE
NodePtr: fNode;
Node = RECORD
Value: INTEGER;
Next: NodePtr
END;
Using this data structure, the linked list of INTEGER values

~r~r~r J' 20 :

can be created. Access to the entire linked list will be provided by a pointer variable declared:
VAR.
FirstPtr: NodePtr;
The first list item and the access link can be implemented by:
BEGIN
NEW(FirstPtr);
FirstPtrf.Value := 1
END
That is, FirstPtr points to a record of type Node whose Value field has value 1, and whose
Next field is unknown:

FirstPtr

---+----!~I 1

To establish the second link, another NEW statement must be executed, but it should place the new
pointer in the just-created record:
BEGIN
NEW(FirstPtrf.Next);
FirstPtrf.Nextf.Value := 2
END
The list is now:

564 LINKED STRUCTURES

· - --

'
- ~·
FirstPtr

,.. 1 ...... 2
I
?

The third list element could be created by the statement:


BEGIN
NEW(FirstPtrf.Nextt.Next);
FirstPtrt.Nextf.Nextt.Value := 3
END
Naming the place for each new pointer value beginning with F irstPtr will get longer and longer
with each additional element, but a pointer variable NewPtr can be moved along the structure to
make the reference easier. NewPtr is to point at the record just created, and to advance as the
structure grows:
BEGIN
{Create single element list with value 1}
NEW(FirstPtr);
FirstPtrt.Value := 1;
NewPtr := FirstPtr;
FOR Index := 2 TO 20
DO {Create element with value and position Index}
BEGIN
NEW(NewPtrt.Next);
NewPtr := NewPtrt.Next;
NewPtrt.Value := Index
END;
NewPtrt.Next := NIL
END
The final statement ends the list by assigning NIL to the last pointer field.
When pointers are heavily used it is important to reclaim storage that was once pointed at,
but is no longer. Careful programming is necessary to avoid creating garbage while reclaiming the
storage occupied by list structures. In the preceding example,
DISPOSE(FirstPtr)
returns its target's storage for reuse. That target is the first element in the list, containing the
pointer to the remaining elements. However, these elements are not released, leaving the situation:

FirstPtr

Thus 19 elements of the list remain as garbage, and F irstPtr is a dangling reference. In order to
reclaim the storage occupied by the list structure, more care is required. The list must be traversed
and a DISPOSE statement executed for each pointer, in the proper order so that nothing escapes.

20.2.3 Insertion Sort 565


20.2.3 Insertion Sort
The insertion sort program developed in Section 20.1.2 can be reimplemented using pointers in pla
of an array. The new implementation lifts the restriction that an array large enough to contain a
expected records be available before reading the first record . In this implementation, records a r=
added as needed and no extra records are created. The integer Next field of the array records ~
replaced by a Next field which is a pointer to the records of the linked list.
Design Part 2
PROGRAM InsertionSort(INPU~,OUTPUT);
{sort the characters in INPUT into ascending order}
TYPE
NodePtr = fNode;
Node =
RECORD
Next: NodePtr;
Key: CHAR
END;
VAR
FirstPtr, NewPtr: NodePtr;
BEGIN {InsertionSort}
FirstPtr := NIL;
WHILE NOT EOLN
DO
BEGIN
NEW(NewPtr);
READ(NewPtrf.Key);
{link NewPtrf into existing list}
END;
{print list starting with FirstPtrf.Key}
END. {InsertionSort}
Two new NodePtr variables, Prev and Curr are added to obtain pointers to the elements which
will be on either side of the new element after the linking is completed.
Design Part 2.1
{link NewPtrf into existing list}
Prev := NIL;
Curr := FirstPtr;
{find values for Prev and Curr (if they exist) such that
Prevf.Key <= NewPtrf.Key <= Currf.Key}
NewPtrf.Next := Curr;
IF Prev = NIL
THEN
FirstPtr := NewPtr
ELSE
Prevf.Next := NewPtr
To move down the list searching for the correct position, the pointer Curr that began at
FirstPtr is moved by repeatedly assigning it the Next field from the record to which it points:

566 LINKED STRUCTURES

.
------==-=
. --~ -
.
Design Part 2.1.1
{find values for Prev and Curr (if they exist) such that
Prevt.Key <= NewPtrt.Key <= Currt.Key}
Found := FALSE;
WHILE (Curr <> NIL) AND NOT Found
DO
IF NewPtrt.Key > Currt.Key
THEN
BEGIN {move to next element}
Prev := Curr;
Curr := Currt.Next
END
ELSE
Found := TRUE;
inally, the elements are printed by making NewPtr reference each list element in sorted order.
Design Part 2.2
{print list starting with FirstPtrt.Key}
NewPtr := FirstPtr;
WHILE NewPtr <> NIL
DO
BEGIN
WRITE(NewPtrt.Key);
NewPtr .- NewPtrt.Next
END;
WRITELN

20.2.4 A Binary Tree Sort


2-trees are usually called binary trees, and one can be used to implement a clever sorting algorithm
in which the sorting is all done by tree traversal. The idea is to store the data and two pointers at
each node, and to place data below a node in the left subtree if it falls earlier in sequence, or in the
right subtree if it falls later in sequence . Once data are inserted according to this scheme, they can
be retrieved in sorted order by a depth-first leftmost traversal of the tree . The data structure .is:
TYPE
Tree = tNodeType;
NodeType = RECORD
Ch: CHAR;
LLink, RLink: Tree
END; -
A character C is placed in the tree as follows:
Starting at the root, C is compared with the charact er stored at a node. If it is the same or
less, the tree is followed to the left, otherwise to the right. When the link to be followed is
NIL, a new node is created with Cas its data value.
An elegant recursive procedure does the insertion:

20.2.4 A Binary Tree Sort 567

--- -~
PROCEDURE Insert(VAR Ptr: Tree; Data: CHAR};
{Insert the value of Data into the tree rooted at Ptr.
Maintain the lexicographic ordering of the tree.}
BEGIN {Insert}
IF Ptr = NIL
THEN
BEGIN {Create leaf with value Data}
NEW(Ptr};
Ptrf. Ch := Data;
Ptrf.LLink := NIL;
Ptrf.RLink := NIL
END
ELSE
IF Ptrf.Ch > Data
THEN
Insert(Ptrf.LLink,Data}
ELSE
Insert(Ptrf . RLink,Data}
END; {Insert}
For example, the tree corresponding to the input string tCBDt is:

B
/~D
...······················· ························...

(The dotted links represent NIL pointers.) C was first placed at the root. Then B went left and D
right. Suppose now that another C arrives, followed by F . The result will be:

c
/~D
...........
B
... , """" ...........
... , """"
c F
/ ·················..... / ····················..

Printing the characters in alphabetic order is accomplished by traversing the tree in the proper
order. The rule for any node is to print everything in its left subtree, then the contents of the node,
then everything in its right subtree. This also has a concise recursive implementation:

568 LINKED STRUCTURES


PROCEDURE PrintTree (Ptr: Tree);
{Print the values of the t ree rooted at pointer.}
BEGIN {PrintTree}
IF Ptr <> NIL
THEN
BEGIN
PrintTree(Ptr j . LLink);
WRITE(Ptrj.Ch);
PrintTree(Ptr j. RLink)
END
END {PrintTree};
These two procedures are t he backbone of a program that sorts a file of characters (ignoring
line boundaries and special characters):
PROGRAM TreeSort(INPUT,OUTPUT);
{sort the characters in INPUT into ascending order}
TYPE
Tree = jNodeType;
NodeType = RECORD
Ch: CHAR;
LLink, RLink: Tree
END;
VAR
Root: Tree;
Ch: CHAR;
{Include Insert and PrintTree}
BEGIN {TreeSort}
Root := NIL;
WHILE NOT EOF
DO
BEGIN
READ(Ch);
WRITE(Ch);
IF ChIN ['A' .. 'Z', 'a' .. 'z', 'O' . . '9']
THEN
Insert(Root,Ch);
END;
WRITELN;
PrintTree(Root)
END {TreeSort}.
Execution
INPUT :bed
bcefga
OUTPUT:bcd bcefga
abbccdefg

20.2.5 Pointers, Functions, and Abstract Types


Pascal functions can return pointer values as well as scalar values.
<result type> ::= <simple type identifier> l <pointer type identifier>
This facility can be used to return aggregate values by allocating the value within the function and
returning a pointer to the value as the result of the function.

20.2.5 Pointers, Functions, and Abstract Types 569


In developing HarmonicSer ies in Section 18.2.3,
{Sum :=Sum+ 1/Ctr)}
was refined to:
MakeRat(R,1,Ctr); {R := 1/Ctr}
AddRat(Sum,Sum,R); {Sum := Sum + R}
rather than:
Sum := AddRat(Sum,MakeRat(1,Ctr))
because a Rational value was represented as a RECORD, and functions cannot return RECORD
results. The more natural functional statement could be used if the Rational type were a pointer
to the record, which functions could return. The appropriate declarations are:
TYPE
RatRec =
RECORD
Num, Den: INTEGER
END;
Rational = fRatRec;
Now the solution to HarmonicSeries (shown without the implementation of the Rational
module) can be more naturally expressed:
PROGRAM HarmonicSeries (INPUT, OUTPUT);
{There is an integer n in INPUT,
(n>O - ->Sum:= 1 + 1/2 + 1/3 + 1/4 + ... + 1/n)
(n<=O --> Sum := 0) }
TYPE
RatRec =
RECORD
Num, Den: INTEGER
END;
Rational = fRatRec;
VAR
Terms: INTEGER;
Ctr: 0 .. MAXI NT;
Sum: Rational;
BEGIN {HarmonicSeries}
READ(Terms);
Ctr := 0;
{Sum := 0/1;}
Sum:= MakeRat(0 , 1);
{WRITELN('Term= 0, Sum=',Sum)}
WRITE('Term= 0, Sum= ') ;
WriteRat(OUTPUT,Sum);
WRITELN;
WHILE Ctr < Terms
DO
BEGIN
Ctr := Ctr + 1;
{Sum := Sum + 1/Ctr;}
Sum:= AddRat(Sum,MakeRat(1,Ctr));
{WRITELN('Term= ', Ctr:2, ', Sum=', Sum)}
WRITE('Term= ', Ctr:2, ', Sum=');
WriteRat(OUTPUT,Sum);
WRITELN
END

570 LINKED STRUCTURES


END. {HarmonicSeries}
The module operations must be imple en ed so ha.t they return appropriate pointers of type
Rational. A local variable of type Rationa is declared in an operation that must return a
pointer, a RatRec record and the Rationa l pointer to it are allocated using this local variable,
and its result is assigned as the func ion's value. A local variable must be used instead of the
function's name because NEW sta emen are not permitted with a function identifier. For the
function AddRat the new local variable is R and the changes are:
FUNCTION AddRat(Opl , Op2: Rational): Rational;
{abs: <AddRat> := <Opl + Op2> }
{con: <AddRatt . Den,AddRatt.Num> :=
<Oplt.Den * Op2t.Den,
Oplt.Num * Op2t.Den + Oplt.Den * Op2t.Num> }
VAR
R: Rational;
BEGIN {AddRat}
NEW(R);
Rt.Num := Oplt.Num * Op2t.Den
+ Oplt.Den * Op2t.Num;
Rt.Den := Oplt.Den * Op2t.Den;
AddRat := R
END; {AddRat}
Using pointers to represent rational numbers really hides information about the
implementation since users not only cannot tell how rationals are represented, but also cannot tell
whether they are aggregates or scalars. Of course, this advantage is not without cost because
accessing a rational number requires following a pointer in addition to the usual variable reference.

20.2.6 Exercises
20.2.1 Each Pascal fragment below contains the identifier Qu. You are to deduce a possible type of
Qu from the context in each separate case. Express the answer as a type specification for the
declaration:
VAR
Qu: ?
Replace the question mark with the type required of Qu. For example, if you decide that one ans.wer
is a subrange of CHAR between 3 and 6, fill in:
VAR
Qu: '3' .. '6'
If there are several possible types, explain briefly.
a) Qut := 'X'
b)IF Qu <>NIL THEN WRITELN('Continue')
20.2.2 Give a type declaration for T to make the following Pascal fragment legal:
VAR
P: T;
BEGIN
Ptf.Q := 'X'
END
20.2.3 Suppose that a linked list of INTEGER values is to be created, and the choice is to be made
between an array with INTEGER subscripts and a pointer implementation . Explain why the
pointers are "safer" in that it is impossible to confuse the values with the pointers, but that in the
array implementation this confusion is allowed.

20.2.6 Exercises 571


20.2.4 Suppose that for a particular Pascal machine, the DISPOSE statement sets the pointer value
involved to NIL . Explain how it is still possible to create dangling references.
20.2.5 The collection {tl, t2, ... } was used for pointer values in an execution state where several
pointers of the same type were used. Does this one collection serve for all pointers of all possible
types at once, or must a different one be used for each type? Explain.
20.2 .6 For the variable First:
TYPE
Refint = tiNTEGER;
VAR
First: tRefint
that might be the beginning of a list of pointers (that might themselves each point to a list of
integers), write Pascal statements that create the value 73 and the pointers to connect First to it .
Show a diagram and the contents of an execution state at each point in the process.
20.2.7 Write a fragment of Pascal code that properly releases the 20-element list at the end of
Section 20.2.2.
20 .2.8 Write a fragment of Pascal code that could be used with InsertionSort (Section 20.2 .3)
to print the list in reverse order (in place of Design Part 2.2).
a) Use an iterative method.
b) Use a recursive method.
20.2.9 The design of TreeSort (Section 20.2.4) is done "bottom-up" instead of in the usual way.
The essential procedures are developed first, then combined to solve the problem.
a) Give a design using the usual top-down method .
b) For small problems there doesn't appear to be much difference between top-down and bottom-
up design. Explain how bottom-up design could go wrong for a large problem.
20.2.10 Identify as many "typical" cases as you can in the TreeSort problem, without considering
the implementation in Section 20.2.4. These are cases in which the actiQn required might be
expected to be the same for many different actual patterns in the data structure . For example, it is
a typical case when the character to be inserted falls to the left of a node, and the node's left pointer
is NIL.
a) Make a list of the cases identified, and of input test data that would excite each case.
(Consider both inserting and printing operations.)
b) Now examine the actual implementation of TreeSort. Which of the cases in a) are really
distinct in the sense that the program might work for one but fail for another?
20.2.11 Write a procedure to be used with TreeSort that determines the height of the tree. The
height is defined as the longest path from the· root to a leaf (a node with two NIL pointers).
20.2.12 Give the implementations for MakeRat and Wr i teRat using the pointer structure of
Section 20.2.5.

20.3 Chapter Summary


Whether they are represented as arrays or pointers, linked structures trade increased access time
(using an extra memory reference to obtain a value) for decreased copying costs (copying a reference
to an object rather than the object itself). While the size of a linked structure represented by an
array is static, pointers and the NEW operation provide programmers with an exact means of
representing objects whose size varies during execution.
The level of indirection provided by pointers makes them particularly well suited for
representing abstract data types. Any object represented by a pointer can be returned as the value
of a function or passed as a value parameter without incurring the penalty for copying the object
itself.

572 LINKED STRUCTURES


CHAPTER21

GOTOSTATEMENTSANDSTRUCTUREDPROGRAMS

Chapter Preview

The presence of GOTO statements affecting the flow of control in a program makes it
unstructured, and in most cases more difficult to understand and analyze. GOTO statements
may have a place for dealing with exceptions and for improving program efficiency, but in most
cases they must be eliminated for t he sake of understanding.
A GOTO statement can transfer control to almost any statement in a program. Programs with
GOTO statements can be difficult to understand because the transfers destroy the hierarchical,
part-function structure of the other Pascal statements. As a result, even though a Pascal program
with (any number of) GOTO stat ements has a program function, that program function cannot be
calculated one statement at a t ime. Instead, a program with GOTO statements must be analyzed in
larger pieces, considering many statements simultaneously. Because of this intertwining of
statements, the analysis is more complex, more error-prone. In short, a program with GOTO
statements can be a real "can of worms" to unravel, and though the analysis is theoretically possible,
it is impractical.

21.1 The GOTO Statement

Preview

Execution of a GOTO statement causes a break in the normal statement-to-statement control


flow. The next statement executed after a statement GOTO I is the one at label l. Some error
conditions are best handled by escaping from the normal sequence with a GOTO.
The GOTO statement provides a means of transferring control from the currently executing
statement to another labeled statement in the program. Labels are unsigned integers containing
from one to four digits, set off from the statement labeled by a colon.
<statement> ::= <unlabeled statement> I <label> : <unlabeled statement>
<unlabeled statement> ::= <simple statement> i <structured statement>
<simple statement> ::= <assignment statement> I <procedure statement>
I <null statement> I <goto statement> I <case statement>
l <READ statement> l <WRITE statement>
<goto statement> ::= GOTO <label>
<label> ::= <digit sequence>
<structured statement> ::= <BEGIN statement> I <IF statement>
I <repetitive statement> l <with statement>
<repetitive statement> ::= <WHILE statement> l <repeat statement>
l <for statement>
Pascal requires that labels be explicitly declared in a label declaration at the start of the block that
contains the statement bearing the label.

21.1 The GOTO Statement 573


<program> ::= <program heading> ; <block>
<program heading> ::= PROGRAM <identifier> ( <identifier list> )
<block> ::= <label declaration part> <constant definitions>
<type definitions> <variable declarations>
<procedure and function declarations> <BEGIN statement>
<label declaration part> ::= LABEL <label list> ;
<label list> ::= <label list> , <label> I <label>
AB with other declarations, duplicate declarations of labels within a unit of scope is forbidden
although labels may be reused in disjoint or inner units of scope.

21.1.1 IF and WHILE Statements with GOTO


The GOTO statement is something of an anachronism in Pascal, a statement intended more to take
advantage of a computer's execution capabilities than to express program designs. In assembly
languages and in some versions of FORTRAN and BASIC, the Pascal IF and WHILE statements are
not available, and GOTO statements must be used to simulate them as follows:

IF B IF B
THEN THEN
s GOTO 1;
ELSE T; GOTO 2;
T 1: S·'
2:

WHILE B 1: IF NOT B
DO THEN
s GOTO 2
S;
GOTO 1;
2:
Few programmers would choose to give up the Pascal IF and WHILE statements in favor of these
alternatives. The GOTO statements destroy the textual structure of the program intended by the
programmer . Programs with GOTO statements are difficult to read because control can be
transferred either forward or backward, and to learn the conditions under which a labeled statement
may be executed, it is necessary to consider each GOTO statement that references its label.
However, the GOTO statement is a natural way to express an intended break in the control pattern,
most often to escape from an iteration early.

21.1.2 Escaping from an Error Condition


Because the WHILE statement continues to repeat its BEGIN statement so long as the condition is
met, it is difficult to abort an iteration early. The situation arises in searching. Suppose that the
array Line contains character values, and index positions 1 to 100 are to be searched for F, and its
subscript returned. It is natural to write

574 GOTO STATEMENTS AND STRUCTURED PROGRAMS


FOR Sub := 1 TO 100
DO
IF Line(Sub] 'F' =
THEN
{What??}
The programmer needs to terminat e t he itera t ion early, and so may resort to using a BOOLEAN loop
control and a WHILE statement:
Found := FALSE;
Sub := 1;
WHILE (NOT Found) AND (Sub <= 100)
DO
BEGIN
IF Line(Sub] 'F'=
THEN
Found := TRUE;
Sub := Sub + 1
END;
IF Found
THEN
WRITELN('F found at position', Sub:3)
ELSE
WRITELN('F missing from list.')
With a GOTO another version using a FOR statement can duplicate this behavior:
FOR Sub := 1 TO 100
DO
IF Line(Sub] =
'F'
THEN
GOTO 99;
IF FALSE
THEN
99: WRITELN('F found at position', Sub:3)
ELSE
WRITELN('F missing from list.')
As another example, consider the problem of reading two matrices (i.e., two-dimensional
arrays) of integers from input, perhaps to compute their product as in Section 19.4.2. Suppose that
the matrices are given a row at a time, in sequence. For example, the matrices:

[~ : !] and [: !]
would appear as the input string:
t3 7 2 1 4 9 2 4 3 1 8 2t.
In this example, 12 integers are expected. If only 7 are supplied, the first matrix is defined, but not
the second. The following partial design deals with the possibility of insufficient data. A BOOLEAN
variable DataOK is used to indicate that enough data were present to define both matrices.

21.1.2 Escaping from an Error Condition 575


Design Part 1
PROGRAM Matrices(INPUT, OUTPUT);
CONST
Sub1 = 2;
Sub2 = 3;
Sub3 = 2;
TYPE
Matrix1 = ARRAY[1 .. Sub1, 1 .. Sub2] OF INTEGER;
Matrix2 = ARRAY[1 .. Sub2, 1 .. Sub3] OF INTEGER;
VAR
M1: Matrix1;
M2: Matrix2;
I: 1 .. Sub1;
J: 1 .. Sub2;
K: 1 .. Sub3;
DataOK: BOOLEAN;
BEGIN {Matrices}
IF NOT EOF
THEN
{read and echo M1};
IF NOT EOF
THEN
{read and echo M2};
{DataOK := M1 and M2 both full};
IF DataOK
THEN
{compute using M1 and M2}
END {Matrices}.
Design Parts 1.1 and 1.2 are very similar-nested WHILE statements are used to fill the matrices
one row at a time. Integers are being read from a text file (which includes an line marker after the
last integer), so care is required not to read past the end of file. As described in Section 14.3.2, it is
necessary to skip over blanks that might end the file, and the procedure SkipBlanks does this.
Design Part 1.1
BEGIN {read and echo M1}
I := 1;
WHILE (NOT EOF) AND (I <= Sub1)
DO
BEGIN
J := 1;
WHILE (NOT EOF) AND (J <= Sub2)
DO
BEGIN
SkipBlanks;
IF EOF
THEN
WRITELN('** Too little data for M1 **')
ELSE
BEGIN
READ(M1[I,J]);
WRITE (M1 [I, J]) ;
J := J + 1
END
END;

576 GOTO STATEMENTS AND STRUCTURED PROGRAMS '"


. I := I + 1
END;
WRITELN
END;
Design Part 1.2
BEGIN {read and echo M2}
J := 1;
WHILE (NOT EOF) AND (J <= Sub2)
DO
BEGIN
K := 1;
WHILE (NOT EOF) AND (K <= Sub3)
DO
BEGIN
SkipBlanks;
IF EOF
THEN
WRITELN('** Too little data for M2 **')
ELSE
BEGIN
READ (M2 [ J, K] ) ;
WRITE(M2(J,K]);
K := K + 1
END
END;
J := J + 1
END;
WRITELN
END;
Ml and M2 are both full if the last WHILE statement terminated because the value of J passed
that of Sub2.
Design Part 1.3
{DataOK : = Ml and M2 both full}
DataOK := J = Sub2+1;
While this treatment of insufficient data is satisfactory, it can be optimized by use of a single
label and two GOTO statements to handle just the case of insufficient data. The label declaration
should be inserted just after the program header, and the entire program must be shown at once to
see both the label and the GOTO statements that target it:

21.1.2 Escaping from an Error Condition 577


Design Part 2
PROGRAM Matrices2(INPUT, OUTPUT);
LABEL
1;
{Insert the declarations· from program Matrices}
BEGIN {Matrices2}
FOR I := 1 TO Subl
DO
FOR J := 1 TO Sub2
DO
BEGIN
SkipBlanks;
IF EOF
THEN
BEGIN
WRITELN('** Too little data for Ml **');
GOTO 1
END;
READ(Ml[I,J])
END;
FOR J := 1 TO Sub2
DO
FOR K := 1 TO Sub3
DO
BEGIN
SkipBlanks;
IF EOF
THEN
BEGIN
WRITELN('** Too little data for M2 **');
GOTO 1
END;
READ(M2[J,K])
END;
Mult(Ml,M2,M3); {M3 := Ml * M2}
FOR I := 1 TO Subl
DO
BEGIN
FOR K := 1 TO Sub3
DO
WRITE (M3 [I, K]) ;
WRITELN
END;
1:
END {Matrices2}.
This second program is shorter than the first, and perhaps easier to understand. Some programmers
use GOTO statements only to handle error processing so that readers of a program can ignore these
statements when first trying to understand the "normal" cases treated by the program. However,
the example shows a drawback of using GOTO statements even in this limited way: unless the label
and the statements that reach it can all be viewed at once , the clarity is lost. In many cases this
would mean that the program cannot be split into sufficiently small pieces to retain ink II· • tual
control of its design.

b78 GOTO STATEMENTS AND STRUCTURED PROGRAMS


.
21.1.3 Transfers into Composite Statem.en
Although it is possible to jump ou t of a sta e.., e OR statements in Matrices2 above),
it is illegal to jump into some composi ;.e sta erne from o :side. (The er ror is often not detected on
particular Pascal machines.) An example o ch a.n · egal jump is shown below:
GOTO 1; {Illegal because ~nside FOR s t atemen t}
FOR I := 1 TO N
DO
BEGIN
S1;
1:82
END
The restriction forbidding t he j ump to label 1 is sensible, since by avoiding the header of the FOR
statement, it transfers to a place wher e th e index variable I might be used, but has not been
initialized. Thus there is no similar restriction on tr ansfer into a WHILE statement, where the an
index value, if any, is under explicit control. Simila rly , one may not jump into the BEGIN statement
of a procedure or function from outside, but it is a ll righ t t o jump into the main BEGIN statement of
a program from anywhere. The reason is t hat the program header has necessarily been encountered
to set up the main BEGIN st a t ement, but a jump into a procedure avoids its header, and local
variables and parameters migh.t not be properly set up.
GOTO statements not only change t he flow of control, but may also change the execution
state. In the following example , t he jump from Q into the BEGIN statement of P removes the local
A from the state:
PROGRAM P (OUTPUT} ;
LABEL
18;
VAR
A: INTEGER;
PROCEDURE Q;
VAR
A: INTEGER ;
BEGIN {Q}
A := 1;
GOTO 18; {exi t Q and d eallocate A}
WRITELN(A)
END; {Q}
BEGIN {P}
A := 0;
Q;
18:WRITELN(A)
END {P} .
Execution
OUTPUT: 0
This example and the others in this section make it clear that the analysis techniques of the program
calculus would have to be modified to deal with programs containing GOTO statements.

21.1.4 Exercises
21.1.1 A label must be a digit sequence whose length is 4 or less, yet the syntax given in Section
21.1 does not control the length. Is this necessa rily done with a context condition, or can syntax
equations be written for exactly those sequences that are labels?
21.1.2 What mistakes involving labels and GOTO statements occur in the following program?

21.1.4 Exercises 579


PROGRAM TooManyGotos (INPUT,OUTPUT);
LABEL 10, 20;
PROCEDURE Inner;
LABEL 30;
BEGIN {Inner}
20: GOTO 30;
30: GOTO 10
END; {Inner}
-BEGIN {TooManyGotos}
GOTO 20;
GOTO 30;
10: WRITELN ( 1 That 1 1 s enough 1 )
END. {TooManyGotos}
21.1.3 Rewrite the following Pascal fragment so that no GOTO statements appear but the program
performs the same computation.
LABEL 10, 20;
BEGIN
10: Statement1;
IF B
THEN
GOTO 20;
Statement2;
GOTO 10;
20:
END
Does another _program with the same computation and no GOTO statements exist that does not
repeat either Statement1 or Statement2?
21.1.4 One way to attempt to bring GOTO statements under the control of the program calculus is
to treat them like procedure statements. That is, encountering a label is like encountering a.
procedure declaration, and transferring to that label is like calling the procedure. Investigate this
idea for a program consisting of a WHILE statement rewritten using two GOTO statements as in
Section 21.1.1.

21.2 Analysis of Programs with GOTO Statements

Preview

The analysis techniques of the program calculus depend on having structured programs
containing no GOTO statements. When a program is unstructured it _can be converted to a
structured program for analysis.

A Pascal program with the hierarchical part-function structure of the program calculus is
called a structured program. Conversely, a program that lacks this structure (because it uses
unrestrained GOTO statements) is called an unstructured program. The program calculus presented
in this book applies only to structured programs, and all methods known for calculating the meaning
of unstructured programs are much more difficult to use. Thus the way to analyze an unstructured
program is to first convert it to structured form.

580 GOTO STATEMENTS AND STRUCTURED PROGRAMS


21.2.1 Structuring an Unstructured P-~· ·~
"'A closed statement is a Pascal stateme
all labels for its GOTO sta erne
all GOTO statements for i
That is, a closed statement may co 0 a enen ;s, but no GOTO statements may enter or
leave it. An open statement is one o closed. The following statements are closed:
The BEGIN statement of a p as no procedures or functions .
Any statement lacking bo h GOTO sta ements and labels.
A procedure or function can ha e GOTO sta ements without their corresponding labels, so its
BEGIN statement is not necessarily closed. T he analysis and verification of a program containing
open procedures can be very diffi.c . Ho ·ever, by copying the procedure text in place of the call
(suitably altered to deal with name confuct.s and parameters), eventually the internal GOTO
statements will be in the same BEGll'\ s a tement as their t arget labels. Therefore, without any loss
in generality, we assume that the BEGIN statement of a program and of every procedure and
function is closed.
Since unstructured programs can be much harder to understand than structured programs, it is
natural to wonder also how much more they can do. Are there problems that can be solved using an
unstructured program that have no struct ured solution? The answer may be surprising:
unstructured programs can do no more than structured programs. The ease with which this can be
shown may also be surprising. ·
Given an arbitrary unstructured program U, a method will be given for converting it into a
structured program that duplicates the behavior of the original. The program is assumed to contain
only GOTO statements and statements of CF Pascal; the additional cases for full Pascal are similar
and are treated in the Exercises.
First, U may be assumed to be a program in which the main BEGIN statement and the BEGIN
statements of all procedures and functions are closed, as explained above. Since each BEGIN
statement will be treated in the same way, the case of just a main program will be treated without
loss of generality ..
The BEGIN statement of U will be transformed to structured form. Make up a label for each
statement of U that lacks one, so that each statement is labeled, say with the labels L 11 L 2 , ... , LA:,
for the k statements in the BEGIN statement of U. Label a null statement at the final END LA:+l·
Declare an INTEGER variable not in U, which will here be taken to be LabelValue. Now, replace
the BEGIN statement of U with a new BEGIN statement of the form:
BEGIN
LabelValue := !nit;
WHILE LabelValue <> Exit
DO
CASE LabelValue OF
Ll:
L2:

LA::
END
END
where Ini t is a constant with the value of the first label L 1 in U and Exit is similarly the value
of the final label LA:+ 1 .
The statements to be inserted in the CASE statement are modifications of the ones from U,
depending on the form of the original statement.
When the statement S with label L in U is a null statement, an assignment statement, a
READ or WRITE statement, or a procedure statement, place the following at L within the CASE
statement:

21.2.1 Structuring an Unstructured Program 581


- ~ -· -- - - - - -~ - -

BEGIN
S;
LabelValue := Af
END
where Af is the label of the statement following Sin the original program U.
When the statement in U is an IF statement:
L: IE' B
THEN
T:
ELSE
E:
Then within the CASE statement at label L place:
IE' B
THEN
LabelValue := T
ELSE
LabelValue := E
and similarly if there is no ELSE part to the IF statement. (The statements labeled T and E will
transformed and also appear in the CASE statement.)
When the statement in U is a WHILE statement:
L: WHILE B
DO
BEGIN
D:
END;
E:
Then within the CASE statement at label L place:
IE' B
THEN
LabelValue := D
ELSE
LabelValue := E
The easiest case is the one for which the whole scheme was invented. When the statement in r.,-
lS:

L: GOTO Af
the statement at L within the CASE is:
LabelValue := Af
The program thus constructed from U is structured, since no GOTO statements were
introduced, and those of U were all replaced. The control flow of U is exactly reproduced in the
sense that each test performed in U and each statement executed in U are done in the same order,
but between statements Label Value is computed, and the WHILE and CASE statements direct
the execution based on this value. However, since Label Value was not present in U, these
additional calculations cannot alter any results U produced, and hence the structured program will
exactly reproduce any input-output behavior of U.
The structured program produced from an unstructured original is not an example of good
programming style. However, it can be analyzed using the program calculus, and it may prove
possible to transform it into a simpler equivalent form.

582 GOTO STATEMENTS AND STRUCTURED PROGRAMS .

-
-~~----
---~~

~
- '

.. . -
21.2.2 A Structuring Example
In order to illustrate the structuring const c ·on, consider the program:
PROGRAM GreatCalculat ionDo_ e (:N? , OUTPUT);
LABEL
10, 20, 30, 40, 5 0, 60 , 70, 80, 90, 100;
VAR
I1, I2: INTEGER ;
BEGIN
10: READ(I1, I2) ; GOTO 20 ;
20: IF (I1 > 0) AND (1 2 > 0) THEN GOTO 80
ELSE GOTO 100 ;
30: IF I1 < I2 THE N GOTO 70 ELSE GOTO 50;
40: IF I1 = I2 THEN GOTO 90 ELSE GOTO 30;
50: IF I1 > I2 THEN GOTO 60 ELSE GOTO 40;
60: I1 := 11 - 12 ; GOTO 40;
70: I2 := I2 - Il; GOTO 40;
80: WRITE('DATA IS ' , Il:3, I2:3); GOTO 40;
90: WRITELN(', ANSWER IS', I1:3); GOTO 10;
100 :
END.
The program uses GOTO statements to present a neat appearance, with all READ, IF, assignment,
and WRITE statements grouped together. But what the program does- its program function-is
another matter . If its WRITE statements are to be believed, it takes two input integers and
produces a third integer as result. But what is the result? If GreatCalculationDone were
structured, its program function could be obtained step by step using techniques of the program
calculus. But that will not work here. Instead, the entire set of statements is locked together with
all those GOTO statements, so that no part can be separated out for analysis and study .
GreatCalculationDone is a real can of worms. It doesn't help very much to perform trial
executions, or to work out their execution tables. One quickly learns that the program echos its
input in pairs, and terminates when either input is not positive, but it isn't obvious how the result is
obtained.
The structured version of GreatCalcul ationDone is obtained by adding labels to the
unlabeled statements and carrying out the construction of the last section. Since there are no
procedures, only the BEGIN statement of the program must be structured. First the additional
labels:
PROGRAM GreatCalculationDone1(INPUT, OUTPUT);
LABEL
10, 15, 20, 23, 27, 30, 33, 37, 40, 43, 47,
50, 53, 57, 60, 65, 70, 75, 80, 85, 90, 95,
100;.
VAR
I1, I2: INTEGER;
BEGIN
10: READ(I1, I2); 15: GOTO 20;
20: IF (I1 > 0) AND (I2 > 0) THEN 23: GOTO 80
ELSE 27: GOTO 100;
30: IF Il < I2 THEN 33: GOTO 70 ELSE 37: GOTO 50;
40: IF I1 = I2 THEN 43: GOTO 90 ELSE 47: GOTO 30;
50: IF I1 > I2 THEN 53: GOTO 60 ELSE 57 : GOTO 40;
60: I1 .- I1 - I2; 65 : GOTO 40;
70: I2 := I2 - I1; 75: GOTO 40;
80: WRITE('DATA IS', I1:3, I2:3); 85: GOTO 40;
90: WRITELN(', ANSWER IS', 11 :3); 95: GOTO 10;

21.2.2 A Structuring Example • 583


100:
END.
Introducing the new variable L1 for the label values, the structured version is:
PROGRAM GreatCalculationDone2(INPUT, OUTPUT);
VAR
I1, I2, L1: INTEGER;
BEGIN
L1 := 10;
WHILE L1 <> 100
DO
CASE L1 OF
10: BEGIN READ(I1, I2); L1 := 15 END;
15: L1 := 20;
20: IF (I1 > 0) AND (I2 > 0) THEN L1 := 23
ELSE L1 := 27;
23: L1 := 80;
27: L1 := 100;
30: IF I1 < I2 THEN L1 := 33 ELSE L1 := 37;
33: L1 := 70;
37: L1 := 50;
40: IF I1 = I2 THEN L1 := 43 ELSE L1 := 47;
43: L1 := 90;
47: L1 := 30;
50: IF I1 > I2 THEN L1 := 53 ELSE Ll :=57;
53: Ll := 60;
57: Ll := 40;
60: BEGIN I1 := Il - I2; Ll := 65 END;
65: L1 := 40;
70: BEGIN I2 := I2 - I1; L1 := 75 END;
75: Ll := 40;
80: BEGIN
WRITE('DATA IS', I1:3, I2:3);
L1 := 85
END;
85: L1 := 40;
90: BEGIN
WRITELN(', ANSWER IS', I1:3);
L1 := 95
END;
95: L1 := 10
END
END.
Now that the program is structured, it can be simplified through a series of transformations.
The first transformation eliminates cases that are just assignments to Ll. A case like:
15: L1 := 20;
can be eliminated by changing all the assignment statements of the the form:
L1 := 15
to
L1 := 20
Thus the statement labeled 10 changes to:

584 • GOTO STATEMENTS AND STRUCTURED PROGRAMS


10: BEGIN READ(I1, I2 ); ~: : =
and the statement labeled 15 is dele ransform a ion is:
PROGRAM GreatCalcu l at io~ U':'P ) ;
VAR
I1, I2, L1: I NTEG.=- ;
BEGIN
L1 := 10;
WHILE L1 <> 100
DO
CASE L1 of
10: BEGIN READ (I1 , I2); L1 : = 20 END;
20: IF (I1 > 0) AND (I2 >0) THE N L1 := 80
ELSE L1 . - 100;
30: IF I1 < I2 THEN L1 : = 70 ELSE L1 := 50;
40: I F I1 = I2 THEN L1 : = 90 ELSE L1 := 30;
50: IF I 1 > ! 2 THEN L1 : = 60 ELSE L1 := 40;
60: BEGIN I 1 : = I 1 - I2; L1 := 40 END;
70 : BEGI N I 2 := I2 - I1; L1 := 40 END;
80: BEGIN
WRITE (' DATA IS', I1:3, I2:3);
L1 : = 40
END;
90 : BEGIN
WRITELN(', ANSWER IS', I1:3);
L1 := 10
END
END
END.
Next, suppose a statement contains no assignment of its own label to Ll. Then that statement
can be inserted in place of every assignment of L1 to its label. For example,
20 : IF (I1 > 0) AND (I2 > 0) THEN L1 := 80 ELSE L1 := 100
does not assignment 20 to Ll. Therefore this IF statement can be substituted for
L1 := 20
everywhere, in particular in:
10: BEGIN READ(I1, I2); L1 := 20 END
to yield:
10: BEGIN
READ (I 1 , I 2) ;
IF (I 1 > 0) AND (I 2 > 0)
THEN
L1 := 80
ELSE
L1 := 100
END
Label 40 is one that could be eliminated in this way, but it would produce a four-fold duplication, so
only the labels 30, 50, 60, 70, 80, and 90 are eliminated:

21.2.2 A Structuring Example 585


PROGRAM GreatCalculationDone4(INPUT, OUTPUT);
VAR
I1, I2, L1: INTEGER;
BEGIN
L1 : =10;
WHILE L1 <> 100
.
DO
CASE L1 OF
10: BEGIN
READ (I 1 , I 2) ;
IF (I1 > 0) AND (I2 > 0)
THEN
BEGIN
WRITE('DATA IS', I1:3, I2:3);
L1 := 40
END
ELSE
L1 := 100
END;
40: IF I1 = I2
THEN
BEGIN
WRITELN(', ANSWER IS', I1:3);
L1 := 10
END
ELSE
IF I1 < I2
THEN
BEGIN
I2 := I2 - I1;
L1 := 40
END
ELSE
IF I1 > I2
THEN
BEGIN
I1 := I1 - I2;
L1 := 40
END
ELSE
L1 := 40
END
END.
The statement at 40 can be simplified by observing that the most deeply nested IF condition is
unnecessary because previous IF statements have ruled out the cases I 1=I 2 and I 1 <I 2. All
possibilities in the statement end with the assignment to 40, so this can be factored out. These
changes give:

586 GOTO STATEMENTS AND STRUCTURED PROGRAMS '


40:IF Il = I2
THEN
BEGIN
WRITELN(', ANSWER IS ', I l: 3 );
L1 := 10
END
ELSE
BEGIN
IF 11 < !2
THEN
I2 := !2 - I1
ELSE {!1 > I2}
!1 := I1 - !2;
L1 : = 40
END
It can now be seen that the statement at 40 will be repeatedly executed by the main CASE
statement so long as the values of I1 and I2 differ. It can therefore be converted into a WHILE
statement that does not use the outer CASE mechanism, but simply iterates until I 1 and I 2 are
the same:
40:BEGIN
WHILE I1 <> I2
DO
IF I1 < I2
THEN
I2 := I2 - I1
ELSE
I1 := I1 - I2;
WRITELN(', ANSWER IS ' , I1:3);
L1 := 10
END
Since there are no longer any assignments L1 : = 40 within t he statement labeled 40, it can
be substituted into case 10 in place of the assignment there . The CASE statement itself can now be
removed, since only one case remains. The resulting program is:
PROGRAM GreatCalculationDoneS(INPUT, OUTPUT) ;
VAR
I1, I2, L1: INTEGER;
BEGIN
L1 := 10;
WHILE Ll <> 100
DO
BEGIN
READ (I 1 , I 2) ;
IF (I1 > 0) AND (I2 > 0)
THEN
BEGIN
WRITE('DATA IS', I1:3, I2:3);
WHILE I1 <> I2
DO
IF I1 < !2
THEN
!2 := I2 - !1
ELSE
I1 := I1 - !2;

21.2.2 A Structuring Example 587

---- - . --------"---

--1\,

., ..
WRITELN(', ANSWER IS', I1:3)
END
ELSE
L1 := 100
END
END.
The variable L1 now only controls exit from the WHILE statement, and can be eliminated by
changing the WHILE condition to
WHILE (I1 > 0) AND (I2 > 0)
which reflects the only way that the iteration terminates. There is one complication. The
termination test follows a READ statement, but moved into the WHILE it would precede that
READ. A way to deal with this is to duplicate the READ statement before the WHILE statement
and move it to the end of the DO statement:
PROGRAM GreatCalculationDone6(INPUT, OUTPUT);
VAR
I1, I2: INTEGER;
BEGIN
READ (I1, I2);
WHILE (I1 > 0) AND (I2 > 0)
DO
BEGIN
, WRITE('DATA IS', I1:3, I2:3);
WHILE I1 <> 12
DO
IF 11 < 12
THEN
12 := 12 - 11
ELSE
11 := 11 - 12;
WRITELN(', ANSWER IS', 11:3);
READ (I 1 , I 2)
END
END.
It is interesting to compare the practically inscrutable unstructured program
GreatCalculationDone with the structured version GreatCalculationDone6. The latter
program can be seen to calculate with successive pairs of input integers. In order to discover what
the calculation is, it only remains to study the inner WHILE statement W:
WHILE I1 <> I2
DO
IF 11 < 12
THEN
I.2 := I2 - I1
ELSE
Il := Il - I2
It is not obvious at first glance that W ever terminates. However, in the IF statement, the
greater of I 1 and I 2 is decremented by the lesser of these on each iteration. Thus one of the two
values always decreases, and neither can go to zero, since they start out unequal. This decrementing
process bounded below cannot continue indefinitely, so the iteration must terminate.
The termination argument shows that I wlhas the form:

588 GOTO STATEMENTS AND STRUCTURED PROGRAMS


{I1=I2 -+ ) 1
{!1<>!2 AND Il>O AND -2> := (::. , I2),g<(Il ,I2) )
for some function g. Two specia ........,.......,..," : e. uppose hat I 2 is initially greater than
I 1, and when I 2 is repeatedly es are eventually the same. Then I 2 began
as a multiple of !1, and
g(Il,I2) = !1 , !2 = lX:- .
By symmetry,
g(I1,I2) = !2, !1 = kXI2 .
It would therefore be a good guess a IS e GaD function discussed in Section 18.2.4.
In the general case tha. nei he a.riable is a multiple of the other, reducing I 2 by a multiple
of !1 until !1>!2 is equivalent to finding a. k> 1 and an h, O<h<I1, such that:
!2 - kXI1 + h = !1.
That is,
!2 = kXIl + {Il-h)
or in more familiar terms:
!2 = kXI1 + r
where k = I 2 + I 1 and r is the remainder. As the reduction process is repeated, first on one
variable and then on the other, both are eventually reduced to a value that divides them both, and
is the least such value. That is, W computes the GCD of !1 and !2.

21.2.3 Exercises
21.2.1 Eliminate the GOTO statement from the following program, distorting its structure as little
as possible.
PROGRAM Search{INPUT,OUTPUT);
{Read a list of integer values from input and print
the distinct values and the number of times they occur}
LABEL
10;
CONST
MaxSize = 10;
TYPE
!Type= l .. MaxSize;
VAR
Vals: ARRAY [!Type] OF INTEGER;
Searches : ARRAY [!Type] OF INTEGER;
CurrSize: O .. MaxSize;
Key: INTEGER;
I: !Type;

21.2.3 Exercises 589


BEGIN {Search}
CurrSize := 0;
READ(Key);
WHILE Key <> -1
DO
BEGIN
FOR I := 1 TO CurrSize
DO
IF Vals[l] = Key
THEN
GOTO 10;
I := CurrSize + 1;
CurrSize := I;
Vals [I] := Key;
Searches[!] := 0;
10:Searches[l] :=Searches[!] + 1;
READ(Key)
END;
WRITELN('Values',' Searches');
FOR I := 1 TO CurrSize
DO
WRITELN(Vals[I] :6,Searches[I] :9)
END. {Search}
21.2.2 Remove the GOTO statement from the following program, distorting its structure as little as
possible.
PROGRAM Format(INPUT,OUTPUT);
{Read INPUT and produce formatted text in OUTPUT.
Translate // to a new line and / to a tab character.
Add an extra space after each period.}
LABEL
10;
CONST
Escape = '/';
BEGIN {Format}
WHILE NOT EOF
DO
BEGIN
IF INPUTt = Escape
THEN
BEGIN
GET(INPUT);
IF NOT EOF
THEN
IF INPUTt = Escape
THEN
BEGIN
WRITELN;
GOTO 10
END
ELSE
WRITE(CHR(9))
END;
OUTPUTt := I NPUTt;

590 GOTO STATEMENTS AND STRUCTURED PROGRAMS


- - - - - - - - - -- - - -

PUT(OUTPUT);
IF INPUTt I

THEN
WRITE (I I) ;
10 :GET (INPUT)
END;
WRITELN
END. {Format}
Execution
INPUT :one. two. three.///fou.r/f.ive/six.
OUTPUT:one. two. three.
four fiv~ e six.
21.2.3 Show that if every procedure a.nd function of a Pascal program has a closed BEGIN
statement, then the program 's ma.in BEG statement is necessarily also closed.
21.2.4 In the construction of a structured program from an unstructured one in Section 21.2.1,
explain why the statements from within IF and WHILE statements are not inserted at the labels for
the IF and WHILE.
21.2.5 In the construction of Section 21.2.1 , suppose that the labels placed on the original program
statements are all in numerical order , increasing throughout the program text.
a) Explain why the original program will not necessarily produce the same results if the order of
statements is changed by moving a complete labeled statement.
b) Explain why moving labeled statements within the CASE statement of the structured version
does not change the program's meaning.
21.2.6 The construction of Section 21.2.1 covers only the CF Pascal language with GOTO
statements added. This exercise is to show that the construction can be extended to all of Pascal.
a) Extend the construction to cover CASE statements.
b) Extend the construction to cover REPEAT statements.
c) Extend the construction to cover FOR statements.
d) Explain why the additional data types of D and 0 Pascal do not enter into the construction.
21.2.7 Prove that for every CF Pascal program, an equivalent program can be written using exactly
one WHILE statement. What constructions not in CF Pascal are required to write this equivalent?
21.2.8 Suppose a program is structured, but the scheme for structuring is applied to it anyway.
Compare the quality of the original with that of the new structured program.
21.2.9 Construct an execution table for the input
t 12 16 -1 -1t
a) to the program GreatCalculationDone (Section 21.2 .2).
b) to the program GreatCalculationDone2.
21.2.10 Instead of creating the structured GreatCalculationDone6, use a single GOTO
statement to transform GreatCalculationDone5 (Section 21.2 .2) into a program not containing
the variable Ll, but in which the READ statement need not be duplicated.
21.2.11 Demonstrate formally that g = GCD for the WHILE statement W analyzed at the end of
Section 21.2.2. In addition to the properties of GCD listed in Section 18.2.4, the following may be
helpful:
GCD(X,X) = X,
GCD(X, Y) = GCD(X- Y, Y) for X> Y.

21.3 Chapter Summary 591


21.3 Chapter Summary
It is something of an exaggeration to call programs containing GOTO statements unstructured
because GOTO statements can be used in a disciplined manner. However, early programming
languages typically contained only IF and GOTO statements to implement all control structures,
and undisciplined use of GOTO statements created "spaghetti code" which was very difficult to
understand. Disciplined use of GOTO statements fell into familiar execution patterns: selection
from alternatives, and bounded or potentially unbounded repetition. As languages matured, special
control structures were added to express these patterns and fewer GOTO ·statements were needed .
Programmers sometimes still turn to GOTO statements for efficiency {because otherwise statements
would be duplicated) or for separating normal execution from error processing.

592 GOTO STATEMENTS AND STRUCTURED PROGRAMS


CHAPTER22

REAL NUMBERS

Chapter Preview

Pascal provides an approximation to real numbers in the data type REAL. As with type
INTEGER, there may be errors when t he size limits of the Pascal machine are exceeded; in
addition, a new source of difficulty arises in roundoff errors.

Real numbers may be represented in Pascal with an explicit decimal point or in floating-point
(scientific) notation . In floating-poin t not ation, a number is thought of as described by two parts, a
real number m called the mantissa and an integer e called the exponent or scaling factor. For some
positive integer base b, the number value is
m X be.
The base is usually a power of two (often sixteen) in computer hardware used for the Pascal
machine, but the examples in this chapter will be given in base ten- they are easier to understand
a nd the ideas do not depend on the base. The mantissa is restricted to a range that will make the
floating-point representation unique. In the usual base-ten scientific notation this range is [1, 10),
t hat is, all reals r such that 1 < r <10. Using the letter "E" to separate mantissa and exponent,
some examples are:

Decimal Fraction Scientific Notation


1.0 1.0EO
10.0 l.OE1
3100.0 3.1E3
0.001223 1.223E-3
-923.4 -9.234E2

In most Pascal machines the space available for mantissa and exponent of a floating-poin t value is
limited, which leads to potential arithmetic errors.

22 .1 Type REAL

Preview

The REAL data type is convenient for processing which requires fractional values, or where
values cover a wide range.

22.1.1 Syntax and Operations


The identifier REAL may be used in a type declaration to specify a value that approximates a real
nu mber. REAL constants may be written with an explicit decimal point or in scientific notat ion
(base ten). They may not st art or end with a decimal point. The syntax is:
<constant>::= <sign> <unsigned number> I <unsigned number>
I <sign> <constant identifier> I <constant identifier>
I <character string>

22.1.1 Syntax and Operations 593


<unsigned number> ::= <unsigned integer> I <unsigned real>
<sign>::= + I-
<unsigned real> ::=<unsigned integer> . <digit sequence>
I <unsigned integer> . <digit sequence>· E <signed integer>
I <unsigned integer> E <signed integer>
REAL expressions may use arithmetic and relational operators:
<expression> ::=<simple expression>
I <simple expression> <relational operator> <simple expression>
<simple expression> ::= <simple expression> <add operator> <term>
I <term> I <sign> <term>
<term>::= <term> <mult operator> <factor> I <factor>
<factor>::= <variable access> I <unsigned constant>
I <function designator> I ( <expression> ) I <set constructor>
I NOT <factor>
<mult operator> ::= *II
<add operator> ::= + I-
The binary arithmetic operators produce a REAL result from two REAL operands. INTEGL
operands may be mixed with REAL operands, the INGEGER value being treated as if it were a rea.
value instead of an integer. For mixed operations the result returned is REAL. The difference
between operating on two INTEGER values and using one or two REAL values with the same
operator is a source of confusion. When INTEGER values are used, the result can be in error only if
MAX/NT is exceeded, as described in Section 14.3.3. With mixed or REAL values, the range of
values available is much wider, but even when the results stay within range, there can be a loss of
precision that never occurs with INTEGER results. (Such errors are treated in Section 22.2 below.)
For the division operator, there is a more obvious difference: integer division ( -;-) and real division (/)
are different operations. For example, in the Pascal syntax,
3 DIV 2 = 1
has value TRUE, but
3/2 = 1.5
is also TRUE.
REAL is not an ordinal data type; the operations SUCC and PRED are not defined for REAL
operands. Thus REAL values may not be used directly as array indices, in FOR statements, etc.
Two predefined operations convert REAL values to INTEGER: TRUNC and ROUND. TRUNC
returns the INTEGER obtained by discarding the fractional part of the REAL value. ROUND
produces the INTEGER closest in value to the REAL. These operations are defined only if the result
lies within the MAX/NT bounds of the INTEGER type. For example:

X TRUNC(X) ROUND(X)
5.3 5 5
7.5 7 8
-6.3 -6 -6
-9.7 -9 -10

The predefined operations involving REAL values are:

594 REAL NUMBERS


Real-+ Real
Real-+ Real
EXP Real (positive)-+ Real
Integer (positive) -+Real
LN Real -+ Real
Integ;er -+ Real
S ORT Real (posit ive) -+Real
ARCTAN Real (radians) -+ Real
Integer {radians) -+Real
SI N Real (radians) -+Real
In t eger (radians) -+Real
cos Real (radians) -+Real
In t eg;er (radians) -+Real

ABS and SQR have the same meaning as the INTEGER counterparts. EXP computes the
-
exponential function f(x) = ez, and LN computes the natural logarithm (for positive arguments).
Since
en In b = (eln b)n = bn,
a power function like
X * X * X * X * X {5th power of X}
can be calculated with
EXP(5 * LN(X)) {5th power of X}
SQRT computes the square root of its positive argument and is otherwise undefined. ARCTAN, SIN,
and COS compute these trigonometric functions for arguments expressed in radians.

22.1.2 Real Input/Output


REAL values can be read from and written to TEXT files and ~njoy the same kind of automatic
conversions that the INTEGER type does. On input, READ statements with REAL variables as
actual parameters will take successive values from a TEXT file, and those values may be expressed
in the same syntax as REAL constants, either with an explicit decimal point or in scientific notation.
On output, values will be printed in a fixed-width field in scientific notation . However, REAL
expressions in a WRITE statement can include a formatting directive to specify the number of digits
that should appear to the right of a decimal point, in addition to the directive setting the size of the
output field . For example, the statement
WRITELN(O.Ol5007, 1.23456789E3: 12:2)
will add to OUTPUT the string:
t1.5007000000000E-02 1234.S7t
The default field size and precision of the scientific format are defined for each Pascal machine; when
the size directives appear, the value is rounded to the number of fractional digits specified, and
placed right-justified in the field . Thus in the example above there are 5 blanks to the left of the
second number, which occupied only 7 places of the 12-place field when rounded to two fractional
digits.
Both the default output format and the one controlled by the programmer can be abused to
make it appear that results are more accurate than in fact they are . This topic is discussed in
Section 22.2.

22.1.3 Compound Interest and Simulation 595

c>.
~ --

'
~ .... '
'

. . ~ ..
22.1.3 Compound Interest and Simulation
The use of type REAL will be illustrated by a simple example and a more complex one .
If an amount of principal P is invested at interest rate r for T years, compounded m times
year, the balance accrued will be
P(l + r/m)mT.
Given input containing the principal, interest rate, number of compoundings per year, and years, t~
following program prints a table of balances at the end of each year .
PROGRAM Compoundinterest(INPUT,OUTPUT);
{Reads principal (real), interest rate (real),
number of compoundings (integer), and
years of investment (integer). Computes
balance at the end of each year.}
VAR
Balance, IRate: REAL;
Periods, Years, I: INTEGER;
BEGIN {Compoundinterest}
READ(Balance,IRate,Periods,Years);
WRITELN('Principal',Balance:11:2);
WRITELN('Rate ', 100*IRate:5:2, '%');
WRITELN('Compounded',Periods:4, 'times');
WRITELN;
WRITELN('Year New Balance');
FOR I := 1 TO Years
DO
BEGIN
Balance := Balance *
EXP(Periods*LN(1+IRate/Periods));
WRITELN (I: 2, ' ',Balance: 11: 2)
END
END. {Compoundinterest}
Execution
INPUT :10000.00 0.08 365 20
OUTPUT:Principal 10000.00
Rate 8.00"
Compounded 365 times

Year New Balance


1 10832.78
2 11734.90
3 12712.16
4 13770.79
5 14917.59
6 16159.89
7 17505.65
8 18963.48
9 20542.71
10 22253.46
11 24106.67
12 26114.22
13 28288.95
14 30644.78
15 33196.80

596 REAL NUMBERS


'16 35961 . 35
17 38956.13
18 42200.30
19 45714.64
20 4952 1 .64
For a more complicated example, Pascal can be used to perform simulated experiments on
models of systems. Systems are described by a time sequence of events that occur in them. For
"Xample, in a banking system, the events might be customers entering a service line and a teller
serving them from that line. To simulate such a system, program objects to represent the events are
reated and stored in a time-ordered sequence. From the sequence a history of the operation of the
system can be constructed. Events do not necessarily occur in the order in which they are created.
In the bank example, customer X might arrive at time t when there is no line, need 5 minutes of
service, and so depart at time t+S. Meanwhile customer Y might arrive at time t+2 needing 1
minute of service. But customer Y cannot be served before the teller has finished with customer X,
so Y departs at t+6. The order in which these events are created and occur is:

Creation Order Occurrence Order


event time event time
X arrives t X arrives t
X departs t+S Y arrives t+2
Y arrives t+2 X departs t+S
Y departs t+6 Y departs t+6

The second column of this table describes the system as it is simulated.


Simulations in which all the events are given are less interesting than those in which the events
are generated according to some model of the system. For example, the bank customers might arrive
at fixed intervals with a random fluctuation superimposed. The service times each customer requires
could be imagined as another quantity randomly distributed around some mean. These assumptions
about how customers operate are of course oversimplified, but from them the complete system
behavior can be derived. The point of simulation is to allow experimentation without incurring the
expense of using the real system. If the simulation does not behave as expected, its assumptions can
be changed. But no customers get angry when simulated lines are too long. When the events are
generated as a part of the simulation, the only input parameter is the length of time for which the
simulated system is run.
A program will now be designed to carry out the customer simulation described above.
Design Part 1
PROGRAM Simulate(INPUT,OUTPUT);
{Simulate the arrivals and departures of customers
in a line until a time read from INPUT has elapsed.}
TYPE
EventType = (Arrival, Departure);
{EventQueue};
VAR
Served , Waiting: INTEGER;
Time, MaxTime: REAL;

22.1.3 Compound Interest and Simulation 597


BEGIN {Simulate}
Time := 0.0;
READ(MaxTime);
Served := 0;
Waiting := 0;
{create first arrival and departure};
REPEAT
{process events in time-ordered sequence}
UNTIL Time >= MaxTime;
WRITELN('customers waiting ',Waiting:3);
WRITELN('customers served ',Served:3)
END. {Simulate}
The objects that represent events are records kept in a data abstraction called a priority
queue. This structure is like a queue (Section 13.4) except that it is ordered by information in
addition to arrival order (here the simulated time). When two events have the same arrival or
departure time, they go in the queue in the order they arrived; when an event has an earlier time, it
skips ahead to its time-ordered place. The operations on events are like those for queues, but
AddEvent adds an event to the event sequence so that the priority (time) order is preserved.
PrintEvents is not really an operation on events, but will be useful for program testing. The
essence of the simulation algorithm is the maintenance of a "system time" that ticks off not like a
clock, but skips from event to event as they occur.
Design Part 1.1
{TYPE EventQueue}
TYPE
EventQueue = fEventElt;
EventElt = RECORD
Next: EventQueue;
Time: REAL;
Kind: EventType;
Duration: REAL
END;
VAR
Events: EventQueue;
PROCEDURE InitEvent(VAR E: EventQueue);
{E := empty sequence}
PROCEDURE PrintEvents(VAR E: EventQueue);
{Display contents of E}
PROCEDURE RemoveEvent(VAR E: EventQueue;
VAR Kind: EventType;
VAR Length, Time: REAL);
{Remove the first event on E, return its Kind
and Length, and update the system time to the
Time of this event}
PROCEDURE AddEvent(VAR E: EventQueue;
Kind: EventType;
Start, Length: REAL);
{Add a new event to E so that E remains in
increasing time order. The new event occurs
at time Start and lasts for time Length.}
To start the simulation, the first customer's arrival and departure are scheduled.

598 REAL NUMBERS


Design Pa.rt 1.2
{ create fi rs t arr ival and depa"~~e
I ni tEvent(Even ts) ;
{generate ArrivalTime and Se_ ·.:.ce:::>e::...a •a_ e s .l;
freeTime : = Arriva lTine T" Se:-v.:.ce:Je::..ay;
AddEvent (Events, Arr iva , ..Ar::-.:.. a::._ .:.ce , S e rvi ceDe a y) ;
AddEvent(Events,Depart e , ~ ::- ee · me ,O.O);
This design part requires t he following decla ations to be a dded to Design Part 1:
VAR
ArrivalTime: REAL ; t "me of c u stomer arrival}
ServiceDelay: REAL ; {time of c u stomer service}
freeTime: REAL; {time o f c u stomer departure}
Customers are assumed to arrive a random int ervals with a given mean value . The function
Random creates a quasi-random sequence of integers Xo, X 1, X 2 , .. . in the range [0, U) by choosing X0
in this range, and computing
Xi+l = (MXi + C) MOD U,
where M and C are suitable const an t s. These integers are converted to the real range [0, 1) by
dividing by U.
Design Pa.rt 1.2.1
{generate ArrivalTime and ServiceDelay values}
ArrivalTime := ArrivalGap * Random;
ServiceDelay := ServiceTime * Random;
The constant declarations:
CONST
ArrivalGap = 10.0;
ServiceTime = 8.0;
should be added to Design Part 1.
Design Pa.rt 1.2.1.1
FUNCTION Random : REAL;
CONST
Modulus = 8192;
Multiplier = 3125;
BEGIN {Random}
Seed := (Multiplier * Seed) MOD Modulus ;
Random := Seed / Modulus
END; {Random}
Seed must be declared in Design Part 1, and it must be given an initial value, say:
Seed := 17;
During the simulation, events are removed from the time-ordered queue of events and
processed. New events are generated each time an arrival is processed .

22.1.3 Compound Interest and Simulation 599


Design Part 1.3
{process events in time-ordered sequence}
RemoveEvent(Events,KindOfEvent,ServiceDelay,Time);
CASE KindOfEvent OF
Arrival:
BEGIN
WRITELN('customer arrives at ',Time:5:2,
' for ',ServiceDelay:5:2);
Waiting := Waiting + 1;
{generate next events and add them to the queue}
END;
Departure:
BEGIN
WRITELN('customer leaves at time ',Time:5:2);
Waiting := Waiting - 1;
Served := Served + 1
END
END
The declaration
VAR
KindOfEvent: EventType;
must be added as usual.
Each time a customer arrives, the corresponding departure is scheduled. If the teller is busy
(FreeTime > Arrival Time), the customer's departure must be delayed until after the teller is
free. However, if FreeTime <= Arrival Time, the teller is idle and can serve the customer
immediately.
Design Part 1.3.1
{generate next events and add them to the queue}
ArrivalTime := Time + ArrivalGap * Random;
ServiceDelay := ServiceTime * Random;
IF FreeTime > ArrivalTime
THEN {teller busy, schedule delayed service}
FreeTime := FreeTime + ServiceDelay
ELSE {teller free, schedule immediate service}
FreeTime := ArrivalTime + ServiceDelay;
AddEvent(Events,Arrival,ArrivalTime,ServiceDelay);
AddEvent(Events,Departure,FreeTime,O.O)
Piecing these design parts together gives:

600 REAL NUMBERS


Design Part 1
PROGRAM Simulate(I NP
{Simulate the arr iva:s ~~~ ~e~a"~~es of c u stomers
in a line until a ~e h as elapsed.}
CONST
ArrivalGap = 10. ;
ServiceTime = 8. ;
TYPE
EventType = (Arr" v a ::. , :::Je_ar._ ·.,....e) ;
{EventQueue} ;
VAR
Served, Waiti ng: : l~G-~ ;
Time, MaxTime: ~ ;
ArrivalTime : REAL; { ~~e of c u stomer arrival}
ServiceDelay : REAL ; {ti ~e of c u stomer service}
FreeTime: REAL ; {t"me of c u stomer departure}
Seed: INTEGER ;
KindOfEvent: Ev e ntType ;
FUNCTION Random : REAL;
CONST
Modulus = 8192 ;
Multiplier = 3125;
BEGIN {Random}
Seed := (Multiplier * Seed) MOD Modulus;
Random := Seed / Modulus
END; {Random}
BEGIN {Simulate}
Time := 0.0;
READ(MaxTime);
Served := 0;
Waiting := 0;
Seed : = 17;
{create first arrival and departure}
InitEvent(Events);
{generate ArrivalTime and ServiceDelay values}
ArrivalTime := Arr i valGap * Random;
ServiceDelay := ServiceTime * Random;
FreeTime := ArrivalTime + ServiceDelay;
AddEvent(Events, Arrival,ArrivalTime,ServiceDelay);
AddEvent(Events,Departure,FreeTime,O.O);
REPEAT
{process events in time-ordered sequence}
RemoveEvent(Events,KindOfEvent,ServiceDelay,Time);
CASE KindOfEvent OF
Arrival:
BEGIN
WRITELN('customer arrives at ',Time:5:2,
' for ',ServiceDelay:5:2);
Waiting := Waiting + 1;
{generate next events and add them to the queue}
ArrivalTime : = Time + ArrivalGap * Random;
ServiceDelay : = ServiceTime * Random;
IF FreeTime > ArrivalTime
THEN {teller busy, schedule delayed service}

22.1.3 Compound Interest and Simulation 601


FreeTime := FreeTime + ServiceDelay
ELSE {teller free, schedule immediate service}
FreeTime := ArrivalTime + ServiceDelay;
AddEvent(Events,Arrival,ArrivalTime,ServiceDelay);
AddEvent(Events,Departure,FreeTime,O.O)
END;
Departure:
BEGIN
WRITELN('customer leaves at time ',Time:5:2);
Waiting := Waiting - 1;
Served := Served + 1
END
END
UNTIL Time >= MaxTime;
WRITELN('customers waiting ',Waiting:3);
WRITELN('customers served ',Served:3)
END. {Simulate}
The procedures that implement the operation for a priority event queue construct the queue a.=
a singly-linked list in time order by arrival.
Design Part 1.1.1
PROCEDURE InitEvent(VAR E: EventQueue);
{E := empty sequence}
BEGIN {InitEvent}
E := NIL
END; {InitEvent}
Design Part 1.1.2
PROCEDURE PrintEvents(VAR E: Ev entQueue);
{Display contents of E}
VAR
TPtr: EventQueue;
BEGIN {PrintEvents}
TPtr := E;
WRITELN;WRITELN;
WHILE TPtr <> NIL
DO
BEGIN
WRITE(TPtrt.Time:8:3);
IF TPtrt.Kind =Arrival
THEN
WRITELN(' customer arrives for',
TPtrt.Duration:5:2)
ELSE
WRITELN(' teller free');
TPtr := TPtrt.Next
END;
WRITELN;WRITELN
END; {PrintEvents}

602 REAL NUMBERS


Design Part 1.1.3
PROCEDURE RemoveEvent ( -- = -e.:-.~ e e;
- - < -:c : ~ en~':'ype ;
::ze..-:g-.....__, _i=.e: REAL) ;
{Remove the firs t ev~­ ~ ~ ~ retur its Kind
and Length, and upda~e ~e syste= time to the
Time of this e v e nt}
BEGIN {RemoveEvent}
IF E <> NIL
THEN
BEGIN
Time : = Et. Tine;
Kind := Et. Kind;
IF Kind = Arriva
THEN
Length := Et.Duration;
E := Et .Next
END
END; {RemoveEvent}
Design Part 1.1.4
PROCEDURE AddEvent(VAR E: EventQueue;
Kind: EventType;
Start, Length: REAL);
{Add a new event to E so that E remains in
increasing time order. The new event occurs
at time Start and lasts for time Length.}
VAR
EPtr, LPtr, CPtr: EventQueue;
Found: BOOLEAN;
BEGIN {AddEvent}
{create new event referenced by EPtr};
IF E = NIL
THEN {empty sequence}
{insert first event}
ELSE {sequence with one or more events}
BEGIN
{LPtr=previous event; CPtr=following event}
IF LPtr = NIL
THEN {insert as new first event}
E := EPtr
ELSE {insert as future event}
LPtrt.Next := EPtr;
EPtrt.Next := CPtr
END
END; {AddEvent}
Although the records created are all the same size, only those representing customer arrivals have
values in the Duration field .

22.1.3 Compound Interest and Simulation 603


Design Part 1.1.4.1
{create new event referenced by EPtr}
NEW(EPtr);
IF Kind = Arrival
THEN
EPtrf.Duration : =Length ;
EPtrf.Kind := Kind;
EPtrf.Time := Start;
Design Part 1.1.4.2
{insert first event}
BEGIN
E := EPtr;
Ef.Next :=NIL
END
LPtr and CPtr are calculated so that CPtr points to each event in turn, and LPtr to the
previous event. When the correct position is found, the event is linked into the priority queue.
Design Part 1.1.4.3
{LPtr=previous event; CPtr=following event}
LPtr : = NIL;
CPtr : = E;
Found := FALSE;
WHILE (CPtr <> NIL) AND NOT Found
DO
BEGIN
IF CPtrf.Time > Start
THEN
Found := TRUE
ELSE
BEGIN
LPtr := CPtr;
CPtr := CPtrf.Next
END
END;
A simulation for 25 time units is shown below; in this time 7 customers arrive and 6 leave.
Execution
INPUT :25.0
OUTPUT:customer arrives at 4.85 for 4.63
customer arrives at 5.59 for 6.31
customer arrives at 7.61 for 4.56
customer leaves at time 9.48
customer arrives at 13.17 for 2.86
customer leaves at time 15.80
customer arrives at 16.42 for 0.73
customer arrives at 18.37 for 1.66
customer leaves at time 20.35
customer arrives at 21.91 for 5.15
customer leaves at time 23.21
customer leaves at time 23.94
customer leaves at time 25.60
customers waiting 1
customers served 6

604 REAL NUMBERS


22.1.4 Exercises
22.1.1 In the following P ascal _ 'ch sta. ements are syntactically correct? What is the
type of each expression in eac
VAR
Rl, R2: REAL;
Il, I2: INTEGER;
BEGIN

Il : = Rl;
R2 : = I 2;
WRITELN(Il- TRUNC (Rl *I 2 )) ;
WRITELN(Il < Rl);
WRITELN(Il + Rl < Rl ) ;
WRITELN(Il + Rl - I2 );
WRITELN(Il / Rl + I2 )
END

22.1.2 Explain the rationale that lies behind the Pascal restriction that the controlled variable of a
FOR statement may not be REAL.
22.1.3 Investigate what your Pascal machine does for REAL input/output, in particular:
a) What is the default format and field width for REAL output?
b) Does the problem of end of file with INTEGER input (discussed in Section 14.3.2) exist for
REAL input as well?
c) What happens on output if the field width is inadequate?
d) Are leading or trailing decimal points allowed on input?
e) What happens on output if the number of digits after the decimal point is specified to be more
than the actual precision available?
f) Is it possible to read in a REAL value, then write it, so that the result appears different than
what was in the input?
22.1.4 Write a procedure taking one REAL parameter that prints this value in "dollars and cents
format"; that is, with a leading $ at the left (no spaces offset), and the number divided into groups
of three digits separated by commas, and rounded to two digits following the decimal point. For
example, 1.234566666E5 should appear as $123,456. 67 .
22.1.5 Design a module that implements the data abstraction Reali in which a real number is
represented using type INTEGER.
a) Represent the real number by two INTEGER values, one for its mantissa and one for its
exponent. Implement the operations of input conversion (from two INTEGER parameters),
multiplication, addition, and output conversion (print directly in scientific notation). Arrange
to give correct results so long as it is possible to keep both values within the bounds of
MAXINT, represented in the program by a constant declaration.
b) Repeat a), but for a representation in which the two values are the part of the real number
before and after the decimal point.
22.1.6 Change the representation of type Rational in Section 18.2.1 from two INTEGER values
to a single REAL value, and make the necessary changes in the operation implementations. Explain
the changes that will be required in HarmonicSer ies (Section 18.2.3) because of the new
representation.
22.1.7 Design a module to implement type Complex. A complex number z = a + ib where i =
Y(-1) should be represented by REAL components a and b. Implement the operations of addition
and multiplication for Complex. Devise an appropriate input/output format and write a program
using your module which reads two complex numbers and prints their sum and product.

22.1.4 Exercises 605


22.1.8 Check the formula for compound interest (Section 22.1.3) by solving the same problem using
the brute-force approach. That is, at each compounding time, calculate the interest and add it to
the principal. Compare the brute-force and formula results for the case of $1 invested at 5% for 1
year, compounded hourly.
22.1.9 Using the formula for compound interest in Section 22.1.3, write a program that calculates
the necessary interest rate to the nearest %%, compounded daily, to realize a given balance starting
from a given principal over a given number of years. That is, take the starting principal, number of
years, and ending balance as input, and find the needed rate.
a) Use binary search to solve the problem by trial and error.
b) Solve the formula for r and evaluate it .
c) Compare the speed and ease of obtaining the programs in a) and b).
22.1.10 Modify the simulation program of Section 22.1.3 so that
a) in the output the customers are identified by a number assigned on arrival: 1,2,3,... Explain
why it is not necessary to store this number in the event records, but possible to instead
calculate it when the printing is done . (But do the implementation any way you like.)
b) the simulation completes a number of customers taken from the input, instead of a maximum
time.
c) at the end of the simulation, information is given about how well the system worked, in
particular the maximum length of the line waiting for the teller, the longest wait in line by any
customer, the number of customers served without waiting, and the total time when the teller
was idle. If necessary, adjust the parameters of the simulation so that interesting values arise
for each quantity.
d) in addition to the assumptions about customer arrival and service needs already implemented ,
there are two "bursts" of activity, one at the middle of the simulated time, and the other at
the end. At a burst time, N customers arr ive all together, where N is half the number whose
service has been completed up to that time.

22.2 Roundoff Errors

Preview

The REAL data type is usually implemented using special hardware in a Pascal machine,
hardware in which both mantissa and exponent are limited in size, and so subject to overflow .
Conversion between number bases for input/output can introduce additional errors.

Scientific calculations usually make use of real numbers, and the speed with which the
necessary arithmetic can be executed may be the limiting factor in deciding whether or not a
problem can be solved in a reasonable time . (Speeds are measured in "megaflops"-millions of
floating-point operations per second; 10 megaflops is a fast machine .) However, the payment for
speed is a potential for error: the way REAL quantities are stored for fast arithmetic limits both
mantissa and exponent in size. This means that the range of real values is limited, and so is the
precision. When overflow occurs in the range, t he situation is much like that for type INTEGER.
The range of REAL values is greater, but that r ange can be exceeded, particularly by repeated
multiplication or division . The difficulty with precision is more subtle, and falls under the heading of
roundoff error.
Because the mantissa is limited, only a certain number of digits (thinking in base ten) can be
stored. If a value requires more digits than there are available, it cannot be exactly represented and
must be "rounded off" to the nearest value that is available. The most important cause of roundoff
error is the natural loss of precision in intermediate computations. For example, consider the sum :

606 REAL NUMBERS


0.37X103 + 0.25X102 = 0.37X1 - 0.025X103 = {0.37 +0.025)X103 = 0.395X103
If the calculation must be ca r ried · base t en wit h two-digit accuracy, the final value cannot be
represented, so it is rounded to 0.40X103. In he example, the two-digit precision is retained despite
roundoff; however, in a calcula tion like:
0.37X103 + 0.25X102 - 0.15Xl02
the rounded result should be 0.38X103, but rounding each calculation from left to right yields
0.39X103,. a real loss of precision.
Roundoff errors also occur in conver ing from one number base to another. This is a source of
trouble in Pascal because REAL values are expressed in base ten in program constants and input
values, but must often be converted to a base that is a power of two for fast use in the Pascal
machine. A number represen table with limited precision in one base may fail to have a limited
precision representation in another base. For example, the number 0.37X101 (base ten) has no exact
counterpart in base two. Suppose it were to be converted to base two with a mantissa limited to six
binary places. The following table will be helpful:

Equivalent Values
base two base ten
.1 .5
.01 .25
.001 .125
.0001 .0625
.00001 .03125
.000001 .015625

Conversion of the fractional part proceeds by repeatedly finding the largest value in the base-ten
column and subtracting it from the remainder to be converted, at the same time adding the base-two
equivalent to the result .

3.7to = 112 + 0.7to


=(11 2 + 0.1 2) + (0.7 10 - 0.5 10)
= 11.1 2 + 0.2 10
= 11.1 2 + 0.001 2 + (0.2 10 - 0.125 10)
= 11.1012 + 0.07510
= 11.1012 + 0.00012 + {0.07510- 0.062510)
= 11.10112 + 0.012510

Rounded to six "bigets" {usually called bits) the result is 11.1011 = 0.111011X22. Errors of this kind
are called representational errors.
The approximate nature of REAL computations implies that programs should not test REAL
values for equality. The following Pascal fragment may never terminate because successive additions
of 1/10 to 0.0 may never yield the exact value 1.0.
RValue := 0.0;
WHILE RValue <> 1.0 DO
BEGIN
... {loop body}
RValue : = RValue + 1 / 10
END
It is much better to control the loop with the test:
RValue < 1 . 0
The usual course of scientific computation is to read input data (and express program
constants) in base ten, convert to the internal base of the Pascal machine, carry out many
arithmetic operations, then convert the result back to base ten for output. Each conversion and

22.2 Roundoff Errors 607


arithmetic operation carries the potential for roundoff error. Base-conversion roundoff occurs onl.
once for each value, so its worst case can be found quite easily- no more than the final digit is
usually affected. (However, the appearence of .99999999 for 1.0000000 is annoying.) Arithmetic
operations can be performed repeatedly, so there is the potential for seriously compromising the
validity of the calculation through operation roundoff. The roundoff in each operation is limited, bu
the cumulative effect of many operations on the same variable can become large- in fact, larger
than the final value of the variable itself. Such accumulated roundoff's can not only lead to large
final errors in data, but can also affect the conditional and iteration tests of a program, leading to
unexpected execution sequences.
In analyzing the roundoff errors in a computation, the relative errors (that is, the ratio of error
to exact value) in each variable are the most interesting. Consider evaluating the polynomial
0.16x3 + 0.86x2- 2.9x + 7.4
at x 2.3 in base-ten arithmetic rounded to two significant decimal digits, using Horner's rule:
(((((0.16X2.3) + 0.86)X2.3)- 2.9)X2.3) + 7.4
The operations and roundoff errors at each step are shown below.

Arithmetic Rounded Actual Computed Local


step operation value value error
1 0.16X2.3 0.368 0.37 0.002
2 0.37+0.86 1.23 1.2 -0.03
3 1.2X2.3 2.76 2.8 0.04
4 2.8-2.9 -0.1 -0.10 0.00
5 -0.10X2.3 -0.23 -0.23 0.00
6 -0.23+7.4 7.17 7.2 0.03

The Actual value column is the result of a perfect calculation, but using the rounded value
(Computed value column) from the previous step. The last column (Local error) is the difference
between the actual and rounded result for each step. However, the complete story is not told in this
table, because it takes no account of the accumulation of errors throughout the whole computation.
Imagine that the "perfect" results of each operation (signified by a symbol within a circle) were
available. Then the following table could be constructed:

Arithmetic Perfect Perfect Total Relative


step operation value error error
1 0.16 ® 2.3 0.368 0.002 0.00540 ...
2 o.368 EB o.86 1.228 -0.028 -0.0233 ...
3 1.228 ® 2.3 2.8244 -0.0244 -0.00871 ...
4 2.8244 8 2.9 -0.0756 -0.0244 0.244
5 -0.0756 ® 2.3 -0.17388 -0.5612 0.286
6 -0.17388 EB 7.4 7.22612 -0.02612 -0.00362 ...

The last two columns are the difference between the composite rounded result (Computed value in
the previous table) and the perfect value, expressed as a magnitude (Total error) and as a fraction of
the perfect value (Relative error).
Errors accumulate in complex and unexpected ways even in this simple calculation. In step 4,
the relative error jumps from less than 1% to more than 24%. The local error in step 4 is 0, but a
previously accumulated error is amplified because the difference of two nearly equal numbers is
computed. Steps 4 and 5 have large relative errors, but the final result of step 6 does not. This is
the happy result of the dominance of the perfect value 7.4 in the computation.

608 REAL NUMBERS


22.2.1 Roundoff Notation
In order to study the effects of roundoff more systematica lly, the following notation is adopted for an
operation at step i, using the results of prerious steps j and k. The rounded operation is represented
by +, while EB is the corresponding perfec opera ion.

Rounded Compn ed Local Perfect Perfect Total


Step operation value error operation value error
qr+-<Ik W·I r·I
The definitions of these quantit ies are:
(I) qi = qj + qk
(2) ri = rj EB rk
(3) wi = qj + qk - (qj EB qk)
(4) ei =qi- ri
This notation can be applied to the polynomial evaluation above as follows. Negative indices
will identify initial data (for which qi and ri are the same), and positive indices will be used for
intermediate and final values. The initial data consist of the values:
0.16, 0.86, -2 .9, 7.4, 2.3
which are arbitrarily called q_ 1 through q_ 5 .

Initial Data
1 -1 -2 -3 -4 -5
qi 0.16 0.86 -2.9 7.4 2.3

The six values computed in order are:

Computed Data
step i 1 2 3 4 5 6
value qi 0.37 1.2 2.8 -0.10 -0.23 7.2

Furthermore, the six steps had operation and operand indices given by the table below.

Operation and 0 perand Indices


stepi 1 2 3 4 5 6
operand j -1 1 2 3 4 5
operation X + X + X +
operand k -5 -2 -5 -3 -5 -4

These tables conveniently record all the information needed to analyze roundoff error.

22.2.2 An Analysis of Roundoff Error


The definitions (1) - (4) above can be solved for the accumulated error ei, which will then be
expressed in terms of local errors and intermediate values. To be useful, any error analysis must be
given in terms of observable values. If the perfect calculation were available, the error could be
calculated directly, but in that case there would be no reason to do so--the perfect value could be
used and the error made ze ro! Since perfect values are not available, the quantities that should
occur in formulas are computed values qi. The local error wi is not observable, but since it results
from a single roundoff its maximum size can be found. Starting with definition (4):

22.2.2 An Analysis ·of Roundoff Error 609


ei = qi- ri
= qj + qk- (rj EB rk) (using (1) and (2))
= qj EB qk + wi- (rj EB rk) (using (3))
= qj EB qk- ( (qrej) EB (qk-ek)) + wi (using (4)).
That is, the accumulated error ei in a new computed value qi is expressed in terms of the old
observable values qj and qk used in computing it, their errors ej and ek, and the local error wi . In the
example above, substituting the computed values qi yields:
e1 = (0.16@2.3)- (0.16@2.3) + w 1 = w 1
e2 = (0.37@.86)- ((0.37-e 1)E£0.86) + w 2 = e 1 + w 2
e3 = (1.2@2.3) - ((1.2-e 2)@2.3) + w3 = 2.3e 2 + w 3
e4 = (2 .882.9)- ((2.8-e 3 )82.9) + w4 = e3 + w4
e5 = (-0.10@2 .3)- ((-0.10-e 4)@2 .3) + w 5 = 2.3e 4 + w 5
e6 = (-0.23ffi7.4)- ((-0.23-e5)ffi7.4) + w 6 = e 5 + w 6
This system of equations can be solved for ei by substitution:
el = wl
e2 = wl + w2
e3 = 2.3w 1 + 2.3w 2 + w3
e4 = 2.3w 1 + 2.3w2 + w3 + w4
e5 = 2.3 2w 1 + 2.3 2w 2 + 2.3w3 + 2.3w 4 + w 5
e6 = 2.3 2w 1 + 2.3 2w 2 + 2.3w 3 + 2.3w 4 + w 5 + w 6
Equations expressing the accumulated errors in terms of the local errors may be found in this way
for any calculation, but in general the form is more complex, involving ratios of multinomials in the
local errors and computed values.
To obtain the value e 6 requires only estimating the local errors. Each is the result of a
roundoff to two decimal digits. The worst case occurs when all of the local errors take the largest
possible value, and all are in the same direction. For example, if every one of the computed values
had a third significant figure of 5 it would be rounded up, and no local errors would cancel. The
largest possible rounding error depends on the exponent of the value being rounded- it is the value of
a 5 in the third significant figure. Thus for the polynomial evaluation:

Local Error Ma~nitudes


l 1 2 3 4 5 6
qi 0.37 1.2 2.8 -0.10 -0.23 7.2
maximum wi 0.005 0.05 0.05 0.005 0.005 0.05

The magnitude of the final error is therefore:


:e 6 : < 2.3 2(0.055) + 2.3(0.055) + 0.055 = 0.472 .. .
This maximum possible error is much larger than that observed.

22.2.3 Exercises
22.2.1 Find a number that can be exactly represented in base ten with two-digit precision, but when
converted into base two with four-bit precision, then converted back to base ten with two-digit
precision, is not the original value . Explain why this sort of thing does not happen in Pascal input-
output conversion.
22.2.2 Using real values with rounding to two decimal digits, construct examples to demonstrate
that the usual associative and distributive laws of arithmetic do not hold. What lesson can be
drawn from these examples by t he Pascal scient ific programmer?

610 REAL NUMBERS


22.2.3 Calculate the area of a. rec angle of width 5 and height 2 by dividing the width into 2k
pieces, k = 0, 1, 2, ... , and adding the area of com ponent rectangles of height 2 based on each piece.
Continue until a large enough value of k shows the effect of substantial roundoff error. What lesson
can be drawn from this example by he Pascal programmer doing numerical integration?
22.2.4 Consider the solutions
-b + Yb 2 - 4ac - b - Yb 2 - 4ac
2a 2a
to the quadratic equation ax2 + bx + c = d.
a) The Pascal fragment:
Rt := SQRT(B*B- 4*A*C);
Den := 2*A;
Xl := (Rt - B)/Den;
X2 : = -(B + Rt)/Den
can be used to obtain these roots. If b is a large negative number and a and c are small positive
numbers, problems can arise. Find problem values of a, b, and c.
b) The value of x 2 can be computed from x 1 as follows:

X1 X X2 = -b+Vb 2 -4ac X _;;,_,


-b-Yb 2 -4ac
_ ___;;;,_,_..=,.:;_
2a 2,..a_ __
_ (-b +Y b2 - 4ac) (-b - v'/}2 - 4ac)

b2 - b 2 +4ac 4ac c
4a 2 = 4a 2 =-;
Thus, after computing x 1 with the Pascal fragment
Rt := SQRT(B*B- 4*A*C);
Den := 2*A;
IF B < 0
THEN
Xl .- (Rt - B)/Den
ELSE
Xl := -(B + Rt)/Den
x 2 can b~ computed with:
X2 : = C/(Xl*A)
Check to see if the problem that arose in (a) persists in this method, and explain.
22.2.5 Consider polynomial evaluation by a different method than Horner's rule. Compute the
needed powers of the variable x by repeated multiplication, then multiply each by its coefficient, and
finally add the terms beginning with the highest-power term and continuing down to the constant
term.
a) Obtain the formula for the final accumulated error when the polynomial
0.16x3 + 0.86x2- 2.9x + 7.4
from Section 22.2 is evaluated in this way.
b) Compare the results with the Horner-rule analysis of Section 22.2.2.
22.2.6 Prove that when the polynomial
Cnxn + Cn_ 1xn- l + ... + C 1 x + C 0
is evaluated using Horner's rule the accumulated roundoff error is

22.2.3 Exercises 611


xn·l(wl+w2) + xn·2(w3+w4) + ... + (w2n-l+w2n)
where w 11 w 2, ... w2n are the local errors in the 2n multiply /add operations required. Note that the
result is independent of the intermediate computed values and the coefficient values!

22.3 Chapter Summary


The data type REAL comprises a wider class of values than INTEGER, both in magnitude and by
the inclusion of a fractional part. However, REAL values only approximate real numbers, and REAL
operations only approximate real arithmetic. It is difficult to estimate the size of the errors that
occur when the practical limitations of the Pascal machine come into play, but without an analysis
of possible errors the results of calculations can be arbitrarily wrong.

612 REAL NUMBERS


Programs change because . solve change, but not radically enough to
justify writing a new prog:ra ~· g changes, and by the use of techniques to localize
and protect information and e programmer can make it significantly easier to
modify a program.

Programming with da. a types makes changing programs easter because


implementation-dependen informa. ion alxm objects is centralized in type definitions and operation
implementations rather than sea. ered hrough several users ' programs. This chapter examines the
use of constant and type declarations in making programs easier to modify. New language features,
procedure/function parameters and variant records, are introd uced to aid in writing software fitting
many applications withou t requiring difficult alterations.

23.1 CONST and TYPE Declarations

Preview

Centralizing information in CONST and TYPE declarations is a way to allow easy program
change, particularly when these declarations are used to control module implementations.
However, it is all too easy to build unwarranted assumptions into a program so that extensive,
difficult changes are required.

In Section 19.2.2, a Stack was represented using an array and an index to its top element.
The declarations hiding the size and representation were:
CONST
Depth = 20;
TYPE
EltType = CHAR;
Stack RECORD
Val: ARRAY [! .. Depth] OF EltType;
StackTop: O .• Depth
END;
Using a constant declaration for Depth keeps the INTEGER 20 out of the further declarations
and out of the Push and Full operations. A single change can therefore expand the Stack size,
and it is very likely that the changed module will perform correctly if the original one did. Similarly,
by making E 1 tType a TYPE instead of spreading CHAR throughout the module , it is easy to create
(say) a Stack of SET values in place of characters. The changes that are thus enabled are far-
reaching. For example, to stack up to 2500 personnel records, the only changes required are:

23.1 CONST and TYPE Declarations 613

~--- 01 - --

-:
CONST
Depth = 2500;
TYPE
Month= (NoMonth , Jan , Feb, Mar, Apr, May, Jun,
Jul, Aug , Sep , Oct , Nov , Dec);
Date = RECORD
Mo: Month;
Day: 1 .. 31;
Year: I NTEGER
END;
PRecord = RE CORD
• • • I

Bir thDay: Dat e;

END;
EltType = PRecord;
Not every possible change can be made in this way. For example, the array subscripts canna-
be changed to CHAR values by setting
Depth = 'Z';
because Depth is used in ranges beginning with an INTEGER constant, and the operations use
INTEGER operations to manipulate the subscripts. Nor can a stack of files be created by
EltType = TEXT;
because assignments are used to move the Stack elements, and files may not be -assigned. It is
some consolation that an attempt to make these changes would result in syntactically incorrec
Pascal programs, so at least the programmer could not be misled into believing the change was
accomplished when it was not. When changes must be made in information that is distributed, it is
all too common to think a change is accomplished, and receive no syntactic clues that something has
been missed.
The Stack representation as an array and a pointer is fundamental to the module
implementation, yet it could require changing. Suppose that in an application where use of computer
memory is very expensive, stacks are required to grow large and shrink again. It might be discovered
that allocating an array large enough for the maximum size is too wasteful. Instead, the
representation should be a singly linked list of the stacked values in order from top to bottom.
Then, for example, a Stack of CHAR with B on the top and X on the bottom would appear as:

=rHr "J' X

rc
and pushing C would alter the structure to:

B .... "J' X

The text of a complete program utilizing Stack appears below. The module applicat ion Rever se
is unchanged bec a use the module hides the form of Stack, but the module implementat ion itself
had t o be completely rewritten.

614 PLANNING FOR CHANGE


PROGRAM Reverse(INPUT,OUTPUT);
{reverse the characters i n INP ::' a=: vr:.. ~ ~e::.
to OUTPUT}
TYPE
EltType = CHAR;
Stack = fStackRep;
StackRep = RECORD
Val: E tType;
Next: Stack
END;
VAR
S: Stack;
Elt: EltType;
PROCEDURE NewStack (VAR S: Stack);
{S := <>}
BEGIN {InitStack}
S : = NIL
END; {InitStack}
PROCEDURE Push (VAR S: Stack ; E: EltType);
{S := S & <E> }
VAR
Temp: Stack;
BEGIN {Push}
Temp : = S;
NEW(S);
Sj.Val := E;
Sj.Next : = Temp
END; {Push}
PROCEDURE Pop(VAR S: Stack);
{there are Stack X, EltType E such that
S = X & <E> --> S : = X }
VAR
Temp: Stack;
BEGIN {Pop}
IF S <> NIL
THEN {not already empty}
BEGI N
Temp : = S;
S := Sf .Next;
DISPOSE(Temp )
END
END; {Pop}
FUNCTION Top(VAR S: Stack): EltType;
{there are Stack X, EltType E such that
S = X & <E> --> Top := E }
BEGIN {Top}
IF S = NIL
THEN
WRITELN('** READING EMPTY STACK**')
ELSE
Top := Sf.Val
END; {Top}

23.1 CONST and TYPE Declarations 615


FUNCTION Empty(VAR S: Stack): BOOLEAN;
{Empty := (S = <>)}
BEGIN {Empty}
Empty := S = NIL
END; {Empty}

FUNCTION Full(VAR S: Stack): BOOLEAN;


{Full := (Length(S) =Depth)}
BEGIN {Full}
Full := FALSE
END; {Full}
BEGIN {Reverse}
NewStack(S);
WHILE NOT EOF AND NOT Full(S)
DO
BEGIN
READ(Elt);
Push(S,Elt)
END;
WHILE NOT Empty(S)
DO
BEGIN
WRITE(Top(S));
Pop (S)
END;
WRITELN
END. {Reverse}

23.1.1 Exercises
23.1.1 Describe in detail the changes that would be required to use the Stack module of Section
19.2.2 for a Stack of
a) REAL values.
b) Month values.
c) SET OF CHAR values.
d) TEXT values.
e) Stack of CHAR values.

23.2 PROCEDURE and FUNCTION Parameters

Preview

Parameters permit the actions of a procedure to be tailored to different applications on different


calls. A surprising amount of power is added to this mechanism by allowing the parameter to
itself be a procedure or function.

Procedures and functions can be passed as parameters. In order to indicate that a formal
parameter is to be a procedure or a function, the name of the formal is prefixed with either the
reserved word PROCEDURE or FUNCTION. The number and type for each parameter and the result
type of a function parameter must be specified. The procedure Invoke shown below has a
procedure parameter ProcQ and a function parameter ProcR that itself has no parameters but
returns an INTEGER . The body of Invoke contains procedure statements that actually call the

616 PLANNING FOR CHANGE


parameter routines:
PROCEDURE Invoke(PROCEDURE ~- ---
- -
--~ ~ ;
FUNCTION Pr --- ;
VAR
IVar: INTEGER;
BEGIN {Invoke}
ProcQ(IVar);
IVar := ProcR + IVar
END {Invoke}
If Invoke is called in a. fra.g:men e g:
PROCEDURE Invoke (PROC'".c::>U3.3 ProcQ (Z: INTEGER) ;
FUNC':' • ?rocR: INTEGER) ;
... ,
PROCEDURE Oneint (FParn: INTEGER);
... ,
FUNCTION FuncF: INTEGER:

BEGIN

Invoke(Oneint, FuncF);

END
then Oneint and FuncF are not called as a part of the call on Invoke. Instead, these routines
are made available so that they are called when the statements within Invoke use the parameter
names. The procedure statement ProcQ (IVal) in Invoke actually calls Oneint and the
function call ProcR actually calls FuncF.

23.2.1 Syntax for Parameters


The following syntax rules describe formal procedure and function parameters.
<formal parameter>::= <value parameter>
I <variable parameter>
: <procedural parameter>
l <functional parameter>
<procedural parameter> ::=<procedure heading>
<functional parameter> ::= <function heading>
<actual parameter>::= <expression> l <procedure identifier>
l <function identifier>
The procedure or function passed as actual parameter must match the formal parameter exactly, as
illustrated by the following:

23.2.1 Syntax for Parameters 617


PROGRAM Main (OUTPUT);
PROCEDURE TakeProc(PROCEDURE Proc(X: INTEGER));
BEGIN {TakeProc}
Proc (7)
END; {TakeProc}
PROCEDURE Procl(Fl, F2: INTEGER);
BEGIN {Procl}
WRITELN(Fl+F2)
END; {Procl}
PROCEDURE Proc2(VAR Fl: INTEGER);
BEGIN {Proc2}
WRITELN(Fl);
END; {Proc2}
PROCEDURE Proc3(Fl: INTEGER);
BEGIN {Proc3}
WRITELN(Fl)
END; {Proc3}
BEGIN {Main}
TakeProc(Procl); {wrong number of parameters}
TakeProc(Proc2); {wrong kind of parameters}
TakeProc(Proc3) {OK}
END. {Main}
The first procedure statement is incorrect because TakeProc requires a parameter that is a
procedure with a single parameter while Procl has two parameters. The second procedure
statement is also incorrect because although Proc2 has one parameter, it is a variable one and
TakeProc requires it to be a value one.

23.2.2 Point of Evaluation


A procedure cannot appear as an actual parameter in a call unless the corresponding formal
parameter is declared PROCEDURE. The natural way to think about such a parameter is the
correct way: that its name is being passed for later invocation. However, function names may
appear as parameters in two ways: the function may be an expression being passed to a value
parameter, or it may be a name passed to a FUNCTION parameter. There is a crucial difference
between these two meanings, but their syntactic form can be the same. When a function identifier is
bound to a value parameter in a procedure call, the function is called, the value returned by the
function is bound to the value parameter, and the procedure is invoked. When a function identifier
is bound to a FUNCTION parameter in a procedure call, no call of the parameter function takes
place. Instead, the parameter name is supplied so that the procedure may later call it. The
program Passing illustrates the difference between these two types of binding.

618 PLANNING FOR CHANGE


PROGRAM Passing(OUTPUT);
PROCEDURE Takeinteger(IVar : I NTEGER) ;
{Parameter is value}
BEGIN {Takeinteger}
WRITELN('enter Takeinteger ');
WRITELN (IVar) ;
WRITELN ('leave Takel nteger ')
END; {Takeinteger}
PROCEDURE TakeFunction (FUNCTION IFunc: INTEGER);
{Parameter is FUNCTION}
BEGIN {TakeFunction}
WRITELN('enter TakeFunction');
WRITELN(IFunc);
WRITELN('leave TakeFunction')
END; {TakeFunction}
FUNCTION Zero: INTEGER;
BEGIN {Zero}
WRITELN('enter Zero');
Zero := 0;
WRITELN('leave Zero')
END; {Zero}
BEGIN {Passing}
Takeinteger(Zero);
TakeFunction(Zero)
END {Passing}.
Execution
OUTPUT:enter Zero
leave Zero
enter Takeinteger
0
leave Takeinteger
enter TakeFunction
enter Zero
leave Zero
0
leave TakeFunction
Zero is entered and returns before Takeinteger is entered because Zero provides an integer
value for IVar. However, TakeFunction is entered before Zero is called. It is not until
WRITELN (IFunc) is executed within TakeFunction that Zero is entered. The WRITE
statements within Takeinteger and TakeFunction have exactly the same form; to discover the
meaning one must look at the declaration of the corresponding formal parameter.

23.2.3 Nonlocal References


Passing a function or procedure name for later invocation is a straightforward idea, but it raises a
new possibility for confusion of variable names. In the following program, the variable Vl occurs
both as a local that is in the execution state when a procedure parameter is invoked, and as a global
that was in the stat e when t h a t procedure was dec la r ed. One of these is altered , but whic h one?

23.2.3 Nonlocal References 619


PROGRAM NonLocal(OUTPUT);
VAR .
Vl: INTEGER;
{Global variable available when ProcP declared}
PROCEDURE ProcP(Val: INTEGER);
BEGIN {ProcP}
Vl := Val + 10
END; {ProcP}
PROCEDURE ProcQ(PROCEDURE ProcParm(X: INTEGER));
VAR
Vl: INTEGER;
{Lo.cal variable available when ProcParm called}
BEGIN {ProcQ}
Vl := 2;
ProcParm(Vl);
{If ProcParm is ProcP, which Vl is used?}
WRITELN (Vl)
END; {ProcQ}
BEGIN {NonLocal}
Vl := 1;
ProcQ(ProcP);
WRITELN (Vl)
END. {NonLocal}
Execution
OUTPUT: 2
12
The execution shows that the global Vl is altered; that is, when a parameter procedure makes
global references, they refer to the state at its declaration, not the state at its call. Thus the
assignment
Vl := Val + 10
within ProcP changed the global Vl, not the local of ProcQ, even though only the local was
available before and after ProcP was called. This is another example of Pascal's static binding rule
first encountered in Section 9.2.5. Identification of variables depends not on the dynamic declaration
order at execution, but on the declaration order of the static program text.

23.2.4 Zero Finding using Bisection


FUNCTION parameters allow the creation of routines that operate not on single values, but on the
whole collection that the function describes. The resulting software is a better encapsulation
because it receives the function once as a parameter, and can use it as often as needed. If only
functional values could be passed, it would be necessary to make repeated calls to obtain many
values, and control would reside not in the routine using the function but in the program that passed
it.
As an example, Bisect contains a routine F indZero that finds a zero of its function
parameter. Passing a function parameter to F indZero means that F indZero can operate on any
function, not just one whose definition is embedded inside; at the same time, the complete zero
calculation is contained in the routine, and a single call to F indZero suffices. In the sample
program, F indZero is supplied with Plusl , computing f(x) = x+l.
The idea used in F indZero is a variation of binary search. Suppose that the function
supplied is monotonic-it steadily increases or decreases. If two argument values are supplied at
which the function takes on values of different signs, then there must be a zero crossing between
them. The interval is halved, and the zero confined to one half or the other, this process being
repeated until the int erval is so small that it effectively pins down the zero (as defined by a program
constant Epsilon).

620 PLANNING FOR CHANGE


. '
" ..
:"~ ,, l~'l'l~illiltl, ' .'

PROGRAM Bisect(OUTPUT);
CONST
Epsilon = lE-5;
VAR
Z: REAL;
Cnt: INTEGER;
PROCEDURE FindZero(FUNCTION Func(X: REAL): REAL;
Low, High: REAL;
VAR Zero: REAL; VAR It: INTEGER);
{Find a value for Zero such that
Low<=Zero<=High and monotonic Func(X)=O
for an X within Epsilon of Zero. The
number of bisections is returned in It.}
LABEL
4;
BEGIN {FindZero}
It := 0;
Zero := Low; {in case loop body is not executed}
If Func(Low) * Func(High) > 0.0
THEN {no zero crossing for monotonic Func}
BEGIN
WRITELN('Bad endpoints supplied to FindZero');
GOTO 4
END;
{Otherwise, proceed with binary search}
WHILE ABS(High - Low) > Epsilon
DO
BEGIN
It := It + 1;
Zero := (High + Low) I 2;
IF Func(Zero) < 0
THEN
IF Func(Low) < 0
THEN
Low := Zero
ELSE
High := Zero
ELSE
IF Func(High) >= 0.0
THEN
High := Zero
ELSE
Low := Zero
END ;
4:
END; {FindZero}
FUNCTION Plusl(RArg: REAL): REAL;
BEGIN {Plusl}
Plusl := RArg + 1.0
END; {Plusl}

23.2.4 Zero Finding using Bisection 621

- ·- --~ . -

. '
BEGIN {Bisect}
FindZero(Plus1, -2.0, 2.0, Z, Cnt);
WRITELN(Z:12:8, Epsilon:10:6, Cnt:4)
END. {Bisect}
Execution
OUTPUT: -1.00000763 0.000010 19
The program required 19 iterations to find the zero to within 0.00001.

23.2.5 Sorting Different Kinds of Data


Many sorting algorithms have been investigated in this book, but they have been limited to
character data as the simplest case. The following procedure makes use of FUNCTION and
PROCEDURE parameters to allow the sorting of many different kinds of objects. The procedure uses
an exchange sort, with the data stored in an array. The type of object sorted, the number of
objects, and the methods for comparing and exchanging objects are all supplied external to the
procedure Sort.
TYPE
EltArray =ARRAY [1 .. NumElts] OF EltType;
PROCEDURE Sort(VAR Val: EltArray; NumToSort: INTEGER;
FUNCTION Ordered(VAR A, B: EltType): BOOLEAN;
PROCEDURE Swap(VAR X, Y: EltType));
{Permute the elements Val[1], ... , Val[NumToSort]
so that for all i,
O<=i<NumToSort<=NumElts: Val[i]<=Val[i+l]}
VAR
Sorted: BOOLEAN;
I, J: 1 .. NumElts;
BEGIN {Sort}
Sorted := False;
I := NumToSort;
WHILE (I >= 2) AND NOT Sorted
DO
BEGIN
Sorted := TRUE;
FOR J := 1 TO NumToSort-1 DO
IF NOT Ordered(Val[J],Val[J+1])
THEN
BEGIN
Swap(Val[J],Val[J+1]);
Sorted := FALSE
END;
I := I - 1
END
END; {Sort}
The user of Sort supplies the comparison and exchange routines for the parameters Ordered and
Swap, a CONST declaration for NumEl ts, and a TYPE declaration for El tType.
To illustrate the use of Sort, two programs, SortStrings and SortDates, will be
written to handle quite different data. To sort character strings, it is necessary to define a type to
represent strings and operations to compare and exchange strings.

622 PLANNING FOR CHANGE


'

~~~ ··

CONST
NumElts = 5;
EltLen = 10;
TYPE
EltType =PACKED ARRAY [l .. EltLen] OF CHAR;
FUNCTION Compare(VAR X, Y: EltType): BOOLEAN;
BEGIN {Compare}
Compare := X <= Y
END; {Compare}
PROCEDURE Exchange(VAR X, Y: EltType);
VAR
Temp: EltType;
BEGIN {Exchange}
Temp := X;
X := Y;
Y : = Temp
END ; {Exchange}
T he driver program below reads a nd writes one string of chara cters per line, storing them in
the array ToBeSorted.
PROGRAM SortStrings(INPUT, OUTPUT);
{Include string type declarations and operations}
VAR
ToBeSorted: EltArray;
TempElt: ARRAY [l .. EltLen] OF EltType;
I, J: 0 . . NumElts;
{Include PROCEDURE Sort(VAR Va l : El tArray;
NumToSort: INTEGER;
FUNCTION Orde red(VAR A, B: EltType) : BOOLEAN;
PROCEDURE Swap (VAR X, Y: EltType))
Permute the elements Val[l], .. . , Val[NumTo Sort]
s o that f o r al l i,
O<= i<NumToS or t <=NumEl ts : Va l[i ]<=Va l[i +1 ]}
BEGI N {SortStr i ngs }
WRI TELN('The input:') ;
{re a d and e cho inpu t}
I := 0 ;
WHI LE (I < NumElts) AND NOT EOF
DO
BEGI N
I : = I + 1;
J := 0;
WHILE (J < EltLen) AND NOT EOLN
DO
BEGIN
J := J + 1;
READ(TempElt[J]);
WRITE(TempElt[J] )
END;
READLN;
WRITELN;
{blank fil l the strings}
FOR J := J+l TO EltLen
DO

23.2.5 Sorting Different Kinds of Data 623


TempElt[J] :=' ';
PACK(TempElt,1,ToBeSorted[I])
END;
Sort(ToBeSorted,I,Compare,Exchange);
{write output}
WRITELN('The sorted output:');
FOR I := 1 TO I
DO
WRITELN(ToBeSorted[I])
END. {SortStrings}
Execution
INPUT :z01
abc
defg
ab
OUTPUT:The input:
z0 1
abc
defg
a.b
The sorted output:
ab
abc
defg
z01
Date objects that ,represent dates in which months a r e abbreviated as in Section 14.1.2 can be
sorted using Sort without many changes. The declar ations are:
CONST
NumElts = 15;
TYPE
Month= (NoMonth, Jan, Feb, Mar , Apr, May, Jun,
Jul , Aug, Sep, Oct, Nov, Dec);
Date = RECORD
Mo: Month;
Day: 1 . . 31
END;
EltType = Date;
for storing up to 15 Date values. The same Exchange function that moved strings will work for
dates, but a new comparison routine is required:
FUNCTION Less(VAR Dl, D2: Date): BOOLEAN;
{Less := D1<D2 }
BEGIN {Less}
IF D1.Mo = D2.Mo
THEN
Less := D1.Day < D2.Day
ELSE
Less := Dl.Mo < D2.Mo
END;
These changes to convert from sorting strings to sorting dates reflect the care with which Sort was
written. The test program was not designed to be easily reused, and it must be completely
rewritten. The routines to read and write Month values come from Section 14.1.2.

624 PLANNING FOR CHANGE


PROGRAM SortDates(INPUT, OUTPUT);
{Include date type declarati ons}
VAR .
ToBeSorted: EltArray;
I: 0 .. MAXINT;
{Include ReadMonth, WriteMonth, Less, Exchange, Sort}
BEGIN {SortDates}
WRITELN('The input:');
{read and echo input}
I := 0;
WHILE (I < NumElts) AND NOT EOF
DO
BEGIN
I := I + 1;
ReadMonth(INPUT,ToBeSorted[I] .Mo);
READ(ToBeSorted[I] .Day);
READLN;
WriteMonth(OUTPUT,ToBeSorted[I] .Mo);
WRITE(ToBeSorted[I].Day:3);
WRITELN
END;
Sort(ToBeSorted,I,Less,Exchange);
{write output}
WRITELN('The sorted output:');
FOR I := 1 TO I
DO
BEGIN
WriteMonth(OUTPUT,ToBeSorted[I] .Mo);
WRITE(ToBeSorted[I] .Day:3);
WRITELN
END
END. {SortDates}
Execution
INPUT :FEB 1
JAN 29
JAN 30
JAN 25
OUTPUT:The input:
FEBRUARY 1
JANUARY 29
JANUARY 30
DECEMBER 25
The sorted output:
JAN 29
JAN 30
FEB 1
DEC 25

23.2.6 Exercises
23.2.1 Given the syntactically correct procedure statement
CallitAndPassit(Fun(X))
but no access to the declarations of the identifiers, give as much information as is certain about the
parameter of CallitAndPassit.

23.2.6 Exercises 625


23.2 .2 Use box functions to give a formal meaning for PROCEDURE parameters, as was done (~
example) for value parameters in Section 15.4. Confine the definition to the simplest case or
procedure with one procedure parameter, which itself has no parameters.
23.2.3 Give the output produced by the following program, and explain how it comes about.
PROGRAM P(OUTPUT};
VAR
Z: INTEGER;
PROCEDURE T(PROCEDURE U);
BEGIN
z := z - 2;
u
END;
PROCEDURE Q(PROCEDURE R(PROCEDURE S};
VAR X: INTEGER) ;
VAR
Y: INTEGER;
PROCEDURE V;
BEGIN
X := X + 1
END;
BEGIN
y := 2;
X := Z - Y;
R(V)
END;
BEGIN
z := 6;
Q(T,Z);
WRITELN(Z)
END.
23.2.4 Give the output produced by the following program, and explain how it comes about.
PROGRAM P(OUTPUT};
VAR
A: INTEGER;
PROCEDURE A(PROCEDURE T(VAR X: INTEGER}}; FORWARD;
PROCEDURE Q(VAR B: INTEGER);
VAR
C: INTEGER;
BEGIN
C := B+A;
WRITELN(C};
IF B > 0
THEN
s (Q)
END;
PROCEDURE S;
VAR
C: INTEGER;
BEGIN
c := 0;
T (C)
END;
BEGIN

626 PLANNING FOR CHANGE


A := 7;
Q (A)
END.
23.2.5 Write a procedure with a func io a i es a table of arguments and function
values, where the arguments to prin are ee parameters giving starting and stopping
values, and the number of points bet een.
23.2.6 Write a procedure wi h a c ·on parameter that produces a graph of the parameter
function. The X and Y ranges are also to be supplied as parameters. ·
23.2.7 Use F indZero (Section 23.2.4) to write a program that calculates square roots. The input
is a REAL value z, and the square roo is ob ained by finding a zero for the function f(x) = x2- z.
23.2.8 Eliminate the GOTO statemen from F indZero (Section 23.2.4) .
23.2.9 Rewrite F indZero (Section 23.2.4) as a recursive procedure.
23.2.10 Can the value of Epsilon in F indZero (Section 23.2.4) be made so small that a zero will
never be found? Hint: suppose that there is a good deal of arithmetic subject to roundoff error in
computing Func.
23.2.11 Rewrite F indZero (Section 23.2.4) as a procedure that need not be given two points
between which to search for the zero of a monotonic function .
a) Give it only one point at. which to begin.
b) Give it no information about where to look .
Hint: A kind of inverse of binary search moves outward from a point, doubling the interval on each
side. How important is the restriction to monotonic functions?
23.2.12 Describe some of the possibilities that can occur if . F indZero (Section 23.2.4) is given an
actual parameter that is a function which is not monotonic.
23.2.13 Describe the changes needed for the Sort procedure of Section 23.2.5 (not the driver
program) to be able to sort
a) REAL values
b) CHAR values
c) SET OF CHAR values
d) TEXT file values
23.2.14 Are there data types that can be sorted by Sort (Section 23.2.5) for which the Exchange
procedure written for the string type would not be adequate? Explain.
23.2.15 Design a driver program for Sort that is more amenable to change than those given in
Section 23.2.5, and show how it could handle the two examples of that section.
23.2.16 Design a MergeSort program (Section 19.3.2) that is as easy to change as possible.

23.3 Variant Records

Preview

Information cannot always be divided into one set of fixed fields to form a Pascal record. When
different formats could be conveniently mixed, the variant record provides a type to encompass
them all.

A record binds together several different types as its fields. The record types presented
beginning in Chapter 16 have each had a fixed list of types; that is, each value had the same fields.
Real information is often not so simple. For example, in a collection of personnel records (name,
address, social security number, etc.) there will be some fields that are appropriate for only some
employees. Not all people will be on yearly salaries; instead some records will need fields for hourly

23.3 Variant Records 627


pay rate and the number of hours worked. Many operations on these data (e.g., correcting the
spelling of a name, or producing a sorted list of employees) do not depend on the information in such
"variant" fields. Instead of enlarging the record to contain all possible different fields, or separating
employees into groups with similar fields, variant records can be used to make one TYPE for records
with varying content. An employee variant record might be described as follows:
TYPE
Month = (NoMonth, Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
Date = RECORD
Mo: Month;
Day: 1 .. 31;
Year: INTEGER
END;
Compensation= (Salary, Hourly);
Person = RECORD
Name: PACKED ARRAY[1 .. Len] OF CHAR;
SocialSecurity: INTEGER;
Address: PACKED ARRAY[1 .. Len] OF CHAR;
BirthDate: Date;
CASE WageType: Compensation OF
Hourly: (Hours: 0 .. 168;
HourlyRate: REAL);
Salary: (YearlyRate: 0 .. MAXINT)
END;
The variant part of the record is the CASE construction at the end (and to make sure it appears
last, it is terminated by the same END that terminates the RECORD.) The fields that are not part
of every record are presented as alternatives in the CASE, and a finite ordinal type (here
Compensation) provides a list of identifiers (Hourly, Salary) to mark the variant parts. This
record contains an implicit field WageType of type Compensation called the tag, and the record
is said to be discriminated by this tag.
Person is like a union of two different record types:
TYPE
HPerson = RECORD
Name: PACKED ARRAY[1 .. Len] OF CHAR;
SocialSecurity: INTEGER;
Address: PACKED ARRAY[l .. Len] OF CHAR;
BirthDate: Date;
WageType: Compensation
Hours: 0 .. 168;
HourlyRate: REAL
END;
SPerson = RECORD
Name: PACKED ARRAY[l .. Len] OF CHAR;
SocialSecurity: INTEGER;
Address: PACKED ARRAY[l .. Len] OF CHAR;
BirthDate: Date;
WageType: Compensation;
YearlyRate: 0 .. MAXINT
END;
A variable of type Person is allocated enough storage to hold values of the largar of the two types
HPerson and SPerson so that Hours and HourlyRate occupy the same relative position as
YearlyRate.

628 PLANNING FOR CHANGE


The advantage of having a single type
single routine can manipulate type Perso • ·
an SPerson. Within such a routine, the a
record is valid, as in the following example:
VAR
P: Person;
BEGIN

CASE P.WageType Of
Hourly: WRITELN (P.Hours *' P.HourlyRate);
Salary: WRITELN (P .YearlyRate DIV 52)
END;

END
The CASE statement selects s t a t ements in which the fields match the similar part of the CASE
construction in the record declaration. Care must be taken to set the correct tag value in any value
of type Person. The tag is just like other fields; it has no initial value and does not automatically
adjust when the variant fields are changed. Strange results can be obtained by inconsistently
manipulating the tag and the variant fields , as in:
P.WageType := Hourly;
P.YearlyRate := 35000; {P.WageType is still Hourly}
CASE P.WageType Of {execute the Hourly case
with unpredictable results}
Hourly: WRITELN(P.Hours * P.HourlyRate);
Salary: WRITELN(P.YearlyRate DIV 52)
END; I

The variant part of a record need not have a tag at all- then it is said to b~ free. The fields of
a free variant are expected to occupy the same storage space in a particular Pascal machine, and
provide a way to circumvent the type restrictions of the language. For example, if a programmer
knows that a CHAR variable and an INTEGER variable occupy the same space, then the INTEGER
value of A can be printed as follows:
PROGRAM Convert(OUTPUT);
TYPE
CvtType = (Int, Chr);
Cvt = RECORD
CASE CvtType Of
Int: (IVal: INTEGER) ;
Chr: (CVal: CHAR)
END;
VAR
Both: Cvt;
BEGIN
Both.CVal :='A';
WRITELN (Both.IVal)
END.
Execution
OUTPUT: 65
Variant records with free variants are a feature of Pascal that should be avoided, since they cannot
be expected to work in the same way on different Pascal machines.

23.3.1 Variant Record Syntax 629


23.3.1 Variant Record Syntax
The syntax rules for variant records are shown below.
<unpacked structured type> ::=RECORD <field list> END
<field list> ::=<fixed part> <variant part> I <fixed part>
I <variant part> I
<fixed part> ::= <record sections>
<record sections> ::=<record sections> ; <record section> I <record section>
<record section> ::= <identifier list> : <type denoter >
<variant part>::= CASE <variant selector> OF <variant list>
<variant selector> ::=<optional tag field> <ordinal type identifier>
<optional tag field> ::=<identifier> : I
<variant list> ::=<variant list> ; <variant> I <variant>
<variant> ::=<constant list> : ( <field list> )

23.3.2 Pointers to Variant Records


Pointers can reference an object whose type is a variant record. For example:
TYPE
Compensation= (Salary, Hourly);
PList = fPerson;
Person = RECORD
Name: PACKED ARRAY[l .. Len] OF CHAR;
SocialSecurity: INTEGER;
Address: PACKED ARRAY[l .. Len] OF CHAR;
BirthDate: Date;
Next: PList;
CASE WageType: Compensation OF
Hourly: (Hours: 0 .. 168;
HourlyRate: REAL);
Salary: (YearlyRate: 0 .. MAXINT)
END;
VAR
First: PList;
Storage for a Person pointed to by First can be obtained with NEW (First) as usual. The
resulting Person target will be able to have either an Hourly or Salary variant part.
However, if the allocation is made with NEW (Fir s t, Salary), it is expected that the variant will
never be Hourly. In this case, since the Hourly variant is probably the larger, strange things
may happen if the programmer is not careful to set the tag to Salary. When there are more than
two tag values, any sequence of them may be arguments to NEW, but they must occur in the order of
the declaring CASE. Only enough storage for the la rgest type listed is guaranteed. If tag constants
are used in a NEW statement to obtain storage, the same constants must be used in a DISPOSE
statement to return the storage.

630 PLANNING FOR CHANGE


23.3.3 Concordance
A concordance for a text is a list of the words that appear in the text, and count of how often each
word appears. Common words like articles and pronouns are usually omitted from the concordance
(they are said to be blocked). Thus a concordance program could be. written using a list of records
with a string field for the word and an integer field for the count. A frequent operation in computing
a concordance is looking up a word to see if it is blocked. Thus searching the blocked list for a word
should be a fast operation, and since the list can be large, the search time should depend as little as
possible on its size. A hash table is a storage structure with these properties.
A hash function is a transformation mapping data values to hash table indices. A good hash
function spreads the data items over the hash table uniformly so that looking up an item is no more
expensive than an array reference. However, if two data items map to the same hash table index, a
collision occurs, and something must be done to resolve the conflict. The simplest collision-resolving
algorithm is to keep all the data items with the same hash-function value in a list called a bucket.
Concordance will use a hash table HashT and a list of words WordList whose types will
be defined as part of later refinements. The text to be processed is in the input file; Common is an
external file containing the blocked words.
Design Part 1
PROGRAM Concordance(INPUT,OUTPUT,Common);
{Declare:
Common - blocked-word file
HashT - internal table of blocked words
WordList - concordance of nonblocked words}
BEGIN {Concordance}
Hashinit(HashT);
{read words from Common and insert in HashT};
Listinit(WordList);
WHILE NOT EOF
DO
BEGIN
{read a word from INPUT; a word not in Common
or WordList should be added to WordList with
1 occurrence, while a word not in Common but
already in WordList should have an extra
occurrence recorded}
END;
{print the words and their occurrences}
ListPrint(WordList)
END. {Concordance}
Procedure ReadWord from Section 19.4.1 can be used to move the next word from Common to the
local variable Temp, and to set Found to TRUE if a word was read. Hashinsert installs the
word just read in HashT.

23.3.3 Concordance 631

. -
' '
' .~ • • t
Design Part 1.1
{read words from Common and insert in HashT}
RESET(Common);
WHILE NOT EOF(Common)
DO
BEGIN
ReadWord(Common,Temp,Found);
IF Found
THEN
Hashinsert(HashT,Temp)
END;
ReadWord can also be used to obtain words from INPUT. HashProbe is a function that returns
TRUE if a word occurs in a hash table, and FALSE if it does not.
Design Par~ 1.2
{read a word from INPUT; a word not in Common
or WordList should be added to WordList with
l occurrence, while a word not in Common but
already in WordList should have an extra
occurrence recorded}
WHILE NOT EOF
DO
BEGIN
ReadWord(INPUT,Temp,Found);
IF Found AND NOT HashProbe(HashT,Temp)
THEN
Listinsert(WordList,Temp)
END
The declarations for HashT and WordList can be given now that the processing algorithm
has been fixed. WordList is a list of nodes, each containing a word and an integer. HashT is
essentially an array of word lists, so the same procedures can be used to maintain it that maintain
WordList, by making them variants of the same RECORD type . Although each element of a word
list needs an integer (Occurs) to count how often the word appears, in a hash table element this
component is missing.
Design Part 1.3
{Declare:
Common - blocked-word file
HashT - internal table of blocked words
WordList - concordance of nonblocked words}
CONST
Max = 20; {Longest word}
TSize = 17; {Size of the hash table}
TYPE
WordType =PACKED ARRAY[l .. Max] OF CHAR;
NodeType = (HTable, WList);
List = tNode;
Node = RECORD
Next: List;
Word: WordType;
CASE Tag: NodeType OF
HTable: ();
WList: (Occurs: O .. MAXINT)
END;

632 PLANNING FOR CHANGE


HTableType = ARRAY[O .. TSi ze] OF List;
VAR
Common: TEXT;
HashT: HTableType;
WordList: List;
Temp: WordType;
Found: BOOLEAN;
A single routine to process HashT elements and WordList reduces the size of the concordance
program, but its more important advantages are in debugging and maintenance. Improvements or
corrections in the list insertion algorithm benefit both structures, and any changes can be made m
just one routine.
Listini t assigns the value NIL to the pointer that references the first item in a list.
Design Part 1.4
PROCEDURE Listinit(VAR L: List);
{ L := <>}
BEGIN {Listinit}
L := NIL
END; {Listinit}
Hashini t simply calls Listini t once for each of its table entries.
Design Part 1.5
PROCEDURE Hashinit(VAR T: HTableType);
{ T : = [] }
VAR
I: 0 .. TSize;
BEGIN {Hashinit}
FOR I := 1 TO TSize
DO
Listinit(T[I])
END; {Hashinit}
Listinsert obtains an address for the element that is being inserted, sets the element's Tag
field to indicate the proper variant, and (based on whether or not the element was already in the
list) initializes or updates the ele.ment's Occurs field.
Design Part 1.2.1
PROCEDURE Listinsert(VAR L: List; VAR Txt: WordType);
{If Txt is in L, then increase the number of its
occurrences by 1. Otherwise add a new member to L
whose Word field has the same value as Txt and whose
Occurs field shows a single occurrence.}
VAR
WdPtr: List;
Found : BOOLEAN;
BEGIN {Listinsert}
Insert(L,WdPtr,Txt,Found) ;
WdPtrf.Tag := WList;
IF NOT Found
THEN
WdPtrf.Occurs := 1
ELSE
WdPtrf.Occurs := WdPtrf.Occurs + 1
END; {Listinsert}

23.3.3 Concordance 633

~ --
.,, '--

-
The design of the insertion routine is a challenge-how much common processing can be done
for word lists and hash buckets? Both are free of duplicate entries, but a word-list element has the
extra Occurs component to process. If the word to be be inserted is already on the list, Insert"
returns a pointer to it; otherwise, a new list member is created and a pointer to it returned
Found can be used to distinguish these cases.
Design Part 1.2.1
PROCEDURE Insert(VAR L, Elt: List;
VAR Txt: WordType;
VAR Found:BOOLEAN);
{L is a list of n records, one of whose fields,
Word, is a WordType value.
(If Txt has the same value as the Word field of
the node at position j -->
Elt,Found := j,TRUE)
(If Txt does not occur as any Word field in L -->
L,Elt,Found :=
L with Txt appended in position n+l,
n+l, FALSE) }
VAR
Prev, Curr: List;
BEGIN {Insert}
{(Txt matches the Word field of the element
at position j in L -->
Found,Prev,Curr :=
TRUE, address of L ' s element whose Next
field contains the address of L's j'th
element, address of L's j'th element) 1
(Txt matches none of the Word fields of L -->
Found,Prev,Curr :=
FALSE, address of L's last element, NIL)};
IF Found
THEN
Elt := Curr
ELSE
BEGIN
NEW(Elt);
Eltf.Word := Txt;
{insert Elt after Prevf}
END
END; {Insert}
.At!. in Section 20.2.3, two pointer variables Prev and Curr are used to search for a word. If the
search is unsuccessful, Prev is made to point to the last element in the list, where an insertion may
be made.

634 PLANNING FOR CHANGE


Design Part 1.2.1.1
{(Txt matches the Word field of the element
at position j in L -->
Found,Prev,Curr :=
TRUE, address of L's element whose Next
field contains the address of L's j'th
element, address of L's j'th element)
(Txt matches none of the Word fields of L -->
Found,Prev,Curr :=
FALSE, address of L's last element, NIL )};
Found := FALSE;
Prev := NIL;
Curr := L;
WHILE (Curr <> NIL) AND NOT Found
DO
IF Currf.Word =Txt
THEN
Found := TRUE
ELSE
BEGIN
Prev := Curr;
Curr := Currf.Next
END;
When a new element is to be inserted, either the list is empty and it becomes the first element, or
Prev points to where it should be inserted.
Design Part 1.2.1.2
{insert Elt after Prevf}
IF L = NIL
THEN {insert as first element}
L := Elt
ELSE
BEGIN {insert as element after Prevf}
Eltf.Next := Prevf.Next;
Prevf.Next := Elt
END
Hash! nsert uses the function Hash to determine the bucket in which the new word should
be inserted. Then it simply sets the Tag field of the new element to the appropriate value.
Design Part 1.1.1
PROCEDURE Hash!nsert(VAR T: HTableType;
VAR Txt: WordType);
{ T := T union [Txt] }
VAR
WdPtr: List;
Occurs: BOOLEAN;
BEGIN {Hash!nsert}
Insert(T[Hash(Txt)],WdPtr,Txt,Occurs);
WdPtrf.Tag := HTable
END; {Hash!nsert}
A simple hash function adds the integer representation of the first two characters of a word and
takes the modulus of this sum with the size of the table. This is a particularly poor hash function if
many blocked words start with the same letters (e.g., the, this, these, those, they, et.r.) because such
words wind up in the same (long) bucket.

23.3.3 Concordance 635


Design Part 1.1.1.1
FUNCTION Hash(VAR Txt:WordType): INTEGER;
{Hash:= an integer in the range O.. TSize-1}
BEGIN {Hash}
Hash := (ORD(Txt[l))+ORD(Txt[2])) MOD TSize;
END; {Hash}
HashProbe searches the hash table to determine if a word is there, using Hash to find the proper
bucket.
Design Part 1.6
FUNCTION HashProbe(VAR T: HTableType;
VAR Txt: WordType): BOOLEAN;
{ HashProbe := Txt is a member of T }
VAR
P: List;
Index: 0 .. TSize;
BEGIN {HashProbe}
HashProbe := FALSE;
Index : = Hash(Txt);
IF T[Index] <> NIL
THEN
BEGIN
P := T[Index];
WHILE (P <> NIL)
DO
BEGIN
IF Pf.Word = Txt
THEN
HashProbe := TRUE;
P : = Pf.Next
END
END
END; {HashProbe}
Finally, ListPrint prints the word list as the output from Concordance. Since it might be
necessary to print the hash table during development , this procedure checks each element's Tag field
before trying to print the number of occurrences.
Design Part 1. 7
PROCEDURE ListPrint(L: List);
{write the contents of L to OUTPUT}
VAR
Ptr: List;
BEGIN {ListPrint}
WRITELN('List values are:');
Ptr := L;
WHILE Ptr <> NIL
DO
BEGIN
WRITE(Ptrf.Word);
IF Ptrf.Tag = WList
THEN
WRITE(Ptrf.Occurs);
WRITELN;
Ptr : = Ptrf.Next

636 PLANNING FOR CHANGE


END
END; {ListPrint}
The development of Concordance is comple e and the parts can be assembled. If we decided
that the concordance should be prin ed in sorted order, I nsert could be changed to maintain a
word list in sorted order. As a beneficial side effect of such a change, the buckets of the hash table
would also be sorted and t he search in Has hProbe could be rewritten t o improve its performance.

23.3.4 Exercises
23.3.1 In the Person example of Section 23.3, why would two different procedures need to be
written to process the t ypes HPe r s on and SPer son?
23.3.2 A procedure Draw is to be written t o handle objects representing points, lines, and circles.
Assume that a point is represented by a pa ir of coordinates (two INTEGER values), a line by two
points, and a circle by a point a nd an INTEGER r adius.
a) Give the type declarations needed t o permit Draw to use a single parameter whose value
might be any of these types.
b) Give a skeleton of t he code within Draw t hat would use the parameter to choose which part of
the code to execute . Suggest some parts of the processing in Draw that would benefit from
having just one routine for all three objects.
23.3.3 A programmer claims he can print the INTEGER representation of the BOOLEAN value
TRUE with the following program fragment:
new(Z);
Zf.Bool :=TRUE;
WRITELN (Zf . Int)
If this can be done, give the type definitions that make this fragment work . Otherwise, explain why
this is not possible .
23.3.4 Write a procedure GetToken that scans a TEXT file and returns a series of tokens (words,
integers, punctuation marks, and an end token). A token is represented as a variant record; the
punctuation and end token have empty variant fields.
PROGRAM Scan(INPUT,OUTPUT);
{break input into a series of tokens}
CONST
MaxWordLength = 20;
TYPE
Word= PACKED ARRAY [l . . MaxWordLength] OF CHAR;
TokenType = (Txt, Int, EoT, Period, QMark,
XMark, Comma, Colon, Semicolon,
Undefined);
Token RECORD
CASE T: TokenType OF
Txt: (CValue: Word);
Int: (!Value: INTEGER) ;
EaT, Period, Comma, QMark, XMark,
Colon, Semicolon, Undefined: ()
END;
PROCEDURE GetToken(VAR Symbol : Token);
{Fill in the code};

23.3.4 Exercises 637


BEGIN {Scan}
GetToken(Tok);
WHILE Tok.T <> EoT DO
BEGIN
CASE Tok.T OF
Undefined: WRITELN('Undefined token');
Txt: WRITELN('Word = ''',Tok.CValue, ' ' ' ');
Int: WRITELN('Number =',Tok.IValue:S);
Period: WRITELN('Period');
Comma: WRITELN{'Comma');
Colon: WRITELN('Colon');
Semicolon: WRITELN{'Semicolon');
XMark: WRITELN{'Exclamation mark');
QMark: WRITELN('Question mark')
END;
GetToken(Tok)
END
END. {Scan}
Your program should produce output like that shown below.
Execution
INPUT :Number 54.671 ,asdfjkl#;:
OUTPUT :Word 'Number
Number 54
Period
Number 67
Exclamation mark
Comma
Word 'asdfjkl
Undefined token
Semicolon
Colon
23.3.5 Modify the Concordance program of Section 23.3.3 so that at the end of execution it
prints statistics about the quality of the hashing, notably the number of collisions, and the size and
contents of the hash buckets.
23.3.6 Modify the Insert procedure of Concordance (Section 23.3.3) so that it keeps sorted
word lists. Also modify HashProbe to improve its speed.

23.4 Chapter Summary


Judicious use of CONST and TYPE declarations and PROCEDURE parameters makes Pascal
programs more flexible. Altering a single declaration or the value of an actual parameter can then
change the meaning substantially, but in a controlled way. Variant records factor data into disjoint
parts and cluster the similar parts at the head of a composite record .. An operation that uses only
the common fields can then be used to centralize all record processing. The benefits of single-
procedure implementation are enormous when it becomes necessary to extend the operation or repair
an error--only one copy of the operation needs to be changed.

638 PLANNING FOR CHANGE


CHAPTER24

ONE LAST SOLUTION

Chapter Preview

The solution to a final problem illustrates the design and analysis methods of this text. By using
analysis within the design, confidence in the program is maintained as it grows. The design
method scales up to problems of arbitrary size by decomposing large problems into a hierarchy
of smaller ones, and maintaining intellectual control of the components. Complete formality
cannot be sustained for a large problem, but formal methods can be selectively ap'plied to
problems of any size.

This book has introduced two major design strategies that complement and build on one another.
The first is stepwise refinement of Pascal program parts, particularly Pascal statements; at each step
a relational or functional specification is refined into a small design and further subspecifications.
The second is designing modules with data abstractions to gain intellectual control. When carefully
chosen, such modules can be reused as building blocks for other designs.
This chapter contains an integrated example that illustrates both stepwise refinement and data
abstraction. The example, to find and print a set of prime numbers, is large enough to require the
judicious use of formal proofs, embedded in a broader activity of informal reasoning and design. In
particular, the example illustrates that stepwise refinement permits the control of details, step by
step, in both data and operations. For example, a subspecification need only treat variables that are
used in the design it supports and not the variables that are introduced for its implementation.
Deferring details while maintaining control of the design is essential in scaling up methods of
"programming in the small" to "programming in the large." Both stepwise refinement and data
abstraction are vital in scaling up. Stepwise refinement permits the deferring of details directly as a
program is elaborated a part at a time. Data abstraction permits the deferring of details indirectly,
but with even more power, by increasing the capability of the underlying design primitives.

24.1 The Prime Number Problem Again

Preview

The problem: to find and print the prime numbers up to a given integer.

The set of prime numbers up to natural number N is formally defined as:


{P: 2<P<N, P is a prime}.
The condition "P is a prime" means, of course, that P is not divisible by any natural number except 1
(which divides any natural number). That condition can be elaborated in a redefinition of the set of
primes:
{P: 2<P<N, for no Q>l is P mod Q = 0}.
This problem was solved in Chapter 16 in a direct way, using the Pascal SET type.
More information about primes and nonprimes is needed for the solution of this chapter. One
useful property of primes is the fact that if Q divides P, than any divisor of Q also divides P. T~us
small primes can be used to check P as a potential prime and their multiples need not be tned.
There are many multiples of small primes (compared to N) so the savings can be significant.
The value of using small primes to check if large numbers are prime l~ads to a design strategy
of finding the primes in order from small to large. Each prime found can then be used to save work
in checking larger numbers. For this reason, the primes will be represented as a list of numbers

24.1 The Prime Number Problem Again 639

~ ;- \

' '' . -
' .
rather than a set . As each new prime is found, it will be added to the list.

24.1.1 Developing and Proving the Solution


Let Primes be the list of prime numbers up to INTEGER N. To calculate Primes, the following
observation is helpful: 2 is the first prime and the only even prime. All other primes are odd, so only
odd numbers need be tested as additional prime candidates. Under these conditions the problem
statement can be refined to:
Design Part 1
{Compute Primes}
{Primes := <2>};
{Primes :=Primes & OddPrimes(3,N)};
{print Primes}
OddPr imes is defined as follows:

<L: Prime?(L)> & OddPrimes(L+2,U) if L<U and Odd(L)


OddPr imes (L, U) = { <> if L>U

TRUE if VP, P is in OddPrimes(3,X - 1), X mod P ~ 0


Prime?(X) = { FALSE otherwise

This definition allows the refinement:


Design Part 1.1
{Primes :=Primes & OddPrimes(3,N)}
NextP := 3;
{Odd(NextP) -+Primes :=Primes & OddPrimes(NextP, N)}
Design Part 1.1.1
{Odd(NextP) -+Primes :=Primes & OddPrimes(NextP, N)}
WHILE NextP <= N
DO
{(Primes,NextP :=Primes & <NextP: Prime?(NextP)>,NextP+2)}
H the design is to remain under intellectual control at this point, it is necessary to prove that the
WHILE statement in Design Part 1.1.1 is correct. That is, if
f = (Odd(NextP) -+Primes :=Primes & OddPrimes(NextP, N))
and W is:
WHILE NextP <= N
DO
{(Primes,NextP :=Primes & <NextP: Prime?(NextP)>,NextP+2)}
then the three conditions of the WHILE statement verification rule are met.
f and I WI
cannot be identical since NextP is altered in the loop body of W but is not changed
in the concurrent assignment for f. NextP is not a part off because its value is not of interest after
the iteration is complete. Omitting NextP from the verification process eliminates details
introduced by purely local variables. With this understanding we turn to the three steps of the
proof.
1. domaintn = domain( 1wl)
f is defined for all odd numbers. For these numbers W terminates immediately if NextP>N. If
NextP<=N, the body of W is executed and NextP incremented assuring the eventual termination of
the statement. We are not interested in the even numbers for which f is not defined, because the
initialization assignment to NextP and advancing by 2 each time eliminates them from the
iteration.

,
640 ONE LAST SOLUTION
2. (NextP > N -+ f) = (NextP > N -+ )
The definition of OddPr imes assures us that if NextP>N then
OddPrimes(NextP, N) = <>.
Thus I reduces to
(NextP>N and Odd(NextP) -+ Primes := Primes & <>)
which is an identity function with a smaller domain than the identity function on the right side of
step 2. Again, the even case is not of interest .
3.1=
( (NextP < N -+
Primes,NextP :=Primes & <NextP: Prime?(NextP)>,NextP+2)
(NextP > N -+ ) ) o I
The composition can be studied with a trace table:

Condition Primes NextP


NextP<N Primes & <NextP : Prime?(NextP)> NextP+2
Odd(NextP+2) Primes & <NextP : Prime?(NextP)> &
OddPrimes(NextP+2,N)

This trace table computes the part function:


Odd(NextP+2) -+
Primes :=Primes & <NextP: Prime?(NextP)> & OddPrimes(NextP+2,N)
NextP+2 is odd if NextP is odd and even if NextP is even, so:
Odd (NextP+2) = Odd (NextP)
When NextP is odd:
(<NextP: Prime?(NextP)> & OddPrimes(Next~+2,N)) = OddPrimes(NextP,N)
(NextP+l cannot be prime because it is an even integer.) Thus the simplified part function is:
NextP<N and Odd(NextP) -+Primes :=Primes & OddPrimes(NextP,N)
The other case resulting from this composition is:
NextP>N and Odd(NextP) -+Primes :=Primes & OddPrimes(NextP,N)
Combining these two part functions yields:
(NextP<N and Odd(NextP) -+Primes :=Primes & OddPrimes(NextP,N))
(NextP>N and Odd(NextP) -+Primes :=Primes & OddPrimes(NextP,N))
which is identical to f.
The body of the WHILE statement is refined next.
Design Part 1.1.1.1
{(Primes,NextP :=Primes & <NextP: Prime?(NextP)>,NextP+2)}
{IsPrime := (For all members P of Primes, NextP mod P <> 0)};
IF IsPrime
THEN
Append(Primes,NextP);
NextP := ~extP + 2
Append will be defined as an operation on the abstract data type "list of integers" to add the
value of NextP to the end of the list Primes. This is equivalent to concatenating to Primes the
single element list containing NextP.

24 .1.1 Developing and Proving the Solution 641


PROCEDURE Append(VAR L: List; Elt: EltType);
{abs: (Length(L) < MaxSize --> L := L & <Elt>)
(Length(L) >= MaxSize --> )
The initial value of I sPr ime is TRUE, which is the value of
(For all members P of Primes, NextP mod P <> 0)
if Primes is empty. The elements of Primes are examined sequentially to determine if any of
them divide NextP, and the results of these tests are combined with previous test results in
IsPrime.
Design Part 1.1.1.1.1
{IsPrime := (For all members P of Primes, NextP mod P <> 0)}
IsPrime := TRUE;
Head(Primes, Trial);
{(Primes= (<P1, ... ,Pi>, <Pi+1, ... ,Pn>) and Trial= Pi and i>=1 -->
IsPrime := IsPrime and (NextP mod Trial <> 0) and
(for all Pj in <Pi+1, ... ,Pn>, NextP mod Pj <> 0))
(Trial= EndList --> )}
Operations must be added to the abstract data type so that values of list elements can be
obtained sequentially, just as characters are read from a TEXT file. Like RESET for a file,
Head (Primes, Trial) assigns Trial the value of the first element in Primes (if Primes is not
empty) and the value EndList otherwise. In addition, Primes is prepared for reading, which we
indicate by writing its elements as
<already-read values of Primes> & <to-be-read values of Primes>.
The other operation, Next (Primes, Tria 1) , performs a function like that of the READ
statement, assigning the next value to be read in Primes to Trial and advancing the reading
pointer. If no more values remain to be read from Primes, Next assigns Trial the value
EndList. Formal specifications allow reasoning about these operations.
PROCEDURE Head(VAR L: List; VAR Result: INTEGER);
{abs: (L = <> --> Result := EndList) I
(L = <L1, ... ,Li-1> & <Li, ... ,Ln> -->
L, Result:= <Ll> & <L2, ... ,Ln>, L1)}
PROCEDURE Next(VAR L: List; VAR Result: INTEGER);
{abs: (L = <Ll, ... ,Ln> & <>-->Result:= EndList)
(L = <L1, ... ,Li-1> & <Li, ... ,Ln> -->
L, Result:= <L1, ... ,Li> & <Li+1, ... ,Ln>, Li)}
Returning to the design part, the domain guard
Primes= <P1, ... ,Pi> & <Pi+1, ... ,Pn> and Trial= Pi and i>=1
implies that at least one element of Primes has already been read, that its value is the same as
that of Trial, and that its value differs from EndList (which must be a value that differs from
all list values). We show that the design part computes its specified function. If Primes is empty,
Head assigns Trial the value EndList. The first condition in the following comment must be
false because Primes contains no element with value EndList. Since Trial=EndList, the
comment specifies an identity function that leaves I sPr ime with the value TRUE in the first
assignment statement. The value of I sPr ime is thus identical to
(For all members P of Primes, NextP mod P <> 0)
which is vacuously true when Primes is empty. If Primes is not empty, Head assigns Trial a
value different from EndList, so we need only consider the first of the two cases in the comment.

642 ONE LAST SOLUTION


Condition IsPrime Trial Primes
TRUE
Primes = <P1, . .. ,Pn> P1 <P1> &
<P2 ... Pn>
<P1> & <P2, ... , Pn> = TRUE and
<P1, ... , Pi> & (NextP mod P1 <> 0} and
<Pi+ 1, ... , Pn> (for all Pj in <Pi + 1 , . . . , Pn> ,
and P1 = Pi and i>=O NextP mod P J· <> 0 )

Condition:
Primes= <P1, ... ,Pn> AND
<P1> & <P2, ... ,Pn> = <P1, ... ,Pi> & <Pi+1, ... ,Pn> AND
P1 = Pi and i >= 1
Picking i=1 simplifies the condition to Primes = <P 1, ... , Pn>.
Assignment to IsPrime:
TRUE AND (NextP mod P1 <> 0) AND
(for all Pj in <Pi+1, ... ,Pn>, NextP mod Pj <> 0}
which is
TRUE AND (NextP mod P1 <> 0) AND
(for all Pj in <P2, ... ,Pn>, NextP mod Pj <> 0)
or
(for all Pj in <P1, ... ,Pn>, NextP mod Pj <> 0)
Thus whether or not Primes is empty, the desired function is computed. Since this design part
meets its specification, the final comment can be refined .
Design Part 1.1.1.1.1.1
{(Primes= <P1, ... ,Pi> & <Pi+l, ... ,Pn> and Trial= Pi and i>=1 -->
IsPrime := IsPrime and (NextP mod Trial <> 0) and
(for all Pj in <Pi+1, ... ,Pn>, NextP mod Pj <> 0)}
(Trial= EndList --> )}
WHILE Trial <> EndList
DO
BEGIN
IsPrime := IsPrime AND (NextP mod Trial) <> 0;
Next(Primes, Trial)
END
To demonstrate that the WHILE statement computes the Design Part function, we make several
simplifying assumptions. The only output variable is IsPrime; changes to Trial and Primes
are ignored. We also discount the possibility of the result of the mod function being undefined in the
case where the second argument is not a positive integer. Since these arguments are chosen from
Primes whose members are all positive, the operation will always be defined.
The first two steps of the WHILE statement verification rule are straightforward.
Next (Primes, Trial) assigns the value of the next element in the list Primes to Trial so
eventually EndList must be returned. By assumption, the mod function is always defined. Thus
the loop will terminate on the same domain for which the function is defined. The loop and the
function both compute an identity function for the same domain. That is, when Trial=EndList,
Primes is the empty list.
Let g be the specified function and T name the following program text:

24.1.1 Developing and Proving the Solution 643


IF Trial <> EndList
THEN
BEGIN
IsPrime := IsPrime AND (NextP mod Trial) <> 0;
Next(Primes; Trial)
END
The third condition of the WHILE statement verification rule is:
g =[I)o g.
Since there are two parts to the domain of Next, [!]is
(Primes= <P1, ... ,Pk> & <Pk+1, ... ,Pn>
and Trial = Pk and Trial ~ EndList and k>1 -+
Primes, Trial, IsPrime :=
<P1, ... ,Pk+1> & <Pk+2, ... ,Pn>, Pk+1,
IsPrime AND (NextP mod Trial) <> 0)
(Primes= <P1, ... ,Pn> & <>
and Trial = Pn and Trial ~ EndList and n>1 -+
Trial, IsPrime := EndList, IsPrime AND (NextP mod Trial) <> 0)
(Trial = EndList -+ )
The following trace tables capture the composition of the first two parts of [I) with g.
Case 1
Condition IsPrime Trial Primes
Primes = IsPrime and Pk+1 <P 1 , ... , Pk+ 1 > &
<P1, ..• , Pk> & (NextP mod Trial ~ 0) <Pk+2, ... , Pn>
<Pk+1, ... , Pn>
and Trial = Pk
and Trial ~ EndList
and k > 1
<P 1 , .. , Pk + 1 > & IsPrime and
<Pk+2, .. . ,Pn> (NextP mod Trial ~ 0)
= and
<P1, ... , Pi> & (NextP mod Pk+1 ~ 0)
<Pi+ 1 , ... , Pn> and
and (for all Pj in <Pi+1, ... ,Pn>,
Pk+1 = Pi NextP mod Pj ~ 0)
and
i >= 1

Setting i=k+l, we have


Condition:
Primes= <Pl, ... ,Pi-1> & <Pi, ... ,Pn> and Trial= Pi-1 and
Trial ~ EndList and k > 1 and i > 1 and i = k+1
Having eliminated k from the description of Primes and Trial, the last three parts of the
condition can be replaced by i>=2. Trial=Pi-1 implies Trial~EndList so the latter
condition can also be omitted.
Assignment to IsPrime:
IsPrime and (NextP mod Trial ~ 0) and (NextP mod Pi) ~ 0) and
(for all Pj in <Pi+1, ... ,Pn>, NextP mod Pj <> 0)
The last two terms of the assignment combine to:

644 ONE LAST SOLUTION


(for all Pj in <Pi, . .. , P:l> , _·e.x-2 - p . <> 0)
The resulting conditional assignmen ·
(Primes = <P1, ... ,Pi-:> & <?:.. • ... , ?_> and Tr ia l = Pi-1 and i>2 -
IsPrime :=
IsPrime and _·ex-....? =.od Tr · a ¥=- 0) and
(for a l l ? ~~ <?:.. , ... , Pn> , NextP mod Pj <> 0))
Composing the second a nd hird o · h he fi rst part of g yields empty functions. The
second part function of [!]~ _
e a e EndLi ~t t o . Trial and the third part function
equates these two va lues rn 1 do a _ Those states m wh1ch Trial=EndList are outside the
domain of the first part o~.
The second part of L!.Jis
composed wi h he second part of g below.

.
C ase 2
Condition IsPrime Trial Primes
Primes = IsPrime and EndList
<Pl, ... , Pn> & <> (NextP mod Trial ¥=- 0)
and Trial = Pn
and Trial ¥=- EndList
and n>=l
EndList = EndList

Condition:
Primes= <Pl, ... ,Pn> & <>and Trial=Pn and Trial¥=-EndList and n>=l
Trial=Pn implies Tr ial¥=-EndList so the latter condition can be omitted.
Assignrnent to IsPrime:
IsPrime and (NextP mod Trial ¥=- 0)
Thus the conditional assignment is
Primes= <Pl, ... ,Pn> & <>and Trial= Pn and n > 1 -
IsPrime :=
IsPrime and (NextP mod Trial ¥=- 0) and
(for all Pj in <Pn+l, ... ,Pn>, NextP mod Pj <> 0)
since the last conjunct is vacuously true. The remaining compositions of the third part of[!] with g
result in an empty function and an identity function with domain guard Tr ial=EndList.
The function resulting from this composition is:
(Primes= <P1, ... ,Pi-1> & <Pi, .. . , Pn> and Trial= Pi-1 and i > 2 -
IsPrime :=
IsPrime and (NextP mod Trial ¥=- 0) and
( Pj in <Pi, ... ,Pn>, NextP mod Pj ¥=- 0) )
(Primes= <Pl, ... ,Pn> & <>and Trial= Pn and n>1-
IsPrime :=
IsPrime and (NextP mod Trial ¥=- 0) and
( Pj in <Pi+l, . .. ,Pn>, NextP mod Pj ¥=- 0) }
(Trial = EndList - )
To convince ourselves that this function is the same as g, we observe the similarities between the
guards of the first two parts of this function.
1. P r imes is a list of integers separated into t wo parts, the first of wh ich contains at least one
eleme nt.
2. The value of Trial is the same as that of the last element in the first part of Primes, and
that value differs from EndList .
Thus the second part of the function is just a special case of the first, and the two can be combined

24.1.1 Developing and Proving the Solution 645


yielding a function identical to g.
The final design parts initialize Primes and print its final values.
Design Part 1.2
{N>l -+ Primes := <2>}
EmptyList(Primes);
Append(Primes,2);
Design Part 1.3
{print Primes}
Head(Primes,Trial);
WHILE Trial <> EndList
DO
BEGIN
WRITELN(Trial);
Next(Primes,Trial)
END
The assembled design parts are shown below.
PROGRAM PrintPrimes(OUTPUT);
CONST
N = 20;
MaxSize = 20;
EndList = 0;
TYPE
EltType = O .. MAXINT;
List = . . . ;
VAR
Primes: List;
NextP: EltType;
Trial: EltType;
IsPrime: BOOLEAN;
{include
PROCEDURE EmptyList(VAR L: List);
abs: L := <>
PROCEDURE Append(VAR L: List; Elt: EltType);
abs: (Length(L) < MaxSize --> L := L & <Elt>)
(Length(L) >= MaxSize --> )
PROCEDURE Head(VAR L: List; VAR Result: INTEGER);
abs: (L = <> --> Result := EndList) 1
(L = <Ll, ... ,Li-1> & <Li, ... ,Ln> -->
L, Result:= <Ll> ~ <L2, ... ,Ln>, Ll)
PROCEDURE Next(VAR L: List; VAR Result: INTEGER);
abs: (L = <Ll, ... ,Ln> & <>-->Result:= EndList)
(L = <Ll, ... ,Li-1> & <Li, ... ,Ln> -->
L, Result:= <Ll, ... ,Li> & <Li+l, ... ,Ln>, Li) }

646 ONE LAST SOLUTION


BEGIN {PrintPrimes}
{N>l -+ Primes := <2> }
EmptyList(Primes);
Append(Primes,2);
NextP := 3;
{3<=NextP<=N and Odd(NextP) -->
Primes :=Primes & OddPrimes(NextP, N)}
WHILE NextP <= N
DO
BEGIN
{IsPrime := (for all Pi in Primes, NextP mod Pi <> 0)};
IsPrime := TRUE;
Head(Primes,Trial);
{(Primes= <Pl, ... ,Pi> & <Pi+l, ... ,Pn>
and Trial = Pi and i>l -->
IsPrime := IsPrime and (NextP mod Trial <> 0} and
(for all Pj in <Pi+l, ... ,Pn>, NextP mod Pj <> 0}}
(Trial = EndList --> )}
WHILE (Trial <> EndList) AND IsPrime
DO
BEGIN
IsPrime := (NextP MOD Trial) <> 0;
Next(Primes,Trial)
END;
IF IsPrime
THEN
Append(Primes,NextP};
NextP := NextP + 2
END;
{print Primes}
Head(Primes,Trial);
WHILE Trial <> EndList
DO
BEGIN
WRITELN(Trial);
Next(Primes,Trial)
END
END. {PrintPrimes}

24.1.2 Verifying the Data Abstraction


The abstract type used to represent Primes is "list of integers with a reading pointer." Although
Pr intPr imes needs only a list of integers, we parametrize the TYPE declaration for List so that
its size or the type of its elements could later change.

24.1.2 Verifying the Data Abstraction 647


CONST
MaxSize = 20;
EndList = 0;
TYPE
EltType = 1 .. MAXINT;
List = RECORD
Values: ARRAY [1 .. MaxSize] OF EltType;
Size, Current: O .. MaxSize
END;
The representation function for List is:
Aust = {(s,t): t is thesame state ass except that in t List has value:
<L.Values[1], ... ,L.Values[L.Current]> &
<L.Values[L.Current+l], ... ,L.Values[L.Size]>
if O<=L. Current<=L. Size<=MaxSize; and t contains no members whose first
elements are L.Values, L.Current, or L.Size}
The operations of this type are EmptyList, Append, Head, and Next. Ini tList is the
special operation that produces List values.
PROCEDURE EmptyList(VAR L: List);
{abs: L := <>
con: L.Size, L.Current := 0, 0}
BEGIN {EmptyList}
L.Size := 0;
L.Current := 0
END; {EmptyList}
The correspondence between the body of EmptyList and its concrete comment is apparent.
Showing that the concrete comment is consistent with the abstract comment requires demonstrating
that
EmptyLista.bs C EmptyListcon o AList·
That is,
<> = <L.Values[1], ... ,L.Values[O]>
& <L.Values[1], ... ,L.Values[O]>
which verifies EmptyList.
Head is the special operation that carries List objects back to their INTEGER components
in the concrete world, and Next maps List objects to List objects in the abstract world.
PROCEDURE Head(VAR L: List; VAR Result: EltType);
{ abs: (L = <> - -> Result : = EndList) I
(L = <L1, .. ~,Li-1> & <Li, ... ,Ln> -->
L, Result:= <L1> & <L2, ... ,Ln>, L1)
con: (L.Size > 0 --> L.Curr~nt, Result := 1, L.Values[1])
(L.Size <= 0 --> L.Current, Result := 1, EndList) }
BEGIN {Head}
IF L.Size > 0
THEN
BEGIN
L.Current := 1;
Result := L.Values[1]
END
ELSE
Result := EndList
END; {Head}

648 ONE LAST SOLUTION


PROCEDURE Next(VAR L: Lis t; VAR Resul t: EltType);
{abs: (L = <Ll, ... ,Ln> & <>-->Result:= EndList)
(L = <L1, ... ,Li- 1 > & <Li • ... , Ln> -->
L, Result := <L1, . • . • Li> & <Li+1, ... ,Ln>, Li)
con: (l<=L.Current<L . Si ze -->
L.Current,Resul t : = L . Current+1,L.Values[L.Current+1]) 1
(L.Current < 1 o r L .Cu rrent . <= Size--> Result:= EndList)}
BEGIN {Next}
IF (1 <= L.Current) AND (L.Current < L.Size)
THEN
BEGIN
L.Current := L.Current + 1;
Result := L.Values[L.Current]
END
ELSE
Result := EndList
END; {Next}
Again the correspondence between the bodies of Head and Next and their concrete comments are
clear. Correctness of the implementations requires:
AList o Headabs C Headcon o AList
and
AList o Nextabs C Nextcon o AList·
Only the first proof will be given.

AList o Headabs
Condition L Result
O<L.C<L.S< MaxSize <L.V[l], ... ,L.V[L.C]> &
<L . VrL.C+11 ... L.VrL.Sl>
<L. V [1], . .. , L. V [L. C] > & EndList
<L.V[L.C+l], ... ,L.V[L.S]>
= <>
The condition evaluates to TRUE when
<L.V[l], ... ,L.V[L.C]> =<>AND
<L.V[L.C+1], ... ,L.V[L.SJ> = <>
Picking L . S and L. C to be 0 achieves this result. Thus the function is
MaxSize>=O AND L . S=O AND L.C=O -+ L,Result := <>,EndList

AList 0 Headabs
Condition L Result
o<L.C<L.S<
- - - MaxSize
<L.V[1], ... ,L.V[L.CJ> &
<L.V[L.C+1], . . . ,L.V[L.S]>
=
<L1, ... ,Li-1> & <Li, .. . ,Ln> <L1> & <L2, .. . ,Ln> L1

This condition is TRUE when i>1 (i.e., L. s>L. c>1) and L. V [1] =L1, • • • I L. V [L. SJ =Ln.
Thus the function is

24.1.2 Verifying the Data Abstraction 649


(MaxSize>L.S>L.C>l-+ L, Result:= <L1> & <L2, ... ,Ln>, L1
The composition AList o Heada.bs yields
(MaxSize>=O AND L.S=O AND L.C=O -+ L,Result := <>,EndList) I
(MaxSize>=L.S>=L.C>=1-+ L,Result := <L1> & <L2, ... ,Ln>, L1)

Headcon o AList 1 L. C I Result


Condition L
L.S>O 1 I L.Vr11
0<1<L.s< MaxSize <L.V[1], ... ,L.V[L.C]> &
<L.V[L.C+1], ... ,L.V[L.S]>
Thus the function is
(MaxSize>=L.S>=1 -+
L,Result := <L.V[1]> & <L.V[2], ... ,L.V[L.S]>, L.V[l])

Headcon o AList
Condition L L.C Result
L.S<=O EndList
o<L. c<L. s< MaxSize <L.V[l], ... ,L.V[L.C]> &
<L.V[L.C+1], ... ,L.V[L.S]>
Since the condition requires L. S<=O and L. S>=O, it must be the case that L. S=O, and hence
L. C=O, so the list
<L.V[1], ... ,L.V[L.C]> & <L.V[L.C+1], ... ,L.V[L.S]>
must be empty. Thus the function is
MaxSize>=O AND L.S=O AND L.C=O -+ L,Result := <>,EndList
The composition Headcon o AList yields
(MaxSize>=L.S>=1 -+
L,Resu1t := <L.V[l]> & <L.V[2], .. . ,L.V[L.S]>, L.V[1])
(MaxSize>=O AND L.S=O AND L.C=O -+ L,Result := <>,EndList)
This composition yields the same results as the previous composition, but on a slightly larger domain
since it places no restriction on the initial value of L. C.
Append maps a List and an El tType value to a List value.
PROCEDURE Append(VAR L: List; Elt: EltType);
{abs: (Length(L) < MaxSize --> L := L & <Elt>)
(Length(L) >= MaxSize --> )
con: (L.Size < MaxSize --> L.Values, L.Size :=
alpha(L . Values,L.Size+1,Elt), L.Size + 1)
(L.Size >= MaxSize --> ) }
BEGIN {Append}
IF L.Size < MaxSize
THEN
BEGIN
L.Size := L.Size + 1;
L.Values[L.Size] := Elt
END
END; {Append}

650 ONE LAST SOLUTION


24.1.3 Integrating and Testing the Design Parts
The test plan for Pr intPr imes should cover not only the program but the abstract data type
List defined within it. The data abstraction is potentially reusable, so its testing will be
independent of Pr intPr imes.
To test the data abstraction functionally, we should consider the possible logical sequences of
~he operations: EmptyList, Append, Head, and Next. For example, to check valid
combinations of operations we might:
1. build a list,
2. sequence through a list,
3. go back to the head of the list after partially sequencing through the list (after a Head or a
Next),
4. append an element to the list while sequencing through it (after a Head or a Next),
5. empty the list after sequencing through or building it.
These operations should be considered with at least three different kinds of list values: an empty list,
a partially full list, and a full list (since we have chosen an array implementation). Some other
combinations of operations are not legal and are not tested. For example, applying the Next
operation to a list without executing the Head operation first will cause an error.
To check that all cases are covered, consider all combinations of operations, noting whether
they are legal or illegal combinations and whether the combination can appear in the program
PrintPrimes. We must at least cover those that appear in PrintPrimes.

Case Operations Legality PrintPrimes?


1 EmptyList;EmptyList legal no
2 EmptyList;Append legal yes
3 EmptyList;Head legal no
4 EmQty:List;Next illegal no
5 Append;EmptyList legal no
6 Append; Append legal no
7 Append;Head legal yes
8 AEQend;Next illegal no
9 Head;EmptyList legal no
10 Head; Append legal no
11 Head; Head legal no
12 Head; Next legal yes
13 Next;EmptyList legal no
14 Next; Append legal no
15 Next;Head legal yes
16 Next; Next legal yes

To test the data abstraction properly so that it can be reused, we should at least check the possible
combinations of operations listed above on three representative list values: an empty list, a partially
full list, and a full list. To make testing easier, the maximum list size is temporarily restricted to
four elements.

24.1.3 Integrating and Testing the Design Parts 651

••
.. ..
:-·•, i ~ '

• •
PROGRAM TestList(OUTPUT);
CONST
MaxSize = 4; {short lists for now}
EndList = 0;
TYPE
EltType = O .. MAXINT;
List = ...
VAR
L: List;
NextP: EltType;
Trial: EltType;
{include
PROCEDURE EmptyList(VAR L: List);
abs: L := <>
PROCEDURE Append(VAR L: List; Elt: EltType);
abs~ (Length(L) < MaxSize --> L := L & <Elt>)
(Length(L} >= MaxSize --> )
PROCEDURE Head(VAR L: List; VAR Result: INTEGER);
abs: (L = <> --> Result := EndList) 1
(L = <L1, ... ,Li-1> & <Li, ... ,Ln> -->
L, Result:= <L1> & <L2, ... ,Ln>, L1}
PROCEDURE Next(VAR L: List; VAR Result: INTEGER);
abs: (L = <L1, ... ,Ln> & <>-->Result:= EndList)
(L = <L1, ... ,Li-1> & <Li, ... ,Ln> -->
L, Result:= <L1, ... ,Li> & <Li+1, ... ,Ln>, Li)
PROCEDURE Test(VAR L: List); }
BEGIN {TestList}
EmptyList(L);
WRITELN('testing <>');
Test(L};
EmptyList(L);
Append(L,1); {case 2 EmptyList; Append}
Append(L,2); {case 6 Append; Append}
WRITELN('testing <1,2>');
Test·(L);
EmptyList(L);
Append(L,1};
Append(L,2);
Append(L,3);
WRITELN('testing <1,2,3>');
Test(L)
END. {TestList}
Procedure Test shown below covers most legal combinations of operations except those ending with
EmptyList. Note that there are two different methods used to write list elements: sequencing
through the list using Next and printing the array elements directly. We do this so that the list
elements can be displayed without changing the last operation executed .

652 ONE LAST SOLUTION

.
PROCEDURE Test(VAR L: List);
VAR
Temp: EltType;
I: INTEGER;
BEGIN {Test}
WRITE( 1 enter test, 1= < 1 ) ;
FOR I := 1 to L.Size
DO
WRITE(L.Values[I]: 4)
WRITELN (I >I) ;
Head(L,Temp);
WRITELN( 1 (emptylist or append); head = 1 Temp);
Head(L,Temp);
WRITELN( 1 head; head = 1 , Temp);
WRITE( 1 full sequence: head; next**i, i>=O: < 1 ) ;
WHILE Temp <> EndList
DO
BEGIN
WRITE(Temp: 4);
Next(L,Temp)
END;
WRITELN (I >I) ;
Head(L,Temp);
WRITELN( 1 next**i; head = 1 Temp);
IF Temp <> EndList
THEN
BEGIN
Next(L,Temp);
WRITELN( 1 part sequence: head; next = 1 Temp)
END;
Head(L,Temp);
WRITELN( 1 head; (next or null);head -~ Temp);
WRITE( 1 head; append(lS) = < 1 ) ;
Append(L,l5);
FOR I := 1 to L.Size
DO {write the list elements without Head or Next}
WRITE(L.Values[I]: 4)
WRITELN( 1 > 1 ) ;
Head(L,Temp);
IF Temp <> EndList
THEN
BEGIN
WRITE( 1 next; append(25) = <');
Next(L,Temp);
Append(L,25);
FOR I : = 1 to L.Size
DO
WRITE(L.Values[I]: 4)
WRITELN(' >')
END ;
Head(L,Temp);
WRITELN('append; head=' Temp);
WRITE('full sequence: head; next**i, i>=O: <');
WHILE Temp <> EndList

24.1.3 Integrating and Testing the Design Parts 653


DO
BEGIN
WRITE(Temp: 4);
Next(L,Temp)
END;
WRITELN (I >I) ;
WRITELN
END; {Test}
Sample output for the three tests and the cases covered are shown below.

Test Results
Output Case
testing <>
enter test, 1= < >
(emptylist or append); head= 0 3
head; head = 0 11
full sequence: head; next**i, i>=O: < >
next**i; head = 0 11
head; (next or null);head = 0 11
head; append (15) = < 15 > 10
next ; append(25) = < 15 25 > 14
append ; head = 15 7
full sequence : head; next ** i, i>=O: < 15 25 > 12,16
testing <1,2>
enter test, 1= < 1 2 >
(emptylist or append) ; head = 1 7
head; head = 1 11
full sequence: head ; next* * i, i >=O : < 1 2 > 12,16
next**i; head = 1 15
part sequence: head; next = 2 12
head; (next or null);head = 1 12,15
head; append (15) = < 1 2 15 > 10
next; append (25) = < 1 2 15 25 > 8,14
append; head = 1 7
full sequence: head; next** i, i>=O : < 1 2 15 25 > 12,16
testing <1,2,3>
enter test, 1= < 1 2 3 >
(emptylist or append); head = 1 7
head; head = 1 11
full sequence: head; next** i, i>=O: < 1 2 3 > 12,16
next**i; head = 1 15
part sequence: head; nex t = 2 12
head; (next pr null);head = 1 12,15
head; append (15) = < 1 2 3 15 > 10
next; append (25) = < 1 2 3 15 > 8,14
append; head = 1 7
full sequence: head; next* * i, i>=O: < 1 2 3 15 > 12,16

We act as test oracles judging that each Head or Next operation returns the correct result. We
also note that appending two elements to an empty list yields a 2-list, to a 3-list yields a 4-list (the
last Append is ignored), and t o a 4-list leaves t he list unchanged. Our test program fails to cover
sever al simple cases. Cases 2 and 6 are covered in t he body of the test program when the list objects
are constructed. C ases 1, 5, 9, and 13 all concern setting an object t o the EmptyList aft er ot her

654 ONE LAST SOLUTION


operations have been performed on it, and is best left for another test program.
Now consider the test data from the point of view of some structural coverage criterion.
Suppose the criterion was to check whether the tests execute every statement in the List
operations. These tests satisfy it because the List operations have simple implementations and the
criterion is weak. To satisfy a more realistic criterion would require more combinations of
operations.
PrintPrimes is a small program and its design parts may be integrated in two development
programs. Design Parts 1-1.1.1.1, 1.2, and 1.3 could be integrated at once and Design Part 1.1.1.1.1
{IsPrime := (For all members P of Primes, NextP mod P <> 0)}
could be replaced with the assignment statement
IsPrime := TRUE;
to produce the first development program that printed a list containing 2 followed by all the odd
numbers less than or equal to N. The second development program would integrate the final two
design parts needed to produce the appropriate value for I sPr ime.
To test the program Pr intPr imes structurally, we should pick interesting values for N such
that N>=l. Thus we should set N to its minimum .value (2), the value of the MaxSizeth (i.e., 20th)
prime (71), some intermediate prime (e.g., 29) and non-prime (e.g., 30) values, and a value greater
than the MaxSizeth prime that includes other primes (e.g., 100). Any value of N greater than 2
will cause each of the statements in the body of PrintPrimes to be executed. Since 3 is prime, it
will exercise both inner WHILE statements and the Append.

24.2 Chapter Summary


In this chapter, we have summarized many of the program development and analysis techniques
presented in the text. Both stepwise refinement and data abstraction were used to produce the
solution. Analysis of the program proceeded in parallel with the development to make certain that
each design part computed a function that was consistent with its specification. In trying to make
the analysis task "scale up" to more complex problems, we ignored variables that were used for
purely local computation and dealt with a data type (List) with nice mathematical properties
instead of manipulating arrays directly (which would have added unnecessary detail to the proof).
Testing proceeded in two stages: the data abstraction was tested alone, and then the design
parts of the program were combined into development programs so that intermediate results could
be checked. Both functional and structural testing criteria were used to select test data and
evaluate how thoroughly the data exercised the pieces of the program.

24.2 Chapter Summary 655

·~·--: f~ ' ~ '


.:;:... '
.. . .
....~
' . .,
. .
Appendix 1: Collected Syntax for CF Pascal

Syntax Rules
A string is a CF Pascal program (subject to the restrictions on standard words given in Appendix II
and the context rules given below) if and only it it can be obtained by reading the leaves from a
syntax tree rooted at <program> from left to right and inserting blanks and comments
appropriately . Tlie syntax trees for CF Pascal are constructed using the following rules.
SRl. <program> ::= <heading> ; <block> .
SR2. <heading>::= PROGRAM <identifier> (INPUT, OUTPUT)
SR3. <identifier> ::= <letter>
I <identifier> <letter>
I <identifier> <digit>
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
SR4 · <letter> ··-A .. - I B I C I DI E I F I G IH I I I J I KI L IM I NI 0 I P I Q I R I S I T I
1 1 1 1 1 1 1 1 1 1 1
U I V I W1I X I Y I Z I a I b I c I d I e I f I g 1I h 1I i 1I J' 1I k 1I 1 1I m 1I n I1
o 1I p 1I q 1I r 1I s 1I t 1I u 1I v 1I w 1I x 1I y 1I z
SR5. <digit> ::= 0 ll I 2 I 3 I 4 I 5 I 6 I 7 I 8 I 9
SR6. <block> ::= <BEGIN statement>
I <declarations> ; <BEGIN statement>
I <procedure list> ; <BEGIN statement>
I <declarations> ; <procedure list> ; <BEGIN statement>
SR7. <declarations> ::= VAR <identifier list> : <type>
I <declarations> ; <identifier list> : <type>
SR8. <identifier list> ::= <identifier>
I <identifier list> , <identifier>
SR9. <BEGIN statement> ::= BEGIN <statement list> END
SRIO. <statement list> ::= <statement>
I <statement list> ; <statement>
SRll. <statement> ::= <READ statement>
<WRITE statement>
<assignment statement>
<null statement>
<BEGIN statement>
<IF statement>
<WHILE statement>
<procedure statement>
SR12 . <READ statement> ::=READ ( <identifier list> )
I RESET ( <identifier> ) I READLN ( <identifier list> )
SR13. <WRITE statement> ::=WRITE ( <write list> )
I WRITELN ( <write list> )
IWRITELN
I REWRITE ( <identifier> )
SR14. <write list> ::=<write item>
I <write list> , <write item>
SR15. <write item>::= <identifier>
I 1 <character string> 1
SR16. <character string> ::= <character>
I <character string> <character >

656 Appendix 1: Collected Syntax for CF Pascal


SR17. <character>::= <letter> l < digit> l <special character>
SR18. <special character> ::= ( : ) : = : { : } : : l ; : '
1°:<1>1, l · li
SR19. <assignment statement > :: = <ident ifier> : = <expression>
SR20. <expression> ::= <identifier>
: ' <character> '
SR21. <null statement> ::=
SR22. <IF statement> ::= IF <condition> THEN <statement>
:IF <condition> THEN <statement> ELSE <statement>
SR23. <condition> ::=<expression> <comparison> <expression>
: NOT ( <condition> )
: ( <condition> ) AND ( <condition> )
l ( <condition> ) OR ( <condition> )
l EOF : EOF ( <identifier> ) l EOLN l EOLN ( <identifier> )
SR24. <comparison> ::= < : = l > l <= : <> : >=
SR25. <WHILE statement>::= WHILE <condition> DO <statement>
SR26. <procedure list> ::= <procedure>
: <procedure list> ; <procedure>
SR27. <procedure>::= <procedure heading> ; <block>
SR28. <procedure statement> ::= <identifier> l
<identifier> ( <actual parameter list> )
SR29. <type> ::= CHAR I TEXT
SR30. <actual parameter list> ::= <identifier list>
SR31. <procedure heading> ::=PROCEDURE <identifier> l
PROCEDURE <identifier> ( <formal parameter list> )
SR32. <formal parameter list> ::= VAR <identifier list> : <type> l
<formal parameter list> ; VAR <identifier list> : <type>

Context Rules
In addition to the syntax rules, CF Pascal programs must satisfy the following context rules.
CRl. All identifiers in a declaration's identifier list must be unique.
CR2. No reserved word, e.g. IF, WHILE, etc. can be used as an identifier. (A list of the
reserved words in Pascal is given at the end of the next Section.)
CR3. Any <identifier> in a <statement> that is not a <procedure statement>, other than
standard identifiers (e.g., READ, WRITE, etc.) must appear in the <identifier list> of the
<declarations>.
CR4. The procedure identifier used in a <procedure statement> must appear in a
<procedure heading> of a <procedure> declaration. The <actual parameter list> of a
<procedure statement> must agree in length with the <formal parameter list> of the
associated <procedure heading>. The identifiers in the <formal parameter list> and the
<actual parameter list> must agree in type.
CRS. Variable and procedure names can't be the same <identifier>.

Context Rules 657

1.,..~ .... .
., ~
~.·

------

CR6. A TEXT identifier is an <identifier> declared to be of <type> TEXT. If a TEXT


identifier appears in a <READ statement> or a <WRITE statement>, it must be the first
<identifier>. A TEXT identifier, other than INPUT or OUTPUT, that appears in a READ
statement or WRITE statement must appear in a <declarations>. Only TEXT identifiers
other than INPUT and OUTPUT may appear in RESET or REWRITE statements; only TEXT
identifiers that have appeared in a RESET statement may appear in EOF or EOLN
<condition>s. Any identifier declared in a procedure's <formal parameter list> may only be
referenced in the <BEGIN statement> of the procedure.
CR7. An identifier may not be used outside its scope.

658 Appendix 1: Collected Syntax for CF Pascal


Appendix II: STANDARD PASCAL WORDS

Reserved Words
Although the standard Pascal word PROGRAM appears to be an <identifier>, it may not be used as
one in forming Pascal programs. PROGRAM is a reserved word in Pascal, and its use is restricted to
the places it appears literally in a sy ntax rule (only in a <heading>). The reserved words of Pascal
are listed in the following t able. None of these words may be used as an <identifier> in CF Pascal
or in full Pascal.

Reserved Words in Pascal


AND END NIL SET
ARRAY FILE NOT THEN
CASE FUNCTION OR TYPE
CONST GOTO PACKED UNTIL
DIV IF PROCEDURE VAR
DO IN PROGRAM WHILE
DOWNTO LABEL RECORD WITH
ELSE MOD REPEAT

Standard Identifiers
Some standard Pascal words (for example , INPUT, OUTPUT, WRITE) are not reserved, but
nevertheless should be avoided . They are standard identifiers that are by default attached to some
special purpose. The standard identifiers of Pascal are:

Standard Identifiers in Pascal


ABS GET PAGE SQR
ARCTAN INPUT PRED SQRT
BOOLEAN INTEGER PUT succ
CHAR LN READ TEXT
CHR MAXI NT READLN TRUE
cos NEW REAL TRUNC
EOF ODD RESET UNPACK
EOLN ORD REWRITE WRITE
EXP OUTPUT ROUND WRITELN
FALSE PACK SIN

Standard Identifiers 659


Appendix ill: Syntax of Pascal

In alphabetical order by left side:


<actual parameter> ::= <expression> l <procedure identifier> l <function identifier>
<actual parameter list> ::= <actual parameter list> , <actual parameter>
l <actual parameter>
<actual parameter list> ::= ( <actual parameter list> )
<add operator> ::;:::: + l - l OR
<array variable> ::=<variable access>
<assignment statement> ::= <identifier> : = <expression>
<base type> ::= <ordinal type>
<BEGIN statement>::= BEGIN <statement list> END
<block> ::= <label declaration part> <constant definitions>
<type definition part> <variable declarations>
<proc/func declarations> <BEGIN statement>
<buffer variable> ::= <file variable> t
<case> ::= <constant list> : <statement>
<case list> ::= <case list> ; <case> l <case>
<case statement> ::=CASE <expression> OF <case list> END
<character> ::= <letter> l <digit> I <special character>
<character string> ::= <character> I <character string> <character>
<comparison>::=< I= I> I<= I<> I>=
<component type> ::= <type denoter >
<component variable> ::= <field designator> I <indexed variable>
<condition> ::= <expression> <comparison> <expression> I NOT ( <condition> )
I ( <condition> ) AND ( <condition> ) I ( <condition> ) OR ( <condition> )
I EOF I EOF ( <identifier> ) l EOLN I EOLN ( <identifier> )
<constant> ::= <sign> <unsigned number> I <unsigned number>
I <sign> <constant identifier> I <constant identifier> I <character string>
<constant definition> ::= <identifier> = <constant> ;
<constant definition part> ::= CONST <constant definitions> I
<constant definitions> ::=<constant definitions> <constant definition> l <constant definition>
<constant identifier> ::= <identifier>
<constant list> ::= <constant list> , <constant> I <constant>
<digit> ...·-
- 0 II 1 II .2 II 3 II 4 II 5 II 6 II 7 II 8 II 9
<digit sequence> ::= <digit sequence> <digit> l <digit>
<enumerated type> ::= ( <identifier list> )
<expression> ::=<simple expression>
I <simple expression> <relational operator> <simple expression>
<expression list> ::= <expression list> , <expression> I <expression>
<factor>::= <variable access> l <function designator> l <unsigned constant>
I ( <expression> ) I NOT <factor> l <set constructor>
<field designator> ::= <record variable> . <field specifier> l <field designator identifier>
<field designator identifier> ::= <identifier>
<field list> ::=<fixed part> <variant part> l <fixed part> l <variant part> l
<field specifier> ::= <identifier>

660 Appendix Ill: Syntax of Pascal


<file variable> ::=<variable access>
<fixed part> ::= <record sections>
<for list> ::= <expression> TO <expression> I <expression> DOWNTO <expression>
<for statement>::= FOR <variable iden tifier>:= <for list> DO <statement>
<formal parameter>::= <value parameter > I <variable parameter> I <procedural parameter>
<formal parameter list> ::= ( <formal parameters> ) I
<formal parameters> ::= <formal parameters> ; <formal parameter> I <formal parameter>
<function designator> ::= <function identifier> <actual parameter list>
<function identifier> ::= <identifier>
<goto statement> ::= GOTO <label>
<heading> ::=PROCEDURE <identifier> <formal parameter list>
I FUNCTION <identifier> <formal parameter list> : <result type>
<identified variable> ::= <pointer variable> t
<identifier> ::= <letter> I <identifier> <letter> I <identifier> <digit>
<identifier list> ::= <identifier> I <identifier list> , <identifier>
<IF statement>::= IF <condition> THEN <statement>
I IF <condition> THEN <statement> ELSE <statement>
<index type list> ::= <index type list> , <ordinal type> I <ordinal type>
<indexed variable> ::=<array variable> [ <expression list> ]
<label> ::= <digit seq-Jence>
<label declaration part> ::= LABEL <label list> ; I
<label list> ::= <label list> , <label> I <label>
<letter> ::=A I B I C I D I E I F I G I H I I I J I K I L I M I N I 0 I P I Q I R I S I T I U IV I W I X I Y I Z
lalblclctlelflglhliljlklllmlnlolplqlrlsltlulvlwlxlylz
<member designator> ::= <expression> I <expression> .. <expression>
<member designators> ::= <member designators> , <member designator> l
<mult operator> ::= * I DIV I MOD I AND
<new ordinal type> ::= <enumerated type> l <subrange type>
<new pointer type> ::= t <type identifier>
<new structured type> ::= <unpacked structured type>
I PACKED <unpacked structured type>
<new type> ::= <new ordinal type> I <new structured type> I <new pointer type>
<null statement> ::=
<optional tag field> ::= <identifier> : I
<ordinal type> ::= <new ordinal type> I <ordinal type identifier>
<ordinal type identifier> ::= <type identifier>
<pointer variable> ::=<variable access>
<procedural parameter> ::= <heading>
<procedure statement> ::= <identifier> I <identifier> ( <actual parameter list> )
<proc/func declaration> ::=<heading> ; <block> ;
<proc/func declarations> ::= <proc/func declaration>
I <proc/func declarations> <proc/func declaration>
<program> ::= <program heading> ; <block> .
<program heading> ::=PROGRAM <identifier> ( <identifier list> )
<READ statement> ::=READ ( <identifier list> )

Appendix Ill: Syntax of Pascal


I RESET ( <identifier> ) I READLN ( <identifier list> )
<record section> ::= <identifier list> : <type denoter >
<record sections> ::= <record sections> ; <record section> I <record section>
<record variable> ::=<variable access>
<record variable list> ::=<record variable list> , <record variable> I <record variable>
<relational operator> ::= = I <> I < I <= I > I >= I IN
<repeat statement> ::=REPEAT <statement list> UNTIL <expression>
<repetitive statement> ::= <WHILE statement> I <repeat statement> I <for statement>
<result type> ::= <simple type identifier> l <pointer type identifier>
<set constructor> ::= [ <member designators>]
<sign> ::= + I -
<signed integer> ::= <sign> <unsigned integer> I <unsigned integer>
<simple expression> ::= <simple expression> <add operator> <term> I <term> I <sign> <term>
<simple statement> ::= <assignment statement> I <procedure statement> I <READ statement>
I <WRITE statement> I <null statement> I <goto statement> I <case statement>
<special character> ::= ( I ) I = I { I } I : I ; I ' I D I < I > I , I · I #
<statement>::= <unlabeled statement> I <label> : <unlabeled statement>
<statement list> ::= <statement list> ; <statement> I <statement>
<structured statement> ::= <BEGIN statement> I <IF statement>
. '
l <repetitive statement> I <with statement>
<subrange type> ::= <constant> .. <constant>
<term>::= <term> <mult operator> <factor> I <factor>
<type definition>::= <identifier>= <type denoter> ;
<type definition part> ::= TYPE <type definitions> I
<type definitions> ::=<type definitions> <type definition> I <type definition>
<type denoter > ::= <type identifier> I <new type>
<type identifier> ::= <identifier>
<unlabeled statement> ::= <simple statement> I <structured statement>
<unpacked structured type> ::=SET OF <base type> I FILE OF <component type>
I RECORD <field list> END I ARRAY [ <index type list> ] OF <component type>
<unsigned constant>::= <unsigned number> I <constant identifier> I <character string>
<unsigned integer> ::= <digit sequence>
<unsigned number> ::= <unsigned integer> l <unsigned real>
<unsigned real> ::= <unsigned integer> . <digit sequence>
I <unsigned integer> • <digit sequence> E <signed integer>
I <unsigned integer> E <signed integer>
<value parameter>::= <identifier list> : <type identifier>
<variable access> ::=<variable identifier> I <buffer variable>
I <component variable> I <identified variable>
<variable declaration> ::=<identifier list> : <type denoter >
<variable declaration part> ::= VAR <variable declarations> ; I
<variable declarations> ::=<variable declarations> <variable declaration>
l <variable declaration>
<variable parameter> ::= VAR <identifier list> : <type identifier>
<variant> ::=<constant list> : ( <field list> )
<variant list> ::=<variant list> ; <variant> l <variant>
<variant part>::= CASE <variant selector> OF <variant list>

662 Appendix Ill: Syntax of Pascal


<variant selector>::= <optional tag field> <ordinal type identifier>
<WHILE statement> ::=WHILE <condition > DO <statement>
<with statement> ::=WITH <record variable list> DO <statement>
<write item> ::=<identifier> l ' <character string> '
<write item list> ::=<file variable> <write parameters> l <write parameters>
<write list> ::=<write item> I <write list> , <write item>
<write parameter> :: = <expression> : <expression> l <expression>
<write parameter list> ::= ( <write item list> )
<write parameters>::= <write parameters> <write parameter> l <write parameter>
<WRITE statement> ::=WRITE ( <write list> )
I WRITELN ( <write list> ) l WRITELN I REWRITE ( <identifier> )

Appendix Ill: Syntax of Pascal 663


INDEX

ABS (integer or real function) 429, 595 operators 99, 379


abstract values 99, 379
data objects 349, 483, 485, 569 . box notation 160
operation 350, 485 buffer variable 442
operation correctness 500
alias 246 canonical form 489
AND (Boolean operator) 101 CASE statement 467
meaning 193 meaning 470
annotated output 21 CHAR data type 15
appearance rule 82 constants 20, 143
ARCTAN (real function) 595 CHR (character function) 430
arithmetic operators closed statement 561
(+- *I DIV MOD) 386, 594 collisions 533, 631
ARRAY data type 513 comment 15, 62
array-value relation 518 abstract 489
analysis of programs 543 concrete 489
declaration meaning 518 include 255
dimension 538 commutativity
expression meaning 518 operators 104
operations 513 diagrams 501
PACKED attribute 533 compiler 7
parameters 519 composition
representing functions 513 function, relation (o) 166, 222
representing other types 521, 549 power notation (rn) 216
searching and sorting 525 composition
assignment rule 188 list, string (9) 145, 149
assignment statement 20 con eaten at ion
accumulating 406 list, string(&) 144, 149
array 513, 543 concordance
meaning 173, 401, 519, 543 concrete
associativity 104, 381 data 349, 484
authentication 372 part function 350
concurrent assignment 180
BEGIN statement 8, 26, 184 conditional assignment 197, 339
design rule 187, 403 comparison 204
meaning 175 com position ( o) 222
bisection 620 conditions (see Boolean)
block 51 CONST declarations 415, 613
meaning 170 context rules 51
BNF (Backus-Naur Form) 46, 273, 334 control variable 472
BOOLEAN copy rule 82
condition rule 231 correctness of module 500
conditions 23, 99, 105, 192 Correctness Theorem 336
constants 192, 379 COS (real function) 595
data type 379 cursor 16
expressions 102
expression meaning 192, 383 dangling reference 551
identities 103 data type 15, 372
input/ output 384 abstract 342, 483, 549, 569

Index 8GS·
aggregate 373, 436 Fibonacci number 409
ordinal 373, 594 field 457
simple 373 tag 628
declaration 15 file data type 442, 521
array 518 operations 443
constant 415 meaning of declaration 169, 443
extent (§) 252 TEXT (see TEXT file)
meaning of V AR 168 finite state machine 352, 443
meaning of procedure 170, 325 floating point notation 593
meaning of file 443 FOR statement 471
scope 252, 461, 620 meaning 474
type 373, 417 analysis 477
deMorgan's laws 104 format directives 389, 595
dereference operation FORWARD declarations 433
(1 on pointer value) 560 function 157, 370
design part 68 domain 157
development program 68 composition (o) 166
DISPOSE (pointer operation) 560, 630 constant 157
distribu tivity 104 empty ( {}) 157
DIV (integer operator) 386-387 identity (I) 157
division (/) (real operator) 594 inverse 168, 403
DO Part rule 231 power notation (r 0 ) 216
domain of a function or relation 157 range 157
domain of an array 513 transpose (rT) 168
undefined 157
echoed input 21 value notation 157
encapsulation 344 zero of a function 620
enumerated data type 373 FUNCTION
meaning 375 declaration 423, 569
input/output 375 meaning 424
EOF 131, 390, 456 parameter 616
meaning 192, 444 predefined 426
EOLN 130, 456
meaning 192 garbage 563
equality symbol (=) GCD 492, 494, 583
(see relational operators) (greatest common divisor)
execution GET statement
aborted 20 meaning 444
conditional 23, 92 global references 251, 256, 619
endless 32 GOTO statement 573
iterative 30 and compound statements 579
normal20 guarded assignment(-+) 198
sequential 10, 163
symbolic 182 harmonic series 486, 570
state 164, 561 ha.sh
table 11 code 532
existence rule 231, 404 function 631
EXP (real function) 595 table 631
exponent 593 head of a string or list (8) 146, 149
exponentiation 595 Horner's rule 397, 608
host type 401
factorial 409

888 Index
---------
.,.
. '

. '

identifier 8 number
identity function 157, 199 base 261, 397
IF statement 25, 28 conversion 397
design rule 210 natural 261
meaning 195
implementation diagrams 501 observable values 609
IN (set membership operator) 436 ODD (integer function) 429
include comment 255 open statement 561
induction 321 operators
information hiding 344 associative 104, 381, 387
INTEGER data type 386 binary 100
constants 386 commutative 104
expression meaning 396 distribution 104
operators 386 infix 100
input/output 389 precedence 382, 387
inverse of function, relation 168, 403 prefix 99
unary 99
label 573 OR (Boolean operator) 101
lazy evaluation 379, 455 meaning 193
lexicographic order 254, 535 ORD (integer function) 430
line marker (/) 11, 130 overflow 392, 403, 492
linked structures 548, 564
list 148, 548, 564 PACK (array operation) 533
composition (V) 149 PAGE statement 448
concatenation (&) 149 parameters 241, 418, 420
empty ( < >) 149 actual 242
membership (E) 148 array 519
sublists 149 formal 242
LN (real function) 595 FUNCTION 616
local references 243, 249, 256 PROCEDURE 616
locator value (of pointer) 559 value 420, 461, 535
value parameter meaning 421
machine VAR 242,420
Pascal 7 V AR parameter meaning 245
state 342, 351 perfect results (real operation) 608
mantissa 593 period meaning 166
matrix 538 pointer data type 559
product 539 locator value 559
MAXINT 386 operations 560
membership in list, set (E) 148, 154 representing other types 569
MOD (integer operator) 387 target value 560
module 342, 344, 483 to variant records 630
precedes symbol ( <) 23
NEW (pointer operation) 560, 630 (see relational operators)
NIL (pointer constant) 560 PRED (ordinal function) 426
nonlocal references 251, 256, 619 preservation rule 188, 403
NOT (Boolean operator) 100 prime numbers 439
meaning 193 procedure declaration 78, 241
not ·equal symbol ( < >) meaning 170
(see relational operators) procedure parameter 616
null statement 27, 199 procedure statement 79, 241
meaning 175 meaning 179, 258, 325, 421

Index OM
recursive verification rule 326, 331 24, 25, 192-194, 594
program 7 relative errors (in real expressions) 608
analysis 404 REPEAT statement 480
correctness 335, 500 meaning 481
libraries 239, 255, 269 replication 372
meaning 142, 159 representation function 349, 484, 502
specification 239, 333, 339 representational errors
structured 560 (for real values) 607
unstructured 560 reserved word 51
program header 8 RESET statement 110, 448
meaning 165 meaning 177, 444
proof 337 REWRITE statement 110
by contradiction 234 ROUND (real function) 594
by induction 321 meaning 176, 443
PUT statement roundoff errors (in real expressions) 606
meaning 444
scaling factor 593
quasi-random number 599 scientific notation 593
question mark (?) 16 scope of identifiers 252, 461, 620
queue search 525
FIFO 354 binary 526
priority 370, 598 depth-first 559, .567
hash 532, 631
range of a function or relation 157 sequential 526
rational numbers 486, 570 sentinel 391, 526
READ statement 16, 19, 110, 447 set 153, 370
meaning 177 builder notation 154
READLN statement 131 constant 153, 436
meaning 177 data type 435
REAL data type 593 difference(-) 155, 436
constants 593 empty ( {}) 154
errors analysis expression meaning 438
input/output 595 intersection (nor*) 155, 436
operations 594 membership ( E or IN) 154, 436
RECORD data type 457, 483 power set 155, 436
meaning 459 singleton 154
variant part 627 subset (C or <=) 154, 436
recurrence equation 221, 326 superset(>=) 436
recursion 239, 308 union (U or +) 155, 436
in definition 47, 51, 382 side effects 425
mutual 433 simulation 597
tail 313 SIN (real function) 595
relation 156, 370 sort
composition (o) 166 binary tree 567
domain 157 bubble 133, 622
inverse 168 bucket 533
power notation (rn) 216 exchange 529
range 157 IfSort 92
transpose (rT) 168 insertion 532, 552, 566
relational operators key 549
merge 293, 530
(<>< =>==<> )
MinSort 96, 107

668 Index
recursive sorting 308, 313 list description 150
SelectSort 116, 292 state 151
SQR (integer, real function) 429, 595 trace table 182
SQRT (real function) 595 conditional 200
stack 370, 523, 614 transpose
standard description function, relation (rT) 168
(of abstract object) 485 tree
standard words 7 leaf 4, 557
state linked structure 548, 555, 567
abstract 484, 501 syntax 48
concrete 484, 501 trigonometric functions 595
execution 164, 561 TRUNC (real function) 594
state machine 342, 351 truth table 102
final state 352 truth values
start state 351 TYPE declaration 373, 417, 483, 613
transition 342, 352 type equality 417
stepwise refinement 65 assignment compatible 417, 421
stopping rule 188 compatible 417
string 142 same 417
composition ('V) 145
concatenation (&) 144 umon
decomposition 146 discriminated 628
empty (tt) 143 free 629
literal 8, 142 UNPACK (array operation) 534
substring() 145
structuring a program 581 variable
subrange data type 373, 400-402 buffer 442
subscript of an array variable 513 simple 15
meaning 518 subscripted 513
SUCC (ordinal function) 426 variant record 627
succeeds symbol(>) 24 Venn diagrams 155
(see relational operators)
syntax 7, 45 WHILE statement 30
literal 46 design rule 229, 404
metasymbol (::= < > I) 46, 47 iterative meaning 217
part 46 recursive meaning 214
rule 46 verification rule 220-222, 234-236
tree 48, 380 WITH statement 465
WRITE statement 10, 19, 110, 447
tag field 628 meaning 176
tail of a string or list (A) 146, 149 WRITELN statement 8, 19, 447
target value (of pointer) 560 meaning 176
termination
decidability 236
recursive procedures 328
WHILE statement 221, 231, 236
testing
boundary (or special) values 558
partition 279
structural 126, 279
TEXT files 110, 448
external 413

Index 66~

You might also like