peter@yetti.UUCP (Runge) (08/12/85)
The User's Manual for DEC10 Prolog (circa 1978) had several useful sections on definite-clause grammars, grammar rules for "Edinburgh" syntax, example programs, and (early) Prolog references. None of this information was in the C-Prolog manual distributed with C-Prolog 1.5, so before our beloved (:-)) TOPS-10 machine vanished into oblivion, I converted these sections into "troff -ms" format to serve as an appendix to the C-Prolog manual which we distribute to students in a LP course here. There is no copyright on this material, as far as I can tell, so I hope F. Pereira & Co. won't mind. I'm posting the excerpts to the net, at the suggestion of a colleague. I wouldn't mind hearing from anyone who found it useful (or who objects to long articles containing material of ancient vintage); it's hard for me to gauge whether or not purely pedagogical material such as this should be circulated on the net. The excerpts follow: ----------------------------------------------------------------------------- .RP .TL GRAMMARS, SYNTAX, EXAMPLES, and REFERENCES Excerpts from "User's Guide to DECsystem-10 PROLOG", September 1978. .AU Fernando C. N. Pereira, Luis Moniz Pereira, and David H. D. Warren .AB no The following excerpts from the DEC-10 Prolog manual provided by the Department of Artificial Intelligence, University of Edinburgh, contain useful information for C-PROLOG programmers, and so are included as an "addendum" to the .I C-Prolog User's Manual. .R .LP The first excerpt, DEFINITE CLAUSE GRAMMARS, describes the syntax and semantics of grammar rules in Prolog. These are mentioned fleetingly at the end of the C-Prolog User's Manual but are not otherwise documented. They are, however, implemented in Version 1.5, exactly as described in the DEC-10 manual. .LP The FULL SYNTAX section provides a detailed description of DEC-10 Prolog syntax. This is useful for debugging syntax errors, and is of interest if one wishes to consider alternative or additional constructs to Prolog. .LP The EXAMPLES section illustrates a number of Prolog programming techniques. .LP REFERENCES contains useful references to the original papers defining resolution and definite-clause grammars. .LP Some small editorial changes have been made to the original text. .DS P. H. Roosen-Runge .DE .AE .SH DEFINITE CLAUSE GRAMMARS .PP Prolog's .I grammar rules .R provide a convenient notation for expressing .I definite clause grammars .R [Colmerauer 1975] [Pereira & Warren 1978]. Definite clause grammars are an extension of the well-known context-free grammars. .PP A grammar rule takes the general form: .DS .I LHS --> RHS. .R .DE meaning "a possible form for .I LHS is .I RHS ". Both .I LHS and .I RHS are sequences of one or more items linked by the standard Prolog conjunction operator ','. .PP Definite clause grammars extend context-free grammars in the following ways: .IP (1) A non-terminal symbol may be any Prolog term (other than a variable or integer). .IP (2) A terminal symbol may be any Prolog term. To distinguish terminals from non-terminals, a sequence of one or more terminal symbols is written within a grammar rule as a Prolog list. An empty sequence is written as the empty list '[]'. If the terminal symbols are ASCII character codes, such lists can be written (as elsewhere) as strings. An empty sequence is written as the empty list '[]' or '""'. .IP (3) Extra conditions, in the form of Prolog procedure calls, may be included in the right-hand side of a grammar rule. Such procedure calls are written enclosed in '{' '}' brackets. .IP (4) The left-hand side of a grammar rule consists of a non-terminal, optionally followed by a sequence of terminals (again written as a Prolog list). .IP (5) Alternatives may be stated explicitly in the right-hand side of a grammar rule, using the disjunction operator ';' as in Prolog. .IP (6) The cut symbol may be included in the right-hand side of a grammar rule, as in a Prolog clause. The cut symbol does not need to be enclosed in '{' '}' brackets. .LP .PP As an example, here is a simple grammar which parses an arithmetic expression (made up of digits and operators) and computes its value: .DS expr(Z) --> term(X), "*", expr(Y), {Z is X * Y}. expr(Z) --> term(X), "/", expr(Y), {Z is X / Y}. expr(X) --> term(X). term(Z) --> number(X), "+", term(Y), {Z is X + Y}. term(Z) --> number(X), "-", term(Y), {Z is X - Y}. term(Z) --> number(Z). number(C) --> "+", number(C). number(C) --> "-", number(X), {C is -X}. number(X) --> [C], {"0"=<C, C=<"9", X is C - "0"}. .DE .PP In the last rule, C is the ASCII code of some digit. .PP The question .DS ?- expr(Z,"-2+3*5+1",[]) .DE will compute Z=14. The two extra arguments are explained below. .PP Now, in fact, grammar rules are merely a convenient "syntactic sugar" for ordinary Prolog clauses. Each grammar rule takes an input string, analyses some initial portion, and produces the remaining portion (possibly enlarged) as output for further analysis. The arguments required for the input and output strings are not written explicitly in a grammar rule, but the syntax implicitly defines them. We now show how to translate grammar rules into ordinary clauses by making explicit the extra arguments. .PP A rule such as .DS p(X) --> q(X). .DE translates into: .DS p(X,S0,S) :- q(X,S0,S). .DE If there is more than one non-terminal on the right-hand side, as in: .DS p(X,Y) --> q(X), r(X,Y), s(Y). .DE then corresponding input and output arguments are identified, as in: .DS p(X,Y,S0,S) :- q(X,S0,S1), r(X,Y,S1,S2), r(Y,S2,S). .DE Terminals are translated using the predicate 'c(S1,X,S2)', read as "point S1 is connected by terminal X to point S2", and defined by the single clause: .DS c([X|S],X,S). .DE Then, for instance: .DS p(X) --> [go,to], q(X), [stop]. .DE is translated by: .DS p(X,S0,S) :- c(S0,go,S1), c(S1,to,S2), q(X,S2,S3), c(S3,stop,S). .DE .PP Extra conditions expressed as explicit procedure calls naturally translate as themselves, e.g. .DS p(X) --> [X], {integer(X), X>0}, q(X). .DE translates to: .DS p(X,S0,S) :- c(S0,X,S1), integer(X), X>0, q(X,S1,S). .DE Similarly, a cut is translated literally. .PP Terminals on the left-hand side of a rule translate into an explicit list in the output argument of the main non-terminal, e.g. .DS is(N), [not] --> [aint]. .DE becomes: .DS is(N,S0,[not,..S]) :- c(S0,aint,S). .DE .PP Disjunction has a fairly obvious translation, e.g. .DS args(X,Y) --> dir(X), [to], indir(Y); indir(Y), dir(X). .DE translates to: .DS args(X,Y,S0,S) :- dir(X,S0,S1), c(S1,to,S2), indir(Y,S2,S); indir(Y,S0,S1), dir(X,S1,S). .DE .bp .SH FULL SYNTAX .LP A Prolog program consists of a sequence of sentences. Each sentence is a Prolog term. How terms are interpreted as sentences is defined in Section 2 below. Note that a term representing a sentence may be written in any of its equivalent syntactic forms. For example, the 2-ary functor ':-' could be written in standard prefix notation instead of as the usual infix operator. .LP Terms are written as sequences of tokens. Tokens are sequences of characters which are treated as separate symbols. Tokens include the symbols for variables, constants and functors, as well as punctuation characters such as brackets and commas. .LP Section 3 below defines how lists of tokens are interpreted as terms. Each list of tokens which is read in (for interpretation as a term or sentence) has to be terminated by a full-stop token. Two tokens must be separated by a space token if they could otherwise be_ interpreted as a single token. Both space tokens and comment tokens are ignored when interpreting the token list as a term. A comment may appear at any point in a token list (separated from other tokens by spaces where necessary). .LP Section 4 below defines how tokens are represented as strings of characters. But first Section 1 describes the notation used in the formal definition of Prolog syntax. .NH Notation .IP (1) Literal characters in rules appear in inverted-quotes (` `). If not so quoted, punctuation such as .I --> , .I | , .I ? , and .I ... are part of the syntax of the grammar rules themselves. .IP (2) A syntactic rule takes the general form: .DS .I C --> F1 | F2 | F3 . . . .R .DE which states that an entity of category C may take any of the alternative forms F1, F2, F3, etc. .IP (3) Certain definitions and restrictions are given in ordinary English, enclosed in { } brackets. .IP (4) A category written as .I C... .R denotes a sequence of one or more Cs. .IP (5) A category written as .I ?C .R denotes an optional C. Therefore .I ?C... .R denotes a sequence of zero or more Cs. .IP (6) A few syntactic categories have names with arguments, and rules in which they appear may contain meta-variables in the form of capital letters. The meaning of such rules should be clear from analogy with rules in definite clause grammars. .bp .NH Syntax Of Sentences As Terms .PP .DS .I sentence --> clause | directive | grammar-rule clause --> non-unit-clause | unit-clause directive --> command | question | file-list non-unit-clause --> ( head `:-` goals ) unit-clause --> head { where head is not otherwise a sentence } command --> ( `:-` goals ) question --> ( `?-` goals ) file-list --> list head --> term { where term is not an integer or variable } goals --> ( goals `,` goals ) | ( goals `;` goals ) | goal goal --> term { where term is not an integer and is not otherwise a goals } grammar-rule --> ( gr-head `-->` gr-body ) gr-head --> non-terminal | ( non-terminal `,` terminals ) gr-body --> ( gr-body `,` gr-body ) | ( gr-body `;` gr-body ) | non-terminal | terminals | gr-condition non-terminal --> term { where term is not an integer or variable and is not otherwise a gr-body } terminals --> list | string gr-condition --> { goals } .R .DE .NH Syntax Of Terms As Tokens .PP .DS .I term-read-in --> subterm(1200) full-stop subterm(N) --> term(M) { where M is less than or equal to N } term(N) --> op(N,fx) | op(N,fy) | op(N,fx) subterm(N-1) { except the case `-` number } { if subterm starts with a `(`, op must be followed by a space } | op(N,fy) subterm(N) { if subterm starts with a `(`, op must be followed by a space } | subterm(N-1) op(N,xfx) subterm(N-1) | subterm(N-1) op(N,xfy) subterm(N) | subterm(N) op(N,yfx) subterm(N-1) | subterm(N-1) op(N,xf) | subterm(N) op(N,yf) term(1000) --> subterm(999) , subterm(1000) term(0) --> functor ( arguments ) { provided there is no space between the functor and the '(' } | ( subterm(1200) ) | { subterm(1200) } | list | string | constant | variable op(N,T) --> functor { where functor has been declared as an operator of type T and precedence N } arguments --> subterm(999) | subterm(999) , arguments .DE .DS .I list --> `[]` | `[` listexpr `]` listexpr --> subterm(999) | subterm(999) `,` listexpr | subterm(999) `|` subterm(999) | `..` subterm(999) .DE .DS .I constant --> atom | integer atom --> name { where name is not a prefix operator } integer --> number | `-` number functor --> name .R .DE .NH Syntax Of Tokens As Character Strings .PP .DS .I token --> name | number | variable | string | punctuation-char | decorated-bracket | space | comment | full-stop name --> quoted-name | word | symbol | solo-char | `[]` | `{}` quoted-name -->`'` quoted-item... `'` quoted-item --> char { other than ' } | `''` .DE .DS .I word --> capital-letter ?alpha... { in the 'NOLC' convention only } word --> small-letter ?alpha... symbol --> symbol-char... { except in the case of a full-stop or where the first 2 chars are /* } number --> digit... variable --> underline ?alpha... variable --> capital-letter ?alpha.. { in the 'LC' convention only } .DE .DS .I string --> `"` ?string-item... `"` string-item --> char { other than " } | "" decorated-bracket --> `%(` | `%)` space --> space-char... comment --> `/*` ?char... `*/` { where ?char... must not contain */ } | `%` { up to end of the line } full-stop --> `.` space-char .DE .DS .I char --> { any ASCII character, i.e. } space-char | alpha | symbol-char | solo-char | punctuation-char | quote-char space-char --> { any ASCII character code up to 32, includes <blank>, <cr> and <lf> } alpha --> letter | digit | underline letter --> capital-letter | small-letter capital-letter --> { any character from the list ABCDEFGHIJKLMNOPQRSTUVWXYZ } small-letter --> { any character from the list abcdefghijklmnopqrstuvwxyz } digit --> { any character from the list 012346789 } .DE .DS .I symbol-char --> { any character from the list +-*/ ~<>= ^:.?@#$& } solo-char --> { any character from the list ;!% } punctuation-char --> { any character from the list ()[]{},| } quote-char --> { any character from the list '" } underline --> { the character _ } .R .DE .NH Notes .PP .IP (1) The expression of precedence 1000 (i.e. belonging to syntactic category term(1000)) which is written: .DS X,Y .DE denotes the term ','(X,Y) in standard syntax. .IP (2) The bracketed expression (belonging to syntactic category term(0)): .DS (X) .DE denotes simply the term X. .IP (3) The curly-bracketed expression (belonging to syntactic category term(0)): .DS {X} .DE denotes the term '{}'(X) in standard syntax. .IP (4) The decorated brackets `%(` and `%)` are alternatives for the curly brackets `{` and `}` respectively. E. g. .DS {X} = %(X%) .DE .IP (5) Note that, for example, `-3` denotes an integer whereas `-(3)` denotes a compound term which has the 1-ary functor `-` as its principal functor. .IP (6) The character `"` within a string must be written duplicated; similarly for the character `'` within a quoted atom. .bp .SH EXAMPLES .PP Some simple examples of Prolog programming are given below. To clearly differentiate the examples themselves, they are marked with a vertical bar in the left margin. .PP .NH 0 Simple List Processing .PP The goal 'concatenate(L1,L2,L3)' is true if list L3 consists of the elements of list L1 concatenated with the elements of list L2. The goal 'member(X,L)' is true if X is one of the elements of list L. The goal 'reverse(L1,L2)' is true if list L2 consists of the elements of list L1 in reverse order. .DS | concatenate([X|L1],L2,[X|L3]) :- concatenate(L1,L2,L3). | concatenate([],L,L). | | member(X,[X|L]). | member(X,[_|L]) :- member(X,L). | | reverse(L,L1) :- reverse_concatenate(L,[],L1). | | reverse_concatenate([X|L1],L2,L3) :- | reverse_concatenate(L1,[X|L2],L3). | reverse_concatenate([],L,L). .DE .NH A Small Database .PP The goal 'descendant(X,Y)' is true if Y is a descendant of X. .DS | descendant(X,Y) :- offspring(X,Y). | descendant(X,Z) :- offspring(X,Y), descendant(Y,Z). | | offspring(abraham,ishmael). | offspring(abraham,isaac). | offspring(isaac,esau). | offspring(isaac,jacob). .DE .PP If for example the question: .DS ?- descendant(abraham,X). .DE is executed, Prolog's backtracking results in different descendants of Abraham being returned as successive instances of the variable X, i.e. .DS X = ishmael X = isaac X = esau X = jacob .DE .NH Quick-Sort .PP The goal 'qsort(L,[],R)' is true if list R is a sorted version of list L. More generally, 'qsort(L,R0,R)' is true if list R consists of the members of list L sorted into order, followed by the members of list R0. The algorithm used is a variant of Hoare's "Quick Sort". .DS | qsort([X|L],R0,R) :- | partition(L,X,L1,L2), | qsort(L2,R0,R1), | qsort(L1,[X|R1],R). | qsort([],R,R). | | partition([X|L],Y,[X|L1],L2) :- X =< Y, !, | partition(L,Y,L1,L2). | partition([X|L],Y,L1,[X|L2]) :- X > Y, !, | partition(L,Y,L1,L2). | partition([],_,[],[]). .DE .NH Differentiation .PP The goal 'd(E1,X,E2)' is true if expression E2 is a possible form for the derivative of expression E1 with respect to X. .DS | :-op(300,xfy,~). | d(U+V,X,DU+DV) :-!, d(U,X,DU), d(V,X,DV). | d(U-V,X,DU-DV) :-!, d(U,X,DU), d(V,X,DV). | d(U*V,X,DU*V+U*DV) :-!, d(U,X,DU), d(V,X,DV). | d(U~N,X,N*U~N1*DU) :-!, integer(N), N1 is N-1, d(U,X,DU). | d(-U,X,-DU) :-!, d(U,X,DU). | d(exp(U),X,exp(U)*DU) :-!, d(U,X,DU). | d(log(U),X,DU/U) :-!, d(U,X,DU). | d(X,X,1) :-!. | d(C,X,0) :- atomic(C), C == 0, !. .DE .NH Mapping A List Of Items Into A List Of Serial Numbers .PP The goal 'serialise(L1,L2)' is true if L2 is a list of serial numbers corresponding to the members of list L1, where the members of L1 are numbered (from 1 upwards) in order of increasing size, e.g. ?-serialise([1,9,7,7],X). gives X = [1,3,2,2]. .DS | serialise(Items,SerialNos) :- | pairlists(Items,SerialNos,Pairs), | arrange(Pairs,Tree), | numbered(Tree,1,N). | | pairlists([X|L1],[Y|L2],[pair(X,Y)|L3]) :- pairlists(L1,L2,L3). | pairlists([],[],[]). | | arrange([X|L],tree(T1,X,T2)) :- | split(L,X,L1,L2), | arrange(L1,T1), | arrange(L2,T2). | arrange([],void). | | split([X|L],X,L1,L2) :-!, split(L,X,L1,L2). | split([X|L],Y,[X|L1],L2) :- before(X,Y),!, split(L,Y,L1,L2). | split([X|L],Y,L1,[X|L2]) :- before(Y,X),!, split(L,Y,L1,L2). | split([],_,[],[]). | | before(pair(X1,Y1),pair(X2,Y2)) :- X1 < X2. | | numbered(tree(T1,pair(X,N1),T2),N0,N) :- | numbered(T1,N0,N1), | N2 is N1+1, | numbered(T2,N2,N). | numbered(void,N,N). .DE .NH Use Of Meta-Predicates .PP This example illustrates the use of the meta-predicates 'var' and '=..'. The procedure call 'variables(Term,L,[])' instantiates variable L to a list of all the variable occurrences in the term Term. For example, .DS variables(d(U*V,X,DU*V+U*DV), [U,V,X,DU,V,U,DV], []). | variables(X,[X|L],L) :- var(X),!. | variables(T,L0,L) :- T =.. [F|A], variables1(A,L0,L). | | variables1([T|A],L0,L) :- variables(T,L0,L1), variables1(A,L1,L). | variables1([],L,L). .DE .NH Prolog In Prolog .PP This example shows how simple it is to write a Prolog interpreter in Prolog, and illustrates the use of a variable goal. In this mini-interpreter, goals and clauses are represented as ordinary Prolog data structures (i.e. terms). Terms representing clauses are specified using the unary predicate 'clause', e.g. .DS clause( (grandparent(X,Z):-parent(X,Y),parent(Y,Z)) ). .DE .PP A unit clause will be represented by a term such as: .DS clause( parent(john,mary) :- true ) .DE .PP The mini-interpreter consists of four clauses: .DS | execute(true) :-!. | execute((P,Q)) :- !, execute(P), execute(Q). | execute(P) :- clause((P:-Q)), execute(Q). | execute(P) :- P. .DE The last clause enables the mini-interpreter to cope with calls to ordinary Prolog predicates, e.g. evaluable predicates. .NH Translating English Sentences Into Logic Formulae .PP The following example of a definite clause grammar defines in a formal way the traditional mapping of simple English sentences into formulae of classical logic. By way of illustration, if the sentence: .DS Every man that lives loves a woman. .DE is parsed as a 'sentence(P)', P will get instantiated to: .DS all(X):(man(X)&lives(X) => exists(Y):(woman(Y)&loves(X,Y))) .DE where ':', '&' and '=' are infix operators defined by: .DS :-op(900,xfx,=>). :-op(800,xfy,&). :-op(300,xfx,:). .DE .PP The grammar follows: .DS | sentence(P) --> noun_phrase(X,P1,P), verb_phrase(X,P1). | | noun_phrase(X,P1,P) --> | determiner(X,P2,P1,P), noun(X,P3), rel_clause(X,P3,P2). | noun_phrase(X,P,P) --> name(X). | | verb_phrase(X,P) --> trans_verb(X,Y,P1), noun_phrase(Y,P1,P). | verb_phrase(X,P) --> intrans_verb(X,P). | | rel_clause(X,P1,P1&P2) --> [that], verb_phrase(X,P2). | rel_clause(_,P,P) --> []. | | determiner(X,P1,P2, all(X):(P1=>P2) ) --> [every]. | determiner(X,P1,P2, exists(X):(P1&P2) ) --> [a]. | | noun(X, man(X) ) --> [man]. | noun(X, woman(X) ) --> [woman]. | | name(john) --> [john]. | | trans_verb(X,Y, loves(X,Y) ) --> [loves]. | intrans_verb(X, lives(X) ) --> [lives]. .DE .bp .SH REFERENCES .LP .nj .nf Colmerauer A [1975] "Les Grammaires de Metamorphose". Groupe d'Intelligence Artificielle,Marseille-Luminy. Nov 1975. Appears as "Metamorphosis Grammars" in "Natural Language Communication with Computers", Springer Verlag, 1978. van Emden M H [1975] "Programming with Resolution Logic". Report CS-75-30, Dept. of Computer Science, University of Waterloo, Canada. Nov 1975. Kowalski R A [1974] "Logic for Problem Solving". DCL Memo 75, Dept of AI, Edinburgh. Mar 74. Pereira F C N & Warren D H D [1978] "Definite Clause Grammars Compared with Augmented Transition Networks". Forthcoming report, Dept. of AI, Edinburgh. Robinson J A [1965] "A Machine-Oriented Logic Based on the Resolution Principle". JACM vol 12, pp.23-44. 1965. Roussel P [1975] "Prolog : Manuel de Reference et d'Utilisation". Groupe d'Intelligence Artificielle, Marseille-Luminy. Sep 1975. Warren D H D [1977] "Implementing Prolog - Compiling Predicate Logic Programs". Research reports 39 & 40, Dept. of AI, Edinburgh. 1977. Warren D H D, Pereira L M, Pereira, F C N [1977] "Prolog - the Language and its Implementation Compared with Lisp". Procs. of the ACM Symposium on Artificial Intelligence and Programming Languages, SIGART/SIGPLAN Notices, Rochester, N.Y., Aug 1977. -- Peter H. Roosen-Runge, Department of Computer Science, York University Toronto M3J 1P3 , Ontario, Canada _____________________________________________________________________________ "Eccles, is the ship sinking?" "Only below the sea." "We must try to save the ship -- help me get it into the lifeboat." _____________________________________________________________________________