% Prolog representation of a grammar to ask a query of a database
%  This is not meant to be polished or linguistically reasonable, but purely to show what can be done

% This is slightly expanded code of Figure 15.10 and Examples 15.35 and 15.36 in Section 15.7.3 of
% Poole and Mackworth, Artificial Intelligence: foundations of
% computational agents, 3rd Edition, Cambridge, 2023

% Copyright (c) 2024 David Poole and Alan Mackworth. This program is released under
% CC Attribution-NonCommercial-ShareAlike  License http://creativecommons.org/licenses/by-nc-sa/4.0/

% Load using:
%?- [geography_QA].
?- [geography_DB].

% noun_phrase(L0,L4,Ind) is true if
%  L0 and L4 are list of words, such that
%        L4 is an ending of L0
%        the words in L0 before L4 (written L0-L4) form a noun phrase
%  Ind is an individual that the noun phrase is referring to

% A noun phrase is a determiner followed by adjectives followed
% by a noun followed by an optional modifying phrase:
noun_phrase(L0, L4, Ind) :-
    det(L0, L1, Ind), 
    adjectives(L1, L2, Ind),
    noun(L2, L3, Ind),
    omp(L3, L4, Ind).
noun_phrase([N | L], L, E) :- name(E, N).

% Try:
%?- noun_phrase(["a", "Spanish", "speaking", "country"], L1, I1).
%?- noun_phrase(["a", "country", "that", "borders", "Chile"], L2, I2).
%?- noun_phrase(["a", "Spanish", "speaking", "country", "that", "borders", "Chile"], L3, I3).

% Determiners (articles) are ignored in this oversimplified example.
% They do not provide any extra constraints.
det(["the" | L], L, _).
det(["a" | L], L, _).
det(L, L, _).


% adjectives(L0, L1, Ind) is true if 
% L0-L1 is a sequence of adjectives that true of Ind
adjectives(L0, L2, Ind) :-
    adj(L0, L1, Ind), 
    adjectives(L1, L2, Ind).
adjectives(L, L, _).

% A  modifying phrase / relative clause is either
% a relation (verb or preposition) followed by a noun_phrase or
% 'that' followed by a relation then a noun_phrase 
mp(L0, L2, Subject) :-
    reln(L0, L1, Subject, Object), 
    aphrase(L1, L2, Object).
mp(["that" | L0], L2, Subject) :-
    reln(L0, L1, Subject, Object), 
    aphrase(L1, L2, Object).

% An optional modifying phrase is either a modifying phrase or nothing
omp(L0,L1,E) :-
    mp(L0,L1,E).
omp(L, L, _).

% a phrase is a noun_phrase or a modifying phrase
% note that this uses 'aphrase' because 'phrase' is a static procedure in SWI Prolog
aphrase(L0, L1, E) :- noun_phrase(L0, L1, E).
aphrase(L0, L1, E) :- mp(L0, L1, E).

% DICTIONARY
% adj(L0, L1, Ind) is true if L0-L1 
% is an adjective that is true of Ind
adj(["large" | L], L, Ind) :- large(Ind).
adj([LangName, "speaking" | L], L, Ind) :- language(Ind, Lang), name(Lang, LangName).


noun(["country" | L], L, Ind) :- country(Ind).
noun(["city" | L], L, Ind) :- city(Ind).


% reln(L0,L1,Sub,Obj) is true if L0-L1 is a relation on individuals Sub and Obj
reln(["borders" | L], L, Sub, Obj) :- borders(Sub, Obj).
reln(["bordering" | L], L, Sub, Obj) :- borders(Sub, Obj).
reln(["next", "to" | L], L, Sub, Obj) :- borders(Sub, Obj).
reln(["the", "capital", "of" | L], L, Sub, Obj) :- capital(Obj, Sub).
reln(["the", "name", "of" | L], L, Sub, Obj) :- name(Obj, Sub).

% question(Question, QR, Ind) is true if Ind is  an answer to Question
question(["Is" | L0], L2, Ind) :-
    noun_phrase(L0, L1, Ind), 
    mp(L1, L2, Ind).
question(["What", "is" | L0], L1, Ind) :-
    aphrase(L0, L1, Ind).
question(["What" | L0], L2, Ind) :-
    noun_phrase(L0, L1, Ind), 
    mp(L1, L2, Ind).
question(["What" | L0], L1, Ind) :-
    mp(L0, L1, Ind).

% ask(Q, A) gives answer A to question Q
ask(Q, A) :-
    question(Q, [], A).




/* Try the following queries:
?- ask(["What", "is", "a", "country"], A).
?- ask(["What", "is", "a", "Spanish", "speaking", "country"], A).
?- ask(["What", "is", "the", "name", "of", "a", "Spanish", "speaking", "country"], A).
?- ask(["What", "large", "country",  "bordering", "Paraguay", "borders", "Chile"], A).
?- ask(["What", "is", "the", "name", "of", "a", "large", "Spanish", "speaking", "country"], A).
?- ask(["What", "is", "the", "capital", "of", "Chile"], A).
?- ask(["What", "is", "the", "name", "of", "the", "capital", "of", "a", "country"], A).
?- ask(["What","borders", "Chile"], A).
?- ask(["What", "is", "the", "name", "of", "a", "country", "that", "borders", "Chile"], A).
?- ask(["What", "is",  "a", "country", "that", "borders", "a", "country", "that", "borders", "Chile"], A).
?- ask(["What", "country",  "bordering", "Chile", "borders", "Paraguay"], A).

Is the following correct?
?- noun_phrase(["large", "country",  "bordering", "Peru", "borders", "Chile"], L, A).

*/


% To get the input from a line:

q(Ans) :-
    write("Ask me: "), flush_output(current_output), 
    read_line_to_string(user_input, St),
    notin(St, ["quit", "quit.", "q", "q."]), % quit or q ends interaction
    split_string(St, " -", " ,?.!-", Ln), % ignore punctuation
    (ask(Ln, Ans)  
    ;  write("No more answers\n"),  % ; is "or"
      q(Ans)).


/*  TRY SOME OF THE FOLLOWING after loading geography_DB.pl
?- q(Ans).
Ask me: What is a country that borders Chile?
Ans = argentina ;
Ans = peru ;
No more answers
Ask me: What is the name of the capital of a Spanish speaking country that borders Argentina?
Ans = "Santiago" ;
Ans = "Asunción" ;
No more answers
Ask me: 

Some more questions (including ungrammatical ones):
What is next to Chile?
Is Brazil next to Peru?
What is a country that borders a country that borders Chile?
What is bordering a country bordering Chile?
What is borders Chile?
What borders Chile?
What country borders Chile?
What country that borders Chile is next to Paraguay?
What country that borders Chile next to Paraguay?
What is the name of the capital of a country that borders Chile?
What is the name of the capital of a large country that borders Chile?

% What is wrong with these?
What country bordering Brazil borders Peru?
What country bordering Brazil borders Chile?
What is a Portugese-speaking country bordering Peru?
What is a Portugese-speaking country bordering Brazil?
What biof sifj gjs jdjfjf and other nonsense

*/

% notin(E,L) is true if E is not in list L. Allows for E or elements of L to be variables.
notin(_,[]).
notin(E,[H|T]) :-
    dif(E,H),
    notin(E,T).
