Prolog For Programmers Neat
Prolog For Programmers Neat
Q4
PROLOG FOR
PROGRAMMERS
Feliks Kluiniak
Stanislaw Szpakowicz
With a contribution by
Janusz S. Bieri
.-. .- _ _ . - -_ 1-
| 1 '
-,l I .l __ - 1
'I-.- - 1- -I | E Il-
___',_ I _.-
l -1 _ ."l-'.'
._ , _.-
, .
- I -
! ' . ,- _-
;__ .
"' fr -"Y
_.= |" _ *- H. _ '.a "
'11 1.- .=- 1-,_.:-=-{ 11 3-. '
H
R
-. -|- -1 _
| 1
'\- I1
_ 1 _ ____ F | ll
. I
|' \ \
"-/ \ K :,-J)
xa .-1"
/
\\ _J______. ____,.,--"'
PROLOG FOR PROGRAMMERS
This rs volume 24 in A.P.|.C. Studies in Data Processing
General Editors: Fraser Duncan and M. J. R. Shave
A complete list of ri es in this series appears at the end of thrs volume
PROLOG FOR PROGRAMMERS
Feliks Kluiniak
Stanislaw Szpakowicz
Institute of Informatics
Warsaw University
Warsaw, Poland
1985
ACADEMIC PRESS
(Harcourt Brace Jovanovich, Publishers)
Kluiniak. I-'eIiks
Kluiniak, Feliks.
Prolog for programmers.
Includes index.
I. Prolog (Computer program language) I. Szpakowicz,
Stanislaw. II. Bieit, Janusz St. III. Title.
QA76.73.P76K58 I985 00I.64'24 84-I4520
ISBN 0I2-4I6520-6 (hardback)
0I2-4I6$2I-4 (paperback)
PRINTED IN GREAT BRITAIN
AT THE ALDEN PRESS. OXFORD
BSIGITIS 98765432!
Agacie Sarze, i jej mamieF. K.
Zosi i BasiJ. S. B.
777 7 1 _1_i _
CONTENTS
PREFACE xi
INTRODUCTION TO PROLOG I
Data Structures I
Operations II
I-I-I") !"!"1"2 Control 24
3. Ml-ITAMORPHOSIS GRAMMARS:
A POWERFUL EXTENSION 59
3.l. Prolog Representation of the Parsing Problem 59
3.2. The Simplest Form of Grammar Rules 67
3.3. Parameters of Non-terminal Symbols 69
3.4. Extensions 73
3.5. Programming Hints BI
vll
VIII Contents
APPENDICES 255
A. I. Kernel File 256
A.2. Bootstrapper" 258
Contents
REFERENCES
INDEX
g
PREFACE
1.1.1. Constants
Fu NCTORS
To describe a compound object, it is not enough to list its compo-
nents. The ordered pair (I9, 24) can be an object of the type
rectangle = record
height, width : integer
end
4 I An Introduction to Prolog
and
timeofday( I7I3 )
in the same program. Even if the intended interpretation is the same,
these are two different objects: one has two components, and the other
has one. There are also two different functors, both named timcofday.
Whenever we speak of a functor in a context which gives no indication of its
arity, the arity must be given explicitly. The usual notation is to write it after a
slash: timcofday/2 or timcofday/I.
The lexical rules for forming functor names are the same for all ari-
ties. but integers can only be constants. Thus
I23( a, b )
is incorrect, but
l23( a, b )
is perfectly all right. Also, [] is only a constant.
Oruscr Dsscatrrtous
Descriptions of constants and compound objects are referred to as
tenns. Usually, the objects themselves are also called terms: this causes
no confusion in practice, but in this chapter we shall try to distinguish
between the two meanings.
The arguments of a temt are arbitrary terms. For example, one can
write a term describing a record:
customer( name( john, smith ),
address( strect( north_ave ), number( I73 ) ) ).
Of the various functors in this example, the outermost, customer/2, can
be said to de ne the general structure of the temi. It is called the main
functor, or principal functor. Similarly, name/2 is the main functor of the
rst argument.
Here is another example of a common data structure. A list can be
de ned as either the empty list, or a list constructed of any object (a head)
and a list (a tail). A list of the rst three letters in the alphabet could then
be descn'bed by the term
cons( a, cons( b, cons( c, emptylist ) ) ).
The following term would be a description of the two-element list con-
structed of the above list and a list containing the integer zero:
cons( cons( a,cons( b,cons( c,emptylist ) ) ),cons( 0,emptylist ) )
6 I An Introduction to Prolog
Sraruos
Characters are constants whose names consist of single characters.
One can use quoted names for characters which are not correct identi ers
(e.g. , (', 3; and x is equivalent to x).
Strings are lists of characters. One can also write them in double
quotes. For example
"string" and
stand for
s.t.r.i.n.g.[] and ".[]
(Actually, the convention adopted in this book is different from that
of Prolog-I0. There, a string denotes a list of ASCII codes and not a list of
characters, so "string" stands for
ll5. I I6. I I4. I05. I l0.l03.[].
Similarly, in Prolog- I0 operations for reading and writing characters deal
directly with ASCII codes. We refuse to accept these conventions.)
1.1.3. Variables
called a variable name (this is also usually called a variable: as with terms
and objects, we shall try to maintain the distinction throughout chapter I).
A variable name is written as an identi er starting with an upper case
letter or an underscore (e.g. Q, Number_9, _nnn).
A variable is an object whose structure is totally unknown. As a
computation progresses, the variable may become instantiated, i.e. a
more precise description of the object may be determined. The term
embodying this description is called the variables instantiation. An in-
stantiated variable is identical with the object described by its instantia-
tion, so it ceases to be a variable, although the object can still be referred
to through the variables name. (In general, a variable may be instantiated
also to another variabIewe shall soon see the meaning of this.)
There is also an altemative terminology. One says that a free (or
unbound) variable becomes bound to another term and is henceforth in-
distinguishable from that term (which is called its binding). The variable
becomes ground if its binding contains no variables. This terminology
brings to mind the process of binding formal parameters to actual parame-
ters. If the formal parameters were not allowed to change their value (as
in pure Lisp, say), the similarity would be very close indeed. except that a
binding need not be ground.
intuitively, Prolog variables are somewhat like the variables used in
mathematics. When we say that
fl!) = 6 + 3x
is a function of one variable, we mean that the equation allows us to
determine the functions value for any (one) given argument. The variable
denotes a single (albeit arbitrary) substitution and is not in itself an object
to which values can be assigned.
You can also regard a Prolog variable as an invisible pointer. When
not free, the pointer is automatically dereferenced in all contexts, so it is
impossible to distinguish it from the referenced object: in particular, it is
impossible to exchange the object for something else.
1.1.4. Tenns
1.2. OPERATIONS
it
@")*:E" iE
mB
E\ FIG. l.I
kg;
Recurring components of an object.
ned by the user. Standard operations are accordingly called built-in pro-
cedures (or system procedures).
Procedure calls are written according to the usual practice. The pro-
cedure name is followed by an optional list of termsactual parameters,
enclosed in parentheses and separated by commas, e.g.
show( painting(rembrandt,X), etching(rembrandt,X) ).
The procedure name" is often called a predicate symbol (sometimes
shortened to predicate). Like functors, a predicate symbol has two attri-
butes: a name and an arity. Two distinct procedures can share the same
name, provided one has a different number of parameters than the other.
Procedure calls (and, as we shall see, procedure de nitions) have the
same syntax as temis. This notational uniformity is useful when programs
are dynamically modi ed (as is normally the case during an interactive
session), but it may be confusing for the uninitiated. We shall try to help
by reserving the word "argument" for components of terms: procedures
will be said to have parameters.
l.2. Operations I3
Try to think of a procedure which computes the head and the tail of a
list: we shall call it carcdr. What should its speci cation be like?
Let the list be the rst parameter and let the second and third parame-
ters retum its head and its tail. Head and tail are de ned only for non-
I4 I An Introduction to Prolog
1.2.2. Directives
Notice that if we treat both the call and the procedure heading as
terms, then the process of matching successive pairs of parameters is
subsumed by the loop in UNIFY. We extend our terminology accord-
ingly, and say thatlike a pair of termsa call and a procedure heading
do or do not match. Altematively, we say that they do or do not unify (are
or are not uni able). The algorithm uni es matching terms. Uni ed terms
are indistinguishable, so they describe the same object.
If both Formal and Actual describe variables, then uni cation binds
them together. Variables which are bound together also represent the
same object: both their names refer to the same variable. (It is pointless to
ask whether the formal becomes an instantiation of the actual or the other
way round. Our algorithm implements the latter case, but this is not
observable from the outside. You can envisage a set of bound-together
variables as a chain of invisible pointers.)
Time for a very detailed analysis of a simple example: the procedure
p( A. b( c. A ) )
called with the query
p(X.b(X,Y)).
Figure l.2 shows the situation immediately before uni cation. The
horizontal line separates objects local to the directive and objects local to
caller
callee
/Q.if?
Q - z::.':.'
caller
I
g 4 callee
El
l l
X -(
I'D
shall say that a procedure does this and this, we shall not worry about
what it does after an unreasonable call. But you might nd thinking
about these things a useful exercise.
The second remark: effects of uni cation such as merging two varia-
bles are quite consistent with the interpretation of terms as descriptions of
types. We shall illustrate this with a very simple example. The following
two procedures accept only three- eld records whose neighbouring elds
are identical:
rst2( record( Fieldl2, Fieldl2, FieId3 ) ).
last2( record( Fieldl, Field23, Field23 ) ).
Each of these procedures can be thought of as imposing a constraint on a
description of the record type. The constraints are not mutually inconsis-
tent, so the directive
:- rst2( record( Fl,F2,F3 ) ),
last2( record( FI,F2,F3 ) ),
write( record( FI,F2,F3 ) ), nl.
writes out the description of a record whose three elds are identical:
record( XI, XI, XI ).
Similarly, the query
rst2( record( F l,F2, eld ) ), last2( record( F l,F2, eld ) ).
is answered with
Fl = eld
F2 = eld
Third, the convention that only the most interesting aspects of an
object are captured in a type description tumed out to be quite useful.
Example 3 would not have worked if the type of the rst formal parameter
had speci ed that the tail must be a proper list. The term A.B would not
have been accepted.
The fourth, and last, remark. If you follow the uni cation algorithm
carefully, you will notice that it can create cyclic data structures. For
example, if the procedure
same( X, X ).
is invoked with
same( f( V ), V ), write( V ), nl.
I.2. Operations Z3
then we are in trouble. First, X < f(V); then V <X, that is to say
V < f(V). As a result, f becomes its own component and the printout
will be potentially in nite:
f(f(f(f(f(f(f(f(f(f(f(f( ....
Such cyclic structures can also cause trouble during uni cation. If we
write
same( f( V ), V), same( f( W), W), same( V, W ),...
then the uni cation algorithm will not terminate for the third procedure
call (this will probably manifest itself as recursion stack over ow): f
matches f, their rst arguments are both f, and their rst arguments are
both f, and so on.
All this could be avoided if a variable were not uni able with a term in
which that variable occurs. The uni cation algorithm is borrowed from
automatic theorem proving (see Chapter 2). The original algorithm contains
this occur cheek, but most versions of Prolog do not, as it considerably
increases the algorithms time complexity. Fortunately, cyclic stnictures
seldom occur in practice, and one learns to live with the knowledge that terms
are not always DAGs if one blunders badly. One version of Prolog (Prolog II;
see Section 9.2) is built to take advantage of cyclic data structures. They are
called in nite trees and are treated as bona de representations of graphs
arising in the real world. If one is careful, one can use such stnictures even in
more conventional Prolog systems: an example is the calltree program listed in
Appendix A.4.
1.2.4. Clauses
1.3. CONTROL
1.3.2. Baektraclring
This process is called backtracking, and a call which does not match
any clause heading is said to fail. Backtracking closely resembles our
behaviour in systematically searching for a solution to a problem. If we
end up in a blind alley, we get back to the nearest point at which we could
have applied another approach, and apply it. If no approach seems to be
working at that point, we retum to the previous point in which we appar-
ently made a wrong choice, and so on.
In implementation terms, each time a selected clause is not the last in
its procedure, a record is pushed onto a special stack of fail points (also
called choice points). The record contains all information necessary to
restore the state of the computation. When a procedure fails, the topmost
fail point is popped off the stack, the state described by it is restored and
the computation proceeds with the next clause.
It is important to note that not all effects of a computation are obliter-
ated on backtracking. Some system procedures do things which cannot be
undone, such as writing infonnation on a terminal screen. We say that
these procedures have side-effects.
Using our description of backtracking, try to follow the execution of
procedure p in the following example:
P =- q(X). writ(t1'yins(X)). nl.
female( X ), write( ok ), nl.
p :- write( Sorry! ), nl.
q( X ) :- writer( X ).
q( ? ) :- write( No more writers. ), nl.
writer( hesse ).
writer( mann ).
writer( grass ).
female( austen ).
female( sand ).
You should get the following printout:
trying( hesse )
trying( mann )
trying( grass )
No more writers.
trying( ?)
Sorry!
You may have noticed from this example that error handling is some-
what inadequate as a metaphor for backtracking. This is the subject of the
next section.
28 I An Introduction to Prolog
ters are in the relation phone, i.e. succeed if they are in the relation, or
instantiate them so that they will be in the relation and succeed, or fail.
Somewhat less trivially, the unit clause
conscarcdr( Head.Tail, Head, Tail ).
can be used to establish whether the rst parameter is a list formed of the
second and third parameters. It is self-evident that
l. conscarcdr( a, b, c ) fails, because the objects are certainly not in the
relation;
2. conscarcdr( A.2.B, l, C.[] ) succeeds, because there does exist a list
of at least two elements whose second element is 2, such that its head
is I and its tail is a one-element listthe list is I.2.[] and the tail is 2.[ ];
3. conscarcdr( A, B, B.[] ) succeeds, because there do exist objects A
and B such that A is a list constructed of B and B.[]A is B.B.[] and
B can be any object.
Nonunit clauses are indirect de nitions of relations. Thus
append(
append( F; 1'11- ri- :'I',.""'-II" append( T, L, TL ).
can be read as
L is L appended to [], and
H.TL is L appended to H.T tfTL is L appended to T.
It is usually convenient to flavour this a little with the intended meaning,
as in
a list L appended to an empty list is L itself, and
a list L appended to a non-empty list H.T is formed of the head of
that list, H, and the result of appending L to its tail, T.
And. most spectacularly.
intersect( Ll, L2 ) :- member( E,Ll ), member( E,L2 ).
reads:
Ll and L2 intersect if an object E is a member of Ll and a member
of L2.
in other words
two lists intersect if they have a common member.
You will nd more about this in Chapter 2. But note here that this
interpretation does not fully explain procedures such as process of Sec-
tion l.3.3this is further discussed in Section 4.3.l.
34 I An Introduction to Prolog
than the other, but a necessarythough not suf cient!condition for its
applicability is that the problem-de ning sets intersect. We might write
something like
try( Setl, Set2, Solution ) :-
intersect( Setl, Set2 ),
methodl( Setl, Set2, Solution ).
try( Setl, Set2, Solution ) :-
method2( Setl, Set2, Solution ).
Now if methodl fails, we want to try method2. But if the sets are large
and have many elements in common, we are effectively stopped by a
generator. Backtracking from methodl will cause intersect to nd another
way of showing that the sets do indeed intersect: this changes nothing, so
methodl will be attempted again and again until intersect enumerates all
the elements in the intersection of Setl and Set2. In terms of processing
time, this might be a disaster. And note that we are lucky: the generator is
not in nite.
To help in such cases, Prolog provides a commit operation, written as
I and called the cut procedure (old Prolog hands tend to call it the slash,
after the character /, which was its name in the original Marseilles Prolog).
When procedure p executes a cut, everything that was done by p up to
that momentincluding its choice of current clauseis taken as xed
and not to be reconsidered on backtracking. In implementation terms, !
cuts away the top section of the fail point stack, leaving only fail points
created before p was called.
Our problem can be solved by modifying intersect:
intersect( Ll, L2 ) :- member( E, Ll ),
member( E, L2), !.
The cut kills the generator of elements from LI.
A more involved example might be useful in clearing up doubts about
the effects of a cut. We will try to move a single cut around in our example
of section 1.3.2:
p :- q( X ), write( trying( X ) ), nl,
female( X ), write( ok ), nl.
p :- write( Sorry! ), nl.
q( X ) :- writer( X ).
q( ? ) :- write( No more writers. ), nl.
writer( hesse ).
writer( mann ).
writer( grass ).
1.3. Control 37
female( austen ).
female( sand ).
If we insert a cut into the rst clause of writer:
writer( hesse ) :- !.
the printout will be
trying( hesse )
No more writers.
trying( ?)
Sorry!
If we insert it into q instead:
q( X ) :- !, writer( X ).
we will get
trying( hesse )
trying( mann )
trying( grass )
Sorry!
But if we choose to insert it at the end of this clause:
q( X ) :- writer( X ), I.
the program will write
trying( hesse )
Sorry!
By inserting the cut after the call to q in the rst clause of p, we would
obtain only
trying( hesse )
As evidenced by these examples, the cut is a powerful tool. A single
cut can drastically alter the behaviour of a program. It must be used very
carefully: Section 4.3.1 contains some useful hints.
An important property of the cut is that it can be used to implement a
sort of negation. When we want to list all male writers, we can write
:- writer( X ), male( X ), write( X ), nl, fail.
If, however, the program contains only descriptions of female persons (as
in our example), we must de ne male in terms offemale:
male( X ) :-female( X ). !, fail.
malet _ ).
33 I An Introduction to Prolog
When X is such that female succeeds, the second clause of male is cut off
and the whole procedure fails. When female fails, the second clause
takes over and the procedure succeeds. The trick is dirty, but very useful.
One must be careful, however: if the constant christie is not listed among
the females, male(christie) will succeed. (More on this in Section 4.3.2.)
In versions of Prolog described here, not is prede ned and the predi-
cate symbol is predeclared as a pre x symbol. Expanding male in-line, we
would write the directive of Section 1.3.6 as
:- writer( X ), not female( X ), write( X ), nl, fail.
Variable calls can be used to de ne many useful procedures. We shall
end by showing two companions of not: and and or. The rst is
written as a comma and the second as a semicolon; the rst succeeds
when both its pararneterstaken as callssucceed, and the second suc-
ceeds when either of its parameters succeeds (but establishes a fail point if
it is the rst one). Their de nitions are
',( A, B ) :- A, B.
:'( A._):- A-
':( _. B) 1- B-
Comma and semicolon are predeclared as in x symbols. After de n-
ing do, we could write the directive above as
:- do( ( writer( X ), not female( X ), write( X ), nl, fail ) ).
The extra parentheses are needed to avoid confusion with a call to do/5.
Priorities are chosen so that
artwork( X, Y ) :- painting( X, Y ), oil( Y );
etching( X, Y ), brass( Y ).
is equivalent to
artwork( X, Y ) :- ;'( ',( printing( X,Y ),oiI( Y ) ),
','( etching( X,Y ),brass( Y ) ) ).
To make the comma and semicolon appear a part of ProIogs syntax,
Prolog-I0 and some of its offsprings made the cut behave somewhat dif-
ferently for these procedures: they are transparent to it. Thus
artwork( X, Y ) :- painting( X, Y), oil( Y ), ! ;
etching( X, Y ), brass( Y ).
avoids checking the second altemative if the rst succeeds.
A similar exception applies to variable calls. If the procedure
a(X):-b,X.
a(_):-c.
40 I An Introduction to Prolog
is called with
a( ( d, !, fail ))
then the cut will commit all choices made by d and b and athe proce-
dure will fail without executing c.
One should avoid taking advantage of this peculiar property of the
cut. It is doubtful whether it is necessary.
2 PROLOG AND LOGIC
2.1. INTRODUCTION
2.5. STRATEGY
different literals we change only the order in which things are proved, but
the general structure of the proof is not changed. It is convenient to
represent the structure of a proof by means of a proof tree (do not confuse
it with the search space), as illustrated in Fig. 2.3. The rst tree shows the
proofs of the preceding gure, which all used B and C to prove A, and F
to prove B. The other proof trees represent classes of proofs obtained
through a different choice of clauses: the proofs are carried out quite
differently.
Structurally different proofs use different subsets of the available
clauses for performing various subproofs, so the counterexamples of
Section 2.4--which are descriptions of sets of objects for which the
clauses used cannot all be truemay turn out to be di 'erent. Therefore, not
all solutions are the same.
Proof trees are interesting also because they re ect the invocation
tree when clauses are treated as procedures. As long as there are no
failures, the conventional procedure activation stack can be regarded as
an equally conventional stack used for preorder traversal of the proof
tree, orif failure is imminentof a quasi-proof tree which has a failure
A"-= D
B== Dt\E
B<:= F
C<=
C"== FAD
D-1::
F-= 1=A
U_n_mr
'_h _h_rm {U_ I"_ O_" _ _
___
or _ B
ur>mr
_
Ur
Wr
IUrWr3
W!
__Ur
II:
_m___
U__ m_m
r_rr_m_?O_"3 r_<
/<<
\
nq_:2
miO
__.|rI_m_ _r __nmu_r
/_\/\_v\
/ A
A ___V\
_V\
r_ _ _ _m_'mt, __
___I
_II
____
.
.
mr r
_____
/_\ di
__N
_ m_ _ _ _G
K
_
DU
Ur_DDUD
_6_ _,mr
54 2 Prolog and Logic
(cl "A
I
e\
/By 1C
--Dv--Ev-1C -FvHC
I Us
I
HEV -rc WC
I
I I
tail Bl HFVHD
HD
in its rightmost leaf -(see Fig. 2.4). Backtracking to the nearest choice
point in the search space may reopen an attempt to prove a node in a
nished branch of the quasi-proof tree, i.e. to reactivate a terminated
procedure (this would happen if D had a second clause in our example).
Obviously, this cannot be done with a single simple stack: the matter is
discussed in Chapter 6.
The word strategy refers to the way in which a theorem-prover
(whether automatic or human) nds its way through a search space. In
logic programming literature the preferred term is control. It is not all a
question of choosing the order of clauses and literals. Some logic pro-
gramming systems employ no backtracking, choosing to cover the search
space in a breadth- rst manner. This helps avoid problems caused by
misapplied generators and left recursion (see Fig. 2.5 and Section 3.5.2).
There is, of course, the problem of potentially exponential memory re-
quirements. Prologs appetite for memory is at most roughly linear in
number of attempted derivation steps. This is paid for with the cost of
backtracking: the time complexity is still exponential.
HA
HBVHC
HFvHC "B
B//////\\\\\\a l A*:'Bt\C
1 1 --
HC HF HF B<:: F
|:1 1:1 El F:
The clauses used
lhe tree ot possible proot paths in these proofs
FIG. 2.2 Choice of literals with xed choice of clauses from Fig. 2.1. (Prolog would
choose the left.most path, but only after some backtracking caused by choosing the rst
clause of B.)
A A A
B C B C D
F E] F F 0 El
I
El El El El
FIG. 2.3 Proof tr'ees for the example of Fig. 2.1. (The leftmost tree represents the
proofs of Fig. 2.2. Backtracking would cause Prolog to build each tree in tum, from left to
right.)
A
/\
El as
FIG. 2.4 A quasi-proof tree, representing a failing attempt to prove B by using its rst
clause. (The right son of A was not generated.)
Mle,.ll..l2.xlll
e<-I '
l'(-.12.!)
E] Mte..l2.xl
e<-2 '
l(x
U Mte.xl
x<-.le,l'l '
x(.le,l-ll
U Mle,l')
U e'l) e I
(3.(bl8X))
consists of 12 terminal symbols.
There are several commonly used methods of describing the structure
of a list (or, more generally, of a valid sequence of terminal symbols). The
59
60 3 Metamorphosis Grammars
' This manner of presentation is due to Colmerauer; it was also used by I-(owalski
( 1979b).
i For other ways of representing graphs in Prolog, see Sections 4.2.4 and 4.4.3.
3.1. Representation of the Parsing Problem 61
atom
letter
.41, Q
FIG. 3.1 Meaningful combinations of edges.
atom( 9, ll ).
letter( 9, L ), atom( L, ll ).
o( 9, L), atom( L, 11 ).
(3.5) L 1- I0
atom( 10, ll ).
letter( 10, ll ).
x( 10, 11 ).
success
The method of specifying the initial graph is rather awkward, even for
this small example. Moreover, it requires that terminal symbols be only
identi ers (nullary functors)-the restriction is unnatural but, fortunately,
unnecessary. We shall now describe a slightly different and much handier
notation.
Names of nodes need not be consecutive integers. On the contrary, it
is much better to derive (unique) names from the original sequence of
terminal symbols than to introduce another, completely independent no-
menclature. We shall exploit the one-one correspondence between a
node and the sequence of (contiguous) edges following it. As the name of
a node we shall take the list of terminal symbols labelling the correspond-
ing sequence. For example, the leftmost node of the graph (3.2) will be
named
(.a.,.(.b.i.g.,.o.x.).)'.[]
and the name of the rightmost onecorresponding to the empty sequence
of nodes_will be
ll
With this notation, the (implicit) clause describing the atom ox becomes
atom( o.x.').).[], ).').[] ).
Notice how the underlying sequence of terminal symbols can be seen
without resorting to separate clauses for Q and : it is simply the differ-
ence of the rst and the second node names, Q and _x in our case. For a
terminal symbol this difference is guaranteed to consist of the symbol
itself, as, say, in
Jlt
0-)-0
X,')'.'l' . [1 'l.)'. E]
In other words, if an edge connects the nodes X , Y and is labelled with the
terminal symbol T, then
X=T.Y
3.1. Representation of the Parsing Problem 63
letter( K, L ) :- terminal( z, K, L ).
terminal( T, T.Y, Y ). -
3.1. Representation of the Parsing Problem 65
|lt'n$ _
itg_ms
item
list J?
.1?
items
item _ i
rl ms
l 1 |g_j
FIG. 3.2 The graph for the list (a,(big,ox)).
66 3 Metamorphosis Grammars
IISI
I /..\ ern I
item i items
atom item
letter list
/....\.
atom item
letter \|lom at O rn
i letter o letter
g it
FIG. 3.3 The parse tree for the list (a,(big,ox)).
The input and output parameters of the clauses that constitute a pars-
ing program, such as (3.7), are the basis of yet another interpretation of
those clauses: in terms of operations on sequences of terminal symbols.
Take the clause
items( K, N) :- item( K, L), terminal( L, M ), items( M, N).
It can be read as follows: (an instance of) items can be chopped off
(recognized at the beginning of) K, leaving N, if (an instance of) item can
be chopped off K, leaving L, and then a comma can be chopped off L,
leaving M, and nally (another instance of) items can be chopped off M,
leaving N. Now the essence of all this is that items consist of an item, a
comma, and items. The other information can be routinely added to this
fundamental fact. All we need is four variables to stand for successive
remainders of the initial sequence of terminal symbols.
68 3 Metamorphosis Grammars
letter > [ z ].
This is the name invented by Colmerauer (I975. I978). The name de nite clause
grammars was later introduced by Pereira and Warren (I930) for metamorphosis grammars
in nonnal fonn (as de ned by Colmerauer).
3.3. Parameters of Non-Terminal Symbols 69
The procedure terminal need not be explicitly given (it ought to be pro-
vided by the implementation).
This grammar deserves its name. It is best understood independently
of the Prolog program it has been used to conceal. Every rule re ects the
consist of relationship between a whole and its constituents, exactly as
the original BNF grammar does. However, it should be remembered that
the grammar is also a program in disguise, and is executable immediately,
without any additional effort on the programmer's part!
Parsing can be initiated in two ways. First, we can simply call one of
the underlying procedures, e.g.
:- Iist( '('.a.','.(.b.i.g.','.o.x.)'.')'.[], [] ).
Second, we can use the built-in procedure phrase with two parameters:
the nonterminal symbol and the sequence of terminal symbols (which is
supposed to be an instance of the nonterminal). For example:
:- ph|ase( list, '(.a.,'.(.b.i.g.,.o.x.).)'.[] ).
It should be pointed out that the rst way brings out the routine informa-
tion we just managed to hide. On the other hand, the second way is less
exible, e.g. we cannot use phrase to perform calls such as (3.8).
Again, we shall suppress the routine information, i.e. leave out the
input and output parameters. The resulting grammar will be as follows:
at
/\ /\ while
. . .../
\:"3/B
1 /\ ll i 1
./\. /\.
/\.
/\ /\
i 1 i
FIG. 3.4 An abstract syntax tree.
1
Both parts of this example, here and in Section 3.4.1, are modelled on the illustration
in Colmerauer's original paper (I975).
72 3 Metamorphosis Grammars
3.4. EXTENSIONS
3.4.1. Conditions
atom( LETTER.LETTERS, K, M ) :-
letter( LETTER, K, L ), atom( LETTERS, L, M ).
letter( a, K, L ) :- terminal( a, K, L ).
letter( z, K, L ) :- terminal( z, K, L ).
One nal example: in the program above we shall replace the 26
clauses that de ne letters by a single clause:
letter( LETTER, K, L ) :-
terminal( LETTER, K, L ), isletter( LETTER ).
with isletter de ned, say, as
isletter( LETT ) :- a @=< LETT, LETT @=< z.
This new clause can be used as follows:
letter( Lett, x.').).[], Tail ).
terminal( Lett, x.).')'.[], Tail ), isletter( Lett ).
Lett 1- x, Tail 1- ').).[]
isletter( x ).
etc.
The variable in the call on terminal matches every terminal symbol. If
the terminal symbol is not a letter, a call on isletter will fail and a letter
will not be recognized. We call such terminal symbols variable terminals:
the rst (still unprocessed) symbol is selected and is then either accepted
or rejected, e.g. according to the result of a test such as isletter.
Extra calls that do not comprise input and output parameters have
been known as conditions, but the name is slightly misleading. Only in the
last example isletter(Lett) can be interpreted as a condition: the clause
will only be applied if isletter succeeds. The call on pname in the second
example is rather an action performed on the parameters of non-terminal
symbols. Finally, the cut can be reasonably interpreted exactly as in any
other clause, as pragmatic information on the future use of the clause.
Conditions in metamorphosis grammars are enclosed in curly brack-
ets, so that they will not be confused with terminal and non-terminal
symbols. Examples:
Iist( Iist( '('. ')' ) )> I '(. ) 1. 1!}-
item( item( ATOM ) )-> atom( LETTERS ),
{ pname( ATOM, LETTERS ) }.
letter( LETTER )-+ [ LETTER 1, { isletter( LETTER ) }.
As an exception, the cut need not be placed within curly brackets, e.g.
Iist( Iist( '('. ') ) )"* I '('. ')' I. 1-
3.4. Extensions 75
3.4.2. Context
3.4.3. Alternatives
Two or more grammar rules with the same lefthand side (including
context and parameters of the non-terminal symbol) can be combined into
a single rule with the common lefthand side and with the righthand side
taking the form of alternativesa sequence of original righthand sides
separated by semicolons. For example:
list -> I '('. ')' I ; I '( l.it1=mS. I ') ]-
items > item ; item, [ , ], items.
item > atom ; list.
atom > letter ; letter, atom.
letter > [ L ], { isletter( L ) }.
Notice howat lastwe managed to come back rather closely to the
original BNF grammar (3.1).
The translation of a rule with an alternative into an underlying clause
is straightforward. One example should be su icient:
items( K, N ) :- item( K, N ) ;
item( K, L), terminal( ',', L, M ), items( M, N ).
The notation with alternatives is, strictly speaking, a convenience
rather than a real extension, andlike altematives in ordinary clauses
(see Section 1.3.7)it can sometimes adversely affect the grammars
readability.
SO 3 Metamorphosis Grammars
Notice the position of the condition: if we placed it at the end of the rule,
we would run the risk of discovering an improper instance of Op only
after parsing the whole input, say,
(A + b/2)1=c blah_blah 21-( n (x + y )/4)
In its present position the condition fails as soon as it sees an invalid
operator.
Both improvements of the original grammar eliminate possible repeti-
tions. Both, though, seem to decrease the readability and elegance of the
original solution, and we recommend that they be applied (if at all neces-
sary) only in the late stages of program debugging.
4 SIMPLE PROGRAIVIMING
TECHNIQUES
i 7 7171
4.1. INTRODUCTION
Terms can usually be regarded as trees: the main functor labels the
root, subtrees correspond to arguments. This is slightly imprecise, be-
cause multiple occurrences of variables represent more general struc-
turesdirected acyclic graphs (DAGs). However, the term f(A, A)
which should be depicted as
t
can be thought of as
A A
4.2. Examples of Data Structures 39
We must only remember that the two subtrees will remain identical, so
instantiating variables in one will affect the other. Another dif culty is
that it is possible to compute tenns which are not even DAGs, and which
should therefore be regarded as corresponding to in nite trees (see Sec-
tion l.2.3). All the same, an ordinary tree is a good intuition of the (gen-
eral) term.
Terms are a convenient and concise representation of trees with irreg-
ular structure, where the information in the nodes detennines both the
shape of the tree and the repertory of applicable operations. The abstract
syntax tree of Fig. 3.3, Section 3.3, is a typical example. However, pro-
grams that manipulate such irregular structures are usually problem-de-
pendent, in that every principal functor (i.e. every type of node may
require di 'erent computations).
There are other situations, typi ed by binary search trees, when we
need a more uniform representation, because we use trees for contents
rather than for structure. Suppose we represent the BST of Fig. 4.1 as the
term
few(people(many(languages), speak))
Even if we disregard the ambiguity (is languages the left or right de-
scendant of many?), main functors and their arguments must be iso-
lated, that is, we must use the built-in procedure = .. (univ; see Section
5.10). To modify the tree, e.g. by adding a node, we must rebuild it com-
pletely, also using univ. This is not only inelegant, but ine icient as well
(but see Section 4.2.6 for a discussion of such techniques).
We shall therefore represent empty binary trees by the atom
fevv\
people
../ \...
FIG. 4.1 A binary search tree.
90 4 Simple Programming Techniques
fl/...\.
./.\. .%l.\.
nil
\
languages nil
FIG. 4.2 A representation of the tree of Fig. 4.1.
'1:
t thompson 2432
\7
nrl
\
rncbr|de:l7Bl
FIG.4.3 Another BST.
nil
uritc_sorted( nil ).
urite_sorted( Left_subtree)v
write_sortud( Risht_subtree).
In this procedure, we need not test the actual ordering of nodes; this
would not be the case if we wanted, say, to locate a node in a tree. Let the
call
precedes( Nodel, Node2 )
succeed iff Nodel comes before Node2. For our name-number pairs the
procedure can be de ned simply as
precedes( Namel :_, Name2 :_ ) :- Namel Gr< Name2.
It is reasonable to expect that nodes are correctly built, e.g. that each
key is a name, and other information a number. A good place to check this
would be a procedure for inserting a node into a tree:
92 4 Simple Programming Techniques
The procedure fails when it tries to duplicate a key (both calls on precedes
fail). If the keys need not be unique, we must relax one of the tests, e.g.
by changing
precedes(Root, Node)
into
not precedes(Node, Root)
/\ /\
mt tndxme l ml t wmne1M32 mt
FIG. 4.4 The tree of Fig. 4.3 alter deleting adams : _ .
Nonnally we would call the procedure del with only the key given.
We might encapsulate such calls:
delete( Key, Oldtree, Newtree ) :-
del( Key : _ , Oldtree, Newtree ).
94 4 Simple Programming Techniques
-E
/\ A\
nil adams=54BB nil nil white:2t.32 nil
FIG. 4.5 The tree of Fig. 4.3 after deleting thompson : _.
Z 2nd and 3rd ariuoentt the tree built so far1 the final Lroo
a\
1/ C <1
successively push incoming items, but the original sequence will be re-
versed. Alternatively, we can use append to preserve the original order of
items, but this would square the nrnning time. Moreover, each call on
append entails not only a traversal of the entire list, but also creation of its
copy. Strictly speaking, a series of variables is produced and instantiated
to successive tails. When executing the call
append([ Itl, I12], [I13 ], X )
the following instantiations take place:
X 1- [ ltl I Third_tail' ]
Third_tail' 1- [ lt2 I Third_tail" ]
Third_tail 1- [ lt3 ]
- - @-
.. -__ -. _ _ ..___._;[]
i
old tree
'1. '5
,/\
I mcbride:17Bl nil
t/<L\.
FIG. 4.8 The result of insertion into a BST.
carom-: __
AFTER
we get
x <-1 111 | Third_tail' 1
Third_tail' <-1 112 | Third_tail" 1
and nally bind Third_tail". The trick is to keep Third_tail" ready for a
subsequent instantiation:
Third_tail" 1- [ It3 I Third_tail' ]
The situation will be roughly as in Fig. 4.9. Figuratively speaking, we
shall be able to resume append in the next step of computation. We only
need to get hold of the variable Third_tail"', instantiate it:
Third_tail' 1- [ It4 I Third_tail'' ]
and so on. When we are through, we can instantiate, say,
Third_tail' 1- []
and come up with the nal instance of X,
[It], It2, It3, It4]
I00 4 Simple Programming Techniques
For simplicity, we assume the code will not contain extemal references
(we shall also not attempt any optimisations).
The code generators output should be a list of "symbolic" instruc-
tionsterms described schematically as
Opcode( Address )
Each Address is an uninstantiated variable. There should be a unique
Address for every addressable symbol of the source program (variable,
constant, label), and for every label created by the code generator. By
way of explanation, we give a possible translation of the assignment
x := x + y * y + 2
most opcodes have obvious meaning.
[ load( AI ),
store( A2 ),
load( A3 ),
mult( A3 ),
add( A4 ),
add( A2 ),
store( Al ),
$lP( - ).
label( Al ), data( _ ), % x
label( A2 ), data( _ ), % temporary
label( A3 ), data( _ ), % y
label( A4 ), data( 2 ) % constant 2
1
We want the same Prolog variable for all occurrences of a source variable;
for example, Al always stands for x.
To assemble this section of code, we should determine the base ad-
dress and go down the list, counting bytes (or other units of storage).
Each executable instruction would be assigned a nal address. The pseu-
doinstruction label would be treated differently. We would instantiate
Address as the current value of the location counter (without advancing
the counter); this would instantiate all occurrences of Address (or of
van'ables bound to it, if one wants such ne distinctions). Assuming each
instruction takes four bytes and the fragment of code starts at location
1000, we would obtain
1000 : load( 1032 ),
1004 : store( 1036),
1008 : load( 1040 ),
1012 : mult( 1040 ),
etc.
4.2. Examples of Data Stmctures I03
For simplicity, we omitted the symbol table while developing the parser. We can save
this particular program by doing symbol table management in the back-end, but of course the
more proper way is to install symbols in the table in the front-end.
I04 4 Simple Programming Techniques
If the application does not require shortening a list, open lists can be
constructed with no copying whatsoever. Successive instances of the
originally empty lista variableare longer and longer open lists (as-
suming, of course, that we are careful to instantiate nal variables appro-
priately). However, each time we add an item, the list must be traversed
to nd the nal variable. To avoid this, we can keep this variable ready for
instantiation:
End = [ Newltem I NewEnd ]
and make NewEnd available for further processing.
The pair consisting of a list and its nal variable can be considered
another representation of the lista little redundant for the sake of ef -
ciency. It is reasonable to represent the term as a single term. We shall
write it as
OpenList -- ItsFinalVariable
with -- a nonassociative in x functor. For example:
[ a, b I X ] -- X
To add an item at the end of a list we use the procedure
additem (Item, List -- [Item I NewEnd], List -- NewEnd).
The call
additem( 4, [ l, 2, 3 I X ] -- X. NewList )
instantiates, as expected,
NewList < [ l, 2, 3, 4 I NewEnd ] -- NewEnd
because
X<[4INewEnd]
Consequently, the old list becomes
[1, 2, 3, 4 I NewEnd ] -- [4 I NewEnd ]
To get a new list, we had to destroy the old one.
Fortunately, the destruction is apparent. The pair can still be re-
garded as a representation of the sequence l, 2, 3. Notice that [4I
This structure can be very useful in its own rights; see Shapiro (l983b, Section 4.8).
I06 4 Simple Programming Techniques
Once again, it must be stressed that modi cation of such lists is destruc-
tive. For example, the second call below fails, because [c, d, e I Y] does
not match [p I Z]:
d_conc( [ a, b I X ] -- I Y ] -- Y, ABCDE ),
d_conc( [ a, b I X ] -- .?<.?< '. NP- -0 -- Z, ABP ).
We now return to the sorting algorithm based on BSTs (see Section
4.2.1). Instead of traversing the tree, built of a given list, and merely
writing out the nodes, we would rather traverse it in order to construct the
sorted permutation of the list:
tree- sort( List, SortedList ) :-
buildtree( List, nil, Tree ),
buildlist( Tree, SortedList ).
The procedure buildlist " attens" the tree (see Fig. 4.10 for an exam-
ple). The general outline of the algorithm is rather obvious: we atten the
subtrees (recursively) and concatenate the resulting lists together with the
root in between. Difference lists can be used to avoid numerous appends.
Let the results of recursive calls be denoted by
LFlat -- LFlatE and RFlat -- RFlatE
The algorithm is programmed as follows:
atten( nil, X -- X ).
atten( t( L, Root, R ), Flat ) :-
atten( L, LFlat -- LFlatE ),
atten( R, RFlat -- RFlatE ),
/\%
d_conc( LFlat -- LFlatE, [ Root I X ] -- X, A ).
d_conc( A, RFlat -- RFlatE, Flat ).
procedure models a relation. i.e. a set of tuples for which a certain rela-
tionship holds. For example:
name_phone( thompson, 2432 ).
name_phone( adams, 5488 ).
name_phone( white, 2432 ).
name_phone( mcbride, I781 ).
In practice, unit clause procedures are sequences rather than sets, in
that they are accessed sequentially. It is therefore possible to represent a
list by a procedure, e.g.
Iist( b ).
Iist( k ).
Iist( q ).
Iist( y ).
The call
Iist( X )
tests membership for instantiated X, and serves as a generator for unin-
stantiated X. The whole list can be processed thus:
process_list :- Iist( X ), process_item( X ), fail.
process_list.
In general, clauses may be used to represent multidimensional matrices-
we shall discuss this brie y in the next section.
The restriction to unit clauses is not essential. The clause
name_phone( X, 4396 ) :- of ce( X, rooml I9 ).
will generate tuples one at a time, exactly as the other four clauses do.
It is wonh emphasizing that explicit and generated data are functionally
indistinguishable. If ve people sit in room I19, we can get up to nine
name-phone pairs, without ever becoming aware of the "indirection" in
one of the clauses.
Any structure expressible in terms of relations can be naturally cast in
clauses. For example, a tree can be described as follows:
atom
,1):
e1 e2 e3
FIG. 4. || A graph.
]|() 4 Simple Programming Techniques
DEC-I0 Prolog was the rst to offer this possibility. If absent, it can be
mimicked by means of the built-in procedure =.. (see the next section).
Clausal representation sometimes helps reduce the problem at hand
to its bare essence. A case in point is an amazingly concise solution to a
map colouring problem; we quote it after Pereira and Porto (l980b). A
planar map is to be coloured with at most four colours so that contiguous
regions are coloured differently. First we de ne the contiguity relation for
colours:
The original map of Pereira and Porto (l980b) is shown in Fig. 4.12. A
region is represented by its colourthis decision makes the solution
beautifully terse. To nd a colouring (if any) of the map, we must only call
3 _.- -6
2
I
I
i I l
hO5( X: _: X )-
= nuL_o5ide.
retroct( uith_b( X: Y ) ): !:
basoftltem: Condition: _) =-
ossert('BAG'('BAG')): Z a marker
ossert('BAG'(Item)): Z saves it
basof(_: _: Baa) =-
retract('BAG'(NextItem)): I:
basof( Bunch:
( member(U: Uertices):
Bunch
>
>:
Graph ).
both bagof and setofare built-in procedures: setofeven retums its output
sorted. An implementation of setof in Prolog was presented by Pereira
and Porto (l98l).
/\
/\ Q0 I
ltlx:kZ
/.L\
ID/
6)
tree.
/ .m\
FIG.4.l3 Digital search trees: la) Binary digital search tree lb)Tem rydtgtt lse rch
4.2. Examples of Data Stmctures I I5
The procedure fails if the rst parameter is not a correct temary subscript,
or if the second parameter is not an open temary tree. However, it does
not fail when a nonexistent item is referred to. We shall discuss this
phenomenon presently.
Now for a procedure that replaces an item. Two tree parameters are
required, and the new tree is a copy of the old one, except for the replaced
item. The amount of copying is similar to that illustrated in Fig. 4.8.
Both procedures behave in the same way when the subscript is too
large: they create a missing part of the tree, and then nd or change
the newly inserted item. For example, the call
nd_3( [2, l, 0, I ], Tree, A64)
applied to the tree of Fig. 4. I3b changes the node with item A7 into the
tree of Fig. 4.l4, or, in term notation, into
t3( A7, t3( Dummy2l, Empty_i.
t3( A64, Empty_ii, Empty_iii, Empty_iv ),
Empty_v ),
Empty_vi, Empty_vii )
The same effect will be achieved by the call
change_3( [ 2, I, 0, I ], A64, Tree, Tree )
The moral is that, rst, no special insertion procedure is needed, and,
second, the tree need not be full. It will contain only the inserted nodes
together with the branches required to reach these nodes, but intermedi-
ate nodes may contain no meaningful information.
To make the story complete, here is a little procedure that converts
nonnegative integers into lists of temary digits. Note that there are two
procedures here: conv_3/2 and (auxiliary) conv_3/3.
conv_3( O: [O] ).
conv_3( N: Z: ll isits ) =-
Q
FIG. 4.l4 Creation of the missing part of a tree.
would stand for the BST of Fig. 4.l. We shall now show an insertion
routine for such trees. The built-in procedure = .. (univ) is used to circum-
vent the problem raised by the potential diversity of the functors.
[[f.0.0].[[f.i.=l.V].[[0.l<]].V]
with uninstantiated V. (Try to write this reading program: a symbol table
such as those described in Section 4.2.2 must be used to handle variable
names properly.) Now we can glue the intermediate representation to-
gether:
alldisitst Disits ):
sluearsst E]: L] ).
al1disits( [J J.
The procedure glue should be called with the second parameter uninstan-
tiated.
In implementations that do not support indexing (see Section 4.2.4),
unlv helps avoid linear search of matching clauses. Consider. for exam-
ple, a natural language application program which maintains a dictionary
whose entries can look as follows:
dict( program, noun( inanim ) or verb( intrans ) ).
dict( modular, adj ).
dict( an, article( indef ) ).
I20 4 Simple Programming Techniques
Next, assume that each word on input is ltered through this dictionary:
input_a_word( W, Features ) :-
read_a_word( W ),
( dict( W, Features ), I: signal_unknown( W ) ).
Without indexing, dictionary lookup requires time proportional to the
number of entries. Access to a procedure, i.e. to its rst clause, usually
requires approximately constant time (some form of hashing is used). We
can have our dictionary in the form
program( noun( inanim ) or verb( intrans ) ).
modular( adj ).
an( article( indef ) ).
and de ne dict as
dict( W, Features ) :-
Entry =.. [ W, Features ], Entry.
orequivalentlyas
dict( W, Features ) :-
functor( Entry, W, I ), Entry, arg( I, Entry, Features ).
A particularly simple dictionary is a table of keywords for a scanner
of, say, Pascal:
const. type. array. record.
function. var. begin. do.
etc. To create the representation of a source program name, we can use
this procedure:
key_or_id( Name, keyword( Name ) ) :- Name, !.
key_or_id( Name, ident( Name ) ).
As a nal example, here is the crucial part of a de nition of the
procedure phrase which initiates processing based on a metamorphosis
grammar:
phrase( InitialNonterminal, Terminals ) :-
InitialNonterminal =.. [ Name | Parameters ],
InitialCall =.. I Name, Terminals, [] | Parameters ].
InitialCall.
Note that input and output parameters are added at the beginning of the
parameter list (rather than at the end, as suggested in Section 3.3).
4.3. Some Programming Hints IZI
The procedure not, used to invert tests, owes its power and concise-
ness to the combined effect of three extralogical mechanisms in Prolog:
variable calls, the cut, and forced failure. Recall the de nition:
not X :-- X, I, fail.
llOI _.
Observe that the second clause performs no instantiations, and any in-
stantiations in X must have been undone on failure. If not succeeds, its
parameter will remain intact. Therefore, not will not retum anything. For
example, the call
not student( X )
with uninstantiated X will not nd a nonstudent (as might have been
expected). Instead, it will fail if there is at least one student, e.g.
student( jim ). student( jill ).
Otherwise it will succeed with X still a variable. If we insist on nding
nonstudents, we can look for them among NewYorkers:
newyorker( tim ). newyorker( jim ).
newyorker( jill ). newyorker( amy ).
Now the command
:- newyorker( X ), not student( X ),
write( X ), nl, fail.
will print:
tim
amy
It must be emphasized that not called with a term containing variables
does not implement negation properly (see Clark I978). If the call not
student(X) succeeds, then we shall actually prove that
m 3x student( x )
which is equivalent to
Vx m student( x )
On the other hand, suppose not means 1 The command
:- not student( X ).
I26 4 Simple Programming Techniques
Vx mm student( x )
3x m student( x )
This discrepancy was commented upon, for example. by Clark and McCabe
(l980a, l980b) and Dahl ( I980). In IC-Prolog (Clark et al. l982b) the problem
was solved by treating not calls with variables as erroneous. This is to say.
negation in their system is only applicable to ground predicates.
Except for not, forced failure is used primarily for ef ciency. Many
Prolog implementations have no garbage collection, but upon backtrack-
ing almost all of them very ef ciently recover some storage holding con-
trol information and term instances (see Chapter 6). We can take advan-
tage of this in a few rather unobvious but effective tricks. One of them is
double not.
On the face of it, the trick is pointless: the call
not not C
succeeds if and only if C does. We shall trace the execution of this call to
show its hidden effect. Assume rst that C succeeds; here are successive
snapshots:
not not C
not C, !, fail
C, !, fail, !, fail
!, fail, !, fail
% the cut will commit the intemal not
fail, I, fail
% RECOVER the storage used by C,
% and backtrack in the external not
SUCCESS
Now, let C fail:
not not C
not C, !, fail
C, !, fail, !, fail
% backtrack in the internal not.
% succeed via the second clause
4.3. Some Programming Hints I27
!, fail
% the cut commits the external not
fail
FAILURE
Since double not does not instantiate anything. it can only be used
in two situations. Either we want to perform a complicated yes/no test
(with all interesting variables already instantiated), or we are only inter-
ested in some side-effects of C but we want to recover storage after its
execution. For readability, we usually de ne two procedures:
check( Cond ) :- not not Cond.
side_effects( Goals ) :- not not Goals.
One example should suf ce:
prettyprint( Term ) :- side_effects( doprettyprinting( Term ) ).
Suppose now that we need instantiations produced when executing a
call, and that space still matters. To preserve the results (i.e. the appropri-
ate tenns) over backtracking, we must put them aside. Only stored
clauses are immune to failure. The following general-purpose procedure
executes a call, and at the same time garbage collects the storage used
by the call:
with_gc( Call ) :-
once( Call ), assert( ASIDE( Call ) ), fail.
with_gc( Call ) :-
retract( 'ASIDE'( Call ) ), !. % commit retract
This method makes sense when assert requires less storage than Call. or
when the implementation has no general garbage collector but reclaims
storage left by retracted clauses.
with..gc can be employed in loop optimisation, which is an important
application of forced failure. Essentially, recursion is the most natural
Prolog counterpart of Pascal-like iteration. Consider a program that takes
large chunks of an even larger text. extracts some data from them. and
puts these data into an open tree. The storage for a step is worth recover-
ing. Let step assert basta. after having encountered the nal chunk. The
loop can be written as follows:
buildtree( _ ) :- retract( basta ), !. % remove the signal
buildtree( Tree ) :-
with_gc( step( Tree ) ). buildtree( Tree ).
(Find a similar solution for closed trees.)
I This technique was advocated by R. A. Kowalski at the Logic Programming Work-
shop in Debrecen. Hungary. I980.
I28 4 Simple Programming Techniques
' It is only available in Toy (see Section 5.12), but something similar is present or can
be programmed in several other implementations of Prolog.
4.3. Some Programming Hints I29
The execution of
tagfail( loop )
terminates the loop: tag(loop) fails, and the second clause of ear promptly
succeeds. With a step de ned as
step :- read( C ), once( C ).
and loop rede ned as
loop :- repeat, tag( step ), fail.
we can also exit one step by calling
tagfail( step )
long_mes( sym( S ) ) :-
display( Unexpected symbol on input: ' ),
display( S ), nl,
display( The remainder of the command will be ignored. ).
A switch can be easily tumed on:
tumon( Switch ) :- Switch, !. % already on
tumon( Switch ) :- asser1( Switch ).
|3O 4 Simple Programming Techniques
and off:
turnoff( Switch ) :- retract( Switch ), !.
% fails if Switch was off
turnoff( _ ). % already off
We can also revert the state of a switch (on > off, off > on):
ip( Switch ) :- retract( Switch ), !.
ip( Switch ) :- assert( Switch ).
Switches are really cumbersome to program without clausal data. It is
not dif cult to rewrite message:
message( Code, terse ) :- short_mes( Code ), nl, !.
message( Code, wordy ) :- long_mes( Code ), nl, !.
but the Terseness parameter ought to be carried everywhere throughout
the program: and dynamic reversal of a switch can be somewhat messy.
Our nal example demonstrates how assertions can be used to memo-
rize results of expensive computations for future use. Let the procedure
integrate perform symbolic integration of a given formula (and fail if it
cannot be done). If we are going to use this procedure frequently, we may
wish to avoid recomputing integrals. To this end, we should store every
integral, once computed, and always try to nd a ready answer before
launching actual integration. Here is a possible solution:
integral( Expr, IExpr) :- stored_integral( Expr, IExpr ), !.
integral( Expr, IExpr ) :-
integrate( Expr, IExpr ),
assert( stored_integral( Expr, IExpr ) ).
In fact. we have thus furnished our program with a primitive learning
capacity.
Let rev(X) denote the reverse of list X, let X with A denote the result
ofattaching A at the end oflist X (e.g.. lp. q] with r = lp. q. r]). Let X = Y
mean: X matches Y.
Assuming that X with A has already been de ned. a possible de nition
of rev is:
(4-I) rev(ll)=ll
(4.2) rev( [ A I Tail ] ) = rev( Tail ) with A
Now, recall that in Prolog we can comfortably express relations such as
the reversal of X is Y (which implicitly de nes Y as rev(X)) without
resorting to the notion of equality. To re-express (4.2) accordingly, we
begin with the introduction of a new variable to denote rev(Tail):
(4.3) T = rev( Tail ) I) rev( I A I Tail ] ) = T with A
This formula is equivalent to (4.2). Another new variable will denote T
with A:
(4.4) T = rev( Tail ) I) I TA = T with A =>
rev(lA|Tail])=TA)
which is equivalent to
(4.5) (T = rev( Tail ) /\ TA = T with A ) =>
rev(lA|Tail])=TA
We shall rewrite this implication. and the formula (4.l). using re-
ver.s'e(X. Y) instead of rev(X) = Y, and attut'h(X, Y. Z) instead 0fZ = X
tt't'lh Y:
I32 4 Simple Programming Techniques
We now express the longer list by the shorter (see reverse2a above!):
rev(L--lA,,|Z])=X--Y:>
rev(L--Z)=[A,,|X]--Y
and rewrite it in Prolog, with reverse_d(X, Y) instead of rev(X) = Y:
reverse_d( L -- Z, I An I X ] -- Y) :-
reverse_d( L -- I An I Z ], X -- Y ).
The base clause,
reverse_d( Z -- Z, Y -- Y ).
must come (i.e. be tried) rst, because otherwise each call with a variable
second parameter will fall into in nite recursion (you may wish to check
this more thoroughly). This is where the peculiarities of Prolog come into
play, and obscure the so-far clean derivation. It is even worse: we have
missed one weakness of almost all Prolog implementations: the absence
of so-called occur check during uni cation (see Section I.2.3). Therefore,
the base clause matches calls with a non-empty list as the rst parameter,
if only the list ends with a variable. For example, the call
reverse_d( [ a, b I Z I -- Z, Rev )
instantiates Z < [a, b I Z] and Rev < Y -- Y. contrary to our expecta-
tions. One possible remedy is to instantiate the nal variable as [] before
going on, but to this end both clauses of reverse_d must be duplicated.
The complete procedure follows.
reverse_d( E] ~ L]: Y Y ).
reverse_d( Z -- Z: Y ~~ Y ).
reverse_d( L ~ Z: [An I X] - Y ) =-
Check that even this improved version loops for negative lists such as
[b] -- la, b]. As you see. difference lists are useful but can be rather tricky.
4.4.2. Sorting
(4.28) L=X++[A,BIY]/\A>B/\LT=X++[B,AIY]
=> sorted( L) = sorted( LT)
where X + + Y denotes Y appended to X. We should describe explicitly
the less ordered sequence by the more ordered, e.g. thus:
(4.29) L=X++[A,BIY]/\A>B/\LT=X++IB,AIY]
/\SL= sorted(LT):>SL= sorted(L)
trans_sort( L, SL ) :-
=1PPnd(X.lA Yl.L).A>B.
append( X, I re 2.1:: <-M ], LT), trans_sort( LT, SL ).
(4.30) (L=X++IA,BIY]/\A>B)I>L=sorted(L)
When we rewrite this in Prolog, we shall drop the premise and place the
resulting clause after the recursive one. The rst two calls in that clause
can be regarded as tests: does L contain a two-item subsequence, and is
this subsequence unordered? The clause fails if this is not the case, and
the premise of (4.30) becomes trivially true. We are left with the clause
trans_sort( L, L ).
which is exactly the required base clause: we proceed from less or-
dered sequences, so that eventually we must get an ordered permu-
tation.
The arrangement of calls in the rst clause is crucial. To begin with,
we repeatedly isolate any two contiguous items (this fails if the list is too
short), and we look at their ordering. The rst improperly ordered pair
terminates this process, and we recursively sort the improved se-
quence. The procedure is attributed to van Emden (Coelho et al. I980).
4.4. Examples of Program Design I37
is an example of Euler path. If we remove the edge be. the resulting graph
will not be an Euler graph (you may wish to check this).
We shall develop a very simple program. depending only on the most
intuitive properties, which looks somewhat blindly for Euler paths. A
more ef cient algorithm arises from a theorem that characterizes Euler
graphs. We shall quote the theorem at the end of this section.
At rst, we must choose a method of representing graphs. We can
assume that the graph contains no isolated venices (vertices which do not
belong to any edge); otherwise. it is certainly not an Euler graph. A graph
without isolated venices can be represented by its set of edges alone. An
edge is an unordered pair. i.e. a set of two vertices. Since we have no sets
in Prolog (as in most programming languages). we shall represent edges
with ordered pairs:
Vl<>V2
(<> is a non-associative in x functor). and we shall try to make the pro-
gram account for the cummutativity of pairs.
Euler graphs have the following two properties:
l. A graph with one edge is an Euler graph.
2. Suppose we take out an edge, and what remains is a Euler graph with
an Euler path starting with one of this edges vertices: then the whole
graph is an Euler graph (and we happened to have removed a tenninal
edge of an Euler path).
Let paths be represented with lists of vertex names. and let parh(E.P)
mean E is the set of edges of an Euler graph. and P is an Euler path in
this graph". The rst property above can be rewritten as two fonnulae:
(4.39) path({Vl <>V2 },[Vl,V2])
(4.40) path({V2<>Vl }, I Vl, V2])
In other words, both arrangements of vertices are equally satisfactory.
Here is how the second property can be formalized (\ denotes set
subtraction):
(4.41) path( E \ { Vl <> V2 }. [ V2 I RestofPath ] ) I}
path( E, [ VI. V2 I RestofPath ] )
(4.42) path( E \{ V2 <> Vl }. [ V2 I RestofPath I ) I)
path( E. [ Vl. V2 I RestofPath ] )
Before we rewrite (4.39-4.42) into Prolog, we must nally decide how
to represent sets. We can use any structure capable of holding uniform
data; to keep things simple we shall use lists. (Another possibility would
be to represent each edge as a separate clause, but then we would have no
4.4. Examples of Program Design l4l
5 SUMMARY OF SYNTAX
AND BUILT-IN PROCEDURES
=:=. etc., or lists of single characters. will not be described. See Section
5.2 and on for applications of these forms in built-in procedures.
Some comments in plain English will be interspersed in the BNF
description. See also the notes at the end of this section.
digit::=0I I I2I3I4I5I6I7I8I9
symch::=.I:I-I<I=I>I+I/I
*I?I&I$I(wI#I-aI\
COMMENT a lone dot followed by white space is not a symch
but a fullstop
solochar ::= . I ; I !
token ::= functor I variable I integer I
string I hracketbar
COMMENT tokens are listed to explain note 6 below
bracketbar::=(I)I[I]I{I}II
comment ::= % I nonlineend } lineend
COMMENT lineend is an end-of-line ( linefeed )
character; nonlineend is any other
character. Toy converts line-ends to
single linefeeds
whitespace :: = I layoutchar I
COMMENT layoutchar is blank or tab or lineend
or any nonprintable character ( in
ASCII these are characters with codes
=< 3| )
fullstop ::= . layoutchar
Notes:
I. Mixed functors have not been described. but their inclusion is straight-
forward:
term ::= op|,;,_,|_N termN_|
and I I other combinations. In Toy. a mixed functor can only have one
binary and one unary type. both with the same priority.
2. There are numerous ambiguous combinations of contiguous operators.
This grammar does not account for them. See Section 7.4.3 (and Ap-
pendix A.3) for a rather detailed description in Prolog.
3. Not all functors can be declared as operators. Quoted names are al-
ways taken as normal functors.
4. In the de nition of body. commas and semicolons need not have been
actually singled out. because they are regular in x functors. The de ni-
tion
body ::= nonvarint
For the purposes of this chapter. built-in procedures fall into two
groups. System procedures are implemented in the interpreter described
in Section 7.3. Prede ned procedures are written in Prolog; they belong to
the user interface described in Section 7.4. Together. these two groups
cover the basic set of Prolog-I0 procedures. Differences and extensions
are noted where appropriate but this is a description of Toy and is not
intended as a replacement for the Prolog-I0 manual. The procedures are
roughly classi ed according to their purpose.
A system procedure call may fail. succeed or raise an error. Failure or
success is equivalent to a failure or success of a normal procedure call.
The only difference is that success is usually accompanied by a side-
effect, such as writing a character. setting a switch, etc. A failing system
procedure does not usually cause any side-effects (input procedures are a
notable exception).
An error is raised when a system procedure detects an incorrect
parameter (or parameters). If the description of a procedure mentions the
form of expected parameters, parameters of unlisted forms will cause an
error to be raised. There is no guarantee that the error will be raised
before any actions are performed. though this is usually so.
Raising an error consists in invoking procedure error! I . with its single
parameter instantiated to the offending system procedure call. In general.
error behaves as if its call were present in the program instead of the
I43 5 Summary of Syntax and Built-in Procedures
In descriptions, PARI , PAR2 etc. stand for actual parameters in the built-
in procedure call.
Note that I23 is a name, and I23 an integer. 9 is the integer nine. and
'9' is the digit (character). The output procedures do not always distin-
guish between the two (writeq does).
Toy introduces a number of prede ned operators. Some of them are
used as in x or pre x procedure names. Table 5.l is the list of prede ned
operators:
5.3. Convenience I49
TABLE 5.1
Prede ned Operators
xfx I 200
fx I 2(1)
--> xfx I 2(1)
xfy I III)
xfy I (D0
l'|OI fv 9%
xfx 7(1)
is xfx 7(1)
xfx 7(1)
=\= xfx 7(1)
< xfx 7(1)
=< xfx 7(1)
> xfx 700
>= xfx 700
@< xfx 700
@=< xfx 700
@> xfx 7(1)
@> = xfx 7(1)
xfx 7(1)
\== xfx 7G3
xfx 7(1)
+ yfx SM
+ fx 500
yfx 500
fx 511]
yfx 4-00
I yfx 4-00
mod xfx 3(1)
5.3. CONVENIENCE
true
always succeeds
fail
always fails.
not CALL
the not procedure (but see Section 4.3.2!): succeeds only when
the parameter fails. De ned in Prolog:
not C :- C. !, fail.
I101 _.
I50 5 Summary of Syntax and Built-in Procedures
CALL . CALL
the and procedure: succeeds only when both arguments succeed.
De ned in Prolog:
A, B :- A. B.
5.4. ARITHMETIC
In the descriptions. div stands for integer division, and mod for taking
the remainder of integer division.
The following are correct invocation patterns for sum/3 (not in Pro-
log-I0).
sum(INTEGER. INTEGER. INTEGER)
succeeds only if PARI + PAR2 = PAR3
sum(INTEGER. INTEGER. VAR)
succeeds after unifying PAR3 with the value of PARI + PAR2
sum(INTEGER, VAR, INTEGER)
succeeds after unifying PAR2 with the value of PAR3 PARI
5.5. Comparing Integers and Names I51
The following are correct invocation patterns for prod/4 (not in Pro-
log-I0).
prod(INTEGER, INTEGER. INTEGER, INTEGER)
succeeds only if PARI * PAR2 + PAR3 = PAR4
prod(INTEGER. INTEGER. INTEGER. VAR)
succeeds after unifying PAR4 with the value of PARI * PAR2 +
PAR3
prod(INTEGER. INTEGER, VAR. INTEGER)
succeeds after unifying PAR3 with the value of PAR4 PARI *
PAR2
prod(INTEGER. VAR, VAR, INTEGER)
succeeds after unifying PAR2 with the value of PAR4 div PARI and
PAR3 with the value of PAR4 mod PARI
prod(VAR. INTEGER. VAR. INTEGER)
like the previous one. but with PARI and PAR2 exchanged
prod(INTEGER. VAR, INTEGER. INTEGER)
fails if (PAR4 - PAR3) mod PARI is not zero; otherwise succeeds
after unifying PAR2 with the value of (PAR4 - PAR3) div PARI
prod(VAR. INTEGER. INTEGER. INTEGER)
like the previous one. but with PARI and PAR2 exchanged
TERM is TERM
the procedure is assumes PAR2 is an integer expression. i.e. a tenn
composed of integers by means of standard arithmetic functors: +
(binary and unary). (binary and unary), 1-. /. mod. The procedure
fails if PAR2 is not an integer expression. Otherwise it evaluates the
expression and tries to unify the value with PARI. According to
Prolog-I0 conventions. is can also evaluate a list
[ INTEGER ]
as this INTEGER; e.g. 55 is I55] succeeds. (This is needed in Prolog-
I0 mainly for evaluating single character strings to ASCII codes.)
De ned in Prolog.
less(INTEGER. INTEGER)
succeeds only if PARI < PAR2. Not in Prolog-I0.
I52 5 Summary of Syntax and Built-in Procedures
TERM = TERM
tries to unify PARI and PAR2. De ned in Prolog:
X=X.
eqvar(VAR, VAR)
succeeds only when the parameters are two occurrences of the same
nondummy variable. Not in Prolog-I0.
5.7. Input/Output I53
TERM == TERM
succeeds only when the parameters are two occurrences of the same
term. For example, if A. B are uninstantiated.
P( A ) = = P( B )
fails. even though
P(A)=P(B)
succeeds. De ned in Prolog.
TERM \== TERM
succeeds only when the parameters are not two occurrences of the
same tenn. De ned in Prolog.
5.7. INPUT!OUTPUT
seen
closes the current input le; user becomes current. Has no effect if
the current le is user
teIl(FILENAME)
the speci ed le becomes the current output le; the terminals name
is user
telling(TERM)
tries to unify the parameter with the name of the current output le
told
closes the current output le; user becomes current. I-Ias no effect if
the current le is user
5.7.3. Terms
display/(TERM)
writes the term onto the current output. The term is written in stand-
ard notation (pre x with parentheses) and identi ers are not quoted
even if they normally should be. Variables are written as _n. where
n is an address. There is no guarantee that a variable will be printed
as the same address in different invocations of display. In Prolog-I0.
display is a little different: it always writes on the user's terminal.
write(TERM)
writes the term onto the current output. The term is written accord-
ing to operator declarations currently in force. No identi ers are
quoted. Variables are written as X I . X2 etc. Each invocation of write
begins numbering from I. so that e.g. the calls
write( X ). write( f( Y. X ) )
will produce
XIf( XI. X2 )
5.7. Input/Output I55
skips and reprints the input until the rst (still unprocessed) full stop.
and tries to unify PARI with e r r. (If the erroneous line does not
contain a full stop. you should input one before Prolog resumes.) See
the next section for behaviour on le end detecting. De ned in
Prolog in terms of single-character input (see the next section).
op(INTEGER. TERM. ATOM)
declares an operator with PAR3the name. PARIthe priority
(I =< PARI =< I200. and PAR2the type. PARI is usually less
than I000. to avoid con icts with clause-constructing operators (see
the table in Section 5.2); operators with lower priority take prece-
dence over those with a higher priority. PAR2 must be a proper word
or symbol. Admissible types of operators are fx. fy (unary. pre x);
xf. yf (unary. post x); xfx. xfy. yfx (binary. in x). The types fx. xf.
xfx are non-associative; fy, yf. associative; xfy. right-associative;
yfx. left-associative. Any other PAR2 causes an error.
If an operator declaration with this name but another priority is al-
ready in force. the procedure replaces the old declaration with the
new one. If a declaration with the same name and priority exists.
three possibilities arise:
both operators are binary or both unary; the old de nition is re-
placed;
the old operator is unary (binary). the newbinary (unary); a
mixed functor is declared;
I56 5 Summary of Syntax and Built-in Procedures
with ordinal number greater than 32. Does nothing if it already is such
a character; otherwise repeatedly invokes rch.
lastch(TERM)
tries to unify its parameter with current character
wch(CHAR)
writes the character on current output (the linefeed character is inter-
preted as line terminator)
nl
terminates the current output line. De ned in Prolog:
:- ordchr( I0. Ch ), assert( ( nl :- wch( Ch ) ) ).
rdch(TERM)
gets the next character from current input (by invoking rch). Makes a
copy of current character. treating a non-printing character (including
line end) as a blank; tries to unify the copy with its parameter. De-
ned in Prolog.
rdchsk(TERM)
same as above. but preceded by a call on skipbl
5.7.5. Others
These procedures are not really concemed with input/output. but the
only effect of status is to write something. and ordchr is most useful when
reading or writing non-printing characters. They all are not in Prolog-I0.
ordchr(INTEGER, CHAR)
succeeds only when PARI is the ordinal number (ASCII code) of PAR2
ordchr(VAR. CHAR)
succeeds after unifying the variable with the ordinal number of the
character
ordchr(INTEGER. VAR)
succeeds after unifying the variable with the character whose ordinal
number is the value of PARI mod I28
iseoln(TERM)
tries to unify PARI with the end-of-line character. De ned in Prolog:
:- ordchr( I0. Ch ). assert( iseoln( Ch ) ).
status
writes memory utilisation information on the current output
See also consult/I. reconsult/I. listing/0 and listing/I in Section 5.ll.
I58 5 Summary of Syntax and Built-in Procedures
symch(TERM)
tests whether the parameter is one of the following characters:
+--/=@#s&=.'.><>\
nonvarint(TERM)
tests whether the parameter is a NONVARINT (neither a variable
nor an integer); not in Prolog-I0
atom(TERM)
tests whether the parameter is an atom (a NONVARINT without
arguments)
The procedures pname and pnamei are not in Prolog-I0. They replace
name/2. which is similar. but which uses lists of integers (ASCII codes) in
place of our lists of characters (see Section 5.7.4).
pname(NAME. TERM)
builds a list of characters fomting the name and tries to unify it with
PAR2
pname(VAR. CHARLIST)
succeeds after unifying the variable with a NAME formed of the
characters on the list. (Note that pname(X. II. 2. 3]) binds X to the
name I23. and not to the integer I23).
pnamei(INTEGER. TERM)
builds a list of decimal digit characters (constituting the written form
of the integer) and tries to unify it with the term; the integer must not
be negative.
pnamei(VAR, DIGITLIST)
succeeds after unifying the variable with an integer whose written
form is given by the digit characters on the list. Even when the
parameters are formally correct, an error may be raised if the speci-
ed integer is too large.
functor(VAR. INTEGER. 0)
PAR3 is the integer zero; succeeds after unifying the variable with
PAR2 (this version is allowed for completeness. see below for sensi-
ble uses of functor)
functor(VAR. NAME. INTEGER)
succeeds after unifying the variable with a term whose main functor
has the name and atity de ned by PAR2 and PAR3, and whose argu-
ments are different variables; PAR3 must not be negative
functor(INTEGER. TERM. TERM)
tries to unify PAR2 with PARI and PAR3 with the integer zero
I60 5 Summary of Syntax and Built-in Procedures
5.12. CONTROL
halt(ATOM)
stops the interpreter after writing the atom. Not in Prolog-I0.
stop
stops the interpreter. Not in Prolog-I0. De ned in Prolog.
The following procedures are not in Prolog-I0. They are useful for error
handling, but are dirty. and should be used sparingly.
tag(CALL)
this is a form of call/I which can be referred to by tagfail/I . tagexit/2,
tagcut/2 and ancestor/2. The parameter of tag is called a tagged
ancestor of its descendants; it is never removed from the stack as a
result of tail recursion optimisation (see Sections 6.4 and 7. I).
NOTE: a tag is recognized only when explicitly written in its clause.
In particular call(tag(C)) is equivalent to call(call(C)).
ancestor(TERM)
searches for the nearest tagged ancestor uni able with the parameter;
fails if no such ancestor is found. otherwise uni es and succeeds.
tagcut(TERM)
searches for the nearest tagged ancestor uni able with the parameter.
Fails if no such ancestor is found: otherwise uni es. removes all
existing fail points associated with the ancestor and its decendants
and succeeds.
tagfail(TERM)
equivalent to
tagcut( PARI ), fail
i.e. if the appropriate tagged ancestor is found. the ancestor fails
immediately; otherwise tagcut fails.
tagexit(TERM)
searches for the nearest tagged ancestor uni able with the parameter;
fails if no such ancestor is found. otherwise uni es and passes control
to the ancestor, which succeeds immediately.
5.13. DEBUGGING
phrase(CALL, TERM)
treats CALL as a nonterminal symbol of a grammar rule. schemati-
cally
nt( ARGI, ..., ARGn ).
and initiates grammar processing-with this initial symbolby
calling
nt( TERM, []. ARGI, ARGn)
De ned in Prolog (see Section 4.2.6).
5.15. MISCELLANEOUS
length(NONVARINT. TERM)
PARI must be a closed list. Computes the length of this list and tries
to unify the resulting integer with PAR2. De ned in Prolog.
isclosedlist(TERM)
succeeds only when the term is a closed list. De ned in Prolog. Not
in Prolog-I0.
I66 5 Summary of Syntax and Built-in Procedures
6.1. INTRODUCTION
I . 7).
./\@ .
(bl IE: If 10??
I P B
'/\
I I
FIG. 6.l The Non-Structure Sharing representation of terms: (a) t(A. q(A. Y))
and t(p(X). B) before uni cation. (b) t(A. q(A. Y)) and t(p(X). B) instantiated to t(p(X).
q(p(X), Y)) after uni cation.
t _. , 0
'\ I
p Y: -
GIEII)
><= '\
prototype variable trarnes
0
t _.,
....
Q'l'I'I'l I ... I
d
0 <2 >
I
\/ Y.
\
'
I
I
X: 0 3
FIG. 6.2 The Structure Sharing representation of terms: (a) Two instances of t(p(X).
q(p(X), Y)). both sharing the same prototype. (b) tennl instantiated to t(p(c). q(p(c). d)) and
term2 instantiated to t(p(r(a)). q(p(r(a)). l'( )))-
6.2. Representation of Terms I-/I
t \
I
I
Q P WI
GED
as-\-.\.9/ =<=
(b)?.> 7:.-
. \ . \ I
Q ?
/\ 21
FIG. 6.3 The Structure Sharing representation of tenns: (a) t(A.q(A. Y)) and
t(p(X). B) before uni cation. (b) t(A. q(A. Y)) and t(p(X). B) instantiated to t(p(X).
q(p(X), Y)) after uni cation.
I72 6 Principles of Prolog Implementation
< 1 >
/\q ?
/
FIG. 6.4 Structure Sharing: the DAG t(p(X). q(p(X), Y)) represented by a tree.
ensuring that the same variable becomes a part of all the corresponding
terms associated with a clause instance. With Structure Sharing, this is
done by allocating a single frame for all the variables appearing in a
clause, at the moment of its activation. All occurrences of a variable
within this clause's prototypes are encoded as the same ojfset in the
common variable frame (this is just an application of the technique dem-
onstrated in Fig. 6.4).
In practice, most NSS implementations use a very similar approach
to solve the problem (despite its name. it is a hybrid method). Term
instances are also encoded as prototypes, with variables represented by
offsets into a clause's variable frame. One difference is that variable
frame locations hold only single pointers rather than term handles. The
othermore importantdifference is that terms formed in this way are
only virtual instances. This is to say that they may be used only as data
selectors, directing uni cation to instantiate variables in the variable
frame. Whenever one of these terms is to become a variables instantia-
tion. a real instance (a new DAG) must be built. If this new instance
contains a variable. its variable node becomes a copy of the appropriate
location in the variable frame, while the location is made to hold a pointer
to the node. This ensures that all future references to the variable will end
up in the node.
The process is shown in Fig. 6.5. Note that here, too. prototypes can
I ... ....
,;.. EB
I I - *- - -
I 5- 1 m I
anrr
/I
I
El I
I
.
@..____.. .P
\ .
@/ @.___.. @\. a:s_/
t I
8 GED
td)
A=0 x=0
p II
that one may need to alleviate that by introducing arti cial failures in a
few well-chosen places (see Section 4.3.2).
In the simplest form of Structure Sharing. a variable frame is an
integral part of the representation of a number of term instances. and
cannotin principlebe deallocated so long as any of these terms is
accessible. It must be allocated on a variable stack. which closely resem-
bles the copy stack of NSS (except that a garbage collector. if required. is
harder to implement). The activation stack is smaller, as it only holds
control information.
The most important advantage of NSS is that retention past the mo-
ment of procedure termination concems only those terms which become
variable instantiations. With simple Structure Sharing. on the other hand,
all terms are retained. As it tums out. terms are often used as selectors
rather than constructors. and clauses frequently propel a computation
along without creating many long-lived objects. The copy stack is there-
fore usually smaller than the variable stack. and the effects of memory
requirements being a function of time are much less pronounced with
NSS.
Starting with DECProlog-I0, many Structure Sharing implementa-
tions take advantage of the difference between terms which must live
longer than their clauses and those which need not. As a clause is read in,
it is analysed to detect variables which cannot. under any circumstances.
be used to form instantiations of variables outside the clause. These are
classi ed as local variables, whereas the others are called global. The
variable names are all local to the clause, of coursethe terminology is to
convey that global variables are long-lived, while local variables may be
allocated (and deallocated) with the clause's activation frame. The activa-
tion stack is accordingly referred to as the local staclr, and the global stack
holds global variables.
A simple. though not necessarily the most subtle, classi cation crite-
rion is whether a variable appears inside a term (i.e. is not only a proce-
dures parameter). For example. in
a(X.f(Y)):-b(X.g(Z)).
we nd that X is local. The rule about directing variable-to-variable
references towards the bottom of the stack suf ces to ensure that its de-
allocation will not leave dangling pointers. The variable Y is obviously
global, as the clause can export it after having been activated by
a( Something. Variable ).
The status of Z is uncertain. It can be bound to a variable in b. but we are
really interested only in those outside variables which outlive a. If the
6.2. Representation of Terms I77
body of a were
b(s(Z))
then Z could conceivably be classi ed as local (according to our experi-
ence, though, allowing such cases could complicate the implementation).
But as the clause stands. we need to analyse b (assuming it will not
subsequently be modi ed!) to check whether g(Z) can be made an instan-
tiation of a variable to which X is bound. For example. with b de ned as
b( V, V )
the call
a(P.Q)
would instantiate P < g(Z) and Q < f(Y)both Y and Z would be ex-
ported. Variables that do not appear in terms can only be used to carry
information around the clause; it is safest to assume that all others will be
used to form structures.
This assumption does not yet allow Structure Sharing to be really
competitive with NSS. To achieve this, we must declare our intentions by
providing so-called mode declarations. In Prolog-I0 one writes
:- mode member(?, +).
to inform Prolog that the second parameter of member will never be a
variable, though its rst parameter might be one. This means that the
procedure
member( E, [
member( E, [ ><l'I'l l"l" member( E, L ).
will not be invoked as a generator of lists, so the compound tenns will only be
used as selectors and all the variableseven those global by the general
criterioncan be classi ed as local.
Providing mode declarations may seem a nuisance, but they are good
documentation (and are not compulsory). The declarations are static and
must necessarily be less informative than the dynamic special-case analy-
sis of NSS. In common cases. however, the difference is not detectable
and this form of Structure Sharing is. in fact, as good as NSS with regard
to memory utilisation. This does not mean that the two behave identi-
cally. Programs can be written which make any one method almost arbi-
trarily worse than the other (how would you go about devising such a
program?).
6.3. CONTROL
make this information suf cient, stack and heap areas below the levels
indicated by a fail point record are treated as frozen, i.e. under special
protection.
Binding a frozen variable is allowed, but must be logged by pushing its
address onto a fourth stack, called the trail (its size is also remembered in
a fail point record). The control stack, however, is frozen quite literally.
Whenever a terminating procedure would cause control to be retumed to
an activation record (AR) within the frozen area, a copy of the AR is
created just above the protected part of the stack. The copy de nes the
current procedures environment: an ancestor link provides access to the
frozen AR of the procedures caller. To avoid copying that part of an AR
which contains variables, the variables ofa clause are associated with the A R of
its caller rather than with its own AR. An AR's copy will be used to perform a
new call: the original describes the previous call, so its variables are irrelevant.
All this is illustrated in Fig. 6.6.
With these precautions, backtracking consists in undoing bindings
made after creating the most recent fail point record FR (a simple matter
of resetting locations referenced in the top-most fragment of the trail),
popping all stacks to the levels indicated by FR, grabbing the untried
clause list and popping FR itself. This is rapid enough; the unescapable
penalty is that of maintaining (several copies of) frozen substacks which
would normally disappear with the shortening of call chains. One of the
reasons why judicious use of the cut is so important (see Section 4.3.1) is
that it allows Prolog to reclaim stack storage. To invoke the cut is to pop a
number of fail point records, thereby unfreezing areas of memory.
lb
serum 3
.-Q 1
U i
['11
Wx wr|telW) mt
N_I --I,
ll *1
in
\
\-
1 tnlow $5 '
trail level
next clause Ill d-'""""Y
'_ 0
TRAIL FAIL FOTNT STACK comnot STACK COPY STACK
O
O i
7' """ "
Es
' a \\
.' - - - - - - - "1:-~
um 2
\ T
.- - - - - - -- In. -
\
a gw|,wm|'\Ti,Tait
Q
~ - -- Q
\
\ \\_..I H11
\
'-@=< =-- -- \ \
5"-I-
\Q- \\
E a....:..m
trail level
O
TRAIL FAIL POINT STACK CONTROL STACK COPY STAG!
FIG. 6.6 Control stack management: (a) The example program. (b) Clause (iv) is
invoked. (Solid lines in control stack are the ancestor links, dotted lines are variable bind-
ings. Active calls are underscored, remaining calls in each clause are also shown. The model
is NSS.) (c) One step later, d is ready to return. (d) After returning from d and b. (Frame 3 is
a copy of I, executing the next call. The variable Z was destroyed when the stack was
poppedit was just above the freezing level.) (e) After failure, before invocation of clause
(iii). (f) Clause (iii) and (i) terminated. directive in control.
6.4. Tail Recursion Optimisation l3l
id) v:rite[W|,Iait I
3
X: F1111 \
\
ii W|,vvriteIW) hit \\
- . . . 1 I
I II e
(ET blxlf I
_ 2
X; 0- - - - - -- -_
aIW],writeIW),tait ' \
I
. -- I
W: tree
dummy
0
If) v.m|w1,Fa1t |
' I
T
Y
1"
f
W1 0-*1- - - - - - "'
dummy
0
:- ..., p...
p:*" allulq.
q :-
The idea of structure sharing comes from Boyer and Moore (I972). It
was used in the original Marseilles interpreter (Battani and Mloni I973,
Roussel I975), which, actually, was preceded by an earlier, experimental
version (Colmerauer et al. I972). That interpreter did not have anything
like fail point records. Though variables were allocated on a separate
stack, control frames were alsoas a rulepopped only on backtrack-
ing. Classi cation of variables into local and global was introduced with
the DECProlog compiler. Warren (I977a) is the original reference, see
also Warren et al. (I977) and Warren (I980b). A preliminary report on the
rst NSS implementation is Bruynooghe (I976).
The idea of tail recursion optimisation is well known. Bruynooghe
was the rst to use TRO in Prolog, while Warren used a different method
as an afterthought; see Warren (I980a).
A good detailed explanation of the implementation principles is Bruy-
nooghe (l982b). It stresses both the similarity of structure sharing to
conventional handling of procedure instances and the similarity of Pro-
logs control structures to a proof tree. Van Emden (I982) contains a
disciplined derivation of the control algorithm, starting from search-tree
traversal.
Most implementations merge fail point records and control frames
into a single type of record. To our knowledge, they were rst separated
in Donz (I979), an early approach to global optimisation, where they were
talked of as the and-nodes and or-nodes of a search tree. We like the
separation because it brings to light the fact that backtracking is imple-
mented almost exactly as proposedin a more general settingin Bo-
brow and Wegbreit (I973), the classic paper on implementation of uncon-
ventional control structures.
A comparison of NSS and structure sharing can be found in Mellish
(I982), with some comments in Bruynooghe (l982b).
Mellish (I981) is an early approach to automatic production of mode
declarations by means of global ow analysis. Other papers concemed
with global analysis, though not for the sake of ef ciency, are Bruy-
nooghe (I982a) and Mycroft and O'Keefe (I983).
At the time of this writing we know of two new compilers being
developed. The references are Bowen et al. (I983) and Ballieu (I983).
See also Section 2.5 for references on Prolog implementations with
coroutining and parallelism.
As a point of interest, we shall mention two papers describing imple-
mentations of Prolog done by embedding it in another programming lan-
guage: Lisp (Komorowski I982) or POP-ll (Mellish and Hardy I983).
7 TOY: AN EXERCISE IN
IMPLEMENTATION
7.1. INTRODUCTION
Prolog, and only a subset of the usual system (built-in) procedures. The
full user interface and library is implemented in Prolog (see Section 7.4)-
this approach was taken in the original Marseilles implementation, and in
a number of implementations since. A short program called the boot-
strapper (see Section 7.4.l), written in Toy-Prolog, is used to translate
into Toy-Prolog other parts of the user interface, which are written in a
slightly restricted fonn of the usual syntax. Next, various interface pro-
grams can be loaded during initialization (see Section 7.3.6).
A Prolog program called the monitor supports an interactive pro-
gramming regime (see Section 7.4.2). Full Prolog-I0 syntax can be used
(see sections 7.4.3-7.4.5). A program called the translator can be used
to convert Prolog-I0 programs into Toy-Prolog (see Section 7.4.6). The
translator shares most of the monitors routines. lt can be used for large
(interactively debugged) programs which are to be loaded quickly, with-
out repeated syntactic analysis by the rather slow parser in the monitor.
See Appendix A.4 for a few examples of such programs.
We shall nish this section with an example of Toy-Prolog syntax.
There is no point in providing a precise description of this language, as it
is very simple and the recursive-descent parser (a fragment called the
READER, see le READER.PAS on the diskette) is so straightforward that
it can easily be used to resolve all doubts. Our example is
P(la.lb.Ld|X1.Y)=-q(Y.X).r(s(Y).-).
:-p(Z,(t:-u,v)).
Toy uses several disjoint areas of memory for its data structures (see
Fig. 7.I). They are listed below.
CT (character table), used to store strings: print names of Prolog func-
tors and predicate symbols;
AT (atom table), used to store atoms. In this chapter atom does not
denote a functor with no arguments. It is the generic name of a record
I88 7 Toy: An Exercise in Implementation
HTPBH
=1""9I- .-_"9bt
prototypes
cttow mow "N
c1 AT
Ii F - t-| BTHIGH I'll-IIGH ground
pt0IOI)1>IS
| FROTLOW
tree I ree cop STACK HG-I
I
.____.__._...l 4- PT
hadnraclt
l.2 %- stildt
M,
__csbot
stacks
"an reep='ds I Two
|__i. FTLOW BTIDN TTLOW variable
ack
Ft B1 It st MTLDW
MT
FIG. 7.l The main data areas.
The character and atom tables fonn the dictionary: a data structure
used primarily as an aid in translating between the extemal and intemal
forms of Prolog terms and clauses. lt also supports access to procedures,
making it easier to implement variable calls and clause manipulation.
An atom is a record containing infonnation about a functor and/or a
predicate symbol. The difference between a tenn and a procedure de-
pends only on context and is not always recognized. Predicate symbols
are denoted by functors when clauses are treated as terms (e.g. in assert);
conversely, a functor may be used to invoke a procedure (as in call).
The attributes of an atom are
Printing a functor,
Determining arity,
Finding a procedure.
The main table, MT, holds a variety of objects which are distin-
guished partly by their addresses and partly by their contents. Addresses
are used to distinguish between prototypes and term instances ( elds
denoting variables contain variable offsets in prototypes, and variable
bindings in term instances). Prototypes of ground terms, which contain no
variables, are also used as instances: this helps keep down the size of the
copy stack.
Instances of non-ground terms are kept in the stack area. It is divided
into the copy stack and the variable stack. The variable stack holds acti-
vation-record variables and is separated from the copy stack because it
can shrink on procedure return and not only upon backtracking (see
Chapter 6).
Object contents are used to distinguish between integers, variables,
and normal terms with functors.
Integers are two-word objects. The second word holds the integer and
the rsta special marker INT, which prevents the interpreter from
treating integers as pointers.
7.3. The Toy-Prolog Interpreter I9]
Variables hold values less or equal to VARLIM (both INT and pointers
to MT or AT objects have values greater than VARLIM). VARLIM is
kept only inside the dummy variable (-) Prototype, whose address is
DUMVAR)(this prototype is treated as ground. The value
FREEVAR (equal to VARLIM I) lls free variable instances. Values
below FREEVAR are negative: in prototypes their absolute values
denote offsets in variable frames, and in instances their absolute values
Io)
member\
[1 '-IIEID
GIEID _
M}
.../\.. 6
NW
FIG. 7.2 The intemal representation of member:
member( :0, :0._ ) : I]
member( :0, _.:l ):member( :0, :l ). []
(a) The abstract form. (b) The data structures (variable o sets adjusted by offoff; []/0,
J2, J0, memberl2 denote addresses). (continued)
I92 7 Toy: An Exercise in Implementation
'
(bl I
' e
merrtler/2
N
I punot -uou
I
E
: 5 ./0 I
unuij nu:anru
1
IIEi il l
aan heap
I
E
I
Iii!
CT
HI
Hill
v_i._..|
AT
5
H
PUITO
NILPROTX
w DUMVARX
MT
I prototypes)
FIG. 7.2 (Continued)
IIIIII
an mtegr; -5 a variable instance: a vanatle |:lototy|:e;
hotnd totheobject ottset3 Iadjtstedbydlo l
at address 5
qLLr..1..1.'
atom ot pl3 an instance ot atom ot qlt
|=lqtX),_.'H.
X is tree
FIG. 7.3 intemal representation of terms.
7.3.4. Control
?- . [1 ?_..._._._[]
1 ,,,l,_ til |
c oI ....
N
M--==m
utm_g
-
@ ,,,,.,
= |
8-43..
8""? 2
an
III! =-='='
H
__..-.,
=. %sonu ifIII
'95: 0 IE!
III l"I= _ 0
main
E 2
T$'IIr=
imI|va=t.
I'r:env=2
IT BI Ft m
Ieteetul
FIG. 7.4 A more detailed form of Fig. 6.6d.
frozenheap is a pointer to the rst free location below the frozen part of
the copy stack;
frozenvars is a pointer to the rst free location above the frozen part of
the variable stack.
Execution of a Prolog program is driven by the procedure resolve.
Each tum of its loop is an attempt to match a call against a clause head, or
to execute a system procedure. At the beginning of this step the situation
is as shown in Fig. 7.4: a control frame for the current call is on top of the
stack, but the clause is not yet invoked and the associated variable frame
is empty. If the call was an erroneous system procedure call, the error
handler is activated (see below).
If the step is unsuccessful (the head did not match the call, or the
system procedure failed), the interpreter backtracks. Otherwise it enters
the procedure orif it was a system procedure or a unit clauseexits it.
Entering a procedure consists in setting up the control state so that the
next call to be executed will be the rst call in the freshly activated clause.
Exiting is the process of nding the next pending call: either the one
immediately following the successful current call, or (if this was the last in
its clause) a call following the nearest ancestor which is not the last call in
its clause.
To stop the execution, the ag stop must be set. This is done either by
the system procedure halt, or by backtrack when there are no fail points
left (i.e. when the directive failed) or by exitt when it cannot nd a pend-
ing call (i.e. when the directive succeeded).
Two auxiliary variables play the role of a program counter:
ccall contains a pointer to the prototype of the current call (it is the
prototype of the rst element in the list indicated by the current control
frames calls eld, unless that element is an invocation of call or tag:
ccall is then the outermost argument which is neither of these);
cproc contains a pointer to the descriptor of the procedure invoked by
ccall (for Prolog procedures, this is the rst clauses descriptor when in
forward execution, and a pointer recovered from a backtrack point
record's resume eld when immediately after a failure).
Notice that a fail points resume eld points at the predecessor of the
clause which is to be retried. This is so to make retract correct.
The algorithm used for tail recursion optimisation (procedure
trooverlay) merits some explanation. We employ the naive method sug-
gested by Fig. 6.7. After uni cation is over, procedure candotro checks
whether the current call is an untagged tail call and whether the ancestor
frame is not frozen. If so, neither the call nor the variable frame associ-
ated with the ancestor frame will ever be needed again. The current
7.3. The Toy-Prolog Interpreter I97
variable frame is shifted to replace the ancestor variable frame, and the
control stack is popped so that the ancestor control frame becomes top-
most (the most recently activated clause is still accessible through cproc).
The algorithm is made a little complicated by the fact that the shifted
variables may be instantiated to one another or to the destroyed (overlaid)
variables. Both cases are illustrated in Fig. 7.5.
The cut procedure simply removes as many backtrack point records
as necessary (possibly none) to ensure that the call invoking the procedure
containing the cutand all subsequent callswill not be retried. (There
are exceptions to this rule: notice that ,/2, ;/2 and call/I are transparent to
the cut.) After popping off backtrack points, the interpreter must purge
the topmost section of the trail to remove references to variables which
are no longer frozen. This is necessary, because such variables can be
popped off, or shifted, during TRO. Notice that the method of TRO applied
Io)
- vtcp=80
I
8 IIIII
3$!-'5E
Q
---~ F -1
HT
(variable stacltl
FIG. 7.5 Tail recursion optimisation: merging two frames. (a) The initial situation.
Both frames are not frozen, the call is tail recursive. The variables at 57 and $8 are instan-
tiated to the same free variable, the variable at 59 is instantiated to the variable at 44. (b)
Adjustment pass. (i) The rst variable (at 57) points at an overlaid free variable (at 56). The
direction of the pointer is reversed. (ii) The second variable (at 58) is dereferenced to that at
$7 through that at $6. The reference is remapp-ed: the second variable points at 5$the
future location of the variable now at S7. (iii) The third variable (at $9) is dereferenced to that
at 44 through that at $5. (c) Shifting pass overlays the parent's variable frame with the
current variable frame; the parent's control frame becomes current. (continued)
I93 7 Toy: An Exercise in Implementation
I bl
-~ 5 1 -
5 -
51 iB 51 I 51 msavaa
56
ss - -
ss ss
iE 1
3
ti
8 a 3
S2
it
Icl
vtop:5l
ill I
- =41
1|
I-I he P
HT
(variable stack)
FIG. 7.5 (Continued)
here makes it fairly easy to perform delayed frame merging after things
are made detenninistic by the cut. We shall not enter into the details of
this and of tagcut: this is a simple exercise.
The last thing worth mentioning is the handling of erroneous calls to
7.3. The Toy-Prolog Interpreter I99
| I
I I
to)
Gill
st- Irr
8
*.
_.. :._._-______.. _.l- _. -. 3.
.-
MT
Ietacke)
I'_"-_'_":
I
(bl
e-II] '1
topt
ElHi) csho
lifl til
MT
htldol
FIG. 7.6 Handling erroneous calls to system routines. (a) wch detects an incorrect
argument: a(V). (b) a call to error/I is set up.
7.4. Interpretation of Prolog-I0 in Toy-Prolog ZUI
7.3.6. Inltlalisation
this extra effort. Of course, once the monitor works, the bootstrapper is
no longer needed.
The bootstrapper is listed in Appendix A.2. Comments starting with
%% associate mnemonics with variable numbers. The main procedure is
translate (lines 2-I3), with two parametersthe names of the source and
output les. The unit processed with each tum of the failure-driven loop is
a single clause or a comment. The loop stops upon encountering a @ in
place of the rst non-blank character of a unit. The translation of a clause
is a string which is built on the y on a difference list of characters; the
list is represented by the two parameters christened termrepr and rest.
of_termrepr. Here is how the clause in lines 54-55 would look after
rewriting it into full Prolog and combining those parameters:
and then reads and executes a directive. The symbol table (retumed by
the two-parameter read; see the end of the next section) pairs source
names of variables with variables proper. After successful execution,
the symbol table is used to display nal instances of these variables, and
the driver awaits a pI'Il'lIlbIC character. If it is a semicolon, execution
resumes with forced failure, else processing of this directive terminates.
A directive can be pre xed with :- (we call such a directive a com-
mand, and that without the pre x a query). It will then be executed deter-
ministically, and variable instances will not be printed. However, neither
a non-unit clause nor a grammar rule make sense when read directly by
the driver: a two-parameter procedure :- or --> (presumably unde ned)
would be called. User procedures can be de ned by calling the built-in
procedure consult or reconsult; both are implemented in the driver. In
consult mode", term L --> R is treated as a grammar rule and translated
by the procedure transl_rule (see Section 7.4.4). A one-parameter term
:-C is treated as a command and executed. Other terms are treated as
program clauses.
The monitor is listed in Appendix A.3.
7.4.3. Reader
or Uaoelpvqool
only the leftmost delimiter. The rst true terminal becomes the current
input tenninal. In each step, the current input tenninal is compared to the
topmost terminal on the stack. Three situations are possible:
is represented by
tr(f00.tr2(.. rs0(lPl).ars0(5)))
The parser's entry point is the procedure gettr (lines I25-I27), and
the main loop is implemented as the procedure parse (lines I29-I38). The
loop tenninates successfully when the original input (bottom and dot
included) reduces to the sequence
bottom t( IntemaIRepresentation ) dot
The parser fails in two situations:
when the procedure establish_precedence fails, i.e. when the topmost
tenninal on the stack and the current input terminal do not compare;
-when the procedure reduce fails, i.e. the top segment of the stack does
not match any production.
The procedure topterminal (lines I40-I43) retums Top, the topmost
stack terminal, and its position: I means Top is the top item, 2 means it is
covered by a t(_).
The precedence relations are summarized in Table 7.I. We treat all
operators jointly with respect to other terminals. Empty slots signify erro-
neous combinations of contiguous tenninals.
A functor-functor relationship is the only potentially con icting
one: to establish the precedence relation for a given Top and Input, we
must consider their priorities and types (sometimes even some broader
context should be considered but this might require changes in the other-
wise deterministic algorithm). If the priorities differ, the functor with
lower priority is taken as stronger, according to the conventions of Pro-
log-I0. (Notice, however, that when Top is stronger, Input cannot be a
203 7 Toy: An Exercise in Implementation
TABLE 7.1
mrmll
~ IIH
< nu
>-liar
I -=< <IIHl
]
-< <IIHl >* >
{ =<
}
rVIunn-V vsVI I- ._.__|_.
vu-
VV 1
I >3
I VvI-IV
<e
-'3! A
AAA A
A
AW I
>3
' Top can be any pre x or in x functor, i.e. Types = [xf] and Types = Iyfl are ex-
cluded.
Input can be any in x or post x functor, i.e. Types = [fx] and Types = [fy] are
excluded.
pre x functor, and when Input is stronger, Top cannot be post x.) If Top
and Input have equal priorities, their types must be examined (see below).
Mixed functors require special treatment. In most contexts, their
inherent ambiguity is apparent: only one of a functors types can be
properly attributed to it. For example, let Input be &, an [xfy, fy] functor,
and Top a left parenthesis not covered by a non-terminal:
.... .. ( &
Surely, & can only be a pre x variation of this mixed functoran in x
variation is excluded. Likewise, if Top is $, an [xfx, xfl functor, covered
' Recall that our version of Prolog allows a mixed functor to have only one binary and
one unary type, both with the same priority.
7.4. Interpretation of Prolog-I0 in Toy-Prolog 209
.... .. $ Term ]
vnsid
I.
I:t|-I
The next six clauses of p (lines 2I I-222) take care of the six noncon-
icting slots in Table 7.2. The procedure restrict (lines 265-271) is used to
test and possibly disambiguate the type of a functor. The procedure per-
fonns set subtraction for sets given as lists; it will fail if the difference is
an empty set.
Now we must try to resolve a con ict in the remaining slot. A closer
look at the grammar allows a re nement of this slot (see Table 7.3). The
l2th and l3th clauses of p (lines 229-238) are responsible for situations
when the priorities differ. Again, we also attempt a disambiguation of
types.
The Ilth clause (lines 225-227) applies to functors with equal priori-
ties. Table 7.4 shows the precedence relation in this case. We allow all
combinations that can be disarnbiguated without analysing broader con-
text to the left or to the right of the two functors. For example, an xfy
functor f is weaker than an xfx functor g because the tenn
AfBgC
cannot be interpreted as
( AfB )gC
gs left argument would have, incorrectly, the same priority as g.
The relation of Table 7.4 is implemented by the procedure ip (lines
3I9-332), which returns lseq, gt or err. Con ict resolution is performed by
the procedure res_con (lines 273-291), which also returns lseq, gt or err
(err is later rejected by do_rels called in p). It also retums disambi-
guatedsometimes unchangedfunctors.
If only one of the terminals is a mixed functor, we choose a non-
con icting interpretation by comparing slots in Table 7.4. This is done by
TABLE 7.3
A Re nement for Two Operators
Hpre x III x 3 st x
pre x
in x
post x vvnVA
7.4. Interpretation of Prolog-I0 in Toy-Prolog ZI I
TABLE 7.4
Precedence Relations for Operators with Eqnal Priorities
lnputs .
lYP
Top's I
type xfy xfx xf 5- *5. ::= re
>- II
~ II
I I AI A
>~ >- II
A
yf >* >*
H I. -
> > --
......
TABLE 7.5
The Snbtable Template for Two Mixed Fnnctore:
tbeBInaryandUnary'I"ypesAreCo|||paredIrIth
EachOther
- -...,... -...,...
TTopBin RelBB
"M @ R-"=8
ZI2 7 Toy: An Exercise in Implementation
7.4.2). If data are incorrect, the parser will stop on the rst bad symbol
and read/2 will skip characters up to the nearest full stop after this symbol
(which may also be a full stop). The built-in procedure read/I simply
encapsulates read/2.
1.4.5. Library
The library (Appendix A.3, lines 585-1002) contains de nitions of about
20 built-in procedures (note that several simple procedures are also de ned in
the kernel le, appendix A.l). Their de nitions in Chapter 5 can
be treated as design documentation. Their implementation is largely
straightforward. We shall comment on a few not quite obvious passages.
The procedures clause(Head, Body) and retract(Clause) are back-
trackable, i.e. can be used in failure-driven loops that generate or re-
move all matching clauses. Here is a description of the generator (the
other procedure is programmed similarly). We are going to visit all
clauses of a procedure and suspend execution each time we get to a
matching one. This is achieved by setting up a recursive loop with its step
distributed between two clauses (see the procedure remcls/7, lines 814-
822). The rst clause does the matching. Upon mismatch, we immediately
proceed with the second clause, i.e. conclude the step. If the matching
succeeds, the generator succeeds, too, but with a pending altemative. A
failure later on resumes the second half of the step.
The procedures write and writeq both encapsulate the procedure out-
term(Tenn, With_or_withouLquotes) which rst uses numbervars (lines
623-632) to bind all variables in Term, and next calls
outt( TernLafter_numbervars, Context, With_or_withouLquotes ).
Context speci es the essential features of a functor whose argument is
Term. If it is not an operator, or there is no extemal functor, then Context
is fd(_, _). Otherwise, Context is fd(ff(Priority, Associativity), Dir). Term
may be to the left (Dir = I) or to the right (Dir = r) of the functor. Associ-
ativity may be a(l) or a(r) for left- and right-associative functors, and na(l),
na(r), or na(_) for non-associative functors. Context is tested by the pro-
cedure out)fl5 (lines 933-935) to decide whether Term should be paren-
thesized to avoid ambiguity in the case of equal priorities. Actually, the
ZI4 7 Toy: An Exercise in Implementation
7.4.6. Translator
8.1. PLANNING
(o)_ _ (bl
FIG. B.l (a) An initial state of the cubes world. (b) A nal state of the cubes world.
Let the nal state be that of Fig. 8.lb, described by a conjunction of two
goals: c on a, a on b. The rst goal is trivially achieved. To put a on b,
though, we must remove c from a, i.e. destroy an already achieved goal.
The simple strategy of achieving goals one by one (and freezing all rele-
vant facts) would not work in this case.
ln a more crowded world, a state might comprise so many facts
that its direct representation (as a list, say) would be impractical. More-
over, even a small change might require copying large data structures.
Clausal representation is free from this disadvantage but it is unwieldy
when a change must be undone, and of course planning is a trial-and-
error process. What we need is a method of incrementally describing
incremental changes, and making them easily undoable.
A state and an action determine the next state, if we assume that the
action does not affect facts not mentioned explicitly in the description of
the actions effects as added or deleted. Given an initial state and a plan,
i.e. a sequence of actions, we can check whether a fact holds in the
resulting nal state. To undo an action, we remove it from the plan (in
practice, this may be slightly more complicated).
For any particular planning problem, the initial state can be consid-
ered xed. The nal state should be given implicitly, as a conjunction of
facts to be established by a plan we are going to nd. This approach was
taken by D. H. D. Warren in his remarkable planning program, WAR-
PLAN.
8. I. Planning 217
oponntttl boll2)
Iq ,rr
' w ttlt U
.
pomll l
opomtllll
O
pe|ntl5l
room I5)
%%%%%% WARPLAN-eubewonds
E ( rmva( U, V, oor),
UonV 8. notequal(V, oor) 8. elear(U) ).
(move(U, V, W),
elear(W) 8. UonV 8. notequal(U, W) 8. etear(U))
-I l -L.-L-L-L-L lr|'poas(XonY8.elear(Y)).
Qm hu~_,v.0en~|o>m:~uro- l|rpoas()tonY&XonZ8-netequal(Y, 2)).
18 lrrpoas(XonZ&YonZ&netequal(Z, oor)¬aqud(X, Y))
19 lrrpoea( Xonx ).
20
21 %1'hatl1reeblocltsp|'oblem.
22 glven( start, aon oor ).
23 glvant start, bontloor ).
24 glvent start, eona ).
25 olvent start, eteartb) )-
26 glven( stall, elear(e) ).
27
28 :- plans(eona 8. a onb. start ).
29 :- plans(aonb 8. b one. Start ).
2- d0b 'Ol')'), rede ne.
f
I! %%%%%WARPt.AN-t:l1oSTFllPSproblem
8'F
5a .3. .75N
as
c
5E E .75c
.2). U)
:- l, lall.
$ci $ 38 3$li58lt3-' 8
4-B
49 *
no
l( nextto
22 I, tall.
I, tall.
50
51
52
del( nextto
del( Ont33X. 53? 3.'.,=:;.~: t==.:. gages?
del( orilcor, eirrbon l Bl) .
gs
::- -. . xH"cc
?". . "
Z22
LISTING 8.2 WARPLANTl|e general planner
1 %%%%%% WAFIPLAN-ASystemtorGenerathgPlam
2 %%%%%% (pnlbhltadwllttrnklrdpemlbsbnolthehnlnr
3 %%%%%% DavklH.D.Warren)
4
5 %%% Ihegenerdplanner.
6 9;, ..__.._._._.__...
7 :- op(200,Ily,&), op(100,yhr,:).
8
9 % Generate and output a plan.
10 Plarwt 0. _) =-
11 lnconslstamt C, true ), I, write( 'lrrpossble.' ), nl.
12 Plarwt 0. T) =-
13 plan( C, true, T, T1 ), outputt T1 ), I.
14 an _I _) :'
15 write( Cannot do thls.' ), nl.
it#2
16
17 output()ts:X) :-
18
19
20
nurrbarvars(Xs:X,1,_), output1(
output(_) :- wrIo('Nothlngneed rs '.' ).
31
32
33
I, eelvet X, P, ."1,5
plan(X,P,T,T1) :-
ill:-I
5"is
-1.. U ." -I ."T2).
.17 _-|.'-4 all
eolvlngagoal.
always(X).
aw? holcls(X,T), and(X,P,P1).
55525? FxxxxsSw mmi gaaaa552::xv??? U,P,T,T1).
j
in-P ...X
%Metl\odsolael\lev||-|gaqoal-
% byextenslon:
levo(_, U, P,T,T1:U) :-
U,P), ean(U,O), notheonsbterl(C,P),
T,T1), preserves(U,P).
rtlon:
U, P, T:V, T1:V) :-
)t,V), retraee(P,V,P1),
Pt,T,T1), preserved(X,V).
Ilataetholdslnaglvonstate.
_:V) :- acld(X,V).
* holds(X,T), preservecKX,V).
E5525
ggisgg
$ iE83.'S$-5$ci8$ $3-138i % Prove that an action preserves a tact.
58 preservest U, X80 ) :- proservod( X, U ), praservest U, C)
59 preserveat _, tme ).
223
LISTING 8.2 (Continued)
v) =- check(pres(X,V)).
8828
ii i -x
...rrltground( xav 1. not del( x,v).
64 % Fletraclng a goal already achieved.
65 retraeet P, V, P2 ) :-
66 can( V, C ), retraee( P, V. C, P1 ), append( C. P1, P2 ).
67
68 retraee( X&P, V, C, P1 ) :-
69 add( Y, V ), X --. Y, I, reIrace( P, V, C, P1 ).
70 retraeet X&P, V, C, P1 ) :-
71 eIem( Y, C ), X - Y, I, retracet P, V, C, P1 ).
72 retraeet XGP, V, C, X&P1 ) :- retraee( P, V, C, P1 ).
73 retraee( true. _, _, tme ).
74
75 t. lneonsbteney with a goal already achieved.
76 lnconsbletit C, P) :-
77 Wlkawuodt CGP I. lmtm t 8 I.
78 check( lnterseett C. S ) ), ln'pIled( S, CGP ), I.
79
80 % % % Utilities.
81 % .......-
82
8I8l'l')( Y, P], XII Y, I.
"J";sf:
3-.555.,l'._.1)3% l. GPIZIOIIK C, P, P1 ).
8918 128
iiii
E3
89 eIern( X,Y&_) :- eIern( X,Y).
90 elern( X,_8|C) :- I. elem( X,C).
91 elem( X,X).
92
93 ln'olled(S1&S2,C) :- I, h'plIed(S1,C), In'pIled(S2,C)
94 lrrplIed(X,C) :- eIem(X,C).
95 ln'plled( X,_) :- X.
96
97 interseeI(S1,S2) :- elernt X,S1), elem( X,S2).
98
99 notequal( X, Y) :- not X-Y, notlt-'V'(_), notY-'V'(_).
100
101 rrI<ground(X) :- run'bervars(X,O,_).
Z24
LISTING 8.3 WARPLANSam||Ie reldtn
Toy-Prolog Btenlng:
?-)t.IiIeswItl'|outIesteasesbut\vIth end. atthe end
:- eonsuttt planner ). consult cubes ).
?-
:-planstcona 8. aonb, start).
start :
move(
move(
move(
7.
3?;
:-planstaonb 8. bone, start).
start :
rmve(
move(
move(
7.
3?;
;delop( on ), reconailt strbs ).
:- planst at( robot, polri(5) ), strbst ).
Nothlng need be done.
7.
1- 010rBt01t robot. 0051(1) ) 8- it r0001. 0010112) ). 8111001 )-
lnposslble.
7-
:- planst aI( robot, poIrn(4) ), strbst ).
stripst :
goto1(poIr|I(4),room(1)).
:- plans( etaIus( ll9|'|tswltch(1), on ), etrbst ).
strbst :
goto2(box(1),roorn(1)):
pt.|sl1to(box(1),ll9htswltch(1),roorrI(1)):
elimbont box( 1 ) ) :
tumon( llghtswtteht 1 ) ).
7-
:- planet at( robot, poIrd(6) ). strbst ).
slripst :
2
iii
90102( d0'0r( 1 ).
00Ihr00oh(d00rt ).l'00l'l1l5))
0010210004 4).
a0Ihr00oh(d00rt4 ).l'00l'l1l4ll
0010110010116).
?-
r00mt J5 -u-n
BIBLIOGRAPHIC Nores
WARPLAN is described in Warren (I974). Our presentation has been
greatly in uenced by this excellent paper. The program we publish here is
a slightly cleaned-up version of the text given in Coelho et al. (I980),
where all the mentioned examples of worlds can also be found. The ro-
bot's world was introduced by Fikes and Nilsson (I971) as a test case for
their system STRIPS; Warren (I974) used it to compare the performance
of the two systems. An extension of WARPLAN, intended for generating
conditional plans, was described in Warren (I976).
:- toysequel.
To begin with, we specify a few relation schemas:
create EMP < string name, integer salary, integer dno >.
create DEPT < integer dno, string manager >.
create BoardMembers < string name, string position,
integer seniority >.
Now we insert some tuples:
into EMP insert < "Brown", I000, I >, < "White". 800, l >.
< "Miller", 850, I >, < "Barry", 900, 2 >.
< "Thomas", 850, I >, < "Morgan". I050, I >.
into DEPT insert < I, "Jones" >, < 2, "Smith" >.
232 8 Two Case Studies
We can ask what relations the data base contains; Toy-Sequel dis-
plays their names (its responses are italicized):
relations.
B0ardMembers
DEPT
EMP
What is the schema of EMP?
relation EMP.
string name
integer salary
integer dno
A select expression determines a set of tuples. They may be dis-
played. For example, who in departments other than 2 earns at least I000?
select from EMP tuples < name, salary >
where dno < > 2 and salary >= I000.
Brown l0OO
Morgan I050
Or they may be inserted elsewhere:
into EMP insert
select from DEPT tuples < manager, I000, dno >.
In the absence of where the condition is taken as true.
Both managers have the salary I000. We can given Smith a raise:
update EMP so that salary = I200 where name = "Smith".
Fire Barry:
from EMP delete tuples where name = "Barry".
If several relations are involved. e.g. in a join, attribute names may be
ambiguous. To disambiguate, qualify them with relation names. For ex-
ample:
select from EMP, DEPT tuples < name, EMP_dno, manager >-
where EMP_dno = DEPT_dno.
(Actually, EM P_dno may be replaced with dno: an unquali ed attribute
name is quali ed with the leftmost appropriate relation name.)
A relation may be accessed in several places at once. For example, to
compare salaries of different employees we need the product of EMP by
EM P. We must give one of the occurrences an alias name and so allow
8.2. Prolog and Relational Data Bases 233
will be looked for in the following symbol table (see lines 208-223, and
96-I02):
[ EMP : [ attr( name, string, NameEMP ),
attr( salary, integer, SalaryEMP ),
attr( dno, integer, DnoEMP ) ],
Mgr : [ attr( name, string, NarneMgr ),
attr( salary, integer, SalaryMgr ),
attr( dno, integer, DnoMgr ) ],
DEPT : [ attr( dno, integer, DnoDEPT ),
attr( manager, string, ManagerDEPT ) ] ]
(Nested select expressions would push their own frames onto this stack-
see line 277.)
The product of these three relations will be generated by the following
calls retrieved from the catalogue:
EMP( NameEMP, SalaryEMP, DnoEMP ),
EMP'( NarneMgr, SalaryMgr, DnoMgr ),
DEPT'( DnoDEPT, ManagerDEPT )
The condition will be translated into a Prolog goal (see lines 236-237, 239-
362). The goal will be executed immediately after the generators (lines
I66-I68). Attribute names in the condition will be translated into varia-
bles from the symbol table. Thus,
salary > Mgr_salary
will become
SalaryEMP > SalaryMg:r
The "equalities"
DnoEMP = DnoDEPT, ManagerDEPT = NameMg:r
will be processed at compile time, by binding variables together (line 318),
so that actually only six different variables will occur in the generators.
The tuple pattem (lines 225-234) will also contain variables from the
symbol table:
[ NameEMP, DnoMgr, ManagerDEPT ]
One such tuple will be displayed in every step of the failure-driven loop
(lines I66-I68).
A construction that would certainly bene t from a more detailed ex-
planation is update. We shall comment on the example shown in the
236 8 Two Case Studies
Bratrooaxm-11c NOTES
The bibliography on data bases is enormous. We shall only name a
few positions relevant to our presentation which (of necessity) has only
touched on basic facts. Two widely accepted introductory textbooks on
data bases in general are Ullman (1982) and Date (1982). The relational
model of data was introduced by Codd (1970) and further elaborated by
many, including Codd himself (1979). The most popular relational data
languages are probably Quel, used in the data base system INGRES
(Stonebraker et al. I976), Sequel, created for the system R (Astrahan
I976; Chamberlin et al. 1976) and Query-by-Example (Zloof I977).
In the proceedings of conferences on logic in data bases (Gallaire and
Minker I978, Gallaire et al. I981) there are, in particular, papers on the
role of logic programming in data base theory and applications. The ad-
vantages of Prolog (and logic programming at large) for data bases have
been advocated by quite a few authors, e.g. Kowalski (1978), Gallaire
(I983) and Lloyd (I982). A practical demonstration of Prolog's power is
Chat80 (Warren and Pereira 1982; Warren 1981), a system with a natural
language interface. Queries in English are translated into Prolog calls;
they are similar to those produced by Toy-Sequel, but Chat80 performs
238 8 Two Case Studies
some query optimisation. Several other data base applications with natu-
ral language interface are described in Dahl (1977), Coelho (I982), and
Filgueiras and Pereira (1983).
Another example of data base application of Prolog is an implementa-
tion of Query-by-Example (Neves and Williams 1983; Neves et al. I983).
Chomicki and Grudziriski (1983) describe a system, based on extendible
hashing, that manipulates tuples stored on disk. The system has been
designed to support data-base-oriented implementations of Prolog.
The Toy-Sequel interpreter was rewritten as a sized-down version of
SPOQUEL, a program which we had written with Wlodek Grudziriski in
early I982. It helped us through a dif cult winter.
LISTING 8.4 ToySeqneI lIIer|I'eter
Z39
LISTING 8.4 (Continued)
i
namechss([Ch|Chs]) -> letter( Ch ), I, nameeha1s(Chs).
namechars(hCh|Cha]) --> dblt( Ch ), I, nameehatst Chs).
)->
%'"'1nastrlngstandsIcraslngIe"'
.
79
80 slgnedt '+', I, I ).
slgnedt I, Integer) :- Integer Is - I.
-> [' '1, 1, sp. %opt1onalspaeee
-8-8 -> [].
160
161 relation( Ftelll) :- re I'( FtelN,_, Altrs ), I, l1stattrs( Altrs ).
162 relatlont RelN) :- wrIe( Relll ), wrIe(' B not a relatlonr ), nl.
163
184 ll6lall1s( I] ) :- 1.
165 l1slatt1s(|attr( Narne,Type,_)|Attrs]) :-
166 wrl1e( Type ), wrlte(' '), wrle( Name ), nl,
167 1lstat:trs( Attrs ).
166
169 ll.---selectexpresslon-H
190 % Eg: select Irom EMP, Mgr-EMP, DEPT tuples < name, dno >
191 % where salary > l1lgr_aalary65!100
192 % and Mgr_name - manager and DEPT_dno - EMP_dno
193 )6 and < manager > h (<'SmlIh'>, <'Jones">, <'Brown'>) .
194 %(legelname=sanddepartrnerl nrrntlersollltosesrrbordhatesclsmith.
195 %JonesorBrownwhoeamnnrethan65%oltl1elrmar1.ager'ssaIary).
196
197 ll. Generators plclr up tuples Irom named relatlom, Flters pass only
196 lbtuplesllthgtltewhere-darse,Tq:>leBhstar|tlatedlothepassed
199 % tuples (one by one), Types B the tuple's pattem wlh types Instead
200 %ol attrbutes (usedlortypechecltlng).
201 % The exarrple oonplles to :
202 % set((' EMP'(Name. Salary, Dno), ' EMP'(MgrName, MgrSala1y, MgrDno)
203 ll. ' DEPT(Dr1c, l1lgrName) ),
204 ll. (Salary > MgrSalary65I100, tme, true.
205 % (rneni:ler(MgrNarne, <'Srnith', '..lones', 'Brown">), true)),
206 % [Name,Dr1o], [strlng,lnteger] )
207
206 selectexpt set( Generators, Fllter, Tuple, Types ), InitST ) -->
209 [n( select ), n(l1'oI1'|)], relnames( Generators, lnltST, ST),
210 [n( tuples )]. tuplepdtern( Tuple, Types, ST ),
211 whereelarset Fllter, ST ).
212
213 ll. One or more relation names, possbly 'allased'. Syntlol table trag-
214 It. meris are stadted In reverse order, so attrbute search orderwlll
215 % be that or the Irom-Bl (uslng-H lot update) relatlom.
216 reInarnes( ( Gen, Gens ), OldST, NewST ) -->
217 relname( Name, Alas ), |','], I, relnames( Gene, OldST, TempST ),
216 { newrelnarnet Name, Alas, Gen, TernpST, NewST ) }.
219 relnamest Gen, OldST, NewST ) -> relname( Name, Alas ).
220 { newrelnamet Name, Alias, Gen, OldST, NewST) }.
221
222 relnarnet Name,Allas)--> [n( Alias). '-'. I'll Name)], 1.
223 relnamel Name, Name) --> [n( Name )].
224
225 ll. luplepdtem B also Invoked by lnexp.
226 Ivnlemlteml IA I A01. 1'11 Tel. ST I -->
227 [<'], atlrpatt( A, T, ST ), attrpaIts( As, Ts, ST), ['>'].
220
229 e rpattst 141401.111 T -->
zao [','], I, attrpatt(A, .-1.3rpm -I-I atlrpatts( As, Ts, ST ).
201 01110011011]-1]-_) > 1]-
232
233 atIrpatt(Attrl:ute,Type,_) --> corBtar1t(Altrbute,Type), 1.
234 altrpatt(A,T,ST) -> qname(ON), {llnda1tr(ON,A,T,ST)).
235
236 whereelarset Fller, ST) -> [n(where)], I, boolexpt Fllter, ST).
237 whereelarse(true,_) --> [].
236
239 ll.---Booleanexpresslons-~
242
LISTING 8.4 (Continued)
326
5-=~e
324 cornrel( L, '>', Ft, string, let .113!
325 consrell L, '>-', R, strlng.( l -' ; Fl - L) ).
.mr-rir-:1)
95 6595 6053
492 context -> lT01101'l- 1'11""1Tk 1 1' ends
493
494 wtoken(T) =- wt1T.F1001T). wrltet FlealT), write("). |-
495 v1rt(n(N8rI16).1131"01-
498 wq I( Integer), |rI090r I-
497 wll 01 $11100 I. $11100 )-
498 wt( Char, Char).
499
500 narnerr(lrIo) ;- nl, wr1tet'E"0'"-'1- "'-
501 M, "1, ,_ .1, tegte11toeeornneror_._))-
241
9 PROLOG DlALECIS'
9.1. PROLOG I
9.2. PROLOG II
for <ff, x>, while <x, y> and <<ff(x)>, y> are also legal terms (single-
letter names denote variables, ff is a constant). Instead of unifying two
tuples, Prolog II constructs a system of equations. For example, matching
<x> with <ff(x)> corresponds to solving in x the equation
x=ff(x).
The solution of this equation is the in nite tree ff(ff(ff(...))), which is
represented by an appropriate cyclic data structure.
The behavior of Prolog programs is described as incremental solving
of the system of equations introduced by program clauses, which can also
be seen as rewriting rules. The execution of a call is viewed as the opera-
tion of erasing it by applying the rules and solving the appropriate equa-
tions. This viewpoint manifests itself in the syntax of clauses by an arrow
leading from the head to the (possibly empty) body, e.g.
255
APPENDIX A.l
Kernel Flle
1 % KEFINEL llle
2 % standard atoms
3 ';'/2 ','/2 call/1 tag/1
U/0 '.'/2 'error'l1 'user'I0
% atoms ldentilylng system routlnes (keep tall rst and tn.re' last)
'lal|'/0 tag/1 call/1 I70
'tagcut'l1 'Iaglall'/1 tagexlt/1 ancestor/1
halt/1 'status'l0
310 :)-ro>r.nt:> display/1 rch'I0 'lastch'/1 'sk'pbl'I0 'wch'/1
11 'echo'/0 'noecho'l0
12 see/1 'seelng'/1 'seen'I0 tell/1 telling/1 'Iold'I0
13 'orclchr'/2 'sum'l3 'prod'l4 less/2 '@<'/2
14 'smalletter'/1 'blgIeIter'l1 'letter'l1 'dIgll'l1 'alphanum'l1
15 bracket/1 'solochar'/1 symch/1
16 'eqvar'/2 var/1
17 atom/1 Integer/1 nonvarint/1
18 'lunctor'I3 arg'I3 pname/2 pnamei/2
19 '$procl1 '$proclknlt'l0 $proclnltl0
20 clause/5 retract'l3 abolish/2 'assert'l3 'redellne'l0
21 'predellned'l2 protectl0
22 'nonexlstent'I0 'nononexlstent'/0
23 'debugl0 nodebug/0
24 'tn.re'I0
25
26 % kemel lbrary
27 error(:0) : nl . display(-1-r-+ System call error: ') . dlsplay(:0) .
28 nl . lall . U
29 :ordchr(10, :0) . assert(lseoh(:0). U, 0) .
30 assert(nl, wch(:0).U, 0) . U I
31 '-'(:0, :0) :U
32 ','(:0, :1) :call(:0) . calI(:1) . U
33 ';'(:0, _) :call(:0) . U
34 ';'(_, :0) : call(:0) . U
35 not(:0) :call(:0) . '1' . lall . U
36 not(_) :U
37 check(:0) : not(not(:0)) . U
38 side_ettects(:0) : not(not(:0)) . U
39
40 or1ce(:0) :call(:0) . '1' . U
41
42 '@-<'(:0, :1) : '@<'(:1, :0) . '1 . lall . U
43 @-<'(_, _) :U
4-4 '@>'(:0, :1) : '@<'(:1, :0) . U
45 '@>-'(:0, :1) : '@-<'(:1, :0) . U
46
47 % - - - - - - baslc lnp1.rl procedures - - - - - -
48 rdchsk(:0) : rch . skbbl . lastchm) . U
49 r1:lch(:0) : rch . laslch(:1) . sch(:1, :0) . U
50 % convert nonprhtable characters to blanks
51 sch(:0, :0) : '@<'(' ', :0) . '1' . U
52 sch(:0, ' ') : U
53
756
APPENDIX A.l (Continued)
54 repeat : U
55 repeat : repeat . U
56 merr|ber(:0, :0.:1) : U
57 merr|ber(:0, _.:1) : merrber(:0, :1) . U
58
59 proo(:0) : '$proclnIt' . $pr(:0) . U
60 '$pr(:0) : '$procii'nit . I . tall . U
61 '$pr(:0) : '$proc'(:0) . U
62 '$pr(:0) : $pr(:0) . U
63
64 % b a g o I (preserves order of solutions)
65 bagot(:0, :1, _) : asserta('BAG'('BAG')) . call(:1) .
66 asserta('BAG'(1))) . tall . U
67 %% 0 Item, 1 Condition.
68 bago1(_, _, :0) : 'BAG'(:1) . I . lntobagtzt, U, :0) . U
69 %% 0 Bag, 1 Item,
70 imobag('BAG', :0, :0) : I . retract('BAG, 1, 1) . U
71 %% 0 Flnal_bag,
72 intobag(:0, :1, :2) : retract('BAG, 1, 1) . 'BAG'(:3) . I .
73 |ntobag(:3, :0.:1, :2) . |]
74 %% 0 Item, 1 Thls_bag, 2 FInaI_bag, 3 Next_ltem,
75
76 % end oi le - toyprolog will now read Irom the terminal
77 :dlsplay('Keme| le Ioaded.') .nl . see(user) . U I
257
APPENDIX A.2
Bootstnpper
258
APPENDIX A.2 (Continued)
53 /6% 5 mlddletermrepr
54 ctal|aux(:0, :1, :2, :3) :fterm(:0, :4, :1, :3) .
55 Iterl'ns(:4, :5, :2, :3) . U
56 %% 0 tterm_Ilrstoh, 1 termrepr, 2 rest_ol_termrepr,
57 %% 3 sym_tab, 4 item\s_Ilrstoh, 5 rriddleterrrtepr
56 % a IIst oI functor-temte (Ie calls)
59 items(), '.'[.']'.:0, :0, _) : I . U
60 %% 0 rest_oI_termrepr
61 % eoInandbIarits-dotalI/2/
62 Iterms(,, :4. .:0, :1, :2) : I . Ise-oln(:4) .
63 rdchslt(i3) . ctaIIaux(3, :0, :1, :2) . U
64 %% 0 termrepr, 1 rest_oI_termrepr, 2 sym_tab,3 ctaIl_lIrstoh,
65 I/0/o 4 BOII1
66 Item's(:0, _, _, _) : err(ftemB, :0) . U
67 %-----------------
66 %atunctor-term
69 Iterrn(:0, :1, ".:2, :3, :4):
70 Iderlt(:0, :5, :2, ".:6) . I . args(:5, :1, :6, :3, :4) . U
71 %% 0 Id_Ilrstch, 1 lerrrinator, 2 terrnrepr,3 rest_oI_terrnrepr,
72 %% 4 sym_lab, 5 Id_lermInator, 6 mlddleterrrtepr
73 % Identlltersz words, I, quoted names, synbols
74 Ident(:0, :1, 11:2, :3) :
75 wordstart(:0) . rdch(:4) . abhanurns(:4, :1, :2, :3) . U
76 %% 0 Id_llrstoh, 1 terrrlnator, 2tem1repr,
77 %% 3 rest_ot_termrepr, 4 nextch
76 Ident('I', 1), 'I'.:1, :1) : rch . skpb(:0) . U
79 %% 0 terrnlnator, 1 terrrrepr
60 ident(", .1), :1, :2) : rdoht ) .qIderl(3, :0, :1, :2) . U
61 /6% 0 lem1lnator, 1 temrepr, 2 rest_oI_terrnrepr, 3 nextoh
82 iderrt(:0, :1, :0.:2, :3):
83 syrnch(:0) . rdoh(:4) . sy|'riJol(:4, :1, :2, 3) . U
64 %% 0 syrnb_Iirstoh, 1 lerrnlnator, 2 tennrepr,
65 %% 3 resl_oI_termrepr, 4 nextch
66 % quoted Identi ers
87 qldent("", :0, :1, :2) :
68 rdch(:3) . qtdentalll , :0, :1, :2) . I . U
69 %% 0 termlnator, 1 termrepr, 2 rest_ot_termropr, 3 nextoh
90 qIdent(:0, :1, :0.:2, :3) : rdch(:4) .qIdent(:4, :1, :2, :3) . U
91 ' %% 0 char, 1 lerrnlnator, 2 termrepr,
92 %% 3 rest_oI_termrepr, 4 nextch
93 qIderlaIl(", :0, :2) :
94 rdch(:3) . qIdent(:3, :0, :1, :2) . |]
95 %% 0 terrnlnator, 1 termrepr, 2 rest_oI_termrepr, 3 nextoh
96 qiderlail(_, :0, :1, :1) :skpb(:0) . U
97 %% 0 terrnlnator, 1 rest_ot_lerrnrepr
96 % words and symbols
99 3bII3ll.IlTB(IO, :1, :0.:2, :3):
100 alphanum(:0) . I . rdch(:4) . abhanurns(:4, :1, :2, :3) . U
101 /6% 0 an_alphanum, 1 terminator, 2 lennrepr,
102 /6% 3 rest_oI_termrepr, 4 nextch
103 aIphanums(_, :0, :1, :1) :skpb(:0). U
104 %% 0 terrrlnator, 1 rest_ot_lerrnrepr
2.59
APPENDIX A.2 (Continued)
260
APPENDIX A.2 (Conrinned)
261
APPENDIX A.2 (Conlinned)
262
APPENDIX A.3
Um Interface and Utilities
263
APPENDIX A.3 (Continued)
reading a term
$a\$
read(1') :- read(T, Sym_tab).
read(T, Sym_tab) :-
gettr(T_Intemal, Sym_tab), I, maketerrn(T_lntemal,1').
% ii gettr iails, then...
read('err,_) :-
nl, dispIay(+-I-+ Bad term on input. Text skipped: '), skip, nl.
B'8 3$ 28 9
71 % skip to the nearest lull stop not In quotes or in oomment
72 skip :- lastch(Ch), wch(Ch), sklp(Ch).
73
74 skip(.) :- rch, lastch(Ch), e_skb(Ch), I.
75 sklp(%') :- skip_commerl, I, rch, skb.
76 skip(Q) :- isquote(Q), skip_s(0), I, rch. skip.
77 skip(_) :- rch, skip.
78
79 % stop on a layout character
80 e_skip(Ch) :- @-<(Ch, '1.
81 e_skIp(Ch) :- wch(Ch), rch, skip.
82
83 skip_commerlt :- repeat. rch, lastch(Ch), wch(Ch), lseoln(Ch), I.
84
85 isquote(""). lsquote("").
86
87 /0 HSIIIHQ
88 skip_s(Q) :- repeat, rch, lastch(Ch), wch(Ch), -(Ch, Q), I.
89
90 % IIIIIIIIIIZZIIIHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
91 % parse r
92 /o IIIIIIIIIIIZIZIIIIIIIZIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIII
93 % This is an operator precedence parser Ior Prolog-10. g e t t r
94 % constructs the intemal representation oi a term. Next, m a k e-
95 % t e r m constructs the temt proper - see r e a d. Here is an In-
96 % Iorrnal description oi the underlying operator precedence grammar
97 % (each rule corresponds to one clause oi r e d u c e). Sides are
98 % separated by --> and multiple righthand sides by OR.
99 % t --> variable OFI integer OR string
100 /o I II) IdBl'III1I8l
101 /o I II) Id6l'III1IOI' ( I )
102 % I --> U OFI {}
264
APPENDIX A.3 (c liillltd)
265
APPENDIX A.3 (Coniinned)
155 % iail ii reduction Irrpossibie (parse and gettr will Iail, too -
156 % this iallure will be intercepted by gettre caller)
157
156 % reduce top segment oi the stack according to the underlying grammar
159 reduce([vns(X) | S], [t(arg0(X)) 1 S1).
160 redvceil Idtll I SI. ltlaro illl I SD-
161 redvs llbrir. 0'l- lIX).brtl. '0'). ldlll I $1.
162 (t(tr(l. Xll I Sil-
163 redus ilbrir. TYP9). br(l.TvPBl I $1.
164 IIIHYOOITYPBII I $1) 1- "OIHTYPB. (I'll-
165 %'U or {}, see p, 2nd clause
166 wdvcelibrtr. Tyne). IIXI. bI'(|-TYPBI I Si.
167 ltltr(TrP@.Xll I SD-
16B reduceilbrir. 1]). IIYI. bar. IIXI. br(l.[i'l I $1.
169 [t(bar(X,Y)) | S1).
170 reduce([I1(l,Type,_), t(X) 1 S],
171 [t(tr1(I, X1) | S1) :-
172 isrrpost1(Type).
173 reduce([t(Y), I1(I, Type,_), t(X) | S],
174 [t(tr2(l, X,Y)) | S1) :-
175 isminI(Type).
176 reduce([t(X), il(l,Type,_) | S],
177 [t(tr1(I, X)) | S1) :-
176 Isl1'preI(Type).
179 % otherwlseIalI(cI step)
160
161 %- - -auxiliary tests ior the parser
162 ispreI(iy). lspreI(Ix).
163
184 ispostI(yI). ISDO-SI'I(X1).
185
186 isrnpreI(['i'Un]) :- Isprei(TUn).
188
187 Ismprei(L, TUn]) :- IspreI(TUn).
169 isminI([i'Bln]) :- mer1'ber('i'Bln,[rriy,ybr,:rix]).
190 isminI(L, _1).
191
192 ismposti(1'TUn1) :- lspostl(TUn).
193 ismpost1(L, TUn]) :- Ispost1(TUn).
194
195 % - - - establish precedence relation between the topmost
196 % temtinal on the stack and the cunent lrput terrnlnal
197 estabiish_precedence(Top, Input, Pos, Rel, RTop, Rlrput) :-
196 p(Top,I npu t,Pos,Rei0),
199 Iinalize(Rel0. Top, Input, Rel, RTop, Rlnput), I.
200
201 Iinalize(lseq, Top, Input, Iseq, Top, Input).
202 linalize(gt, Top, input, gt, Top, Input).
203 Iinalize(lseq(RTop, Rlrput), _, _, Iseq, RTop, Rlnput).
204 linalIze(gt(RTop, Rlnput). _, _, gt, RTop, Rlnput).
205
206 p(id(_), mu, '0'), 1, iseq).
ms
APPENDIX A.3 (Continued)
267
APPENDIX A.3 (Continued)
168
APPENDIX A.3 (Continued)
169
APPENDIX A.3 (Continued)
361 %IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
362 % ecanner
363 %:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
364 % this scanner retums six kinds ol tokens:
365 % vns(_) variables, nurrbers, strings
366 % ld(Name) atons
367 % iI(Name, Types, Prior) fix Iunctors
366 % br(Which, Type) brackets (lelt/right. ll/I1/1})
369 % bar | (in lists)
370 % dot . followed by a layout character
371
372 % - - - read a token and construct its intemal form
373 %the lrx:tt.ttissupposedtobeposItioned
374 % over the first character oi a token (or preceding white space)
375 gettoken(Token, Sym_tab) :-
376 skipbl, Iastch(Startch), absotbtoken(Startch, Rawtoken), I.
377 maketoken(RawIoken, Token, Sym_tab), I.
376
379 % - - - read in a suitable sequence oi characters
360 % a word, le a regular alphanumeric identifier
361 absotbtoken(Ch, ld([Ch | WordtaIl1)) :-
362 won:Istatt(Ch), getword(WorclaII).
383 yo 8 VHTIQDIB
364 absorbtoken(Ch, var([Ch | Tall1)) :-
365 vatstatt(Ch), getword(Tai).
366 % a solo character is a comma, a semicolon or an exclamation mark
367 absorbtoken(Ch, ld([Ch])) :- solochar(Ch), rch.
366 %abracket,le()[]{}
369 absor1:ttoken(Ch, br(Wh, Type)) :-
390 bracket(Ch), bracket(Ch, Wh, Type), rch.
391 absorbtoken(|, bar) :- rch.
392 % a string In quotes or In double quotes
393 absotbtoken(, qId(Qname)) :-
394 rdch(Nextch), getstrlng("", Nextch, Qname).
395 absorbtoken("", str(Strlng)) :-
396 rdch(Nextch), getstrIng(", Nextch, String).
397 % a positive nunber
396 absorbtoken(Ch, nt.tm([Ch | Digits])) :-
399 dlglt(Ch), getdlgls(DIgIts).
400 % a negative nurrber or a dash (possbly starting a syrrbol, see below)
401 absotbtoken(-, Rawtoken) :- rdch(Ch), nt.tm_or_sym(Ch, Rawtoken).
402 absotbtoken(., Rawtoken) :- rdch(Ch), dot_or_eym(Ch, Rawtoken).
403 %asymboI,buIitoi.:-<->+/'?&$@l_"
404 absorbtoken(Ch, ld([Ch | Syn-bs])) :- symch(Ch), getsym(Symbs).
405 % an embedded comment
406 absotbtoken(%, Rawtoken) :-
407 skbcommert, lastch(Ch), absotbtoken(Ch, Rawtoken).
406 % this shouldn't happen:
409 absorbtoken(Ch, _) :- dlspIay(errInscan(Ch)), nl, fail.
410
41 1 num_or_sym(Ch, num([-, Ch | Dlgits])) :-
412 dlglt(Ch), getdlgls(Dlgits).
270
APPENDIX A.3 (Continued)
2'7]
APPENDIX A.3 (Continued)
272
APPENDIX A.3 (Continued)
273
APPENDIX A.3 (Continued)
see ruterrorwtsssaoel :-
570 nl, dBpIay(+++ Error In this rule: '), mes(Messa09). nl,
571 tagiaIl(trarBI_rule(_, _, _)).
572 % diagnostim are only very brief (and not too informative ...)
573 mes(varlnt) :- display(variable or Integer ltem.).
574 mes(ter) :- dIsplay('terrnlnaIs not on a closed iBt.).
575
576 % - - - Initiate grammar processing
577 phrase(Nortt, TennlnaB) :-
576 nonvarht(Nont), I,
579 expand(Nont, TermlnaB, U, Init_caII),
560 calI(InII_caIl).
561 phrase(N,T) :- error(phrase(N,T)).
562
563 ch.tmmy(X, X).
564
$5 % IIOIIOI-IIIOIOIOOIQOIIIIOIQOIIIOOI
586 % IIOIIOIIIIIOIIIIIIIIOOIOIOOIOIOIOIO
567 % I I b r a r y
566 % IOIIIOIIOIOOIOIIIIIOIIIOIOOIIIOIO
569 % IIOOIIOIQOIIIOIIIQOIOI-QOIOOOI-QOIOO
590 % :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
591 % -.. (readasunIv')
592 % I u I u u I u I I u I I I I I u u O O O u O I O u u I O u u O u u u u u I I u I I u I u u I u u I I I I I u u I I u u I u u u nu
I u I I u u I u I u u I u u u I I u u I u I O u I O u O u I Q u Q u O O u I u I u u I u u u u u I u I u I u u I u I u I u u I II
274
APPENDIX A.3 (Continued)
2'75
APPENDIX A.3 (Continued)
276
APPENDIX A.3 (Continued)
277
APPENDIX A.3 (Continued)
278
APPENDIX AJ (Con ned)
279
APPENDIX AJ (Continued)
661
662 /o IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIZIIZIZIIIIIIIIIIII
663 % wrIte
664 /e IIIZIIIIIIIIZIIIIIIIIIIIIIZIZIIZIIIIIZiIIIIiIZIII1211112I
665 write(Tem1) :- side_eI1ects(outterm(Tem1, noq)).
666
667 % writeq encloses In quotes all Identiliers except words,
666 % syrrbols and solochars (not colnclding with 'llx" functors)
669 writeq(Term) :- sIde_el1ects(outterm(Term, q)).
690
691 writetext([Ch | Chsl) :- l, wch(Ch), wrltetext(Chs).
s92 writetext([]).
693
694 outtem1(T, Q) :- nurrbervars(T, 1, _), outt(T, Id(_,_), Q).
695
696 % the real iob ls done here
697 outt('V'(N), _, _) :- lntsger(N), I, wch('X'). dispIay(N).
696 % C A U T I 0 N : cult Is unable to write 'V'(Integer)
699 outt(Tem1, _, _) :- lnteger(Tem1), display(Tem\), I.
900 % the second parameter specltles a context lor 'llx' functors:
901 % the nearest extemal functor and Tem1's position
902 % (to the left or to the right ol the extemal functor)
903 outt(Term, Context, Q) :-
904 -..(Tem1, [Name | Argsl),
905 outlun(Name, Args, Context, Q).
906
907 % - - - output a lunctor-terrn
906 % - asa"lix"tenn
909 out1un(Name, Args, Context, Q) :-
910 kslix(Name, Args,This_l1, Kind), l,
911 outI1(Kind,Thls_I1, [Name | Args], Context, Q).
912 % - asalist
913 out1un(., [Larg, Rarg], _. Q) :-
914 I, outlist([Larg|Flarg],Q).
915 % - asanormaltunctor-tenn
916 out1un(Name, Args, _, Q) :-
917 outname(Name, Q). outargs(Args, Q).
916
919 % isllx constructs a palr l1(Prlor, Assoclatlvlty) , and
920 % In or pro or 'post' (tails ll not a " x" Iunctor)
921 isIix(Narns, L, _], (PrIor, Assoc), In) :-
922 FF(Name, Types, Prior), rnl<_bIn(Types, Assoc).
923 istix(Name, L], (Prior, Assoc), Kind) :-
924 FF(Name, Types, Prior), rnl-t_un('l'ypes,Kind,Assoc).
925
926 % Bintype (it any) ls belore Untype (it any)
927 mk_bin([Bintype I _], Assoc) :- blnary(Birlype, Assoc).
926 ml-t_un([Untype], Kind, Assoc) :- unary(Ur1ype,Klnd,Assoc).
929 mk_un(L,Untype],KInd,Assoc) :- unary(Untype,KInd,Assoc)
930 % tests - see o p
931
932 % - - - output a "lix' tem1 (thls out has 5 parameters)
260
APPENDIX A.3 (Colltilled)
976 outargs(Args, Q) :-
979 lake(Context), wch('('), outargs(Args, Context, Q), wch(')').
960
961 outargs([Last]. Context. Q) :- I. outt(Last. Context. Q).
962 outargs([Arg|Args], Context. Q) :-
963 outt(Arg, Context, O), dlsplay(', '), outargs(Args, Context, Q).
964
APPENDIX A.3 (Continned)
985 % commas are used to delimit list Items, so we must bracket commas
986 % w I t h I n ltens (it's a trick: we deperd on having
987 % the priority 1000 and being associative)
988 Iake(Id(I1(1000, na(_)), _)).
989
990 % - - - output a list In square brackets (ci outlun - the main
991 % Iunctor is the dot, and the list cannot be empty)
992 outlist([F|rst | Tail], Q) :-
993 Iake(Context), wch('['), outt(Flrst, Context, Q),
994 outIist(Tail, Context, Q), wch(')').
995
996 outIist([], _, _) :- I.
997 outIist([Item | Items], Context, Q) :-
998 I, display(, '), outt(item, Context, Q),
999 outlist(Iten's, Context, O).
1000 % the bar and the closing Item (still bracketed Ii It contains commas)
1001 outIist(CIos'ng, Context, Q) :-
1002 display(' I '). outt(CIosing, Context, 0).
1003
%IOIIOQIIIIOIQOOIQIQQOQIIOQOIIQOOIOQ
1004
% OOOIIIQ-iiQIIQQOIQIIIIIQOIIOQQIOIIOI
1005
1006 % tr a n s I at o r
% IIIOQIOIIIQQOIIOIIOIOIII-IIOIIII-Oil!-I
1007
% IIIOQIOI-liliiliiliIIIIOOOQOOOOIIOOOQO
1008
1009 % read aprogram upto end. and translate it Into "kemel"lom1
1010 translate(Inlile, Out le) :-
1011 see(lnlile), telI(Outlile),
1012 nl, repeat,
1013 read(Cla:se), pul(Clause), nl, -(Clause, end), I.
1014 seen, told, see(user), telI(user).
1015
1016 % - - - produce and output the translation oi one clause
1017 put(:-(Head, Body)) :-
1018 I, puthead(Head, Sym_tab), putbody(Body, Sym_tab).
1019 put(-->(Lelt, FIight)) :-
1020 I, tag(transI_ruIs(Lelt, Right, :-(Head, Body).
1021 puthead(Head, Sym_tab), putbody(Body, Sym_tab).
1022 put(:-(Goal)) :-
1023 I, putbody(GoaI, Sym_tab), wch(l), nl.
1024 once(Goal). % a iailure here wouldn't matter (cl translate)
1025 put(snd) :- I.
1026 put('err) :- I.
1027 put(UnitcIause) :- puthead(Unltclause, Sym_tab), putbody(true, _).
1028
1029 % - - - put a head call (it nus! be a Iunctor-temt)
1030 puthead(Head, Sym_tab) :-
1031 nonvar'nt(Head), I, puttem1(Head, Sym_tab).
1032 puthead(Head, _) :- transl_err(Head).
1033
1034 %---putalistoIcallsand|]attheend
1035 putbodg/(Body, Sym_tab) :-
1036 punct(:), conv_body(Body, B), I, putbody_c(B, Sym_tab).
282
APPENDIX A.3 (Continued)
A simple ctlltor
284
APPENDIX A.4 (Continned)
invoke_Prolog :- tag( loop ). % this works only tor the Toy-Prolog monitor
invoke_Proiog. %( loop temtinated by tagiall )
rl:l([],L,L).
:3I:MiEILi-L2.IEI2II=-aPPBI1dil-.L2.l-l-2I-
286
APPENDIX A.4 (Continned)
Aprhnltiveu-ndngtool
runsystem( I ) :- runcut.
runsystem( Forbidden ) :-
lslo|bidden(Forbldden), I, nl,
display( FORBIDDEN CALL ' ), write( Forbidden ),
display( ' FAILS I), nl, tall.
runsystern( Call) :- not spIed( Call ), I, Cali.
runsystem( Call) :- Call, I, showsuccess(CaIl).
runsystern( Call) :- showlalhure( Call ), tall.
287
APPENDIX A.4 (Continned)
lslorblddenl tagcut( i ii
n
isiorbidden( ancesto?( _ ) )
predeiined( Call ) :-
Cli0Cl<( ( 1t.lI'tCt0t'( Call F N ) prede md( F N ) ) )
APPENDIX A.4 (Continued)
Aprognmsu'ucturennnlyserwIthnnnlyse|-analysed
289
APPENDIX A.4 (Continued)
19 meta_cail_2 I 3
body_caiis I 3 (see 10)
20 is I 2
add_cals I 5 (see 9)
21 pn'nt_cails I 4
II0
22 sta|1_undeiIned I 2
23 nurrber_iine/ 1
24 '<' I 2
is I 2 (see 20)
25 less I 2
26 display! 1
27 tab I 2
II0
26 wch I 1
'is'/ 2 (see 20)
tab I 2 (see 27)
29 writeq I 1
display I 1
30 nl I 0
'is' I 2 (see 20)
print_caiB I 4 (see 21)
31 start_line I 2
nurrber_|ineI 1 (see 23)
II0
tab I 2 (see 27)
32 start_unnumbered_iine/ 1
display! 1
tab I 2 (see 27)
33 repetition I 3
predeiinsd I 2
II0
display! 1
REFERENCES
Gallaire. H. and Minker. J .. eds. ( I978). Logic and Databases". Plenum Press. New York.
Gallaire. H.. Minker. J . and Nicolas. J.-H.. eds. (l98l). Advances in Database Theory".
vol. I. Plenum Press. New York.
Gregory. S. (I980). Towards the Compilation of Annotated Logic Programs. Research Re-
port DOC 80lI6. Imperial College. London.
Gries. D. (I97I). Compiler Construction for Digital Computers. Wiley and Sons. New
York.
Guizol. J . (I975). Synthese du francais a partir dune representation en logique du premier
ordre. Ph.D. thesis. Universite d'Aix-Marseille.
Guizol. J . and Meloni. H. (I976). Prolog modulaire. Groupe d'lntelIigence Arti cielle. Uni-
versite d'Aix-Marseillc.
Hill. R. (I974). LUSH Resolution and Its Completeness. DCL Memo 78. University of
Edinburgh.
Hoare. C. A. D. (I962). Quicksor1. Comput. J. 5(I). I0-I5.
Hogger. C. J. (I979). Derivation of Logic Programs. Ph.D. thesis. Imperial College.
London.
Joubert. M. (I974). Un systeme de resolution de problemes a tendance naturelle. Ph.D.
thesis. Universite d'Aix-Marseille.
Kanoui. H. (I973). Application de la demonstration automatique aux manipulations algebri-
que et a l'integration formelle sur ordinateur. Ph.D. thesis. Universite d'Aix-Marseille.
Kanoui. H. (I982). PROLOG II. Manuel d'exemples. Groupe dIntelligence Arti cielle.
Universite d'Aix-Marseille ll.
Kanoui. H. and Van Caneghem. M. (I980). Implementing a Very High Level Language on a
Very Low Cost Computer. In Information Processing 80" (S. Lavington. ed.). pp.
349-354. North-Holland. Amsterdam.
Klutniak. F. (l98l). Remarks on Coroutines in Prolog. In Szpakowicz I981. pp. I9-29.
Kluiniak. F. (I984). The Marseille lnterpretera Personal Perspective. In Campbell
I984. pp. 65-70.
Kluiniak. F. and Szpakowicz. S. (I983). "Prolog" WNT (Wydawnictwa Naukowo-Tech-
niczne). Warsaw.
Kluiniak. F. and Szpakowicz. S. (I984). Prologa Panacea? In Campbell I984. pp. 7|-84.
Knuth. D. E. (I968). Semantics of Context-Free Languages. Math. Sysl. Theory 2. I27-I45.
Komorowski. H. J. (I982). QLOGThe Programming Environment for Prolog in LISP. In
Clark and T rnlund I982. pp. 3l5-322.
Koster. C. H. A. (I974). Using the CDL Compiler-Compiler. In Compiler Construction.
An Advanced Course" (F. J . Bauer and J . Eickel. eds.). pp. 366-426. Lecture Notes in
Computer Science 2|. Springer-Verlag. Berlin and Heidelberg.
Kowalski. R. A. (I972). The Predicate Calculus as a Programming Language. In Proceed-
ings of the Intemational Symposium and Summer School on Mathematical Foundations
of Computer Science". Jablonna near Warsaw. Poland.
Kowalski. R. A. (I974). Predicate Logic as Programming Language. In "Proceedings of the
IFIP Congress". pp. 569-574. North-Holland. Amsterdam.
Kowalski. R. A. (I978). Logic for Data Description. In Gallaire and Minker I978. pp. 77-
I03.
Kowalski. R. A. (l979a). Algorithm = Logic + Control. Cornmun. ACM 22. 42443I.
Kowalski. R. A. (l979b). Logic for Problem SoIving". North-Holland. Amsterdam.
Kowalski. R. A. and Kuehner. D. (I97l). Linear Resolution with Selection Function. Arti -
cial Intelligence 2(3l4). 227-260. Also in The Automation of Reasoning II" (J. H.
Siekmann and G. Wrightson. eds.) Springer-Verlag. Berlin and Heidelberg. I983.
References 297
Page numbers in italics indicate material referring to implementation issues. P381: numbers
followed by n indicate material in footnotes.
Metamorphosis grammars. 68
micro-Prolog. 259
Identi er. 2 Mixed operators. 7. I55. I56
Inconsistency. 43 Mode declarations. I77
Indexing. I09-IIO Model. 43
Inference rules. 44 true (false) formulae in. 43
In nite trees. 23. 256 Modularisation. in Prolog. 258. 260
In x functor. 6. I55 Molecules. l69n
Initialisationl 201 Monitor. 203
Input I output MPROLOG. 259-260
listing control. I54
single characters. I56-I57 N
switching streams. I53-I54
terms. I54-I56 Namets)
lnstantiation comparing. I5I-I52
of a term. I0-II quoted. 2
of a variable. 9 variable. 9
lnteger(s). 2. I45 Negation. in Prolog. 38
comparing. I5I-I52 Negative literals. 46
Interpretation Nondeterminism. in Prolog. 3I
of formulae. 42-44 Non-associative functor. I55
static. of procedures. 32-33 Non-structure sharing (NSS). I68. I72-I76
Nonterrninal (symbol). 68. I44
I.. Nonterrninal symbols. parameters of. 69-
73
Language. interrnediate. 20!-203 NSS. see Non-structure sharing
Left-associative functor. 6. I55
Library. 2I3-214 O
Linear lists. representation in Prolog. 95
List(s). I45 Occur check. 23
closed. I00 Open lists. 98-I03
difference. I04-I07 Operations. II-I3. see also Clause(s)
linear. 95 directives. I5-I7. I44
open. 98-I03 simplest forr'n of procedure. I3-I5
representation in Prolog. 7-8 uni cation. I7-23
reversal of. I3I-I34 Operator(s). 7
simple. 88-98 mixed. 7
List notation. 95 prede ned. I48-I49
Literals. 46 type of. I55
negative. 46
positive. 46 P
Local stack. I76
Local variables. I 76 Parallelism 57
Logical connectives. 42 Parsing problem. representation of. 59-67
Logical consequence. 44 Planning. 2l5-228
Positive literals. 46
Post x functor. 6. I55
Prede ned operators. I48-I49
Main functor. 5 Predicate(s). 42
Matching. see Uni cation Predicate symbol. I2. 42
Index 305
V W
VariabIes(s). 8-9. 42. I45
anonymous. II WARPLAN. 2I6-228
A.P.l.C. Studies in Data Processing
General Editors: Fraser Duncan and M. J . R. Shave
Out of print.
Computer Architecture: A Structured Approach
R. W. Doran
Logic Programming
Edited by K. L. Clark and S.-A. Tarnlund
Fortran Optimization
Michael Metcalf
Multi-microprocessor Systems
Y. Paker
Introduction to the Graphical Kernel SystemGKS
F. R. A. Hopgood. D. A. Duce. J. R. Gallop and
D. C. Sutcliffe
Distributed Computing
Edited by Fred B. Chambers. David A. Duce and
Gillian P. Jones
Introduction to Logic Programming
Christopher John Hogger
Lucid. the Data ow Programming Language
William W. Wadge and Edward A. Ashcroft
Foundations of Programming
Jacques Arsac
Prolog for Programmers
Feliks Kluiniak and Stanislaw Szpakowicz
This is a self-contained handbook of advanced logic programming. It
assumes the reader is not a novice to computer science. has perhaps read
an introductory book on Prolog and would now like to do some non-trivial
work using the language.
A thorough discussion of Prolog treatment of data structures. an overview
of Prolog's logical foundations. a concise but comprehensive presentation
of logic grammars. simple and advanced programming techniques. style
and efficiency guidelines. e reel exercise in Prolog implementation. and
two non-trivial case studies make this an invaluable handbook for both
professional and student. Experienced Prolog programmers may find the
systematic exposition helpful. and the discussion of implementation issues
instructive.
One of the unique features of the book is its detailed coverage of Meta-
morphosis (or Definite Clause) Grammars: programmers applying Prolog
to natural or formal language processing will definitely appreciate this.
A small but complete interpreter is provided on the disk in source form
(Pascal and Prolog) so that readers can use it to run their programs or
take it apart to study in detail to achieve an in-depth understanding of
Prolog. '