%%% inp.pl 
%%%
%%% Uebung I
%%% Tom Cornell
%%%
%%% Declaring predicates dynamic will, among other things,
%%% create an empty predicate definition. Therefore calling
%%% connects/3, for example, before any clauses have been
%%% asserted, will fail. Without the dynamic declaration,
%%% Prolog would raise an exception instead of failing.

:- dynamic connects/3.
:- dynamic sentence_length/1.


%%% Sample session:
/*
| ?- [inp].
{consulting /home/server/cornell/courses/cl1/code/inp.pl...}
{/home/server/cornell/courses/cl1/code/inp.pl consulted, 10 msec 408 bytes}

yes
| ?- uebung1.
Sentence, period, return> This could be a sentence.

yes
| ?- | ?- listing(connects/3).

connects(this, 0, 1).
connects(could, 1, 2).
connects(be, 2, 3).
connects(a, 3, 4).
connects(sentence, 4, 5).

yes
| ?- sentencelength(X).

X = 5 ? ;

no
| ?- 

*/


uebung1 :- 
        prompt( _, 'Sentence, period, return> ' ),
        get_sent( S ),
        assert_sent( S ).  % unnecessary, really.




%%% get0/1 doesn't backtrack. So this common style of coding
%%% cannot be used:
/*
read_in( [] ):- get0(0'.).
read_in( [C|Cs] ):-
        get0( C ),
        read_in( Cs ).
*/

%%% get_in( ?list_of_characters )
%%% =============================
%%%
%%% This predicate just collects a list of characters
%%% from the user, terminated by a period.
%%%
get_in( String ):-
        get0( C ),
        (   C = 46 ->  /* '.' */
            String = []
        ;
            String = [C|Rest],
            get_in( Rest )
        ).

%%% read_in( ?list_of_atoms )
%%% =========================
%%%
%%% This is here just for comparison purposes. The
%%% read/1 predicate only works if the user enters a
%%% valid Prolog-parseable term, followed by a period.
%%% An ordinary sentence, with spaces between the words,
%%% is not a valid Prolog term, and will cause this
%%% predicate to crash (not fail, crash).
%%%
read_in( String ):-
        read( Atom ),
        (   Atom = '.' ->
            String = []
        ;
            String = [Atom|Rest],
            read_in( Rest )
        ).

%%% get_word( ?list_of_characters, ?atom )
%%% ===========================================
%%%
%%% This predicate collects characters from the user
%%% a word at a time. A word can be terminated by either
%%% a space or a period (in this example---in real life
%%% you have to reckon with many more word terminators).
%%% The calling predicate (in this case get_sent/1, see
%%% below) will behave differently in either case: in
%%% case the terminator is a space, get_sent/1 will do
%%% some processing and then call get_word/2 again. In
%%% case the Terminator is a period, get_sent/1 will
%%% terminate.
%%%
%%% Note that the Terminator returned is actually an atom,
%%% not the character itself. There is no good reason for
%%% this except perhaps readability in the debugger.
%%%
get_word( Word, Terminator ):-
        get0( C ),
        (   C = 46 ->
            Word = [],
            Terminator = period
        ;
            C = 32 ->
            Word = [],
            Terminator = space
        ;
            Word = [C|Rest],
            get_word( Rest, Terminator )
        ).

%%% get_sent( ?list_of_atoms )
%%% ==========================
%%%
%%% This predicate calls get_word/2 repeatedly. For every
%%% word-length list of characters returned by get_word/2,
%%% get_sent/1 (a) converts the characters to lower case
%%% by calling downcase/2 (see below), then (b) turns the
%%% list of characters into an atom by calling the builtin
%%% name/2, then, depending on the value of Terminator, either
%%% finishes up and exits, or calls itself recursively.
%%%
%%% Note the if-elseif structure, with no final else-clause.
%%% If the terminator is not either a space or a period then
%%% then call to get_sent/1 will fail. I should have put an
%%% error message into the (missing) else clause, but I didn't.
%%%
%%% Some issues:
%%%
%%%    1. Everything is downcased, including, e.g., CIA or DFG.
%%%    2. Punctuation other than spaces and the period are not 
%%%       handled.
%%%    3. The period character used as a decimal point will terminate
%%%       the sentence.
%%%    Address 2 and 3 by checking for the carriage return after
%%%    the period? In text corpora, check for whitespace after periods.
%%%    There's still the problem of people's middle initials...

get_sent( Sentence ):-
        get_word( Word0, Terminator ),
        downcase( Word0, Word ),
        name( Atom, Word ),
        (   Terminator = period ->
            Sentence = [Atom]
        ;
            Terminator = space ->
            Sentence = [Atom|Rest],
            get_sent( Rest )
        ).


%%% downcase( -list_of_charcodes, ?list_of_charcodes )
%%% ==================================================
%%%
%%% This maps a list of character codes to another list
%%% by converting all upper case letters to lower case
%%% (and just passing the letters already in lower case
%%% through). Note the use of 0'a and 0'A in an 
%%% arithmetic expression. This is very useful!
%%%
downcase( [], [] ).
downcase( [Code|Codes], [Lower|Lowers] ):-
        (   Code >= 0'A,
            Code =< 0'Z ->
            Lower is Code + (0'a - 0'A)
        ;
            Lower is Code
        ),
        downcase( Codes, Lowers ).


%%% assert_sent( -list_of_words ),
%%% assert_sent_aux( -list_of_words, -integer, ?integer )
%%% =====================================================
%%%
%%% This is the last part of the Uebung. It just 
%%% cleans up the database by calling retractall/2,
%%% then calls assert_sent_aux/3 to actually assert
%%% the needed connects/3 clauses. 
%%%
%%% The 2nd and 3rd arguments of assert_sent_aux/3 form
%%% an accumulator pair. The 2nd argument is the accumulator
%%% on calling assert_sent_aux/3 it contains the point
%%% just before the current word (the head of the list
%%% in argument 1); then it is incremented, both values
%%% are used to build a clause for connects/3, and the 
%%% second value is passed to the recursive call as its
%%% 2nd argument. In the base case, the accumulator is 
%%% passed over to the output channel.
%%%
%%% The final value of the accumulator is then used by
%%% assert_sent/1 to construct a clause for 
%%% sentencelength/1, which is then asserted. We're done.
%%%
assert_sent( Sentence ):-
        retractall( connects(_,_,_) ),
        retractall( sentencelength(_) ),
        assert_sent_aux( Sentence, 0, N ),
        assert( sentencelength( N ) ).

assert_sent_aux( [], N, N ).
assert_sent_aux( [Word|Words], Point0, Point ) :-
        Point1 is Point0 + 1,
        assertz( connects(Word,Point0,Point1) ),
        assert_sent_aux( Words, Point1, Point ).
