I want to parse a logical expression using DCG in Prolog.
The logical terms are represented as lists e.g. ['x','&&','y']
for x ∧ y
the result should be the parse tree and(X,Y)
(were X
and Y
are unassigned Prolog variables).
I implemented it and everything works as expected but I have one problem:
I can't figure out how to parse the variable 'x'
and 'y'
to get real Prolog variables X
and Y
for the later assignment of truth values.
I tried the following rule variations:
v(X) --> [X].
:
This doesn't work of course, it only returns and('x','y')
.
But can I maybe uniformly replace the logical variables in this term with Prolog variables? I know of the predicate term_to_atom
(which is proposed as a solution for a similar problem) but I don't think it can be used here to achieve the desired result.
v(Y) --> [X], {nonvar(Y)}.
:
This does return an unbound variable but of course a new one every time even if the logical variable ('x','y',...) was already in the term so
['X','&&','X']
gets evaluated to and(X,Y)
which is not the desired result, either.
Is there any elegant or idiomatic solution to this problem?
Many thanks in advance!
EDIT:
The background to this question is that I'm trying to implement the DPLL-algorithm in Prolog. I thought it would by clever to directly parse the logical term to a Prolog-term to make easy use of the Prolog backtracking facility:
[x,'&&',y]
[G_123,'&&',G_456]
(now featuring "real" Prolog variables)v
is found so that v(T) = t
or the search space is depleted.I'm pretty new to Prolog and honestly couldn't figure out a better approach. I'm very interested in better alternatives! (So I'm kinda half-shure that this is what I want ;-) and thank you very much for your support so far ...)
Parsing may be implemented in a straightforward manner by creating one predicate for each non-terminal in the grammar. These predicates will take as an argument a list of items representing a possible instance of the non-terminal, having the value true if a given phrase is such an instance, false otherwise.
A Prolog definite clause grammar (DCG) describes a list. Operationally, DCGs can be used to parse, generate and check lists. A DCG is defined by rules. A DCG rule has the form: Head --> Body. A rule's body consists of terminals and nonterminals. A terminal is a list, which stands for the elements it contains.
'C' ( [X|L], X, L). Here, the Prolog's DCG (definite clause grammar) predicate 'C' (L1, X, L2) is introduced to say that L1 begins with terminal symbol X after which L2 remains to be parsed. The following examples illustrate that parsing an expression as the initial prefix of an input list may indeed have several correct answers.
The translation of DCGs to Prolog code is done by term_expansion/2, a mechanism analogous to macros in other languages. For portability, it is best not to rely on a particular expansion method. Instead, use semicontext notation to refer to states and always use the phrase/ [2,3] interface to invoke a DCG.
When parsing with DCGs, using ('|')/2 is preferable over (;)//2 due to existing conventions for writing grammars. seq//1, seqq//1 and ... //0 are very versatile nonterminals and can be used to elegantly solve many tasks that involve reasoning over lists.
You want to associate ground terms like x
(no need to write 'x'
) with uninstantiated variables. Certainly that does not constitute a pure relation. So it is not that clear to me that you actually want this.
And where do you get the list [x, &&, x]
in the first place? You probably have some kind of tokenizer. If possible, try to associate variable names to variables prior to the actual parsing. If you insist to perform that association during parsing you will have to thread a pair of variables throughout your entire grammar. That is, instead of a clean grammar like
power(P) --> factor(F), power_r(F, P).
you will now have to write
power(P, D0,D) --> factor(F, D0,D1), power_r(F, P, D1,D).
% ^^^^ ^^^^^ ^^^^
since you are introducing context into an otherwise context free grammar.
When parsing Prolog text, the same problem occurs. The association between a variable name and a concrete variable is already established during tokenizing. The actual parser does not have to deal with it.
There are essentially two ways to perform this during tokenization:
1mo collect all occurrences Name=Variable
in a list and unify them later:
v(N-V, [N-V|D],D) --> [N], {maybesometest(N)}.
unify_nvs(NVs) :-
keysort(NVs, NVs2),
uniq(NVs2).
uniq([]).
uniq([NV|NVs]) :-
head_eq(NVs, NV).
uniq(NVs).
head_eq([], _).
head_eq([N-V|_],N-V).
head_eq([N1-_|_],N2-_) :-
dif(N1,N2).
2do use some explicit dictionary to merge them early on.
Somewhat related is this question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With