Sei sulla pagina 1di 58

DRAFT

Interpreters, Compilers, Optimisers and


Analysers in Prolog
Michael Leuschel + ...
Lehrstuhl f ur Softwaretechnik und Programmiersprachen
Institut f ur Informatik
Universitat D usseldorf
Universitatsstr. 1, D-40225 D usseldorf
leuschel@cs.uni-duesseldorf.de
2
Chapter 1
Introduction
Goal: implement, optimise, analyse, and verify programming languages and
specication languages.
Tools:
Prolog
DCGs (or even cheaper: operator declarations) for parsing
support for non-determinism allows to model a wide variety of speci-
cation formalisms as well as allows analysis, running programs back-
wards,...
operational semantics, denotational semantics rules can often be trans-
lated easily into Prolog
unication good for type checking, inference
new technologies:
tabling: good for analysis (lfp) and verication (model checking)
coroutining: allows more natural translation of advanced for-
malisms (CSP, B, lazy functional lges)
constraints: provides convenient powerful analysis and verica-
tion technology
Partial Evaluation
optimise an interpreter for a particular program to be interpreted
translate an interpreter into a compiler
can be used for slicing and analysis sometimes
Abstract Interpretation
3
4 CHAPTER 1. INTRODUCTION
Chapter 2
Background
5
6 CHAPTER 2. BACKGROUND
Chapter 3
Logic and Logic
Programming
In this chapter we summarise some essential background in rst-order logic
and logic programming, required for the proper comprehension of this thesis.
The exposition is mainly inspired by [1] and [30] and in general adheres to the
same terminology. The reader is referred to these works for a more detailed
presentation, comprising motivations, examples and proofs. Some other good
introductions to logic programming can also be found in [37], [16, 2] and [35],
while a good introduction to rst-order logic and automated theorem proving
can be found in [19].
3.1 First-order logic and syntax of logic pro-
grams
We start with a brief presentation of rst-order logic.
Denition 3.1.1 (alphabet) An alphabet consists of the following classes of
symbols:
1. variables;
2. function symbols;
3. predicate symbols;
4. connectives, which are negation, conjunction, disjunction, im-
plication, and equivalence;
5. quantiers, which are the existential quantier and the universal quan-
tier ;
6. punctuation symbols, which are (, ) and ,.
7
8 CHAPTER 3. LOGIC AND LOGIC PROGRAMMING
Function and predicate symbols have an associated arity, a natural number in-
dicating how many arguments they take in the denitions following below.
Constants are function symbols of arity 0, while propositions are predicate sym-
bols of arity 0.
The classes 4 to 6 are the same for all alphabets. In the remainder of this
thesis we suppose the set of variables is countably innite. In addition, alphabets
with a nite set of function and predicate symbols will simply be called nite.
An innite alphabet is one in which the number of function and/or predicate
symbols is not nite but countably innite.
We will try to adhere as much as possible to the following syntactical con-
ventions throughout the thesis:
Variables will be denoted by upper-case letters like X, Y, Z, usually taken
from the later part of the (Latin) alphabet.
Constants will be denoted by lower-case letters like a, b, c, usually taken
from the beginning of the (Latin) alphabet.
The other function symbols will be denoted by lower-case letters like
f, g, h.
Predicate symbols will be denoted by lower-case letters like p, q, r.
Denition 3.1.2 (terms, atoms) The set of terms (over some given alphabet)
is inductively dened as follows:
a variable is a term
a constant is a term and
a function symbol f of arity n > 0 applied to a sequence t
1
, . . . , t
n
of n
terms, denoted by f(t
1
, . . . , t
n
), is also a term.
The set of atoms (over some given alphabet) is dened in the following way:
a proposition is an atom and
a predicate symbol p of arity n > 0 applied to a sequence t
1
, . . . , t
n
of n
terms, denoted by p(t
1
, . . . , t
n
), is an atom.
We will also allow the notations f(t
1
, . . . , t
n
) and p(t
1
, . . . , t
n
) in case n = 0.
f(t
1
, . . . , t
n
) then simply represents the term f and p(t
1
, . . . , t
n
) represents the
atom p. For terms representing lists we will use the usual Prolog [14, 42, 8]
notation: e.g. [ ] denotes the empty list, [H[T] denotes a non-empty list with
rst element H and tail T.
Denition 3.1.3 (formula) A (well-formed) formula (over some given alpha-
bet) is inductively dened as follows:
An atom is a formula.
If F and G are formulas then so are (F), (F G), (F G), (F G),
(F G).
3.1. FIRST-ORDER LOGIC 9
If X is a variable and F is a formula then (XF) and (XF) are also
formulas.
To avoid formulas cluttered with the punctuation symbols we give the connec-
tives and quantiers the following precedence, from highest to lowest:
1. , , , 2. , 3. , 4. , .
For instance, we will write X(p(X) q(X) r(X)) instead of the less read-
able (X(p(X) ((q(X)) r(X)))).
The set of all formulas constructed using a given alphabet A is called the
rst-order language given by A.
First-order logic assigns meanings to formulas in the form of interpretations
over some domain D:
Each function symbol of arity n is assigned an n-ary function D
n
D.
This part, along with the choice of the domain D, is referred to as a
pre-interpretation.
Each predicate symbol of arity n is assigned an n-ary relation, i.e. a subset
of D
n
(or equivalently an n-ary function D
n
true, false).
Each formula is given a truth value, true or false, depending on the truth
values of the sub-formulas. (For more details see e.g. [19] or [30]).
A model of a formula is simply an interpretation in which the formula has
the value true assigned to it. Similarly, a model of a set S of formulas is an
interpretation which is a model for all F S.
For example, let I be an interpretation whose domain D is the set of natural
numbers IN and which maps the constant a to 1, the constant b to 2 and the
unary predicate p to the unary relation (1). Then the truth value of p(a)
under I is true and the truth value of p(b) under I is false. So I is a model of
p(a) but not of p(b). I is also a model of Xp(X) but not of Xp(X).
We say that two formulas are logically equivalent i they have the same
models. A formula F is said to be a logical consequence of a set of formulas S,
denoted by S [= F, i F is assigned the truth value true in all models of S. A
set of formulas S is said to be inconsistent i it has no model. It can be easily
shown that S [= F holds i S F is inconsistent. This observation lies at
the basis of what is called a proof by refutation: to show that F is a logical
consequence of S we show that S F leads to inconsistency.
From now on we will also use true (resp. false) to denote some arbitrary
formula which is assigned the truth value true (resp. false) in every interpreta-
tion. If there exists a proposition p in the underlying alphabet then true could
e.g. stand for p p and false could stand for p p.
1
We also introduce the
following shorthands for formulas:
1
In some texts on logic (e.g. [19]) true and false are simply added to the alphabet and
treated in a special manner by interpretations. The only dierence is then that true and
false can be considered as atoms, which can be convenient in some places.
10 CHAPTER 3. LOGIC AND LOGIC PROGRAMMING
if F is a formula, then (F ) denotes the formula (F true) and ( F)
denotes the formula (false F).
() denotes the formula (false true).
In the following we dene some other frequently occurring kinds of formulas.
Denition 3.1.4 (literal) If A is an atom then the formulas A and A are
called literals. Furthermore, A is called a positive literal and A a negative
literal.
Denition 3.1.5 (conjunction, disjunction) Let A
1
, . . . , A
n
be literals, where
n > 0. Then A
1
. . . A
n
is a conjunction and A
1
. . . A
n
is a disjunction.
Usually we will assume (respectively ) to be associative, in the sense
that we do not distinguish between the logically equivalent, but syntactically
dierent, formulas p (q r) and (p q) r.
Denition 3.1.6 (scope) Given a formula (XF) (resp. (XF)) the scope of
X (resp. X) is F. A bound occurrence of a variable X inside a formula F is
any occurrence immediately following a quantier or an occurrence within the
scope of a quantier X or X. Any other occurrence of X inside F is said to
be free.
Denition 3.1.7 (universal and existential closure) Given a formula F, the
universal closure of F, denoted by (F), is a formula of the form (X
1
. . . (X
m
F) . . .)
where X
1
, . . . , X
m
are all the variables having a free occurrence inside F (in some
arbitrary order). Similarly the existential closure of F, denoted by (F), is the
formula (X
1
. . . (X
m
F) . . .).
The following class of formulas plays a central role in logic programming.
Denition 3.1.8 (clause) A clause is a formula of the form (H
1
. . . H
m

B
1
. . . B
n
), where m 0, n 0 and H
1
, . . . , H
m
, B
1
, . . . , B
n
are all literals.
H
1
. . . H
m
is called the head of the clause and B
1
. . . B
n
is called the
body.
A (normal) program clause is a clause where m = 1 and H
1
is an atom. A
denite program clause is a normal program clause in which B
1
, . . . , B
n
are
atoms. A fact is a program clause with n = 0. A query or goal is a clause with
m = 0 and n > 0. A denite goal is a goal in which B
1
, . . . , B
n
are atoms.
The empty clause is a clause with n = m = 0. As we have seen earlier, this
corresponds to the formula false true, i.e. a contradiction. We also use 2
to denote the empty clause.
In logic programming notation one usually omits the universal quantiers
encapsulating the clause and one also often uses the comma (,) instead of
the conjunction in the body, e.g. one writes p(s(X)) q(X), p(X) instead of
X(p(f(X)) (q(X) p(X))). We will adhere to this convention.
3.2. SEMANTICS OF LOGIC PROGRAMS 11
Denition 3.1.9 (program) A (normal) program is a set of program clauses.
A denite program is a set of denite program clauses.
In order to be able to express a given program P in a rst-order language
L given by some alphabet A, the alphabet A must of course contain the func-
tion and predicate symbols occurring within P. The alphabet might however
contain additional function and predicate symbols which do not occur inside
the program. We therefore denote the underlying rst-order language of a given
program P by L
P
and the underlying alphabet by /
P
. For technical reasons re-
lated to denitions below, we suppose that there is at least one constant symbol
in /
P
.
3.2 Semantics of logic programs
Given that a program P is just a set of formulas, which happen to be clauses,
the logical meaning of P might simply be seen as all the formulas F for which
P [= F. For normal programs this approach will turn out to be insucient, but
for denite programs it provides a good starting point.
3.2.1 Denite programs
To determine whether a formula F is a logical consequence of another formula
G, we have to examine whether F is true in all models of G. One big advantage
of clauses is that it is sucient to look just at certain canonical models, called
the Herbrand models.
In the following we will dene these canonical models. Any term, atom,
literal, clause will be called ground i it contains no variables.
Denition 3.2.1 Let P be a program written in the underlying rst-order
language L
P
given by the alphabet /
P
. Then the Herbrand universe |
P
is the
set of all ground terms over /
P
.
2
The Herbrand base B
P
is the set of all ground
atoms in L
P
.
A Herbrand interpretation is simply an interpretation whose domain is the
Herbrand universe |
P
and which maps every term to itself. A Herbrand model
of a set of formulas S is an Herbrand interpretation which is a model of S.
The interest of Herbrand models for logic programs derives from the following
proposition (the proposition does not hold for arbitrary formulas).
Proposition 3.2.2 A set of clauses has a model i it has a Herbrand model.
This means that a formula F which is true in all Herbrand models of a set of
clauses C is a logical consequence of C. Indeed if F is true in all Herbrand models
2
It is here that the requirement that A
P
contains at least one constant symbol comes into
play. It ensures that the Herbrand universe is never empty.
12 CHAPTER 3. LOGIC AND LOGIC PROGRAMMING
then F is false in all Herbrand models and therefore, by Proposition 3.2.2,
C F is inconsistent and C [= F.
Note that a Herbrand interpretation or model can be identied with a subset
H of the Herbrand base B
P
(i.e. H 2
B
P
): the interpretation of p(d
1
, . . . , d
n
)
is true i p(d
1
, . . . , d
n
) H and the interpretation of p(d
1
, . . . , d
n
) is false
i p(d
1
, . . . , d
n
) , H. This means that we can use the standard set order on
Herbrand models and dene minimal Herbrand models as follows.
Denition 3.2.3 A Herbrand model H B
P
for a given program P is a
minimal Herbrand model i there exists no H

H which is also a Herbrand


model of P.
For denite programs there exists a unique minimal Herbrand model, called
the least Herbrand model , denoted by H
P
. Indeed it can be easily shown that the
intersection of two Herbrand models for a denite program P is still a Herbrand
model of P. Furthermore, the entire Herbrand base B
P
is always a model for a
denite program and one can thus obtain the least Herbrand model by taking
the intersection of all Herbrand models.
The least Herbrand model H
P
can be seen as capturing the intended meaning
of a given denite programP as it is sucient to infer all the logical consequences
of P. Indeed, a formula which is true in the least Herbrand model H
P
is true
in all Herbrand models and is therefore a logical consequence of the program.
Example 3.2.4 Take for instance the following program P:
int(0)
int(s(X)) int(X)
Then the least Herbrand model of P is H
P
= int(0), int(s(0)), . . . and indeed
P [= int(0), P [= int(s(0)), . . . . But also note that for denite programs the
entire Herbrand base B
P
is also a model. Given a suitable alphabet /
P
, we
might have B
P
= int(a), int(0), int(s(a)), int(s(0)), . . .. This means that the
atom int(a) is consistent with the program P (i.e. P ,[= int(a)), but is not
implied either (i.e. P ,[= int(a)).
It is here that logic programming goes beyond classical rst-order logic.
In logic programming one (usually) assumes that the program gives a complete
description of the intended interpretation, i.e. anything which cannot be in-
ferred from the program is assumed to be false. For example, one would say
that int(a) is a consequence of the above program P because int(a) , H
P
.
This means that, from a logic programming perspective, the above program cap-
tures exactly the natural numbers, something which is impossible to accomplish
within rst-order logic (for a formal proof see e.g. Corollary 4.10.1 in [13]).
A possible inference scheme, capturing this aspect of logic programming,
was introduced in [40] and is referred to as the closed world assumption (CWA).
The CWA cannot be expressed in rst-order logic (a second-order logic axiom
has to be used to that eect, see e.g. the approach adopted in [28]). Note that
using the CWA leads to non-monotonic inferences, because the addition of new
3.2. SEMANTICS OF LOGIC PROGRAMS 13
information can remove certain, previously valid, consequences. For instance,
by adding the clause int(a) to the above program the literal int(a) is no
longer a consequence of the logic program.
3.2.2 Fixpoint characterisation of H
P
We now present a more constructive characterisation of the least Herbrand
model, as well as the associated set of consequences, using xpoint concepts.
We rst need the following denitions:
Denition 3.2.5 (substitution) A substitution is a nite set of the form
= X
1
/t
1
, . . . , X
n
/t
n
where X
1
, . . . , X
n
are distinct variables and t
1
, . . . , t
n
are terms such that t
i
,= X
i
. Each element X
i
/t
i
of is called a binding.
Alternate denitions of substitutions exist in the literature (e.g. in [17, 15],
see also the discussion in [24]), but the above is the most common one in the
logic programming context.
We also dene an expression to be either a term, an atom, a literal, a con-
junction, a disjunction or a program clause.
Denition 3.2.6 (instance) Let = X
1
/t
1
, . . . , X
n
/t
n
be a substitution
and E an expression. Then the instance of E by , denoted by E, is the
expression obtained by simultaneously replacing each occurrence of a variable
X
i
in E by the term t.
We can now dene the following operator mapping Herbrand interpretations
to Herbrand interpretations.
Denition 3.2.7 (T
P
) Let P be a program. We then dene the (ground)
immediate consequence operator T
P
2
B
P
2
B
P
by:
T
P
(I) = A B
P
[ A A
1
, . . . A
n
is a ground instance of a
clause in P and A
1
, . . . , A
n
I
Every pre-xpoint I of T
P
, i.e. T
P
(I) I, corresponds to a Herbrand model
of P and vice versa. This means that to study the Herbrand models of P one
can also investigate the pre-xpoints of the operator T
P
. For denite programs,
one can prove that T
P
is a continuous mapping and that it has a least xpoint
lfp(T
P
) which is also its least pre-xpoint.
The following denition will provide a way to calculate the least xpoint:
Denition 3.2.8 Let T be a mapping 2
D
2
D
. We then dene T 0 = and
T i + 1 = T(T i). We also dene T to stand for

i<
T i.
The following theorem from [43] links the least Herbrand model with the
least xpoint of T
P
and provides a way of constructing it.
Theorem 3.2.9 (Fixpoint characterisation of the least Herbrand model)
Let P be a denite program. Then H
P
= lfp(T
P
) = T
P
.
14 CHAPTER 3. LOGIC AND LOGIC PROGRAMMING
3.3 Proof theory of logic programs [nicht rele-
vant fuer PS2 Kurs?]
We start with some additional useful terminology related to substitutions. If
E = F then E is said to be more general than F. If E is more general
than F and F is more general than E then E and F are called variants (of
each other). If E is a variant of E then is called a renaming substitution
for E. Because a substitution is a set of bindings we will denote, in contrast
to e.g. [30], the empty or identity substitution by and not by the empty se-
quence . Substitutions can also be applied to sets of expressions by dening
E
1
, . . . , E
n
= E
1
, . . . , E
n
.
Substitutions can also be composed in the following way:
Denition 3.3.1 (composition of substitutions) Let = X
1
/s
1
, . . . ,
X
n
/s
n
and = Y
1
/t
1
, . . . , Y
k
/t
k
be substitutions. Then the composition
of and , denoted by , is dened to be the substitution X
i
/s
i
[ 1 i
n s
i
,= X
i
Y
i
/t
i
[ 1 i k Y
i
, X
1
, . . . , X
n
.
3
When viewing substitutions as functions from expressions to expressions, then
the above denition behaves just like ordinary function composition, i.e. E() =
(E). We also have that (for proofs see [30]) the identity substitution acts as
a left and right identity for composition, i.e. = = , and that composition
is associative, i.e. () = ().
We call a substitution idempotent i = . We also dene the following
notations: the set of variables occurring inside an expression E is denoted by
vars(E), the domain of a substitution is dened as dom() = X [ X/t
and the range of is dened as ran() = Y [ X/t Y vars(t). Finally,
we also dene vars() = dom() ran() as well as the restriction [
V
of a
substitution to a set of variables 1 by [
V
= X/t [ X/t X 1.
The following concept will form the link between the model-theoretic seman-
tics and the procedural semantics of logic programs.
Denition 3.3.2 (answer) Let P be a program and G = L
1
, . . . , L
n
a goal.
An answer for P G is a substitution such that dom() vars(G).
3.3.1 Denite programs
We rst dene correct answers in the context of denite programs and goals.
Denition 3.3.3 (correct answer) Let P be a denite program and G =
A
1
, . . . , A
n
a denite goal. An answer for P G is called a correct answer
for P G i P [= ((A
1
. . . A
n
)).
3
This denition deviates slightly from the one in [1, 30, 37]. Indeed, taking the denition
in [1, 30, 37] literally we would have that {X/a}{X/a} = and not the desired {X/a} (the
denition in [1, 30, 37] says to delete any binding Y
i
/t
i
with Y
i
{X
1
, . . . , Xn} from a set of
bindings). Our denition does not share this problem.
3.3. PROOF THEORYOF LOGIC PROGRAMS [NICHT RELEVANT FUER PS2 KURS?]15
Take for instance the program P = p(a) and the goal G = p(X).
Then X/a is a correct answer for P G while X/c and are not.
We now present a way to calculate correct answers based on the concepts of
resolution and unication.
Denition 3.3.4 (mgu) Let S be a nite set of expressions. A substitution
is called a unier of S i the set S is a singleton. is called relevant i its
variables vars() all occur in S. is called a most general unier or mgu i for
each unier of S there exists a substitution such that = .
The concept of unication dates back to [20] and has been rediscovered in
[41]. A survey on unication, also treating other application domains, can be
found in [23].
If a unier for a nite set S of expressions exists then there exists an idem-
potent and relevant most general unier which is unique modulo variable re-
naming (see [1, 30]). Uniability of a set of expressions is decidable and there
are ecient algorithms for calculating an idempotent and relevant mgu. See
for instance the unication algorithms in [1, 30] or the more complicated but
linear ones in [31, 38]. From now on we denote, for a uniable set S of expres-
sions, by mgu(S) an idempotent and relevant unier of S. If we just want to
unify two terms t
1
, t
2
then we will also sometimes write mgu(t
1
, t
2
) instead of
mgu(t
1
, t
2
).
We dene the most general instance, of a nite set S to be the only element
of S where = mgu(S). The opposite of the most general instance is the
most specic generalisation of a nite set of expressions S, also denoted by
msg(S), which is the most specic expression M such that all expressions in
S are instances of M. Algorithms for calculating the msg exist [27], and this
process is also referred to as anti-unication or least general generalisation.
We can now dene SLD-resolution, which is based on the resolution principle
[41] and which is a special case of SL-resolution [26]. Its use for a programming
language was rst described in [25] and the name SLD (which stands for Selec-
tion rule-driven Linear resolution for Denite clauses), was coined in [4]. For
more details about the history see e.g. [1, 30].
Denition 3.3.5 (SLD-derivation step) Let G = L
1
, . . . , L
m
, . . . , L
k
be
a goal and C = A B
1
, . . . , B
n
a program clause such that k 1 and n 0.
Then G

is derived from G and C using (and L


m
) i the following conditions
hold:
1. L
m
is an atom, called the selected atom (at position m), in G.
2. is a relevant and idempotent mgu of L
m
and A.
3. G

is the goal (L
1
, . . . , L
m1
, B
1
, . . . , B
n
, L
m+1
, . . . , L
k
).
G

is also called a resolvent of G and C.


In the following we dene the concept of a complete SLD-derivation (we will
dene incomplete ones later on).
16 CHAPTER 3. LOGIC AND LOGIC PROGRAMMING
Denition 3.3.6 (complete SLD-derivation) Let P be a normal program
and G a normal goal. A complete SLD
+
-derivation of P G is a tuple
((, L, (, o) consisting of a sequence of goals ( = G
0
, G
1
, . . .), a sequence
L = L
0
, L
1
. . .) of selected literals,
4
a sequence ( = C
1
, C
2
, . . .) of variants
of program clauses of P and a sequence o =
1
,
2
, . . .) of mgus such that:
for i > 0, vars(C
i
) vars(G
0
) = ;
for i > j, vars(C
i
) vars(C
j
) = ;
for i 0, L
i
is a positive literal in G
i
and G
i+1
is derived from G
i
and
C
i+1
using
i+1
and L
i
;
the sequences (, (, o are maximal given L.
A complete SLD-derivation is just a complete SLD
+
derivation of a denite
program and goal.
The process of producing variants of program clauses of P which do not
share any variable with the derivation sequence so far is called standardising
apart. Some care has to be taken to avoid variable clashes and the ensuing
technical problems; see the discussions in [24] or [15].
We now come back to the idea of a proof by refutation and its relation to
SLD-resolution. In a proof by refutation one adds the negation of what is to
be proven and then tries to arrive at inconsistency. The former corresponds to
adding a goal G = A
1
, . . . , A
n
to a program P and the latter corresponds to
searching for an SLD-derivation of P G which leads to 2. This justies the
following denition.
Denition 3.3.7 (SLD-refutation) An SLD-refutation of P G is a nite
complete SLD-derivation of P G which has the empty clause 2 as the last
goal of the derivation.
In addition to refutations there are (only) two other kinds of complete deriva-
tions:
Finite derivations which do not have the empty clause as the last goal.
These derivations will be called (nitely) failed.
Innite derivations. These will be called innitely failed.
We can now dene computed answers, which correspond to the output cal-
culated by a logic program.
Denition 3.3.8 (computed answer) Let P be a denite program, G a
denite goal and D a SLD-refutation for P G with the sequence
1
, . . . ,
n
)
of mgus. The substitution (
1
. . .
n
)[
vars(G)
is then called a computed answer
for P G (via D).
If is a computed (respectively correct) answer for P G then G is called
a computed (respectively correct) instance for P G.
4
Again we slightly deviate from [1, 30]: the inclusion of L avoids some minor technical
problems wrt the maximality condition.
3.3. PROOF THEORYOF LOGIC PROGRAMS [NICHT RELEVANT FUER PS2 KURS?]17
Theorem 3.3.9 (soundness of SLD) Let P be a denite program and G
a denite goal. Every computed answer for P G is a correct answer for
P G.
Theorem 3.3.10 (completeness of SLD) Let P be a denite program and G
a denite goal. For every correct answer for P G there exists a computed
answer for P G and a substitution such that G = G.
A proof of the previous theorem can be found in [1].
5
We will now examine systematic ways to search for SLD-refutations.
Denition 3.3.11 (complete SLD-tree) A complete SLD-tree for P G
is a labelled tree satisfying the following:
1. Each node of the tree is labelled with a denite goal along with an indi-
cation of the selected atom
2. The root node is labelled with G.
3. Let A
1
, . . . , A
m
, . . . , A
k
be the label of a node in the tree and suppose
that A
m
is the selected atom. Then for each clause A B
1
, . . . , B
q
in P
such that A
m
and A are uniable the node has one child labelled with
(A
1
, . . . , A
m1
, B
1
, . . . , B
q
, A
m+1
, . . . , A
k
),
where is an idempotent and relevant mgu of A
m
and A.
4. Nodes labelled with the empty goal have no children.
To every branch of a complete SLD-tree corresponds a complete SLD-derivation.
The choice of the selected atom is performed by what is called a selection
rule. Maybe the most well known selection rule is the left-to-right selection
rule of Prolog [14, 42, 8], which always selects the leftmost literal in a goal. The
complete SLD-derivations and SLD-trees constructed via this selection rule are
called LD-derivations and LD-trees.
Usually one confounds goals and nodes (e.g. in [1, 30, 37]) although this
is strictly speaking not correct because the same goal can occur several times
inside the same SLD-tree.
We will often use a graphical representation of SLD-trees in which the se-
lected atoms are identied by underlining. For instance, Figure 3.1 contains a
graphical representation of a complete SLD-tree for P int(s(0)), where
P is the program of Example 3.2.4.
5
The corresponding theorem in [30] states that = , which is known to be false. In-
deed, take for example the program P = {p(f(X, Y )) } and the goal G = p(Z). Then
{Z/f(a, a)} is a correct answer (because p(f(a, a)) is a consequence of P), but
there is no computed answer {X/f(a, a)}
for any computed answer {Z/f(X

, Y

)} (where either X

or Y

must be dierent
from Z; these are the only computed answers) composing it with {X

/a, Y

/a} will
give {Z/f(a, a), X

/a, Y

/a} (or {Z/f(a, a), Y

/a} if X

= Z or {Z/f(a, a), X

/a} if
Y

= Z) which is dierent from {X/f(a)}.
18 CHAPTER 3. LOGIC AND LOGIC PROGRAMMING
2
int(0)
?
?
int(s(0))
Figure 3.1: Complete SLD-tree for Example 3.2.4
3.3.2 Programs with built-ins
Most practical logic programs make (heavy) usage of built-ins. Although a lot
of these built-ins, like e.g. assert/1 and retract/1, are extra-logical and ruin
the declarative nature of the underlying program, a reasonable number of them
can actually be seen as syntactic sugar. Take for example the following program
which uses the Prolog [14, 42, 8] built-ins = ../2 and call/1.
map(P, [], [])
map(P, [X[T], [P
X
[P
T
]) C = ..[P, X, P
X
], call(C), map(P, T, P
T
)
inv(0, 1)
inv(1, 0)
For this program the query map(inv, [0, 1, 0], R) will succeed with the com-
puted answer R/[1, 0, 1]. Given that query, the Prolog program can be seen as
a pure denite logic program by simply adding the following denitions (where
we use the prex notation for the predicate = ../2):
= ..(inv(X, Y ), [inv, X, Y ])
call(inv(X, Y )) inv(X, Y )
The so obtained pure logic program will succeed for map(inv, [0, 1, 0], R) with
the same computed answer R/[1, 0, 1].
This means that some predicates like map/3, which are usually taken to be
higher-order, can simply be mapped to pure denite (rst-order) logic programs
([44, 36]). Some built-ins, like for instance is/2, have to be dened by innite
relations. Usually this poses no problems as long as, when selecting such a
built-in, only a nite number of cases apply (Prolog will report a run-time error
if more than one case applies while the programming language Godel [22] will
delay the selection until only one case applies).
In the remainder of this thesis we will usually restrict our attention to those
built-ins that can be given a logical meaning by such a mapping.
Chapter 4
Parsing
4.1 Operator Declarations
[TO DO: INSERT: material on op declarations]
4.2 DCGs
[TO DO: INSERT: Some DCG background.]
4.3 Treating Precedences and Associativites with
DCGs
Scheme to treat operator precedences and associativity: one layer per operator
precedence; every layer calls the layer below, bottom layer can call top layer via
parentheses.
Program 4.3.1
plus(R) --> exp(A), plusc(A,R).
plusc(Acc,Res) --> "+",!, exp(B), plusc(plus(Acc,B),Res).
/* left associative: B associates to + on left */
plusc(A,A) --> [].
exp(R) --> num(A), expc(A,R).
expc(Acc,exp(Acc,R2)) --> "**",!, exp(R2). /* right associative */
expc(A,A) --> [].
num(X) --> "(",!,plus(X), ")".
19
20 CHAPTER 4. PARSING
+ +
**
x y
** **
x y z y
plus
exp
num
parse
Figure 4.1: Illustrating the parse hierachy and tree for "x**y+x**y**z+y"
num(id(x)) --> "x".
num(id(y)) --> "y".
num(id(z)) --> "z".
parse(S,T) :- plus(T,S,[]).
This parser can be called as follows:
| ?- parse("x+y+z",R).
R = plus(plus(id(x),id(y)),id(z)) ?
yes
| ?- parse("x**y**z",R).
R = exp(id(x),exp(id(y),id(z))) ?
yes
| ?- parse("x**y+x**y**z+y",R).
R = plus(plus(exp(id(x),id(y)),exp(id(x),exp(id(y),id(z)))),id(y)) ?
yes
Exercise 4.3.2 Extend the parser from Program 4.3.1 to handle the operators
-, *, /.
4.4 Tokenisers
Exercise 4.4.1 Extend the parser from Program 4.3.1 and Exercise 4.3.2 to
link up with a tokenizer that recognises numbers and identiers.
Chapter 5
Simple Interpreters
5.1 NFA
Our rst task is to write an interpreter for non-deterministic nite automaton
(NFA). [REF; Ullman?]
One question we must ask ourselves is, how do we represent the NFA to be
interpreted. One solution is to represent the NFA by Prolog facts. E.g., we
could use Prolog predicate init/1 to represent the initial states, final/1 to
represent the nal states, and trans/3 to represent the transitions between the
states. To represent the NFA from Figure 7.2 we would thus write:
Program 5.1.1
init(1).
trans(1,a,2).
trans(2,b,3).
trans(2,b,2).
final(3).
Let us now write an interpreter, checking whether a string can be generated
by the automaton. The interpreter needs to know the current state St as well as
the string to be checked. The empty string can only be generated in nal states.
1 2 a
b
3 b
Figure 5.1: A simple NFA
21
22 CHAPTER 5. SIMPLE INTERPRETERS
A non-empty string can be generated by taking an outgoing transition from the
current state to another state S2, thereby generating the rst character H of the
string; from S2 we must be able to generate the rest T of the string.
accept(S,[]) :- final(S).
accept(S,[H|T]) :- trans(S,H,S2), accept(S2,T).
We have not yet encoded that initially we must start from a valid initial
state. This can be encoded by the following:
check(Trace) :- init(S), accept(S,Trace).
We can now use our interpreter to check whether the NFA can generate
various strings:
| ?- check([a,b]).
yes
| ?- check([a]).
no
We can even only provide a partially instantiated string and ask for solutions:
| ?- check([X,Y,Z]).
X = a,
Y = b,
Z = b ? ;
no
Finally, we can generate strings accepted by the NFA:
| ?- check(X).
X = [a,b] ? ;
X = [a,b,b] ? ;
X = [a,b,b,b] ? ;
Exercise 5.1.2 Extend the above interpreter for epsilon transitions.
5.2 Regular Expressions
Our next task is to write an interpreter for regular expressions. [REF] We ll
rst use operator declarations so that we can use the standard regular expression
operators . for concatenation, + for alternation, and * for repetition. For
this we write the following operator declarations [REF].
:- op(450,xfy,.). /* + already defined; has 500 as priority */
:- op(400,xf,*).
5.2. REGULAR EXPRESSIONS 23
With append:
Program 5.2.1
:- use_module(library(lists)).
gen(X,[X]) :- atomic(X).
gen(X+Y,S) :- gen(X,S) ; gen(Y,S).
gen(X.Y,S) :- gen(X,SX), append(SX,SY,S), gen(Y,SY).
gen(*(_X),[]).
gen(*(X),S) :- gen(X,SX), append(SX,SSX,S),gen(*(X),SSX).
Observe how to compute the meaning/eect of X.Y we compute the eect of
X and Y by recursive calls to gen. This is a common pattern, that will appear
time and time again in interpreters.
Usage:
| ?- gen(a*,[X,Y,Z]).
X = a,
Y = a,
Z = a ?
yes
| ?- gen((a+b)*,[X,Y]).
X = a,
Y = a ? ;
X = a,
Y = b ? ;
X = b,
Y = a ? ;
X = b,
Y = b ? ;
no
Problem: eciency as SX traversed a second time by append. More prob-
lematic is the following, however:
| ?- gen((a*).b,[c]).
! Resource error: insufficient memory
Solution: dierence lists: no more need to do append (concatenation in
constant time using dierence lists: diff append(X-Y,Z-V,R) :- R = X-V,
Y=Z.
Program 5.2.2 Version with dierence lists:
generate(X,[X|T],T) :- atomic(X).
generate(X +_Y,H,T) :- generate(X,H,T).
generate(_X + Y,H,T) :- generate(Y,H,T).
generate(X.Y,H,T) :- generate(X,H,T1), generate(Y,T1,T).
generate(*(_),T,T).
generate(*(X),H,T) :- generate(X,H,T1), generate(*(X),T1,T).
24 CHAPTER 5. SIMPLE INTERPRETERS
gen(RE,S) :- generate(RE,S,[]).
We can now call:
| ?- gen((a*).b,[c]).
no
[Insert Figure showing call graph for both versions of gen.]
Explain alternate reading: generate(RE, InEnv, OutEnv): InEnv = char-
acters still to be consumed overall; OutEnv = characters remaining after RE has
been matched.
Common theme: sometimes called threading. Can be written in DCG
style notation:
Program 5.2.3
generate(X) --> [X], {atomic(X)}.
generate(X +_Y) --> generate(X).
generate(_X + Y) --> generate(Y).
generate(.(X,Y)) --> generate(X), generate(Y).
generate(*(_)) --> [].
generate(*(X)) --> generate(X), generate(*(X)).
5.3 Propositional Logic
Program 5.3.1
int(const(true)).
int(const(false)) :- fail.
int(and(X,Y)) :- int(X), int(Y).
int(or(X,Y)) :- int(X) ; int(Y).
int(not(X)) :- \+ int(X).
[Discuss Problem with negation int(not(const(X))) fails even though there
is a solution. Explain that negation only sound when call is ground, i.e., contains
no variables. ]
One common technique is to get rid of the need for the built-in negation, by
explicitly writing a predicate for the negation:
Program 5.3.2
int(const(true)).
int(const(false)) :- fail.
int(and(X,Y)) :- int(X), int(Y).
int(or(X,Y)) :- int(X) ; int(Y).
int(not(X)) :- nint(X).
nint(const(false)).
5.4. A FIRST SIMPLE IMPERATIVE LANGUAGE 25
nint(const(true)) :- fail.
nint(and(X,Y)) :- nint(X) ; nint(Y).
nint(or(X,Y)) :- nint(X),nint(Y).
nint(not(X)) :- int(X).
Exercise 5.3.3 Extend the above interpreter for implication imp/2.
Literature: Clark Completion and Equality Theory, SLDNF, Constructive
negation, Godel Programming language,...
5.4 A First Simple Imperative Language
Let us rst choose an extremely simple language with three constructs:
variable denition def, denining a single variable. Example: def x.
assignment := to assign a value to a variable. Example: x := 3
sequential composition ; to compose two constructs. Example: def x ;
x:= 3
Imperative programs access and modify a global state. When writing an
interpreter we thus need to model this environment. Thus, we rst provide
auxilary predicate to store and retrieve variable values in an environment:
Program 5.4.1
/* def(OldEnv, VariableName, NewEnv) */
def(Env,Key,[Key/undefined|Env]).
/* store(OldEnv, VariableName, NewValue, NewEnv) */
store([],Key,Value,[exception(store(Key,Value))]).
store([Key/_Value2|T],Key,Value,[Key/Value|T]).
store([Key2/Value2|T],Key,Value,[Key2/Value2|BT]) :-
Key \== Key2, store(T,Key,Value,BT).
/* lookup(VariableName, Env, CurrentValue) */
lookup(Key,[],_) :- print(lookup_var_not_found_error(Key)),nl,fail.
lookup(Key,[Key/Value|_T],Value).
lookup(Key,[Key2/_Value2|T],Value) :-
Key \== Key2,lookup(Key,T,Value).
We suppose that store and lookup will be called with all but the last argu-
ment bound ?
| ?- store(Old,x,X,[x/3,y/2]).
X = 3,
Old = [x/_A,y/2] ? ;
no
26 CHAPTER 5. SIMPLE INTERPRETERS
Program 5.4.2
int(X:=V,In,Out) :- store(In,X,V,Out).
int(def(X),In,Out) :- def(In,X,Out).
int(X;Y,In,Out) :- int(X,In,I2), int(Y,I2,Out).
test(R) :- int( def x; x:= 5; def z; x:= 3; z:= 2 , [],R).
| ?- int( def x; x:= 5; def z; x:= 3; z:= 2 , [],R).
R = [z/2,x/3]
| ?- int( def x; x:= 5; def z; x:= 3; z:= X , In, [z/2,x/RX]).
X = 2,
In = [],
RX = 3 ? ;
no
Now let us allow to evaluate expressions in the right hand side of an assign-
ment. To make our interpreter a little bit simpler, we suppose that variable uses
are preceded by a $. E.g., x := $x+1.
We need a separate function to evaluate arguments. This is again a common
pattern in interpreter development: we have one predicate to execute state-
ments, modifying an environment, and one predicate to evaluation expressions
returning a value. If the expressions in the language to be interpreted are side-
eect free, the eval predicate need not return a new environment. This is what
we have done below:
:- op(200,fx,$).
/* eval(Expression, Env, ExprValue) */
eval(X,_Env,Res) :- number(X),Res=X.
eval($(X),Env,Res) :- lookup(X,Env,Res).
eval(+(X,Y),Env,Res) :- eval(X,Env,RX), eval(Y,Env,RY), Res is RX+RY.
eint(X:=V,In,Out) :- eval(V,In,Res),store(In,X,Res,Out).
eint(def(X),In,Out) :- def(In,X,Out).
eint(X;Y,In,Out) :- eint(X,In,I2), eint(Y,I2,Out).
| ?- eint( def x; x:= 5; def z; x:= $x+1; z:= $x+($x+2) , [],R).
R = [z/14,x/6] ?
Exercise 5.4.3 Extend the interpreter for other operators, like multiplication
* and exponentiation **.
Exercise 5.4.4 Rewrite the interpreter so that one does not need to use the
$. Make sure that your interpreter only has a single (and correct) solution.
5.5. AN IMPERATIVE LANGUAGE WITH CONDITIONALS 27
5.4.1 Summary
The general scheme is
int( Statement[X1,...XN], I1, OutEnv) :-
int(X1,I1,I2), or eval(X1,I1,R1),I2=I1,
...,
int(Xn,In,OutEnv).
For evaluating expressions:
eval(Expr [X1,...XN], Env, Res) :-
eval(X1,Env,R1), ...,
eval(Xn,Env,Rn),
compute(R1,...,Rn,Res).
5.5 An imperative language with conditionals
Two choices:
treat a boolean expression 1=x like an expression returning a boolean value
introduce a separate class boolean expression with a separate predicate.
Here there are again two sub choices:
Write a predicate test be(BE,Env) which succeeds if the boolean
expression succeeds in the environment Env.
Write a predicate eval be(BE,Env,BoolRes)
Program 5.5.1
eint(if(BE,S1,S2),In,Out) :-
eval_be(BE,In,Res),
((Res=true -> eint(S1,In,Out)) ; eint(S2,In,Out)).
eval_be(=(X,Y),Env,Res) :- eval(X,Env,RX), eval(Y,Env,RY),
((RX=RY -> Res=true) ; (Res=false)).
eval_be(<(X,Y),Env,Res) :- eval(X,Env,RX), eval(Y,Env,RY),
((RX<RY -> Res=true) ; (Res=false)).
5.6 An imperative language with loops
Add a string object that gets converted to an atom:
eval([HS|String],_,Res) :- name(Res,[HS|String]).
Add a println command:
eint(println(S),E,E) :- eval(S,E,ES),print(ES),nl.
28 CHAPTER 5. SIMPLE INTERPRETERS
eint(while(BE,S),In,Out) :-
eval_be(BE,In,Res),
((Res=true -> eint(;(S,while(BE,S)),In,Out)) ; In=Out).
Alternate solution:
eint(skip,E,E).
eint(while(BE,S),In,Out) :- eint(if(BE,;(S,while(BE,S)),skip),In,Out).
Chapter 6
Writing a Prolog
Interpreter in Prolog
Def: object program
How to represent the Prolog program:
Clausal Representation: Like in Section 5.1 use clauses to represent the
object program.
Term Representation (Reied representation): Like in Sections 5.2 and
5.3, use a Prolog term to represent the object program
[ vanilla meta-interpreter (see, e.g., [21, 3]). ]
Program 6.0.1
:- dynamic app/3.
app([],L,L).
app([H|X],Y,[H|Z]) :- app(X,Y,Z).
solve(true).
solve(,(A,B)) :- solve(A),solve(B).
solve(Goal) :- Goal \= ,(_,_), Goal \= true,
clause(Goal,Body), solve(Body).
Debugging version:
Program 6.0.2
isolve(Goal) :- isolve(Goal,0).
indent(0).
indent(s(X)) :- print(+-), indent(X).
29
30 CHAPTER 6. WRITING A PROLOG INTERPRETER IN PROLOG
isolve(true,_).
isolve(,(A,B),IL) :- isolve(A,IL),isolve(B,IL).
isolve(Goal,IL) :- Goal \= ,(_,_), Goal \= true,
print(|), indent(IL), print(> enter ), print(Goal),nl,
backtrack_message(IL,fail(Goal)),
clause(Goal,Body), isolve(Body,s(IL)),
backtrack_message(IL,redo(Goal)),
print(|), indent(IL), print(> exit ), print(Goal),nl.
backtrack_message(_IL,_).
backtrack_message(IL,Msg) :-
print(|), indent(IL), print(> ),print(Msg),nl,fail.
6.0.1 Towards a reined version
First (wrong) attempt:
Program 6.0.3
:- use_module(library(lists),[member/2]).
rsolve(true,_Prog).
rsolve(,(A,B),Prog) :- rsolve(A,Prog),rsolve(B,Prog).
rsolve(Goal,Prog) :- member(:-(Goal,Body),Prog), rsolve(Body,Prog).
At rst sight seems to work:
| ?- rsolve(p,[ (p:-q,r), (q :- true), (r :- true)]).
yes
| ?- rsolve(p(X), [ (p(X) :- q(X)) , (q(a) :- true), (q(b) :- true) ]).
X = a ? ;
X = b ? ;
no
But here it doesnt:
| ?- rsolve(i(s(s(0))), [ (i(0) :- true) , (i(s(X)) :- i(X)) ]).
no
What is the problem ?
| ?- rsolve(i(s(0)), [ (i(0) :- true) , (i(s(X)) :- i(X)) ]) .
X = 0
We need standardising apart, i.e., generate a fresh copy for the variables
inside the clauses being used:
Program 6.0.4
31
rsolve(true,_Prog).
rsolve(,(A,B),Prog) :- rsolve(A,Prog),rsolve(B,Prog).
rsolve(Goal,Prog) :- get_clause(Prog,Goal,Body), rsolve(Body,Prog).
get_clause([ Clause | _], Head, Body) :-
copy_term(Clause,CClause),
CClause = :-(Head,Body).
get_clause([ _ | Rest], Head, Body) :- get_clause(Rest,Head,Body).
Exercise 6.0.5 Improve the data structure for the above interpreter; by group-
ing clauses according to predicate name and arity.
6.0.2 Towards a declarative Interpreter
The rst vanilla interpreter can be given a declarative reading (cite De Schreye,
Martens). However, the reied one is certainly not.
What do we mean by declarative:
We say that a predicate p of arity n is declarative i
p(a
1
, ..., a
n
), , p(a
1
, ..., a
n
) (binding-insensitive)
p(a
1
, ..., a
n
), fail fail , p(a
1
, ..., a
n
) (side-eect free)
copy term is not declarative: copy term(p(X),Q),X=a is dierent from X=a,
copy term(p(X),Q).
Exercise 6.0.6 Find other predicates in Prolog which are not declarative and
explain why.
Why is it a good idea to be declarative:
clear logical semantics; independent of operational reading
gives optimisers more liberty to reorder goals: parallelisation, optimisa-
tion, analysis, specialisation,...
Solution: the ground representation:
Going from [ (i(0) :- true) , (i(s(X)) :- i(X)) ] with variables to
[ (i(0) :- true) , (i(s(var(x))) :- i(var(x))) ] . What if the pro-
gram uses var/1 ?? [ (i(term(0,[])) :- true) , (i(term(s,[var(x)]))
:- i(var(x))) ] .
But now things get more complicated: we need to write an explicit unica-
tion procedure ! We need to treat substitutions and apply bindings !
InstanceDemo or the Lifting Interpreter
Trick: lift var(1) to variables; can be done declaratively !
Try to write lift(p(var(1),var(1)), X) which gives answer X = p( 1, 1).
Lift can be used to generate fresh copy of clauses !
32 CHAPTER 6. WRITING A PROLOG INTERPRETER IN PROLOG
Program 6.0.7
/* --------------------- */
/* solve(GrRules,NgGoal) */
/* --------------------- */
solve([],_GrRules).
solve([NgH|NgT],GrRules) :-
fresh_member(term(clause,[NgH|NgBody]),GrRules),
solve(NgBody,GrRules),
solve(NgT,GrRules).
/* --------------------------------- */
/* fresh_member(NgExpr,GrListOfExpr) */
/* --------------------------------- */
fresh_member(NgX,[GrH|_GrT]) :- lift(GrH,NgX).
fresh_member(NgX,[_GrH|GrT]) :- fresh_member(NgX,GrT).
/* ---------------------------------------- */
/* lift(GroundRepOfExpr,NonGroundRepOfExpr) */
/* ---------------------------------------- */
lift(G,NG) :- mkng(G,NG,[],_Sub).
mkng(var(N),X,[],[sub(N,X)]).
mkng(var(N),X,[sub(N,X)|T],[sub(N,X)|T]).
mkng(var(N),X,[sub(M,Y)|T],[sub(M,Y)|T1]) :-
N \== M, mkng(var(N),X,T,T1).
mkng(term(F,Args),term(F,IArgs),InSub,OutSub) :-
l_mkng(Args,IArgs,InSub,OutSub).
l_mkng([],[],Sub,Sub).
l_mkng([H|T],[IH|IT],InSub,OutSub) :-
mkng(H,IH,InSub,IntSub),
l_mkng(T,IT,IntSub,OutSub).
test(X,Y,Z) :- solve([term(app,[X,Y,Z])], [
term(clause,[term(app,[term([],[]),var(l),var(l)]) ]),
term(clause,[term(app,[term(.,[var(h),var(x)]),var(y),
term(.,[var(h),var(z)])]),
term(app,[var(x),var(y),var(z)]) ])
]).
6.0.3 Meta-interpreters and pre-compilation
[TO DO: Integrate material below into main text]
A meta-program is a program which takes another program, the object pro-
6.1. THE POWER OF THE GROUNDVS. THE NON-GROUNDREPRESENTATION33
gram, as input, manipulating it in some way. Usually the object and meta-
program are supposed to be written in (almost) the same language. Meta-
programming can be used for (see e.g. [21, 5]) extending the programming lan-
guage, modifying the control [9], debugging, program analysis, program trans-
formation and, as we will see, specialised integrity checking.
6.1 The power of the ground vs. the non-ground
representation
6.1.1 The ground vs. the non-ground representation [Rewrite]
In logic programming, there are basically two (fundamentally) dierent ap-
proaches to representing an object level expression, say the atom p(X, a), at
the meta-level. In the rst approach one uses the term p(X, a) as the meta-level
representation. This is called a non-ground representation, because it represents
an object level variable by a meta-level variable. In the second approach one
would use something like the term struct(p, [var(1), struct(a, [])]) to represent
the object level atom p(X, a). This is called a ground representation, as it repre-
sents an object level variable by a ground term. Figure 6.1 contains some further
examples of the particular ground representation which we will use throughout
this thesis. From now on, we use T to denote the ground representation of a
term T . Also, to simplify notations, we will sometimes use p(t
1
, . . . , t
n
) as a
shorthand for struct(p, [t
1
, . . . , t
n
]).
Object level Ground representation
X var(1)
c struct(c, [])
f(X, a) struct(f, [var(1), struct(a, [])])
p q struct(clause, [struct(p, []), struct(q, [])])
Figure 6.1: A ground representation
The ground representation has the advantage that it can be treated in a
purely declaratively manner, while for many applications the non-ground repre-
sentation requires the use of extra-logical built-ins (like var/1 or copy/2). The
non-ground representation also has semantical problems (although they were
solved to some extent in [10, 32, 33]). The main advantage of the non-ground
representation is that the meta-program can use the underlying
1
unication
mechanism, while for the ground representation an explicit unication algorithm
is required. This (currently) induces a dierence in speed reaching several or-
ders of magnitude. The current consensus in the logic programming community
is that both representations have their merits and the actual choice depends
1
The term underlying refers to the system in which the meta-interpreter itself is written.
34 CHAPTER 6. WRITING A PROLOG INTERPRETER IN PROLOG
on the particular application. In the following subsection we discuss the dif-
ferences between the ground and the non-ground representation in more detail.
For further discussion we refer the reader to [21], [22, 6], the conclusion of [32].
Unication and collecting behaviour
As already mentioned, meta-interpreters for the non-ground representation can
simply use the underlying unication. For instance, to unify the object level
atoms p(X, a) and p(Y, Y ) one simply calls p(X, a) = p(Y, Y ). This is very ef-
cient, but after the call both atoms will have become instantiated to p(a, a).
This means that the original atoms p(X, a) and p(Y, Y ) are no longer accessi-
ble (in Prolog for instance, the only way to undo these instantiations is via
failing and backtracking), i.e. we cannot test in the same derivation whether
the atom p(X, a) unies with another atom, say p(b, a). This in turn means
that it is impossible to write a breadth-rst like or a collecting (i.e. performing
something like findall/3
2
) meta-interpreter declaratively for the non-ground
representation (it is possible to do this non-declaratively by using for instance
Prologs extra-logical copy/2 built-in).
In the ground representation on the other hand, we cannot use the under-
lying unication (for instance p(var(1),a) =p(var(2), var(2)) will fail).
The only declarative solution is to use an explicit unication algorithm. Such
an algorithm, taken from [12], is included in Appendix ??. (For the non-
ground representation such an algorithm cannot be written declaratively; non-
declarative features, like var/1 and = ../2, have to be used.) For instance,
unify(p(var(1),a),p(var(2), var(2)), Sub) yields an explicit representation
of the unier in Sub, which can then be applied to other expressions. In con-
trast to the non-ground representation, the original atoms p(var(1),a) and
p(var(2), var(2)) remain accessible in their original form and can thus be used
again to unify with other atoms. Writing a declarative breadth-rst like or a
collecting meta-interpreter poses no problems.
Standardising apart and dynamic meta-programming
To standardise apart object program clauses in the non-ground representation,
we can again use the underlying mechanism. For this we simply have to store the
object program explicitly in meta-program clauses. For instance, if we represent
the object level clause
anc(X, Y ) parent(X, Y )
by the meta-level fact
clause(1, anc(X, Y ), [parent(X, Y )])
2
Note that the findall/3 built-in is non-declarative, in the sense that the meaning of
programs using it may depend on the selection rule. For example, given a program containing
just the fact p(a) , we have that ndall (X, p(X), [A]), X = b succeeds (with the answer
{A/p(a), X/b}) when executed left-to-right but fails when executed right-to-left.
6.1. THE POWER OF THE GROUNDVS. THE NON-GROUNDREPRESENTATION35
we can obtain a standardised apart version of the clause simply by calling
clause(1, Hd, Bdy). Similarly, we can resolve this clause with the atom
anc(a, B) by calling clause(C, anc(a, B), Bdy).
3
The disadvantage of this method, however, is that the object program is
xed, making it impossible to do dynamic meta-programming (i.e. dynami-
cally change the object program, see [21]); this can be remedied by using a mixed
meta-interpreter, as we will explain in Subsection ?? below). So, unless we re-
sort to such extra-logical built-ins as assert and retract, the object program
has to be represented by a term in order to do dynamic meta-programming.
This in turn means that non-logical built-ins like copy/2 have to be used to
perform the standardising apart. Figure 6.2 illustrates these two possibilities.
Note that without the copy in Figure 6.2, the second meta-interpreter would
incorrectly fail for the given query. For our application this means that, on the
one hand, using the non-logical copying approach unduly complicates the spe-
cialisation task while at the same time leading to a serious eciency bottleneck.
On the other hand, using the clause representation, implies that representing
updates to a database becomes much more cumbersome. Basically, we also
have to encode the updates explicitly as meta-program clauses, thereby making
dynamic meta-programming impossible.
1. Using a Clause Representation 2. Using a Term Representation
solve([]) solve(P, [])
solve([H|T]) solve(P, [H|T])
clause(H, B) member(Cl, P), copy(Cl, cl(H, B))
solve(B), solve(T) solve(P, B), solve(P, T)
clause(p(X), [])
solve([p(a), p(b)]) solve([cl(p(X), [])], [p(a), p(b)])
Figure 6.2: Two non-ground meta-interpreters with {p(X) } as object program
For the ground representation, it is again easy to write an explicit standar-
dising apart operator in a fully declarative manner. For instance, in the pro-
gramming language Godel [22] the predicate RenameFormulas/3 serves this
purpose.
Testing for variants or instances
In the non-ground representation we cannot test in a declarative manner whether
two atoms are variants or instances of each other, and non-declarative built-ins,
like var/1 and =../2, have to be used to that end. Indeed, suppose that we
have implemented a predicate variant/2 which succeeds if its two arguments
represent two atoms which are variants of each other and fails otherwise. Then
variant(p(X), p(a)) must fail and variant(p(a), p(a)) must succeed. This,
however, means that the query variant(p(X), p(a)), X = a fails when using
3
However, we cannot generate a renamed apart version of anc(a, B). The copy/2 built-in
has to be used for that purpose.
36 CHAPTER 6. WRITING A PROLOG INTERPRETER IN PROLOG
a left to right computation rule and succeeds when using a right to left compu-
tation rule. Hence variant/2 cannot be declarative (the exact same reasoning
holds for the predicate instance/2). Thus it is not possible to write declara-
tive meta-interpreters which perform e.g. tabling, loop checks or subsumption
checks.
Again, for the ground representation there is no problem whatsoever to write
declarative predicates which perform variant or instance checks.
Specifying partial knowledge
One additional disadvantage of the non-ground representation is that it is more
dicult to specify partial knowledge for partial evaluation. Suppose, for in-
stance, that we know that a given atom (for instance the head of a fact that
will be added to a deductive database) will be of the form man(T ), where T
is a constant, but we dont know yet at partial evaluation time which partic-
ular constant T stands for. In the ground representation this knowledge can
be expressed as struct(man, [struct(C, [])]). However, in the non-ground repre-
sentation we have to write this as man(X), which is unfortunately less precise,
as the variable X now no longer represents only constants but stands for any
term.
4
4
A possible solution is to use the = ../2 built-in to constrain X and represent the above
atom by the conjunction man(X), X = ..[C]. This requires that the partial evaluator provides
non-trivial support for the built-in = ../2 and is able to specialise conjunctions instead of
simply atoms, see Chapter ??.
Chapter 7
Verication Tools
7.1 Kripke Structures and Labeled Transition
Systems
A Kripke structure is a graph with labels on the nodes:
each node is a state of the system to be veried
the labels indicate which property holds in that state.
A labeled transition system is typically a graph with labels on the edges:
each node is again a state of the system to be veried
the labels indicate which particular action/operation of the system can
trigger a state change.
In this chapter we combine these two formalisms as follows:
Denition 7.1.1 A Labeled Transition System (LTS) is a tuple M = S, T, L, I, s
0
)
where
S is a nite set of states,
T S L S is a transition relation (s, a, t) is also written as s
a
t),
L is a nite set of actions,
P is a set of basic properties,
I : S P is the node labeling function, indicating which properties hold
in a particular state,
s
0
is the initial state.
Encoding this as a logic program:
Program 7.1.2
37
38 CHAPTER 7. VERIFICATION TOOLS
open
closed
closed
heat
open_door close_door
start
open_door
stop
Figure 7.1: A simple LTS for a microwave oven
start(s0).
prop(s0,open).
prop(s1,closed).
prop(s2,closed).
prop(s2,heat).
trans(s0,close_door,s1).
trans(s1,open_door,s0).
trans(s1,start,s2).
trans(s2,open_door,s0).
trans(s2,stop,s1).
Note that trans does not have to be dened by facts alone: we can plug in
an interpeter !
We now try to compute reach(M) the set of reachable states of M, which is
important for checking so-called safety properties of a system:
Program 7.1.3
reach(X) :- start(X).
reach(X) :- reach(Y), trans(Y,_,X).
This is elegant and logically correct, but does not really work with classical
Prolog:
| ?- reach(X).
X = s0 ? ;
X = s1 ? ;
X = s0 ? ;
X = s2 ? ;
7.2. BOTTOM-UP INTERPRETER 39
X = s1 ? ;
X = s0 ? ;
X = s1 ? ;
X = s0 ?
...
If we try to check a simple safety property of our system, namely that the
door cannot be open while the heat is on, then we get stuck into an innite
loop:
| ?- reach(X), prop(X,open), prop(X,heat).
..... infinite loop ....
The same problem persists if we change the order of the literals in the second
clause:
reach(X) :- trans(Y,_,X), reach(Y).
7.2 Bottom-Up Interpreter
Top-down interpretation: start from goal try to nd refutation. Used by classical
Prolog, SLD-resolution.
Bottom-up: start from facts and try to reach goal. Used by relational and
deductive databases. In logic programming this corresponds to the T
P
Operator
von Denition 3.2.7.
Below we adapt our vanilla interpreter from [REF] to work backwards (i.e.,
bottom-up) and construct a table of solutions:
Program 7.2.1
:- dynamic start/1, prop/2, trans/3, reach/1.
pred(start(_)). pred(prop(_,_)). pred(trans(_,_,_)). pred(reach(_)).
:- dynamic table/1.
:- dynamic change/0.
bup(Call) :- pred(Call), clause(Call,Body), solve(Body).
solve(true).
solve(,(A,B)) :- solve(A),solve(B).
solve(Goal) :- Goal \= ,(_,_), Goal \= true,
table(Goal).
run :- retractall(table(_)), bup, print_table.
print_table :- table(X), print(X),nl,fail.
40 CHAPTER 7. VERIFICATION TOOLS
print_table :- nl.
bup :- retractall(change),
bup(Sol),
\+ table(Sol),
assert(table(Sol)),
assert(change),
fail.
bup :- print(.), flush_output, (change -> bup ; nl).
7.3 Tabling and XSB Prolog
Tabling combines bottom-up with top-down evaluation.
Whenever a predicate call is treated:
check if a variant of the call has already an entry in the table
if there is no entry, then add a new entry and proceed as usual; whenever
a solution is found for this call it is entered into the table (unless the
solution is already in the table)
if there is an entry, then lookup the solutions in the table; do not evaluate
the goal; if the table is currently empty then delay and do another branch
rst...
A system which implements tabling is XSB Prolog, available freely at:
http://xsb.sourceforge.net/.
Declaring a predicate reach with 1 argument as tabled:
:- table reach/1.
(There also exist mechanisms for XSB to decide automatically what to table.)
One can switch between variant tabling (:- use variant tabling p/n, the
default) and subsumptive tabling (:- use subsumptive tabling p/n ).
Other useful predicates: abolish all tables, get calls for table(+PredSpec,?Call)
This system has a very ecient tabling mechanism: ecient way to check
if a call has already been encountered; ecient way to propagate answers and
check whether a table is complete.
Program 7.3.1
:- table reach/1.
reach(X) :- start(X).
reach(X) :- reach(Y), trans(Y,_,X).
7.4. TEMPORAL MODEL CHECKING 41
reach(X)
start(X)
X=s0
reach(Y),
reach(X) X=s0
X=s1
X=s2
trans(Y,_,X)
trans(s0,_,X)
X=s1
trans(s1,_,X) trans(s2,_,X)
X=s0
X=s2 X=s0
X=s1
Table for reach/1:
Figure 7.2: Tabled execution of reach(X)
| ?- reach(X).
X = s2;
X = s1;
X = s0;
no
| ?- reach(X), prop(X,open), prop(X,heat).
no
7.4 Temporal Model Checking
[Copied from Lopstr paper; needs to be rewritten]
7.4.1 CTL syntax and semantics
The temporal logic CTL (Computation Tree Logic) introduced by Clarke and
Emerson in [18], allows to specify properties of specications generally described
as Kripke structures. The syntax and semantics for CTL are given below.
Given Prop, the set of propositions, the set of CTL formulae is inductively
dened by the following grammar (where p Prop):
:= true [ p [ [ [ _ [ _ [ | [ |
A CTL formula can be either true or false in a given state. For example,
true is true in all states, true is false in all states, and p is true in all states
which contain the elementary proposition p. The symbol _ is the nexttime
operator and | stands for until. _ (resp. _) intuitively means that
holds in every (resp. some) immediate successor of the current program state.
42 CHAPTER 7. VERIFICATION TOOLS
s p
q
p,r
0
s
1
s
2
s p
0
s
p,r 2
q
s q
1
s q
1
s
1
s p,r
2
s q
1
s p,r
2
s p
1
s p
0
q
.
.
.
.
.
.
.
.
.
.
b a
Figure 7.3: Example of a Kripke structure.
The formula
1
|
2
(resp.
1
|
2
) intuitively means that for every (resp.
some) computation path, there exists an initial prex of the path such that
2
holds at the last state of the prex and
1
holds at all other states along the
prex.
The semantics of CTL formulae is dened with respect to a Kripke structure
(S, R, , s
0
) with S being the set of states, R( S S) the transition relation,
(S 2
Prop
) giving the propositions which are true in each state, and s
0
being
the initial state.
The semantics of CTL formulae is dened with respect to a Kripke structure
(S, R, , s
0
) with S being the set of states, R( S S) the transition relation,
(S 2
Prop
) giving the propositions which are true in each state, and s
0
being the initial state. Figure 7.3a gives a graphical representation of a Kripke
structure with 3 states s
0
, s
1
, s
2
, where s
0
is the initial state. The propositions
p, q, r label the states.
Generally it is required that any state has at least one outgoing vertex. From
a Kripke structure, we can dene an innite transition tree as follows: the root
of the tree is labelled by s
0
. Any vertex labelled by s has one son labelled by s

for each vertex s

with a transition s s

in the Kripke structure. For instance,


the Kripke structure of Fig. 7.3a gives the prex for the transition tree starting
from s
0
given in Fig. 7.3b.
If the system is not directly specied by a Kripke structure but e.g. by a
Petri net, the markings and transitions between them give resp. the states and
transition relation of the Kripke structure.
The CTL semantics is dened on states s by:
s [= true
s [= p i p P(s)
s [= i not(s [= )
7.4. TEMPORAL MODEL CHECKING 43
s [=
1

2
i s [=
1
and s [=
2
s [= _ i for all state t such that (s, t) R, t [=
s [= _ i for some state t such that (s, t) R, t [=
s [=
1
|
2
i for all path (s
0
, s
1
, . . . ) of the system with s = s
0
, i(i
0 s
i
[=
2
j(0 j < i s
j
[=
1
))
s [=
1
|
2
i it exists a path (s
0
, s
1
, . . . ) with s = s
0
, such that i(i
0 s
i
[=
2
j(0 j < i s
j
[=
1
))
If we look at the unfolding of the Kripke structure, starting froms
0
(Fig. 7.3b),
we can see, for instance, that
s
0
[= _p since there exists a successor of s
0
(i.e. s
2
) where p is true
(p (s
2
)),
s
0
,[= _p since in some successor of s
0
, p does not hold.
s
0
[= p|q since the paths from s
0
either go to s
1
where q is true or to s
2
and then directly to s
1
. In both cases, the paths go through states where
p is true before reaching one state where q holds.
Since they are often used, the following abbreviations are dened
3 true | i.e. for all paths, eventually holds,
3 true | i.e. there exists a path where eventually
holds,
2 3() i.e. there exists a path where always holds,
2 3() i.e. for all paths always holds.
E.g. 2 states that is an invariant of the system, 3 states that is
unavoidable and 3 states that may occur.
7.4.2 CTL as xed point and its translation to logic pro-
gramming
One starting point of this paper is that both the Kripke structure dening the
system under consideration and the CTL specication can be easily dened
using logic programs. This is obvious for the standard logic operators as well
as for the CTL operators _. The following equality can be easily proved
_ _ . Moreover, the operators | and | can be dened as
xpoint solutions [34]. Indeed, if you take the view that represents the set of
states S where is valid, it can be proved that p|q Y = (q (p _Y ))
where stands for the least xpoint of the equation. Intuitively this equation
tells that the set of states satisfying p|q is the least one satisfying q or having
a successor which satises this property.
3 and 2 can be derived from what precedes. _ can be derived using
the equivalence _ _. Slightly more involved is the denition of the
set of states which satisfy 2. These states can be expressed as the solution
44 CHAPTER 7. VERIFICATION TOOLS
of X = _X where stands for the greatest xpoint of the equation.
Greatest xpoint cannot be directly computed by logic programming systems,
but the equation can be translated into 2 where the set Y of states
which satisfy is dened by Y = _Y . Finally, 3 2
and
1
|
2
(
2
|(
1

2
)) 2
2
. This last equality says that
a state satises
1
|
2
if it has no path where
2
is continuously false until a
state where both
1
and
2
are false nor a path where
2
is continuously false.
We can see that we only use least xpoint of monotonic equations. Moreover,
the use of table-based Prolog (XSB Prolog) ensures proper handling of cycles.
For innite state systems, the derivation of the xpoints cannot generally be
completed. However, by the monotonicity of all the used xpoints, all derived
implications belong indeed to the solution (no overapproximation).
Our CTL specication is independant of any model. It only supposes that
the successors of a state s can be computed (through the predicate trans)
and that the elementary proposition of any state s can be determined (through
prop). In Fig. 7.4 we present a particular implementation of CTL as a (tabled)
logic program. For example, it can be used to verify the mutual exclusion
example from [7], simply by running it in XSB-Prolog. This follows similar
lines as [39, 29] where tabled logic programming is used as an (ecient) means
of nite model checking. Nonetheless, our translation of CTL in this paper is
expressed (more clearly) as a meta-interpreter and will be the starting point for
the model checking of innite state systems using program specialisation and
analysis techniques.
First version:
sat(Formula) :- start(S), sat(S,Formula).
sat(_E,true).
sat(E,p(P)) :- prop(E,P). /* elementary proposition */
sat(E,and(F,G)) :- sat(E,F), sat(E,G).
sat(E,or(F,_G)) :- sat(E,F).
sat(E,or(_F,G)) :- sat(E,G).
sat(E,en(F)) :- trans(E,_Act,E2),sat(E2,F). /* exists next */
sat(E,eu(F,G)) :- sat_eu(E,F,G). /* exists until */
sat(E,ef(F)) :- sat(E,eu(true,F)). /* exists future */
sat(E,not(F)) :- not(sat(E,F)).
sat(E,ag(F)) :- sat(E,not(ef(not(F)))). /* always global */
:- table sat_eu/3. /* table to compute least-fixed point using XSB */
sat_eu(E,_F,G) :- sat(E,G). /* exists until */
sat_eu(E,F,G) :- sat(E,F), trans(E,_Act,E2), sat_eu(E2,F,G).
| ?- [ctl].
[Compiling ./ctl]
7.5. REFINEMENT CHECKING 45
% Specialising partially instantiated calls to sat/2
[ctl compiled, cpu time used: 0.0200 seconds]
[ctl loaded]
yes
| ?- sat(ef(p(closed))).
yes
| ?- sat(ef(and(p(open),p(heat)))).
no
| ?- sat(ef(and(p(closed),p(heat)))).
/* A Model Checker for CTL fomulas written for XSB-Prolog */
sat(_E,true).
sat(_E,false) :- fail.
sat(E,p(P)) :- prop(E,P). /* elementary proposition */
sat(E,and(F,G)) :- sat(E,F), sat(E,G).
sat(E,or(F,_G)) :- sat(E,F).
sat(E,or(_F,G)) :- sat(E,G).
sat(E,not(F)) :- not(sat(E,F)).
sat(E,en(F)) :- trans(E,_Act,E2),sat(E2,F). /* exists next */
sat(E,an(F)) :- not(sat(E,en(not(F)))). /* always next */
sat(E,eu(F,G)) :- sat_eu(E,F,G). /* exists until */
sat(E,au(F,G)) :- sat(E,not(eu(not(G),and(not(F),not(G))))),
sat_noteg(E,not(G)). /* always until */
sat(E,ef(F)) :- sat(E,eu(true,F)). /* exists future */
sat(E,af(F)) :- sat_noteg(E,not(F)). /* always future */
sat(E,eg(F)) :- not(sat_noteg(E,F)). /* exists global */
/* we want gfp -> negate lfp of negation */
sat(E,ag(F)) :- sat(E,not(ef(not(F)))). /* always global */
:- table sat_eu/3. /* table to compute least-fixed point using XSB */
sat_eu(E,_F,G) :- sat(E,G). /* exists until */
sat_eu(E,F,G) :- sat(E,F), trans(E,_Act,E2), sat_eu(E2,F,G).
:- table sat_noteg/2. /* table to compute least-fixed point using XSB */
sat_noteg(E,F) :- sat(E,not(F)).
sat_noteg(E,F) :- not((trans(E,_Act,E2),not(sat_noteg(E2,F)))).
Figure 7.4: CTL interpreter
7.5 Renement Checking
Below requires XSB to be congured ./configure --enable-batched-scheduling
and ./makexsb --config-tag=btc for batched scheduling (to provide answers
before tables are complete; by default XSB will rst complete table and the
print answer).
From XSB Manual:
Local scheduling then fully evaluates each maximal SCC (a SCC that
does not depend on another SCC) before returning answers to any
46 CHAPTER 7. VERIFICATION TOOLS
subgoal outside of the SCC 5 . ... Unlike Local Scheduling, Batched
Scheduling allows answers to be re- turned outside of a maximal
SCC as they are derived, and thus resembles Prologs tuple at a time
scheduling.
:- table enabled/2.
enabled(A,X) :- trans(X,A,_).
:- table not_refines/3.
not_refines(X,Y,[A|T]) :-
trans(X,A,X2),
findall(Y2,trans(Y,A,Y2),YS),
not_ref2(X2,YS,T).
:- table not_ref2/3.
not_ref2(_,[],_).
not_ref2(X,[Y|T],Trace) :-
not_refines(X,Y,Trace),
not_ref2(X,T,Trace).
Appendix A
Exercises
A.0.1 DCGs - Denite Clause Grammars
The given grammar describes valid inputs for a simple calculator for integer
numbers. To keep the grammar simple, all subformulas have to be paranthe-
sised.
53+4 wouldnt be a correct formula, 5(3+4) is its correct representation.
Formula Basic
Formula Basic +Basic
Formula Basic Basic
Formula Basic Basic
Formula Basic/Basic
Basic Number
Basic (Formula)
Number PosNumber [ PosNumber
PosNumber Digit [ Digit PosNumber
Digit 0[1[2[3[4[5[6[7[8[9
1. Write a Prolog program that checks if an input string is in the language
specied by the grammar above. The program should handles queries like:
?- check("5*(-8+(73-25))").
yes
?- check("5*(73-25)","").
yes
?- check("5*73-25","").
no
Hinweis: In SICStus und SWI-Prolog werden Strings als Listen von Ze-
ichen behandelt. Sie knnen also leicht die Grammatik
Beispiel
Beispiel a Beispiel
47
48 APPENDIX A. EXERCISES
mittels
beispiel --> [].
beispiel --> "a", beispiel.
implementieren, so dass die Abfrage beispiel("aaaaa","") akzeptiert
wird. Fr den Leerstring sind die Darstellungen [] und "" quivalent:
?- "" = [].
yes
2. Change your program in a way that it uses member(C,"0123456789") for
checking if the given character C is a digit.
If you use SICStus Prolog, you have to import the list library before using
member/2:
:- use_module(library(lists)).
3. Extend your program that it returns the syntax tree of the input. Convert-
ing the strings representing numbers directly into Prolog numbers would
be optimal.
E.g. the following queries should be possible:
?- parse("5*(-8+(73-25))",S).
S = mult(num(5),plus(neg(8),minus(num(73),num(25))))
?- parse("5*(73-25)",S).
S = mult(num(5),minus(num(73),num(25)))
Hint: If you have a string which represents a number, you can convert it
into a number with name/2:
?- name(X,"12").
X = 12
A.0.2 Interpreter fr eine simple imperative Sprache
In der Vorlesung wurde ein Interpreter fr eine simple Sprache vorgestellt, die
aus folgenden Konstrukten besteht:
Einfhrung einer Variable x: def x
Wertzuweisung einer Variable x: x := Expression
Aneinanderkettung zweier Anweisungen: A;B
If-Then-Else: if(Test,Then,Else)
49
Gleichheit in Tests: =
Addition in Ausdrcken: +
1. In der Vorlesung wurden Operatoren mittels op/3 deniert, damit die
Sprache direkt vom Prolog-Parser untersttzt wird. Dies ist eigentlich nur
fr kleine Programme und Testzwecke sinnvoll. Normalerweise wird ein
Programm von einem Parser gelesen und dann der Syntaxbaum in Form
von Termen als Eingabe fr den Interpreter verwendet.
Schreiben Sie einen Interpreter (oder modizieren Sie den Interpreter aus
der Vorlesung), der folgende Darstellung der oben beschriebenen Program-
miersprache untersttzt:
Zahlen: num(123)
Identier fr eine Variable x: id(x)
Einfhrung einer Variablen x (def x): def(id(x))
Zuweisung x := 6: assign(id(x),num(6))
Sequenz zweier Anweisungen A;B: seq(A,B)
If-Then-Else unverndert: if(Test,Then,Else)
Test auf Gleichheit: equal(A,B)
Addition: plus(A,B)
Statt eines Aufrufs
int( def x; x:=1; def z; if(x=1, z:=1, z:=2); z:=z+x, [], X).
X = [z/2,x/1]
sollte dann folgender Aufruf funktionieren:
?- int(seq(def(id(x)),
seq(assign(id(x),num(1)),
seq(def(id(z)),
seq(if(equal(id(x),num(1)),
assign(id(z),num(1)),
assign(id(z),num(2))),
assign(id(z),plus(id(z),id(x))))))),
[],X).
X = [z/2,x/1]
2. Add substraction, multiplication and integer division to the interpreter.
Erweitern Sie den Interpreter so, dass auch Subtraktionen, Multiplikatio-
nen und Ganzzahldivisionen durchgefhrt werden knnen. Auch sollte ein
unres Minus mglich sein.
3. Add lesser than and greater than for comparing numbers.
4. Add the logical connectives and and or.
5. Add a while loop.
50 APPENDIX A. EXERCISES
A.0.3 Prolog interpreter
The program that should be used by the interpreter should be given as clauses
program/3. The rst argument of program is a unique id chosen by the program-
mer. The second and third argument is the predicate head resp. the predicate
body.
E.g. the following clauses
program(fact1, parent(marge,bart), true).
program(fact2, parent(marge,lisa), true).
program(rule1, sibling(A,B), (parent(P,A),parent(P,B)) ).
represent the following prolog program. The ids fact1, fact2 and rule1 are
arbitrarily chosen.
parent(marge,bart).
parent(marge,lisa).
sibling(A,B) :- parent(P,A),parent(P,B).
1. Write a prolog interpreter that handles query to a program given in the
form above. For example:
?- solve(sibling(lisa,X)).
X = bart
yes
2. Add debugging messages to the interpreter. At the end of each successfull
applied rule of the program, a message with the id of the rule should be
given.
?- solve(sibling(lisa,X)).
fact2: parent(marge,lisa)
fact1: parent(marge,bart)
rule1: sibling(lisa,bart)
X = bart
yes
3. Extend the interpreter in a way that enables the use of the built-ins arg/3,
functor/3 and is/2. You can use the built-in call/1.
Before calling the built-in, check if the call is safe. An unsafe call to a
built in has not enough arguments instantiated. If the arguments are not
safe, display a message that says where (give at least the id of the clause
of the program) the unsafe call occured.
E.g. with the following program
program(example, add4(In,Out), Out is In + 4).
the interpretere should handle the following calls:
51
?- solve(add4(6,X)).
example: add4(6,10)
X = 10
yes
?- solve(add4(A,X)).
example: unsafe call to builtin: _475 is _455+4
no
You can use the following clauses for checking if a call is safe:
is_save_builtin(functor(Term,_,N)) :- nonvar(Term),var(N).
is_save_builtin(functor(Term,_,N)) :- nonvar(Term),nonvar(N),number(N).
is_save_builtin(functor(Term,F,N)) :- var(Term),atom(F),ground(N),number(N).
is_save_builtin(arg(N,Term,_Arg)) :- ground(N),number(N),nonvar(Term).
is_save_builtin(is(_,B)) :- ground(B).
A.0.4 Interpreter denition of functions
Diese Aufgabe baut auf dem Interpreter aus der Vorlesung fr eine simple im-
perative Sprache auf. Sie knnen auch Ihre eigene Version des Interpreters oder
die Musterlsung des letzten bungsblattes verwenden.
a) Erweitern Sie den Interpreter um eine Anweisung zum Denieren von
Funktionen. Funktionen sollen dann in Ausdrcken verwendet werden kn-
nen und einen Wert zurckliefern.
Eine Funktion sollte folgende Eigenschaften erfllen:
Sie hat einen Namen, unter dem Sie im aktuellen Environment gespe-
ichert wird.
Sie hat eine Anweisung, die beim Aufruf ausgefhrt wird.
Sie gibt einen Wert zurck. Dazu wird angegeben, in welcher Variable
nach Ausfhrung der Anweisung der Funktionswert steht.
Sie hat eine beliebige Anzahl von Parametern. Die Parameter sind
Variablennamen, denen beim Aufruf der Funktion Werte zugeordnet
werden. Dazu wird bei einem Funktionsaufruf eine gegebene Liste
von Ausdrcken (gleiche Anzahl wie Parameter) ausgewertet, deren
Werte dann den entsprechenden Parametern/Variablen zugewiesen
werden.
Es reicht aus, wenn die Anweisung der Funktion nur auf die bergebe-
nen Parameter zugreifen kann.
Sie knnen den Anweisung zum Denieren von Funktionen als Term be-
trachten, wir gehen davon aus, dass in einem richtigen Interpreter ein
Parser vorgeschaltet ist, der entsprechenden Programmcode in die Term-
Darstellung bringt.
52 APPENDIX A. EXERCISES
Ein solcher Term knnte z.B. so aussehen (nur ein Vorschlag):
function(add4, [x], seq(def(x),assign(result,add(id(x),num(4)))), result)
fr eine Funktion add4, die ein Argument x bekommt und nach Durchfhrung
der Berechnung den Wert der Funktion aus der Variable result nimmt.
Entsprechend kann der Aufruf der Funktion als Term angesehen werden.
Er msste als Argumente den Funktionsnamen und eine Liste von Ausdr-
cken als Funktionsparameter bekommen.
b) berprfen Sie, ob Ihre Implementation auch mit rekursiven Funktionen
klarkommt. Falls nicht, passen Sie den Interpreter so an, dass Funktionen
auch rekursiv aufgerufen werden knnen.
A.1 DCGs grammar with operator precedence
associativity
Analog zum ersten bungsblatt soll ein Prolog-Programm entwickelt werden, das
Eingaben fr einen Taschenrechner parsed und einen Syntaxbaum zurckliefert.
Dabei sollen statt zwingend vorgeschriebener Klammern allerdings Punkt- vor
Strichrechnung und Operatorassoziativitt bercksichtigt werden.
Der Taschenrechner soll Addition (+), Subtraktion (-), Multiplikation (*)
und Exponentation (^) untersttzen. Klammern sollten auch verwendet werden
knnen.
a) Schreiben Sie einen Parser (in Prolog mit Hilfe von DCGs Denite Clause
Grammars), der die Operatorprioritten bercksichtigt und den entsprechen-
den Syntaxbaum liefert. Dabei bindet ^ strker als * und * strker als +
und -. Es gilt also Punkt- vor Strichrechnung.
Zum Beispiel:
?- parse("5*8^3-4",S).
S = sub(mult(5,exp(8,3)),4)
b) Bei einigen Rechenarten kommt es darauf an, ob ein Operator links-
oder rechtsassoziativ ist. Addition, Subtraktion und Multiplikation sind
linksassoziativ, es gilt also a+b+c = (a+b)+c, a-b-c = (a-b)-c und a*b*c
= (a*b)*c. Im Falle der Subtraktion ist dies sehr wichtig, weil das As-
soziativgesetz (a-b)-c = a-(b-c) nicht gilt.
Die Exponentation ist rechtsassoziativ. Es gilt also a^b^c = a^(b^c).
berprfen Sie, ob Ihr Parser die Assoziativitt richtig beachtet. Wenn nicht,
schreiben Sie den Parser so um, dass die Assoziativitt beachtet wird. Zum
Beispiel sollten folgende Abfragen funktionieren mglich sein:
A.1. DCGS GRAMMAR WITHOPERATOR PRECEDENCE ASSOCIATIVITY53
?- parse("8-2-5",S).
S = sub(sub(8,2),5)
?- parse("8^2^5",S).
S = exp(8,exp(2,5))
Hinweise:
Sie brauchen keine Leerzeichen o.. in der Eingabe zuzulassen.
Auf den Webseiten zur Vorlesung gibt es Musterlsungen zu den alten
bungsblttern, die sie nutzen knnen, insbesondere zum Parsen von Num-
mern.
Zur Lsung des ersten Teils mssen Sie die Grammatik stufenweise gestal-
ten, d.h. erst die Operatoren niedriger Prioritt abarbeiten (z.B. Addition
und Subtraktion), dann die Operatoren hherer Prioritt, am Ende werden
Nummern und Klammern abgearbeitet.
Bei den Musterlsungen bendet sich auch ein Beispielinterpreter, dessen
Parser die Anforderungen aus beiden Aufgabenteilen gengt, auer dass er
keine Exponentation kennt. Er arbeitet allerdings auf Tokens statt auf
Zeichen. Die Cuts (!) knnen ignoriert werden.
54 APPENDIX A. EXERCISES
Bibliography
[1] K. R. Apt. Introduction to logic programming. In J. van Leeuwen, edi-
tor, Handbook of Theoretical Computer Science, chapter 10, pages 495574.
North-Holland Amsterdam, 1990.
[2] K. R. Apt. From Logic Programming to Prolog. Prentice Hall, 1997.
[3] K. R. Apt and F. Turini. Meta-logics and Logic Programming. MIT Press,
1995.
[4] K. R. Apt and M. H. van Emden. Contributions to the theory of logic
programming. Journal of the ACM, 29(3):841862, 1982.
[5] J. Barklund. Metaprogramming in logic. In A. Kent and J. Williams,
editors, Encyclopedia of Computer Science and Technology. Marcell Dekker,
Inc., New York. To Appear.
[6] A. F. Bowers. Representing Godel object programs in Godel. Technical
Report CSTR-92-31, University of Bristol, November 1992.
[7] E. M. Clarke, E. A. Emerson, and A. P. Sistla. Automatic verication of
nite-state concurrent systems using temporal logic specications. ACM
Transactions on Programming Languages and Systems, 8(2):244263, 1986.
[8] W. Clocksin and C. Mellish. Programming in Prolog (Third Edition).
Springer-Verlag, 1987.
[9] D. De Schreye and M. Bruynooghe. The compilation of forward checking
regimes through meta-interpretation and transformation. In H. Abramson
and M. Rogers, editors, Meta-Programming in Logic Programming, Pro-
ceedings of the Meta88 Workshop, June 1988, pages 217232. MIT Press,
1989.
[10] D. De Schreye and B. Martens. A sensible least Herbrand semantics for
untyped vanilla meta-programming. In A. Pettorossi, editor, Proceedings
Meta92, LNCS 649, pages 192204. Springer-Verlag, 1992.
[11] D. De Schreye, B. Martens, G. Sablon, and M. Bruynooghe. Compiling
bottom-up and mixed derivations into top-down executable logic programs.
Journal of Automated Reasoning, 7(3):337358, 1991.
55
56 BIBLIOGRAPHY
[12] D. A. de Waal and J. Gallagher. Specialisation of a unication algorithm. In
T. Clement and K.-K. Lau, editors, Logic Program Synthesis and Trans-
formation. Proceedings of LOPSTR91, pages 205220, Manchester, UK,
1991.
[13] M. Denecker. Knowledge Representation and Reasoning in Incomplete Logic
Programming. PhD thesis, Department of Computer Science, K.U.Leuven,
1993.
[14] P. Derensart, A. Ed-Dbali, and L. Cervoni. Prolog: The Standard, Refer-
ence Manual. Springer-Verlag, 1996.
[15] K. Doets. Levationis laus. Journal of Logic and Computation, 3(5):487516,
1993.
[16] K. Doets. From Logic to Logic Programming. MIT Press, 1994.
[17] E. Eder. Properties of substitutions and unications. Journal of Symbolic
Computation, 1:3146, 1985.
[18] E.M. Clarke and E.A. Emerson. Design and Synthesis of Synchronization
Skeletons using Branching Time Temporal Logic. In D. Kozen, editor,
Proceedings of the Workshop on Logics of Programs, volume 131 of Lecture
Notes in Computer Science, pages 5271, Yorktown Heights, New York,
May 1981. Springer-Verlag.
[19] M. Fitting. First-Order Logic and Automated Theorem Proving. Springer-
Verlag, 1990.
[20] J. Herbrand. Investigations in proof theory. In J. van Heijenoort, editor,
From Frege to Godel: A Source Book in Mathematical Logic, 1879-1931,
pages 525581. Harvard University Press, 1967.
[21] P. Hill and J. Gallagher. Meta-programming in logic programming. In
D. M. Gabbay, C. J. Hogger, and J. A. Robinson, editors, Handbook of
Logic in Articial Intelligence and Logic Programming, volume 5, pages
421497. Oxford Science Publications, Oxford University Press, 1998.
[22] P. Hill and J. W. Lloyd. The Godel Programming Language. MIT Press,
1994.
[23] K. Knight. Unication: A multidisciplinary survey. ACM Computing Sur-
veys, 21(1):93124, March 1989.
[24] H.-P. Ko and M. E. Nadel. Substitution and refutation revisited. In K. Fu-
rukawa, editor, Logic Programming: Proceedings of the Eighth International
Conference, pages 679692. MIT Press, 1991.
[25] R. Kowalski. Predicate logic as a programming language. In Proceedings
IFIP Congress, pages 569574. IEEE, 1974.
BIBLIOGRAPHY 57
[26] R. Kowalski and D. K uhner. Linear resolution with selection function.
Articial Intelligence, 2:227260, 1971.
[27] J.-L. Lassez, M. Maher, and K. Marriott. Unication revisited. In
J. Minker, editor, Foundations of Deductive Databases and Logic Program-
ming, pages 587625. Morgan-Kaufmann, 1988.
[28] H. Levesque, R. Reiter, Y. Lesperance, F. Lin, and R. Scherl. Golog: a
logic programming language for dynamic domains. The Journal of Logic
Programming, 1997. To appear.
[29] X. Liu, C. R. Ramakrishnan, and S. A. Smolka. Fully local and ecient
evaluation of alternating xed points. In B. Steen, editor, Tools and Al-
gorithms for the Construction and Analysis of Systems, LNCS 1384, pages
519. Springer-Verlag, March 1998.
[30] J. W. Lloyd. Foundations of Logic Programming. Springer-Verlag, 1987.
[31] A. Martelli and U. Montanari. An ecient unication algorithm. ACM
Transactions on Programming Languages and Systems, 4(2):258282, April
1982.
[32] B. Martens and D. De Schreye. Two semantics for denite meta-programs,
using the non-ground representation. In K. R. Apt and F. Turini, editors,
Meta-logics and Logic Programming, pages 5782. MIT Press, 1995.
[33] B. Martens and D. De Schreye. Why untyped non-ground meta-
programming is not (much of) a problem. The Journal of Logic Program-
ming, 22(1):4799, 1995.
[34] K. L. McMillan. Symbolic Model Checking. PhD thesis, Boston, 1993.
[35] G. Metakides and A. Nerode. Principles of Logic and Logic Programming.
North-Holland, 1996.
[36] L. Naish. Higher-order logic programming in Prolog. Technical Report
96/2, Department of Computer Science, University of Melbourne, 1995.
[37] U. Nilsson and J. Maluszy nski. Logic, Programming and Prolog. Wiley,
Chichester, 1990.
[38] M. Paterson and M. Wegman. Linear unication. Journal of Computer
and System Sciences, 16(2):158167, 1978.
[39] Y. S. Ramakrishna, C. R. Ramakrishnan, I. V. Ramakrishnan, S. A.
Smolka, T. Swift, and D. S. Warren. Ecient model checking using tabled
resolution. In O. Grumberg, editor, Proceedings of the International Con-
ference on Computer-Aided Verication (CAV97), LNCS 1254, pages 143
154. Springer-Verlag, 1997.
58 BIBLIOGRAPHY
[40] R. Reiter. On closed world data bases. In H. Gallaire and J. Minker,
editors, Logic and Data Bases, pages 5576. Plenum Press, 1978.
[41] A. Robinson. A machine-oriented logic based on the resolution principle.
Journal of the ACM, 12(1):2341, 1965.
[42] L. Sterling and E. Shapiro. The Art of Prolog. MIT Press, 1986.
[43] M. van Emden and R. Kowalski. The semantics of predicate logic as a
programming language. Journal of the ACM, 23(4):733742, 1976.
[44] D. H. D. Warren. Higher-order extensions to Prolog: Are they needed? In
J. E. Hayes, D. Michie, and Y.-H. Pao, editors, Machine Intelligence 10,
pages 441454. Ellis Horwood Ltd., Chicester, England, 1982.

Potrebbero piacerti anche