Question

I am trying to write a small program in Prolog(gnu) that will take user input and give true or false to the question "Is this a valid sentence in this grammar?" I have had a lot of trouble finding solid documentation on Prolog, and if anyone has a reliable source that would be appreciated. From what I have found, this code for the most part should work. When I try to trace the execution, I get strange results that I do not understand.

The test case I am working with now is when the user inputs 'a = ( b + c )'. The problem is that removing the last item from my list is causing a redo loop and I am unsure why.

% Grammar:
% <assign> → <id> = <expr>
% <expr> → <id> <op> <expr>
%        | ( <expr> )
%        | <id>  
% <op> → * | + | / | - 
% <id> → a | b | c 

op('+').
op('-').
op('*').
op('/').


id(a).
id(b).
id(c).
id('a').
id('b').
id('c').
id([a]).
id([b]).
id([c]).



%
% Determine if the Sentence is valid according to the grammar
%
% param (in): Sentence - A list of symbols that make up the sentence to be evaluated
%
assign(Sentence) :-
  list(Sentence), length(Sentence, Length), >=(Length, 3),   %precondition test
  =(Sentence, [First, Second | Tail] ),
  id(First),
  =(Second, '='),
  expr(Tail).

% Test if the list of symbols is a valid expression
expr(X) :-
  length(X, Length), Length >= 1,
  =(X, [First | Tail] ),

  ( Length = 1 ->
      id(X)

  ; Length >= 3, =(First,'(') ->
    =(Tail, [Second | Tail2] ),
    last(Last, Tail),
    append(Middle, [_], Tail),

    =(Last, ')'),
    expr(Middle)

  ; =(Tail, [Second | Tail2] ),
    id(First), 
    op(Second),
    expr(Tail2)
  ).

%
% Input a sentence, then decompose it into a list of symbols. 
% User input should be quoted, e.g., Enter a sentence: 'A = B * C'
%
% param (out): SentenceList  The list of symbols input by the user is bound to this variable
%
read_sentence(SentenceList) :-
   print('Enter a sentence: '),
   read_token(Input),
   write_term_to_chars(InputList, Input, []),
   delete(InputList, ' ', SentenceList).

%
% Start the program
%
start :-
  read_sentence(Sentence),
  assign(Sentence).
Was it helpful?

Solution

Prolog has a syntax that make simpler symbolic processing. Here is how I would wrote the code:

assign([First, '=' | Expr]) :-
    id(First),
    expr(Expr).

expr([First | Tail]) :-
    id(First), 
    right(Tail).
expr(['(' | Rest]) :-
    append(Expr, [')'|Follow], Rest),
    expr(Expr),
    right(Follow).

right([]).
right([Op|Expr]) :-
    op(Op),
    expr(Expr).

note the use of pattern matching, instead of the procedural check based on length/2, etc

assign([First, '=' | Expr]) :-
...

this means: use this clause only if the argument is a list with '=' in second position and the tail is a list we name Expr.

(about syntax: often we don't need to quote atoms, but the rules are somewhat complex. For instance, this is a valid query

?- assign([c,=,a,+,b]).

No need to quote atoms here, so the code could be assign([First, = | Expr]) :- ...)

Then the body issues the appropriate check for first list'element (i.e. id(First)) and the Expr.

To get the Expr between parenthesis, I used an idiomatic approach

expr(['(' | Rest]) :-
    append(Expr, [')'|Follow], Rest),
...

this append/3 can succeed only if Rest contains an Expr followed by ')'.

I think your original approach missed the right(Follow). We need it because the grammar is recursive, after an operator...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top