% Example program for the course STD VT98
% This program performs some simple grammar checks for phrases or
% sentences. This version only checks if neuter determiners are
% followed by a nonneuter noun or if nonneuter determiners are
% followed by a neuter noun for Swedish.
% Usage: start Prolog, load the file and run grammarCheck('abc def').
%        where 'abc def' is a phrase or a sentence.
% Note:  the input should be specified in lower case characters only,
%        with spaces between the words (no tabs) and without any
%        punctuation marks.
% 980302 erik.tjong@ling.uu.se

% lexicon/2: define words and their grammatical features
% arguments: Word: word (string)
%            Features: list of grammatical features
lexicon(det,determiner([gender(neuter),number(singular)])).
lexicon(den,determiner([gender(common),number(singular)])).
lexicon(ett,determiner([gender(neuter),number(singular)])).
lexicon(en,determiner([gender(common),number(singular)])).
lexicon(man,noun([gender(common),number(singular)])).
lexicon(lejon,noun([gender(neuter),number(singular)])).

% grammarCheck/1: list grammar errors of the sentence
% arguments:      Sentence: input sentence
grammarCheck(Sentence):-
   % divide the sentence in characters
   name(Sentence,SentenceChars),
   % divide the sentence in words
   getWords(SentenceChars,[],Words),
   % parse the sentence and list the errors
   parse(Words,0).

% getWords/3: divide a sentence in words
% arguments:  SentenceChars: input sentence (list of characters)
%             Word: input current word (list of characters)
%             Words: output current words
getWords([S|SentenceChars],[W|Word],[WordString|Words]):-
   % if the first character is a space
   name(' ',[S]),
   % since we have a complete non-empty word we will store it in the
   % output list of words: convert it to a string
   name(WordString,[W|Word]),
   % process the rest of the sentence
   getWords(SentenceChars,[],Words).
getWords([S|SentenceChars],[],Words):-
   % if the first character is a space
   name(' ',[S]),
   % the current current word is empty we will just skip the space and
   % continue with processing the rest of the sentence
   getWords(SentenceChars,[],Words).
getWords([S|SentenceChars],Word,Words):-
   % if the first character is a nonspace character
   name(' ',[Space]),
   S \== Space,
   % combine this first character with the current word
   append(Word,[S],Word1),
   % process the rest of the sentence
   getWords(SentenceChars,Word1,Words).
getWords([],[W|Word],[WordString]):-
   % the sentence is empty but we have built a word: store it in the
   % output datastructure after converting it to a string
   name(WordString,[W|Word]).
getWords([],[],[]).

% parse/2:   detect errors in a list of words and report them
% arguments: Words: input phrase/sentence: list of words (strings)
%            ErrorCounter: input number of error found already
parse([Word1,Word2|Words],ErrorCounter):-
   % if the first word is a determiner
   lexicon(Word1,determiner(Features1)),
   % and if the second word is a noun
   lexicon(Word2,noun(Features2)),
   ((% test if the words habe the name gender 
     member(gender(G),Features1),
     member(gender(G),Features2),
     % no error found: continue with the same error counter value
     ErrorCounter1 is ErrorCounter);
    (% we have found an agreement error: report it
     writeln([Word1,' ',Word2,' : gender error.']),
     % increase the error counter
     ErrorCounter1 is ErrorCounter+1)),
   % we do not want to parse this phrase again: prevent backtracking
   !,
   % parse the rest of the sentence
   parse([Word2|Words],ErrorCounter1).
parse([_|Words],ErrorCounter):-
   % we do not have error information for the first word: skip it
   % and parse the rest of the sentence
   parse(Words,ErrorCounter).
parse([],ErrorCounter):-
   % done: report the number of errors found
   writeln([ErrorCounter,' errors found.']).

% member/2:  check if a term is a member of a list
% arguments: X: term
%            Ys: list of terms
member(X,[X|_]).
member(X,[_|Ys]):-
   member(X,Ys).

% writeln/1: write a list of terms
% argument:  X: list of strings
% source: Leon Sterling and Ehud Shapiro, "The Art of Prolog -
%         Advanced Programming Techniques". The MIT Press, 1986. 
%         ISBN 0-262-69105-1. Page 17.
writeln([X|Xs]):-
   write(X),
   writeln(Xs).
writeln([]):-nl.

% append/3:  append two strings
% arguments: Xs: first list
%            Ys: second list
%            Zs: combination of first and second list
append([],Ys,Ys).
append([X|Xs],Ys,[X|Zs]):-
   append(Xs,Ys,Zs).
