Document not found! Please try again

Compositional operational semantics for Prolog programs - CiteSeerX

0 downloads 0 Views 175KB Size Report
In this way, SLD-boxes connect the semantics of Prolog programs to the Box. Model, the .... first described 8 what is now called SLD-resolution. Colmerauer ..... In the nonpropositional case we have to take into account unification between the ...
Compositional operational semantics for Prolog programs M.H.M. Cheng, R.N. Horspool, M.R. Levy, M.H. van Emden Abstract

To relate operational semantics of logic programs to its declarative semantics, we have to rely on SLD-trees. But this form of operational semantics does not make it easy to relate execution behaviour to program structure. The reason is that the traversal of SLD-trees decomposes in a way that is di erent from the decomposition (which is by disjunction and conjunction) of programs. We propose SLD-contours, a variant of SLD-trees, that are, like programs, built up out of simpler components by means of disjunction and conjunction. The traversal of SLD-trees carries over to the traversal of SLD-contours in such a way that the trace events of the Box Model (Try, Succeed, Retry, Fail) are reproduced in the same way as during the execution of the program. Thus SLD-contours relate the trace more closely to the program than SLD-trees. SLD-contours specify the trace more completely than the Box Model does. However, SLD-contours have the disadvantage that similar constructs are repeated. We introduce a way of collapsing repeated occurrences into a single one. The result is an elaboration of the existing Box Model. Hence we call them SLDboxes. In this way, SLD-boxes connect the semantics of Prolog programs to the Box Model, the most widely used aid in tracing Prolog programs.

1 Introduction

Why trace logic programs? According to a certain purist view there is no need to

trace logic programs: if you make sure that your program contains only true assertions about the relations that you need, then you cannot get a wrong answer. According to this view, any urge to trace is only a symptom of a lack of understanding of the proper method. This view is wrong because we not only want to avoid wrong answers, but we also want to make sure we do get an answer, or all answers, as the case may be. On this latter aspect, only recently is some theory starting to emerge, and it is not yet clear what, if any, is ready for practical application by the Prolog programmer. In the meantime, tracing remains important. Department of Computer Science, University of Victoria, P.O.Box 3055, Victoria, BC, Canada V8W 3P6. 

1

2 Another reason why logic programs need tracing is the same reason why speci cations need debugging: people are sometimes mistaken when they think they have written a complete set of true assertions. Of course they shouldn't make mistakes, but they do. Then what? In casting about for something reasonable to do in such a situation, tracing the execution is often the only alternative to just fretting. And tracing is often extremely e ective.

How are traces justi ed by semantics? It is, of course, crucially important that the

trace produced by a Prolog implementation be correct. That is, the trace should be based on the semantics of Prolog programs. In practice, the trace mechanism is built into the Prolog engine and its correctness depends on that of the Prolog engine. So the question of the correctness of traces can be reduced to that of the correctness of a Prolog interpreting or compiling algorithm. The latter is covered in [13].

Compositional semantics is the most useful variety for tracing. In [13] the cor-

rectness of the Prolog engine is based on traversal of SLD-trees. Because of the purpose for which traces are used, SLD-trees do not provide a satisfactory basis for correctness of traces, as we will now explain. Without tracing, the Prolog engine only produces the nal result, the answer substitution. The trace should help the programmer understand how the nal result is obtained. This understanding can only be based on a relation between the program's structure and the trace. The program is built out of components by the operations of disjunction and conjunction. Therefore, the trace should also be understood in terms of decomposition by disjunction and conjunction. The problem is that SLD-trees are not composed out of smaller SLD-trees, by means of disjunction and conjunction, in the same way as programs are. We express this by saying that the operational semantics based on SLD-trees only is not compositional. In this paper we describe SLD-contours, a variant of SLD-trees that are, like programs, built out of simpler components by means of disjunction and conjunction. Hence SLD-contours provide a compositional operational semantics for Prolog programs. The traversal of the SLD-contour of a program is a sequence of events that we denote by Try, Succeed, Retry, and Fail. These are the same events that are reported by the usual trace facility of Prolog implementations based on Byrd's Box Model. Of course, a more complete trace is obtained by attaching to each Box-Model event the associated substitution. In this paper we omit these substitution, although our approach supports such more extensive tracing equally well. After all, the Box Model has gained wide acceptance while also withholding the substitutions associated with the trace events .

The results of this paper. The rst result of this paper is that we connect traces to the semantics of Prolog programs. This connection is established in several steps. The rst step is the well-known connection between semantics (model-theoretic or xpoint) and SLD-trees [3, 1]. The second step is established in this paper: between SLD-trees and

3 SLD-contours. We also take the nal step which consists in showing that traversal of an SLD-contour gives a trace, which is a sequence of Box-Model events. We believe that such a trace is always the one yielded by the trace facility of a Prolog implementation. Indeed, the several examples we have examined are consistent with this belief. But we cannot be sure whether this is always the case, as traces are not explained by Byrd's Box Model. This model only identi es the four events: Try, Succeed, Retry, and Fail. It does not justify how traces are built up from these events. Such a justi cation is provided in this paper. SLD-contours have the disadvantage of containing repeated constructs. The second result of this paper is an improvement of the SLD-contour that consists of collapsing the repetitions into a single occurrence. The result, which we call the SLD-box, has the same traversal as the original SLD-contour. Hence the SLD-box speci es a trace as well. The new construct has this name, because it turns out to be a re nement of Byrd's Box Model. Thus we have connected the Box Model to the semantics of logic programs and in the process found a compositional operational semantics.

Implications for Prolog implementation. In [13] an interpreting algorithm for Prolog

programs was based on SLD-tree traversal. The work in this paper suggests that a Prolog engine be based on SLD-tree traversal (this should be preserved as it gives the semantic justi cation), but, unlike [13], via SLD-boxes. We found that this approach is a natural one for object-oriented programming. We have used it to develop a hybrid system (reported elsewhere [7]) that allows Prolog code to co-exist with C++ code, neither language being subordinate to the other.

Related work. The operational semantics of Prolog derives from two sources. Kowalski

rst described [8] what is now called SLD-resolution. Colmerauer, in de ning and implementing Prolog, determined the special case where search is depth- rst from left to right and where goal selection always proceeds from left to right [4]. The earliest aid in tracing Prolog programs is Byrd's Box Model [2]. It has recently been elaborated into the Box and Plane Model of Numao, Morishita, and Maruyama [10]. Eisenstadt and Brayshaw [6] have used and/or trees as a basis for an aid in tracing. As it stands, Byrd's Box Model is very much underused. The existing expositions follow Byrd's paper [2] in translating the situation of a procedure A calling procedure B to the box for A containing the box for B . But no more structure is represented than that. Numao et al. [10] take an additional step: by introducing planes as larger entities that can contain several boxes, they give a graphical representation of the de nition of the procedure as a disjunction of conjunctions. The result is a version of the Prolog SLD-tree. As will become clear in this paper, it is possible to extend the Box Model to a version of the And-Or tree of the program.

4

Try Fail

a

Succeed -b   Retry 

a :- b



 -

-

Figure 1: On the left the conventional position of the box port. On the right an example of a procedure calling a procedure resulting in nested boxes. Note how, in the conventional Box Model, the ports are not connected.

2 Shortcomings of the SLD-tree as model of Prolog execution The operational semantics of Prolog is given, in principle, by depth- rst, left-to-right traversal of the SLD-tree determined by selecting the leftmost goals. Thus the SLD-tree by itself is not sucient for operational semantics: we should consider the tree in conjunction with the traversal line. Along this traversal line we have a choice of what information to include. The Box Model chooses to neglect information about substitutions. In this paper, as a rst step, we follow this choice. Thus, in this section, we consider how to obtain a trace of Box-Model events from traversal of the SLD-tree. But it may be necessary to review the Box Model rst.

Review of the Box Model. Most Prolog implementations provide a debugger that reports execution events in terms of Byrd's Box Model. This model identi es the two ways in which control can enter and the two ways in which control can leave a Prolog procedure. According to the Box Model, a procedure is symbolized by a rectangular box with four one-way ports, each with an arrow indicating whether control enters or leaves by that port. See Figure 1. In this paper we will refer to the entry ports as Try and Retry ; to the exit ports as Succeed and Fail. During an attempt to solve a goal for the rst time, control enters the box for the corresponding procedure by the Try port. In case of failure, control exits via the Fail port. In case of success, control leaves by the Succeed port. In the latter case, subsequent failure elsewhere may cause the procedure to be reactivated and then control enters by the Retry port. A Box Model event is the event of passing through a port. The events are named by the name of the port together with the procedure to whose box the port belongs. A trace is then a sequence of Box Model events, ordered as they occur in time. See Figure 2 for an example trace in terms of Box Model events. It is clear from descriptions of the Box Model that a box typically contains another box.

5 The outer box symbolizes a procedure call; usually this needs for completion a successful activation of a procedure. This latter activation is symbolized by the inner box. See Figure 1. Although both the outer and inner boxes have ports, Byrd's original paper [2] does not connect these; nor do most of the derivative descriptions typically found in textbooks or Prolog manuals. As a result, although the original Box Model was intended for tracing, it fails to specify what a trace is. The rst publication we know that does connect the ports of nested boxes is Schnupp and Bernard's text [12]. A similar method is followed in [5]. A part of our contribution is that we relate the trace, de ned by connecting the ports of nested boxes, to the standard of the operational semantics of Prolog. We rst brie y review this standard and then explain why it is itself not adequate as a basis for tracing Prolog programs.

Shortcomings of SLD-trees. The basis of the operational semantics of Prolog is an

SLD-tree determined by the initial query and the program and by a rule that selects a goal in each query in the SLD-tree. The Prolog SLD-tree is the one where in each query the leftmost goal is selected. The operational semantics of Prolog is then completed by specifying that this tree is traversed depth- rst from left to right. This formulation of the operational semantics is fundamental because it connects conveniently to the declarative semantics (model-theoretic and xpoint) of Prolog [9]. It is also important because it goes a long way towards providing a basis for implementation. Even as a method of tracing, SLD-trees deserve consideration. The tree together with the traversal line corresponding to the depth- rst left-to-right strategy constitute a tracing model; see Figure 2. Most of the Box Model events can be placed there in a natural way. Therefore the SLD-tree with traversal line is a serious candidate as a model for tracing. This is all the more so as the SLD-tree itself (together with its derivations) is used for proofs of theorems stating the correctness and completeness of pure Prolog. Yet there are shortcomings. We mention two:  The traversal line of an SLD-tree skips certain segments of the trace; see for example Figure 2.  A complex SLD-tree is not the result of composition of simpler SLD-trees. For example, both for purposes of theory and for tracing, it would be preferable if the SLD-tree for the query ?- a,b were a clearly recognizable composition of the SLDtrees for ?- a and for ?- b. This is not the case, but it is true of the constructs that we will derive from SLD-trees: SLD-contours and SLD-boxes.

3 SLD-contours The shortcomings of SLD-trees can be overcome. We rst show how a small modi cation of the SLD-tree diagram together with the traversal line combine to give a complete trace in terms of the Box Model events Try, Fail, Succeed, and Retry.

6

Program: a:- e. a:- c. b:- f. b:- g. c. c:- d. f:- h. f:- i. i. g. d. Trace from query ?- a,b. 1 Try a 16 Retry f 2 Try e 17 Retry i 3 Fail e 18 Fail i 4 Try c 19 Fail f 5 Succeed c 20 Try g 6 Succeed a 21 Succeed g 7 Try b 22 Succeed b 8 try f 23 Retry b 9 Try h 24 Retry g 10 Fail h 25 Fail g 11 Try i 26 Fail b 12 Succeed i 27 Retry a 13 Succeed f 28 Retry c 14 Succeed b 29 Try d 15 Retry b 30 Succeed d 31 Succeed c 32 Succeed a repeat steps 7-26

?-#a,H b 1 # HHH # HH # ?c,a b ?- e, b  aaa 4  2 3 28  a  PP ?d,b ?- b @@ Z # 7 # Z22 29 @ ## 14 Z?-Z g ?b ?- fQ Z # 8 ,, Q13 Q 20 21 ##7 #14 ZZ22Z ?- h ?- i ?- g ?- fQ 13 9 10 11 12 , ,8 QQ 20 21 PP ?- h ?- i 9 10 11 12 PP

Figure 2: Traversal of an SLD-tree is only part of the corresponding trace. Note the gaps 4{7, 14{20, and 22{28. Successful and failed nodes are indicated by showing as descendant a success symbol (2) or a failure symbol (a cross), respectively.

7 Consider Figure 3 where we show one of the subtrees of the SLD-tree in Figure 2 and its traversal line. In addition we have drawn a contour around every SLD-tree except that Program: a:- e. a:- c. b:- f. b:- g. c. c:- d. f:- h. f:- i. i. g. d.

b

7 ,aa26 a

Trace from query ?- b. 7 Try b 17 Retry i 8 Try f 18 Fail i 9 Try h 19 Fail f 10 Fail h 20 Try g 11 Try i 21 Succeed g 12 Succeed i 22 Succeed b 13 Succeed f 23 Retry b 14 Succeed b 24 Retry g 15 Retry b 25 Fail g 16 Retry f 26 Fail b

, ,, f 8 @ 19  @@ @  i h

9 10

PP

11 18

aaa a

aaa g

20 25 21 24

12 17 13 16 14 15

22 23

Figure 3: An SLD-contour. Note that every Box-Model event now corresponds to a crossing of the traversal line with a contour, and vice versa. the success leaves protrude from the contour. Notice that the left success leaf is not only a success leaf of the tree rooted in i but also one for the trees rooted in f and b. Thus the edge of the tree to this success leaf crosses three contours. Now consider the places where the traversal line crosses a contour. An entry crossing near the top we call Try ; an exit crossing near the top we call Fail. An exit crossing near a success leaf we call Succeed ; an entry crossing near a success leaf we call Retry. When we now follow the traversal line, the sequence of contour crossings is a complete trace. This example illustrates SLD-contours, our model for obtaining complete traces of Prolog execution in a way that derives from SLD-tree traversal, the standard by which the operational semantics of Prolog is de ned. More precisely, an SLD-contour for a single-goal query ?- G consists of two components:  The rst component is a closed contour with an entry and exit port called Try and Fail and a pair of exit and entry ports for each of the zero or more successes possible

8 for the given goal G. The latter are called Succeed and Retry, respectively.  The second component of an SLD-contour is the traversal line, a line connecting the Try port to the rst Succeed port, if any, a line connecting every Retry port to the next Succeed port, if any, and a line connecting the last Retry port, if any, to the Fail port. These connecting lines are typically partly made up of traversal lines of component SLD-contours. SLD-contours are primitive or composite. The primitive SLD-contours are of two kinds: Success and Failure. The primitive SLD-contour Success represents a query that succeeds immediately (i.e., it derives the empty query 2). It has one Succeed and one Retry port. Its Try port is connected directly to the Succeed port, and the Retry port is connected directly to the Fail port. The primitive SLD-contour Failure represents a query that fails immediately (i.e., it derives the failure symbol X ). It has no Succeed or Retry port. Its Try port is connected directly to its Fail port. All composite SLD-contours are composed of other SLD-contours in two di erent ways: by disjunction or by conjunction.

Disjunction of SLD-contours. As shown in Figure 5, the disjunction C of SLD-

contours C1; : : : ; Cn is an SLD-contour. We de ne C by de ning its two components.  The traversal line of C is obtained by connecting the Try port of C to the Try port of C1, by connecting, for i = 1; : : : ; n , 1, the Fail port of Ci to the Try port of Ci+1, and by connecting the Fail port of Cn to the Fail port of C .  The contour of C is the smallest contour containing the contours of C1; : : : ; Cn, but not their success leaves. The disjunction operation is related in an obvious way to the logical connective of the same name. Note, however, that the disjunction of SLD-contours is not commutative. See Figure 4.

Conjunction of SLD-contours. So far, we have only de ned SLD-contours for singlegoal queries. To model the trace resulting from a conjunction of goals as in a condition or in a query, it is necessary to perform the conjunction operation on SLD-contours. The conjunction C of the SLD-contours A and B is an SLD-contour. C is de ned by de ning its two components, as follows:  The traversal line of C is obtained by attaching a copy of B to each of the zero or more Succeed-Retry pairs of A, in the following way: connect the Succeed port of the selected pair of A to the Try port of the copy of B and connect the Fail port of the copy of B to the Retry port of the selected pair of A.  The contour of C is the smallest contour containing A and the copies of B , but not the success leaves of the copies of B .

9 Try C

JJFail C JJ



JJ



C  JJ

JJ

C 

Try C Fail C n n

Try C Fail C C  JJ 1 1

JJ

C 



C  JJ

JJ

C 

  

C  JJ

JJ

C 

C

C  n J C1

C JJ



JJ C





Figure 4: Disjunction of SLD-contours. We write C = A; B for the conjunction of SLD-contours A and B . Note that, in spite of what the name may suggest, conjunction of SLD-contours is neither associative nor commutative. The lack of associativity seems to be mostly a formality. Although P; (Q; R) and (P; Q); R are di erent SLD-contours, their traversal lines specify the same trace. To model the behaviour of a multi-goal query, one uses a conjunction of SLD-contours. The trace of a query with goals a1; : : :; an is the sequence of events associated with the traversal line of the SLD-contour resulting from the conjunction of a1; : : : ; an, in that order, according to the way they are associated. Thus the primitive SLD-boxes, together with the operations of conjunction and disjunction, completely specify traces of programs in pure Prolog. We will treat cuts in a later section.

4 SLD-boxes Byrd's Box Model [2] does not explicitly specify the trace that it is intended to help understand. It is also not clear how it relates to SLD-tree traversal, which forms the basis of Prolog's operational semantics. SLD-contours are an improvement in that they are closely related to SLD-trees and specify the trace as the succession of ports along the traversal line of an SLD-contour. What more can one ask? Well, there is still a problem: conjunction may require copying many identical SLD-contours. We propose to avoid this problem by merging all SucceedRetry pairs (if any) into a single pair. The merging implies a fan-in when passing through the merged Succeed port and a fan-out when entering through the merged Retry port. When a trace line leaves through a Succeed port, the port identity must be retained, so that the corresponding Retry port can be selected when the next conjunct fails. The

10

\  \

  



C = A; B

\\

A



\\ \

\\  \\  \\   B  \ 

\\  \ 

\\  \\   A J   J  JJ JJ\ \  \  \\     \\ \\   B \\  \\ B  \  



Figure 5: Conjunction of SLD-contours. memory required to retain the port identity is symbolized by a long narrow bar inserted between the possibly many Succeed-Retry pairs of the SLD-contour and the single pair in the modi ed version of SLD-contours. See Figure 6 for our graphical notation for the memory. It will not have escaped the reader's notice that with this modi cation of the SLDcontour we have re-invented the box of the Box Model. All that remains to be done is to turn the modi ed SLD-contour on its side. We call the result an SLD-box. The similarity to the Box Model is no coincidence. We accept the requirement that a trace of Prolog execution must be a sequence of Box Model events: Try, Succeed, Retry, and Fail. In the process of our re-invention we have added the traversal line, so that the SLD-box model explicitly speci es the trace. In addition, SLD-boxes are an alternative notation for SLD-trees. Just as is the case with SLD-contours, there are primitive SLD-boxes and composite SLD-boxes that can be composed by conjunction and by disjunction. For the purpose of the de nition, an SLD-box is to be regarded as an SLD-contour with a single Succeed-Retry pair of ports. The di erence between the two concepts is the memory, which is internal to the SLD-box, hence does not a ect the de nition. So the de nitions for SLD-contours carry over to corresponding ones for SLD-boxes. See Figure 7 for a conjunction of two SLD-boxes, each of which is a disjunction. This still concerns the propositional case.

Uni cation boxes. In the nonpropositional case we have to take into account uni cation

between the goal and head arguments. The uni cation of a goal and the head can either

11

BB JJ  BN T JJ F JJ   J  JJ   JJ  6 6 S? R S? R Try-

Fail 

BB JJ  BN T JJ F JJ   J  6 6 J  ?   Memory? JJ  J 6 S?R



-

-







Succeed -

 Retry



Figure 6: SLD-box is SLD-contour with memory. Top left: example SLD-contour with two Succeed/Retry pairs of ports. Top right: SLD-box version of same. Bottom: the same SLD-box, but rendered in a shape and position suggested by the usual notation for Byrd's Box Model. succeed or fail; it can never be retried. Hence, uni cation is a box with only three ports: Try, Succeed, and Fail. Accordingly, we show uni cation boxes as triangles. See Figure 8 for the generic example. For each clause H C1; : : :; Cn, there is a uni cation box U containing H, where  is a suitable renaming substitution. Let G be the current goal and  the current substitution. Let be the most general uni er of G and H. If A is the SLD-box activated by G, then  the box events of A are: Try , Succeed n , Redo n, and Fail .  for each uni cation box U inside A with head H, this box's events are: Try , Succeed , and Fail .

5 E ect of cut in SLD-boxes If a tracing method is to help in understanding the execution of Prolog programs, it should help in particular when cuts are encountered. This is not the case for the Box Model. We

12 Program: a:- e. a:- c. b:- f. b:- g. c. c:- d. f:- h. f:- i. i. g. d. a e



c

b

f









i



d

g 

h







Figure 7: Example trace in the form of SLD-boxes. believe SLD-boxes to be an improvement in that respect. The problem with the cut is that it is at times necessary to give Prolog programs acceptable eciency. But its action seems to be determined for the implementer's convenience rather than the programmer's. The cut is not only simple to implement; it is also simple to de ne. We take as authoritative the de nition of Colmerauer and Roussel [11], which goes as follows. Suppose the cut is selected as goal in a query Q that is a node in an SLDtree. This goal succeeds with the side e ect of removing from the SLD-tree all untraversed subtrees that have as roots ancestors of Q up to and including the parent of the query where the cut rst appeared. Simple enough to say and remember. Hard to translate to the act of programming. We nd the cut much easier to work with when visualizing it by means of SLD-boxes, as shown in Figure 9.

13

C1 : A Cm : A A

U1

B1;1; : : :; B1;n1 ::: Bm;1; : : : ; Bm;nm

B1;1

B1;n1

Bm;1

Bm;nm

-  -  SS     SS      U2 S   SS   S   Um





-  SS - -      SS               



-

 

Figure 8: SLD-boxes for unrestricted Horn clauses. Note uni cation boxes U1; : : :; Um.

6 Acknowledgments We are grateful to the referees for suggestions for improvement. Generous support was provided by the British Columbia Advanced Systems Institute, the Canada Natural Science and Engineering Research Council, the Canadian Institute for Advanced Research, the Institute of Robotics and Intelligent Systems, and the Laboratory for Automation, Communication and Information Systems Research.

References [1] K.R. Apt and M.H. van Emden. Contributions to the theory of logic programming. Journal of the ACM, 29(3):841{862, 1982. [2] Lawrence Byrd. Understanding the control ow of Prolog programs. In Logic Programming Workshop, pages 127{138, 1980. Proceedings of the workshop in Debrecen, Hungary, 14{16 July 1980.

14

SS -       SS     cut    S   SS   S 





-





-   SS - -       S      S   

Figure 9: The e ect of the cut is the short circuit in the traversal line indicated by the dotted line. [3] K.L. Clark. Predicate logic as a computational formalism. Technical Report DOC 79/59, Department of Computing, Imperial College, 1979. [4] A. Colmerauer, H. Kanoui, R. Pasero, and P. Roussel. Un systeme de communication homme-machine en francais. Technical report, Groupe d'Intelligence Arti cielle, Universite d'Aix-Marseille II, 1972. [5] Claudio Delrieux, Pablo Azero, and Fernando Tohme. Toward integrating imperative and logic programming paradigms: A WYSIWYG approach to Prolog programming. ACM SIGPLAN Notices, 26(3):35{44, 1991. [6] M. Eisenstadt and M. Brayshaw. Graphical debugging with the transparent Prolog machine. In Proc. 10th International Joint Conference on Arti cial Intelligence, 1987. [7] N. Horspool and M. Levy. Integrating imperative and logic programming. Unpublished manuscript, Department of Computer Science, University of Victoria, 1991. [8] R.A. Kowalski. Logic for Problem-Solving. Elsevier North-Holland, 1979. [9] J.W. Lloyd. Foundations of Logic Programming. Springer-Verlag, 2nd edition, 1987. [10] Masayuki Numao, Shin'ichi Morishita, and Hiroshi Maruyama. How should Prolog computation be represented for practical use? New Generation Computing, 8, 1990.

15 [11] Ph. Roussel. Prolog, manuel de reference et d'utilisation. Technical report, Groupe d'Intelligence Arti cielle, Universite d'Aix-Marseille II, 1975. [12] Peter Schnupp and Lawrence W. Bernard. Productive Prolog Programming. Prentice Hall, 1987. [13] M.H. van Emden. An algorithm for interpreting Prolog programs. In Implementations of Prolog, pages 93{110. Ellis Horwood, 1984.

Suggest Documents