AI-Prolog

 view release on metacpan or  search on metacpan

data/sleepy.pro  view on Meta::CPAN

/* "Sleepy" -- a sample adventure game, by David Matuszek. */

% http://www.csc.vill.edu/~dmatusze/resources/prolog/sleepy.html
/* In standard Prolog, all predicates are "dynamic": they
   can be changed during execution. SWI-Prolog requires such
   predicates to be specially marked. */

% :- dynamic at/2, i_am_at/1, i_am_holding/1, alive/1,
%           lit/1, visible_object/1.

/* This routine is purely for debugging purposes. */

%dump :- listing(at), listing(i_am_at), listing(i_am_holding),
%        listing(alive), listing(lit), listing(visible_object).

lib/AI/Prolog.pm  view on Meta::CPAN

 my $list   = $prolog->list(qw/a b c d/);
 $prolog->query("append(X,Y,[$list]).");
 while (my $result = $prolog->results) {
     print Dumper $result;
 }

=head1 ABSTRACT

 AI::Prolog is merely a convenient wrapper for a pure Perl Prolog compiler.
 Regrettably, at the current time, this requires you to know Prolog.  That will
 change in the future.

=head1 EXECUTIVE SUMMARY

In Perl, we traditionally tell the language how to find a solution.  In logic
programming, we describe what a solution would look like and let the language
find it for us.

=head1 QUICKSTART

For those who like to just dive right in, this distribution contains a Prolog

lib/AI/Prolog.pm  view on Meta::CPAN

Prolog term (and not worry about it accidentally being treated as a variable if
it begins with an upper-case letter).

 my $perl6 = AI::Prolog->quote('Perl 6'); # returns 'Perl 6' (with quotes)
 $prolog->query(qq'can_program("ovid",$perl6).');

At the present time, quoted strings may use single or double quotes as strings.
This is somewhat different from standard Prolog which treats a double-quoted
string as a list of characters.

Maybe called on an instance (the behavior is unchanged).

=head2 C<list(@list)>.

Turns a Perl list into a Prolog list and makes it suitable for embedding into
a program.  This will quote individual variables, unless it thinks they are
a number.  If you wish numbers to be quoted with this method, you will need to
quote them manually.

This method does not add the list brackets.

 my $list = AI::Prolog->list(qw/foo Bar 7 baz/);
 # returns:  'foo', 'Bar', 7, 'baz'
 $prolog->query(qq/append(X,Y,[$list])./);

May be called on an instance (the behavior is unchanged).

=head1 INSTANCE METHODS

=head2 C<do($query_string)>

This method is useful when you wish to combine the C<query()> and C<results()>
methods but don't care about the results returned.  Most often used with the
C<assert(X)> and C<retract(X)> predicates.

 $prolog->do('assert(loves(ovid,perl)).');

lib/AI/Prolog.pm  view on Meta::CPAN


 while (my $result = $prolog->results) {
     # [ 'grandfather', $ancestor, 'julie' ]
     print "$result->[1] is a grandfather of julie.\n";
 }

If C<raw_results> is false, the return value will be a "result" object with
methods corresponding to the variables.  This is currently implemented as a
L<Hash::AsObject|Hash::AsObject> so the caveats with that module apply.

Please note that this interface is experimental and may change.

 $prolog->query('steals("Bad guy", STUFF, VICTIM)');
 while (my $r = $prolog->results) {
     print "Bad guy steals %s from %s\n", $r->STUFF, $r->VICTIM;
 }

See C<raw_results> for an alternate way of generating output.

=head1 BUGS

lib/AI/Prolog/Article.pod  view on Meta::CPAN

 ?- gives(WHO, book, SOMEONE).

This query looks like the previous one, but since the first argument is
capitalized, we're asking "who will give books to whom?".

 ?- gives(WHO, WHAT, WHOM).

Finally, because all arguments are variables, we're asking for everyone who
will give anything to anybody.

Note that no code changes are necessary for any of this.  Because Prolog facts
and rules define relationships between things, Prolog can automatically infer
additional relationships if they are logically supported by the program.

Let's take a closer look at this, first focusing on the C<aiprolog> shell.
Assuming you've installed C<AI::Prolog> and said "yes" to installing the
C<aiprolog> shell, enter the following text in a file and save it as
I<gives.pro>.  Note that lines beginning with a percent sign (C<%>) are
single-line comments.

 % who are people?

lib/AI/Prolog/Article.pod  view on Meta::CPAN

 
 = Goals: 
         male(sally)
 ==> Try:  male(tim) :- null
  <<== Backtrack: 

 [etc.]

Now if you really want to have fun with it, notice how you can rearrange the
clauses in the program at will and Prolog will return the same results (though
the order will likely change).  This is because when one programs in a purely
declarative style, the order of the statements no longer matters.  Subtle bugs
caused by switching two lines of code usually go away.

=head2 Prolog versus Perl

Now that you have a beginning understanding of what Prolog can do and how it
works internally, let's take a look at some of the implications of this.  By
now, you know that the following can be read as "Ovid loves Perl":

 loves(ovid, perl).

lib/AI/Prolog/Article.pod  view on Meta::CPAN


Oh, wait.  This fails too.  You see, I'm fickle.

 loves(ovid, perl).
 loves(ovid, prolog).
 loves(ovid, 'Perl 6').

(The quotes around "Perl 6" tell Prolog that this is a single value and that
it's a constant, not a variable.)

How do I find out everything Ovid loves?  In Prolog, the query doesn't change:

 loves(ovid, WHAT).

In Perl, our original hash wasn't enough.

 my %loves = (
     ovid    => [ qw/perl prolog Perl6/ ],
     alice   => [ 'perl' ],
     bob     => [ 'perl' ],
     charlie => [ 'perl' ],

lib/AI/Prolog/Article.pod  view on Meta::CPAN


How would you represent that in Perl?  There could be multiple gift-givers,
each gift-giver could give multiple things.  There can be multiple recipients.
Representing this cleanly in Perl would not be easy.  In fact, when handling
relational data, Perl has several weaknesses in relation to Prolog.

=over 4

=item * Data must often be duplicated to handle bi-directional relations.

=item * You must remember to synchronize data structures if the data changes.

=item * Often you need to change your code if your relations change.

=item * The code can get complex, leading to more bugs.

=back

=head2 Prolog versus SQL

At this point, there's a good chance that you're thinking that you would just
stuff this into a relational database. Of course, this assumes you need
relational data and want to go to the trouble of setting up a database and

lib/AI/Prolog/Builtins.pod  view on Meta::CPAN

right to rent videos if they have overdue videos, you might use the cut
operator as soon as you see they have any overdue video.  The fact that they
have more than one overdue video doesn't matter.

See the C<cut.pl> program in the C<examples/> directory that comes with this
distribution.

=item assert/1

Add new facts to the database.  Only facts can be added, not rules.  This may
change in the future.  See C<retract(X)>.

 assert(loves(ovid,perl)).

=item call/1

Invokes C<X> as a goal.

=item consult/1

Supplied the name of a file containing Prolog code, this will consult the

lib/AI/Prolog/Builtins.pod  view on Meta::CPAN

Same as C<print(Term)>, but automatically prints a newline at the end.

=item pow/2

Succeeds if both terms are bound.  The value of the term is X ** Y 
(X raised to the Y power).
Use with C<is(X,Y)>.

=item retract/1

Remove facts from the database.  You cannot remove rules.  This may change in
the future.  See C<assert(X)>.

 retract(loves(ovid,java)).

=item trace/0

Turns on tracing of Prolog's attempt to satisfy goals.

=item true/0

lib/AI/Prolog/Builtins.pod  view on Meta::CPAN


Omit the period after the number or put a zero after it:

 X is 5.0 + 7.
 X is 5 + 7.

Because numbers use Perl scalars, you may mix types (ints and floats) and they
will behave as you expect in Perl.

Precedence is C<*> and C</>, left to right, followed by C<+> and C<->, left to
right followed by C<%>, left to right.  (I probably should change that.)
Naturally, parentheses may be used for grouping:

 X is 3 * 5 + 2.   % is(X, plus(mult(3, 5), 2)).
 X is 3 * (5 + 2). % is(X, mult(3, plus(5, 2))).

When using math, note that C<is> is similar to Perl's assignment operator, C<=>.
This can be confusing.

 X is 3 + 2.

lib/AI/Prolog/Engine/Primitives.pm  view on Meta::CPAN

        &{'---'};
    };
    my $e = $@;

    # Undefined subroutine &main::--- called at .../Primitives.pm line 12.
    my ($msg) = $e =~ / \A
                        (.+)    # 'Undefined subroutine'
                        (?<=\s) # ' '
                        \S*     # &main::
                        ---/mx
        or die q[Perl's error message changed! Damn! Fix this regex.];

    $msg;
};

$PRIMITIVES[34] = sub {    # perlcall2/2
    my ( $self, $term ) = @_;

    # Get a function name...
    my $function_term = $term->getarg(0);
    if ( not $function_term->is_bound ) {

t/40parser.t  view on Meta::CPAN

can_ok $parser, 'current';
is $parser->current, ' ', '... and it should return the current character the parser is pointing at';

can_ok $parser, 'advance';
$parser->advance;
is $parser->_posn, 1, '... and calling advance will move the parser forward one character';

can_ok $parser, 'skipspace';
$parser->skipspace;
is $parser->current, 'p', '... and calling skipspace will move the parser to the next non-whitespace character';
is $parser->_start, 0, '... and it will not change the starting position';
is $parser->_posn, 3, '... but the position will indicate the new position';

can_ok $parser, 'peek';
is $parser->peek, '(', '... and calling it should return the next character';
is $parser->current, 'p', '... and leave the current character intact';
is $parser->_start, 0, '... and it will not change the starting position';
is $parser->_posn, 3, '... or the current position';

$parser = $CLASS->new('  /* comment */ p(x)');
$parser->skipspace;
is $parser->current, 'p', 
    'skipspace() should ignore multiline comments';
is $parser->_start, 0, '... and it will not change the starting position';
is $parser->_posn, 16, '... but the position will indicate the new position';

eval {$CLASS->new('/* this is an unterminated comment')->skipspace};
ok $@, 'skipspace() should die if it encounters a comment with no end';
like $@, qr{Expecting terminating '/' on comment},
    '... with an appropriate error message';

eval {$CLASS->new('/ * this is an unterminated comment')->skipspace};
ok $@, 'skipspace() should die if it encounters a poorly formed comment';
like $@, qr{Expecting '\*' after '/'},
    '... with an appropriate error message';

$parser = $CLASS->new(<<'END_PROLOG');
    % this is a comment
    flies(pig, 789).
END_PROLOG
$parser->skipspace;
is $parser->current, 'f', 
    'skipspace() should ignore single line comments';
is $parser->_start, 0, '... and it will not change the starting position';
is $parser->_posn, 28, '... but the position will indicate the new position';

can_ok $parser, 'getname';
is $parser->getname, 'flies', 
    '... and it should return the name  we are pointing at';
is $parser->current, '(',
    '... and the current character should be the first one after the name';
is $parser->_start, 28, '... and have the parser start at that name';
is $parser->_posn, 33, 
    '... and have the posn point to the first char after the name';

t/70builtins.t  view on Meta::CPAN

is $prolog->results, 'p(ovid)', 'call(X) should behave correctly';

#
# I think it's failing because the call contains an if and
# if is defined in terms of once() and once is defined with
# a cut and I don't quite have the cut correct
#

my $boostrap_db = clone($database);
eq_or_diff $database, $boostrap_db,
    '... and the database should not change after its bootstrapped';

$prolog->query("q(X)");
is $prolog->results, 'q(rubies)',
    '... even if called with a variable';
    
$prolog->query('eq(this,this)');
is $prolog->results, "eq(this, this)",
    'eq(X,Y) should succeed if the predicate are equal';

$prolog->query("eq(this,that).");

t/70builtins.t  view on Meta::CPAN

$prolog->do("assert(loves(sally, X))");
$prolog->query('loves(sally,Y)');
is $prolog->results, 'loves(sally, A)',
    '... and we should be able to assert a fact with a variable';

$prolog->query('loves(sally, food)');
is $prolog->results, 'loves(sally, food)',
    '... and it should behave as expected';
$prolog->query('loves(sally,X)');
is $prolog->results, 'loves(sally, A)',
    '... and the asserted fact should remain unchanged.';

$prolog->do("retract(loves(ovid,perl)).");
$prolog->query("loves(ovid,X)");
ok ! $prolog->results,
    "retract(X) should remove a fact from the database";

my @test_me;
sub test_me {
    my ( $first, $second, $third, $fourth ) = @_;
    @test_me = ( "\L$first", "\U$second", "\u$third", $fourth );

t/80preprocessor.t  view on Meta::CPAN

my $CLASS;
BEGIN
{
    chdir 't' if -d 't';
    unshift @INC => '../lib';
    $CLASS = 'AI::Prolog::Parser::PreProcessor';
    use_ok($CLASS) or die;
}

can_ok $CLASS, 'process';
is $CLASS->process('foo.'), 'foo.', '... and it should return standard Prolog unchanged.';

my $prolog = <<END_PROLOG;
factorial(N,F) :-
    N > 0,
    N1 is N-1,
    factorial(N1,F1), 
    F is N*F1.
factorial(0,1). 
END_PROLOG



( run in 0.723 second using v1.01-cache-2.11-cpan-c333fce770f )