% File : DCSG.PL % Author : R.A.O'Keefe % Updated: 3 December 1983 % Purpose: Preprocessor for Definite Clause Slash Grammars % Needs : append/3, member/2, memberchk/2. /* Definite Clause Slash Gammars are a special kind of DCG which allows slash categories, e.g. s/np. A nonterminal with N arguments is translated into a predicate with N+4 arguments, nt(X1,...,Xn) => nt(X1,...,Xn,T0,T,S0,S) where T0 is the "trace" before and T that after parsing an nt, and S0 is the string before and S that after parsing an nt. The translator produces a clause non_terminal(nt(X1,...,Xn), T0, T, S0, S) :- nt(X1,...,Xn, T0, T, S0, S). for each non-terminal. This was meant for variables standing in non-terminal positions. You can't write s --> Alpha, s/Alpha. but you can write s --> Alpha^[np,pp], s/Alpha. which turns into s(T0, T, S0, S) :- member(Alpha, [np, pp]), non_terminal(Alpha, T0, T, S0, S1), s(Alpha, 0, S1, S). Most of the complexity in this translator comes from the decision to allow schematic rules, where a nonterminal or an extraposed item can be a variable, followed by ^[list of possibilities]. I am not sure that it was worth while, as I don't actually have any use for this case. Note: you can block extraposition out of a particular place by using /0. That is, if nonterm/0 appears in a rule, nothing can be extraposed out of nonterm. You can block a particular kind of movement by writing e.g. vp_obj/Alpha --> vp/Alpha, {Alpha\=np(subj,_,_)}. which will let anything out that vp will, except a subject np, and then you can call vp_obj instead of vp in appropriate places. If you haven't heard of Gazdar, you probably won't know what slash categories are for. Generalised Phrase Structure Grammar augments context-free grammars with features (function-free DCG arguments) slash categories(as here) rule schemas (as done badly here) meta-rules (sort of like transformations, but not really) in such a way that only the notational convenience is increased, the grammars still have only context-free power. As I understand it, Gazdar doesn't claim that English *is* a context-free language, only that the arguments to date that it is *not* are about as sound as a 3-dollar note. I have to draw a distinction between DCSGs (which are just logic programs, and are at least as powerful as GPSGs) and the particular parser obtained by using Prolog as the parser. When we use Prolog to parse DCSGs, we run into problems with left-recursion and so on that have nothing to do with the formalism. Henry Thompson, DAI Edinburgh, has a full implementation of GPSGs in Lisp, including meta-rules, which is a chart parser. This file is intended as a cheap way of exploring some of the ideas of GPSG, no more. A serious tool for people who can't hack Prolog would be very much bigger. */ :- public dcsg/1, dcsg_phrase/2. :- mode dcsg(+), % Files -> dcsg_load(+), % Files -> dcsg_hack(+), % Term -> dcsg_head(+, +, -), % NT x Rhs -> Clause dcsg_note(+, +), % {clause|dcsg} x Goal -> dcsg_args(+, ?, ?, ?, ?, -), dcsg_choices(?, +, -), % Var x NT-list -> Goal-list dcsg_head(+, -, +, -, ?, ?), dcsg_body(+, ?, ?, ?, ?, -), dcsg_and(+, +, -), dcsg_or(+, ?, ?, ?, ?, -), dcsg_list(+, ?, ?, -), dcsg_choices(+, -), dcsg_choice(+, -), non_terminal(+, ?, ?, ?, ?, +), dcsg_phrase(+, +). % dscg(Files) % consults a list of Files which are expected to contain grammar % rules. It first of all wipes out all the existing grammar rules, % so acts as a sort of reconsult. I haven't really made up my % mind about that. In any case, I've only the one file to load. dcsg(_) :- clause(non_terminal(_,_,_,_,_), NT), functor(NT, NonTerm, Arity), abolish(NonTerm, Arity), fail. dcsg(_) :- non_dcsg(Functor, Arity), abolish(Functor, Arity), fail. dcsg(Files) :- abolish(non_dcsg, 2), abolish(non_terminal, 5), dcsg_load(Files). dcsg_load([]) :- !. dcsg_load([File|Files]) :- !, dcsg_load(File), dcsg_load(Files). dcsg_load(File) :- atom(File), nofileerrors, see(File), fileerrors, repeat, read(Term), dcsg_hack(Term), Term = end_of_file, !, seeing(FullName), seen, write(FullName), write(' loaded.'), nl. dcsg_load(File) :- write('! can''t load '), write(File), nl. % dcsg_hack(Term) % processes each term as it is read. Grammar rules are checked a bit, % but ordinary clauses (basically dictionary information) are not, though % it does check that a predicate doesn't have grammar rules and ordinary % clauses both. dcsg_hack(end_of_file) :- !. dcsg_hack((:-Command)) :- !, call(Command). % operator declarations dcsg_hack((Head-->Body)) :- !, ( dcsg_head(Head, Body, Clause), assertz(Clause) ; X=(Head-->Body), numbervars(X,0,_), print(X), nl ), !. dcsg_hack((Head:-Body)) :- !, dcsg_note(clause, Head), assertz((Head :- Body)). dcsg_hack(Head) :- dcsg_note(clause, Head), assertz(Head). % dcsg_note(Type, Head) % is responsible for checking that a predicate doesn't have rules % and clauses both. The main point of the dcsg_note predicate is % really to keep track of nonterminals so that variable nonterminals % and dcsg_phrase can work easily. dcsg_note(clause, Head) :- !, nonvar(Head), functor(Head, Functor, Arity), functor(Term, Functor, Arity), ( non_dcsg(Functor, Arity) ; assert(non_dcsg(Functor, Arity)), ( clause(non_terminal(Term, _, _, _, _), _), write('! Rules and clauses for '), write(Functor/Arity), nl ; true ) ), !. dcsg_note(dcsg, Head/Missing) :- !, dcsg_note(dcsg, Head). dcsg_note(dcsg, Head) :- functor(Head, NonTerm, Arity), functor(Term, NonTerm, Arity), dcsg_args(Term, T0, T, S0, S, Goal), ( clause(non_terminal(Term, T0, T, S0, S), Goal) ; assertz(( non_terminal(Term,T0,T,S0,S) :- Goal )), ( non_dcsg(NonTerm, Arity), write('! Rules and clauses for '), write(NonTerm/Arity), nl ; true ), T0 = Term, T = 0, S0 = S, assertz(( Goal )) ), !. % dcsg_args(NonTerm, T0, T, S0, S, Goal) % adds the extra arguments to the nonterminal to form a proper % Prolog goal. Variables and slash categories are handled elsewhere. dcsg_args(NonT, T0, T, S0, S, Goal) :- NonT =.. [NT|Args], append(Args, [T0, T, S0, S], Full), Goal =.. [NT|Full], !. % dcsg_head(NonTerm, Rhs, Clause), % processes the head of a grammar rule, which takes the form % [/ [^ ]] . % The non-terminal itself may not be a variable, but the other % bit may be. It then hands the Rhs on to dcsg_body to process. dcsg_head(Dud, _, _) :- ( var(Dud) ; Dud = Var/_, var(Var) ; Dud = Var^_ ; Dud = Var^_/_ ), !, write('! Rule head may not be a variable.'), nl, fail. dcsg_head(NonT/Missing^Restriction, Rhs, (Head:-member(Missing,Choices),Body)) :- nonvar(Restriction), !, dcsg_choices(Missing, Restriction, Choices), dcsg_head(NonT, Head, Rhs, Body, Missing, 0). dcsg_head(NonT/Missing, Rhs, (Head:-Body)) :- !, dcsg_head(NonT, Head, Rhs, Body, Missing, 0). dcsg_head(NonT, Rhs, (Head:-Body)) :- dcsg_head(NonT, Head, Rhs, Body, _, _). dcsg_head(NonT, Head, Rhs, Body, T0, T) :- dcsg_note(dcsg, NonT), dcsg_args(NonT, T0, T, S0, S, Head), dcsg_body(Rhs, T0, T, S0, S, Body). % dcsg_body(RHS, , Translation) % translates the RHS of a DCSG grammar rule into Prolog. % The method is all but identical to that for DCGs or XGs. dcsg_body(Var, T0, T, S0, S, non_terminal(Var,T0,T,S0,S,0)) :- var(Var), !. dcsg_body(Var^Poss, T0, T, S0, S, non_terminal(Var,T0,T,S0,S,Choices)) :- !, dcsg_choices(Var, Poss, Choices). dcsg_body((A,B), T0, T, S0, S, Trans) :- !, dcsg_body(A, T0, T1, S0, S1, A1), dcsg_body(B, T1, T, S1, S, B1), dcsg_and(A1, B1, Trans). dcsg_body((A;B), T0, T, S0, S, (A1;B1)) :- !, dcsg_or(A, T0, T, S0, S, A1), dcsg_or(B, T0, T, S0, S, B1). dcsg_body(!, T, T, S, S, !) :- !. dcsg_body({Goals}, T, T, S, S, Goals) :- !. dcsg_body([], T, T, S, S, true) :- !. dcsg_body([Term|Terms], T, T, S0, S, Trans) :- dcsg_list([Term|Terms], S0, S, Trans). dcsg_body(NonTerm/Missing^Restriction, T, T, S0, S, (Goal,member(Restriction,Choices))) :- nonvar(Missing), !, dcsg_choices(Missing, Restriction, Choices), dcsg_args(NonTerm, Missing, 0, S0, S, Goal). dcsg_body(NonTerm/Missing, T, T, S0, S, Goal) :- !, dcsg_args(NonTerm, Missing, 0, S0, S, Goal). dcsg_body(NonTerm, T0, T, S0, S, Goal) :- dcsg_args(NonTerm, T0, T, S0, S, Goal). dcsg_list([Term], S0, S, 'C'(S0, Term, S)) :- !. dcsg_list([Term|Terms], S0, S, ('C'(S0,Term,S1),Trans)) :- dcsg_list(Terms, S1, S, Trans). dcsg_or(A, T0, T, S0, S, A3) :- dcsg_body(A, T1, T, S1, S, A1), ( S \== S1, !, S0 = S1, A2 = A1 ; dcsg_and(S0=S1, A1, A2) ), ( T \== T1, !, T0 = T1, A3 = A2 ; dcsg_and(T0=T1, A2, A3) ). dcsg_and(true, X, X) :- !. dcsg_and(X, true, X) :- !. dcsg_and((X,Y), Z, (X,W)) :- !, dcsg_and(Y, Z, W). dcsg_and(X, Y, (X,Y)). % dcsg_choices([list of nt or nt/arity or goal], [list of goal]) % turns e.g. [a, b/3, c(X,Y), d(X), e/1] into e.g. % [a, b(_,_,_), c(X,Y), d(X), e(_)]. These lists are used to % restrict the range of nonterminal variables. % dcsg_choices(Var, Restriction, Choices) % does the translation, after first checking that the restricted thing % is in fact a Prolog variable. Note that the "existential quantifier" % meaning of "^" is still available inside {escape-to-Prolog} braces. dcsg_choices(Var, Restriction, Choices) :- var(Var), !, dcsg_choices(Restriction, Choices). dcsg_choices(_, _, _) :- write('! Restriction on non-variable.'), nl, fail. dcsg_choices([], []) :- !. dcsg_choices([Head|Tail], [This|Rest]) :- dcsg_choice(Head, This), dcsg_choices(Tail, Rest). dcsg_choice(Atom, Atom) :- atom(Atom), !. dcsg_choice(Functor/Arity, Term) :- !, atom(Functor), integer(Arity), Arity >= 0, functor(Term, Functor, Arity). dcsg_choice(Term, Term) :- nonvar(Term), functor(Term, _, Arity), Arity > 0. % rule out numbers % Variables appearing at the top level of a grammar rule are % turned into calls on nonterminal/6. % Var => non_terminal(Var, T0,T, S0,S, 0). % Var/Poss=> non_terminal(Var, T0,T, S0,S, Choices). % non_terminal/6 checks or ensures that the variable is suitably % bound, and then dispatches through non_terminal/5. Note that % Var/Poss may backtrack through different possibilities. non_terminal(Var, T0, T, S0, S, 0) :- var(Var), !, write('! variable non-terminal at run-time'), nl, fail. % we want a better error signal non_terminal(Var, T0, T, S0, S, 0) :- non_terminal(Var, T0, T, S0, S). non_terminal(Var, T0, T, S0, S, Choices) :- nonvar(Var), !, memberchk(Var, Choices), non_terminal(Var, T0, T, S0, S). non_terminal(Var, T0, T, S0, S, Choices) :- member(Var, Choices), non_terminal(Var, T0, T, S0, S). % dcsg_phrase(Nonterminal, Sentence) % is the dcsg analogue of the standard predicate phrase/2, % can the Sentence be completely parsed as a Nonterminal % with nothing missing? dcsg_phrase(NonTerm, Sentence) :- non_terminal(NonTerm, 0, 0, Sentence, []). /* For debugging DCSG.PL in bare Prolog: append([],L,L). append([H|T],L,[H|R]):-append(T,L,R). member(X,[X|_]). memberchk(X,[X|_]):-!. member(X,[_|T]):-member(X,T). memberchk(X,[_|T]):-memberchk(X,T). /**/ /* For debugging DCSG.PL in ToolKit: */ ppg :- non_dcsg(F,N), pp(F/N), fail ; clause(non_terminal(_,_,_,_,_), Goal), functor(Goal, F, N), pp(F/N), fail ; true.