AI-Prolog

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

0.71  Sat June 25, 2005
      Removed _failgoal in Engine.pm.  We now backtrack if we need
        new results.
      Added several new regression tests.
      Fixed bug where code was not warning on unknown predicates.
        (Doug Wilson)
      Fixed bug where tracing fails when it tries to print a non-
        existent "next_clause".  (Doug Wilson)
      retract/1 now works correctly.  (Doug Wilson)
      Fixed unification bug that crept in in .62.
      Added data/sleepy.pro, a game that wasn't working because of
        the retract/1 bug.

0.7   Mon June 20, 2005 (Happy Birthday to me)
      Parsing errors now attempt to report the line number of the
        error.
      Added new predicates:
        consult/1
        listing/1
        ne/2
      Updated the license information display for aiprolog shell.

Changes  view on Meta::CPAN

      Added tests for the cut, number, and clause classes.

      Added stub tests for step and primitive classes.

      Ensured that all classes at least had stub documentation and added
      POD tests.

      Updated the TODO list in AI::Prolog.

0.5   Sun Feb 13, 2005
      Put the database into its own class.

      Pulled engine backtracking out into its own method.

      Reduced the scope of many of the Engine::_run variables to ensure data
        wasn't leaking.

      Added simple "aiprolog" shell.

      Added cut operator, retract(X), and assert(X).

      Added more examples and simple adventure game to both demonstrate the shell
        and how Prolog works.

      Completely reworked the engine internals to allow new predicates to be
        easily added.

      * XXX Also screwed up the numbering scheme.  Grumble XXX *

0.04  Sun Jan 30, 2005
      Added support for quoting terms.  
      
      Added Engine->formatted method which allows results to be returned as
        either strings or as data structures.  The latter is more useful.

      When using AI::Prolog, the results() method now returns a "results"
        object.

      The AI::Prolog interface has been cleaned up and use of the supporting
        modules is now officially discouraged.

      Massive documentation update.

0.03  Sun Jan 23, 2005

MANIFEST  view on Meta::CPAN

bin/aiprolog
Changes
data/sleepy.pro
data/spider.pro
examples/append.pl
examples/benchmark.pl
examples/cut.pl
examples/data_structures.pl
examples/hanoi.pl
examples/if_else.pl
examples/member.pl
examples/monkey.pl
examples/path.pl
examples/schedule.pl
examples/trace.pl
lib/AI/Prolog.pm
lib/AI/Prolog/Article.pod
lib/AI/Prolog/Builtins.pod

MANIFEST  view on Meta::CPAN

lib/AI/Prolog/Parser/PreProcessor/Math.pm
lib/AI/Prolog/Term.pm
lib/AI/Prolog/Term/Cut.pm
lib/AI/Prolog/Term/Number.pm
lib/AI/Prolog/TermList.pm
lib/AI/Prolog/TermList/Clause.pm
lib/AI/Prolog/TermList/Primitive.pm
lib/AI/Prolog/TermList/Step.pm
Makefile.PL
MANIFEST			This list of files
META.yml			Module meta-data (added by MakeMaker)
README
t/01pod.t
t/05examples.t
t/10choicepoint.t
t/20term.t
t/25cut.t
t/25number.t
t/30termlist.t
t/35clause.t
t/35primitive.t

examples/data_structures.pl  view on Meta::CPAN

use strict;
use warnings;
use lib ('../lib/', 'lib/');
use Data::Dumper;
$Data::Dumper::Indent = 0;

use AI::Prolog;

# note that the following line sets an experimental interface option
AI::Prolog->raw_results(0);
my $database = <<'END_PROLOG';
append([], X, X).
append([W|X],Y,[W|Z]) :- append(X,Y,Z).
END_PROLOG

my $logic = AI::Prolog->new($database);
$logic->query('append(LIST1,LIST2,[a,b,c,d]).');
while (my $result = $logic->results) {
    print Dumper($result->LIST1);
    print Dumper($result->LIST2);
}


AI::Prolog::Engine->raw_results(1);
$logic->query('append([X|Y],Z,[a,b,c,d]).');
while (my $result = $logic->results) {

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


=head1 NAME

AI::Prolog - Perl extension for logic programming.

=head1 SYNOPSIS

 use AI::Prolog;
 use Data::Dumper;

 my $database = <<'END_PROLOG';
   append([], X, X).
   append([W|X],Y,[W|Z]) :- append(X,Y,Z).
 END_PROLOG

 my $prolog = AI::Prolog->new($database);
 
 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.

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

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
shell called C<aiprolog> and two short adventure games, C<spider.pro> and
C<sleepy.pro>.  If you have installed the C<aiprolog> shell, you can run
either game with the command:

 aiprolog data/spider.pro
 aiprolog data/sleepy.pro

When the C<aiprolog> shell starts, you can type C<start.> to see how to play
the game.  Typing C<halt.> and hitting return twice will allow you to exit.

See the C<bin/> and C<data/> directories in the distribution.

Additionally, you can read L<AI::Prolog::Article> for a better description of
how to use C<AI::Prolog>.  This document is an article originally published in
The Perl Review (L<http://www.theperlreview.com/>) and which they have
graciously allowed me to redistribute.

See also Robert Pratte's perl.com article, "Logic Programming with Perl and
Prolog" (L<http://www.perl.com/pub/a/2005/12/15/perl_prolog.html>) for more
more examples.

=head1 DESCRIPTION

C<AI::Prolog> is a pure Perl predicate logic engine.  In predicate logic,
instead of telling the computer how to do something, you tell the computer what
something is and let it figure out how to do it.  Conceptually this is similar
to regular expressions.

 my @matches = $string =~ /XX(YY?)ZZ/g

If the string contains data that will satisfy the pattern, C<@matches> will
contain a bunch of "YY" and "Y"s.  Note that you're not telling the program how
to find those matches.  Instead, you supply it with a pattern and it goes off
and does its thing.

To learn more about Prolog, see Roman BartE<225>k's "Guide to Prolog
Programming" at L<http://kti.ms.mff.cuni.cz/~bartak/prolog/index.html>.
Amongst other things, his course uses the Java applet that C<AI::Prolog> was
ported from, so his examples will generally work with this module.

Fortunately, Prolog is fairly easy to learn.  Mastering it, on the other hand,

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

        owns(VICTIM,STUFF),
        not(knows(PERP,VICTIM)).
    thief(badguy).
    valuable(gold).
    valuable(rubies).
    owns(merlyn,gold).
    owns(ovid,rubies).
    knows(badguy,merlyn).
 END_PROLOG

Side note:  in Prolog, programs are often referred to as databases.

=head2 Creating a query

To create a query for the database, use C<query>.

  $prolog->query("steals(badguy,X).");

=head2 Running a query

Call the C<results> method and inspect the C<results> object:

  while (my $result = $prolog->results) {
      # $result = [ 'steals', 'badguy', $x ]
      print "badguy steals $result->[2]\n";

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


None by default.  However, for convenience, you can choose ":all" functions to
be exported.  That will provide you with C<Term>, C<Parser>, and C<Engine>
classes.  This is not recommended and most support and documentation will now
target the C<AI::Prolog> interface.

If you choose not to export the functions, you may use the fully qualified
package names instead:

 use AI::Prolog;
 my $database = AI::Prolog::Parser->consult(<<'END_PROLOG');
 append([], X, X).
 append([W|X],Y,[W|Z]) :- append(X,Y,Z).
 END_PROLOG

 my $query  = AI::Prolog::Term->new("append(X,Y,[a,b,c,d]).");
 my $engine = AI::Prolog::Engine->new($query,$database);
 while (my $result = $engine->results) {
     print "$result\n";
 }

=head1 SEE ALSO

L<AI::Prolog::Introduction>

L<AI::Prolog::Builtins>

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

approach to the same problem space.

=head2 3 Things to Learn

Most programming in Prolog boils down to learning three things:  facts, rules,
and queries.  The basics of these can be learned in just a few minutes and will
let you read many Prolog programs.

=head3 Facts

Facts in Prolog are stored in what is called the I<database> or I<knowledge
base>.  This isn't a PostgreSQL or SQLite database, but a plain-text file.  In
fact, you would call it a program and I'd be hard-pressed to argue with you, so
I won't.  In fact, I'll often refer to these as Prolog "programs" just to avoid
confusion.

Facts look like this:

 gives(tom, book, sally).

B<Note:>  While the order of the arguments is technically not relevant, there
is a convention to more or less read the arguments left to right.  The above

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

 gives(tom, book, charlie).
 gives(tom, book, ovid).

However, this quickly become unweildy as the number of people in our program
grows.

=head3 Queries

Now that we have a rough understanding of facts and rules, we need to know how
to get answers from them.  Queries are typically entered in the "interactive
environment." This would be analogous to the query window in a database GUI.
With C<AI::Prolog>, a shell named C<aiprolog> is included for demonstration
purposes though we'll mainly focus on calling Prolog from within a Perl
program.

 ?- gives(tom, book, SOMEONE).

This looks just like a fact, but with some minor differences.  The C<?-> at the
beginning of the query is a query prompt seen in interactive environments.

You'll also note that C<SOMEONE> is capitalized, making it a variable (only the

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

because we chose to hard-code this fact as the last line of the Prolog program.

=head2 How this works

At this point, it's worth having a bit of a digression to explain how this
works.

Many deductive systems in artificial intelligence are based on two algorithms:
backtracking and unification.  You're probably already familiar with backtracking
from regular expressions.  In fact, regular expressions are very similar to
Prolog in that you specify a pattern for your data and let the regex engine
worry about how to do the matching.

In a regular expression, if a partial match is made, the regex engine remembers
where the end of that match occurred and tries to match more of the string.  If
it fails, it backtracks to the last place a successful match was made and sees
if there are alternative matches it can try.  If that fails, it keeps
backtracking to the last successful match and repeats that process until it
either finds a match or fails completely.

Unification, described in a fit of wild hand-waving, attempts to take two

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

C<O(n)> when we want to stick with our simple C<O(1)> check.  Thus, a more
common solution is to reverse the hash:

 %who_loves = reverse %loves;
 $who       = $who_loves{perl};

(C<AI::Prolog>, being written in Perl, is slow.  So the C<O(n)> versus
C<O(1)> argument doesn't hold.  Versions written in C are far more practical in
this regard.)

But this fails, too.  The first problem is that we have duplicated data.  If we
have to add additional data to the C<%loves> hash, we'll have to remember to
synchronize the hashes.  The second problem is that Perl is just a little too
popular:

 loves(ovid,    perl).
 loves(alice,   perl).
 loves(bob,     perl).
 loves(charlie, perl).

If we simply reverse the hash for those entries, we lose three of those names.
So we have to play with this some more.

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

     charlie => [ 'perl' ],
 );

 my %who_loves;
 while ( my ($person, $things) = each %loves ) {
     foreach my $thing ( @$things ) {
         push @{ $who_loves{$thing} }, $person;
     }
 }

Now that's starting to get really ugly.  To represent and search that data in
Prolog is trivial.  Now do you really want to have fun?

 gives(tom, book, sally).

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
querying it from Perl.  This is a good solution if you only need simple
relations.  In fact, Prolog is often used with relational databases as the two
are closely related.  SQL is a I<special purpose> declarative language whereas
Prolog is a I<general purpose> declarative language.

Firing up SQLite, let's create two tables and insert data into them.

 sqlite> CREATE TABLE parent_2 (parent VARCHAR(32), child VARCHAR(32));
 sqlite> CREATE TABLE male_1   (person VARCHAR(32));

 sqlite> INSERT INTO parent_2 VALUES ('sally', 'tom');
 sqlite> INSERT INTO parent_2 VALUES ('bill', 'tom');
 sqlite> INSERT INTO parent_2 VALUES ('tom', 'sue');
 sqlite> INSERT INTO parent_2 VALUES ('alice', 'sue');
 sqlite> INSERT INTO parent_2 VALUES ('sarah', 'tim');

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


We can then find out who the fathers are with the following query.

 SELECT parent 
 FROM   parent_2, male_1 
 WHERE  parent_2.parent = male_1.person;

This is very similar to Prolog but we are forced to explicitly state the
relationship.  Many Prolog queries are conceptually similar to SQL queries but
the relationships are listed directly in the program rather than in the query.
When working with a relational database, we can get around this limitation with
a view:

 CREATE VIEW father AS
     SELECT parent 
     FROM   parent_2, male_1 
     WHERE  parent_2.parent = male_1.person;

And finding out who the fathers are is trivial:

 SELECT * FROM father;

Further, databases, unlike many Prolog implementations, support indexing,
hashing, and reordering of goals to reduce backtracking.  Given all of that,
why on earth would someone choose Prolog over SQL?

As stated earlier, Prolog is a general purpose programming language.  Imagine
if you had to write all of your programs in SQL.  Could you do it?  You could
with Prolog.  Further, there's one huge reason why one might choose Prolog:
recursion.  Some SQL implementations have non-standard support for recursion,
but usually recursive procedures are simulated by multiple calls from the
programming language using SQL.

Let's take a look at recursion and see why this is a win.

=head2 Recursion and Lists

In Prolog, lists are not data structures.  They're actually implemented in
terms of something referred to as the "dot functor" (./2).  For our purposes,
we'll ignore this and pretend they're really data types.  In fact, there's
going to be a lot of handwaving here so forgive me if you already know Prolog.
If you know LISP or Scheme, the following will be very familiar.

A list in Prolog is usually represented by a series of terms enclosed in square
brackets:

 owns(alice, [bookcase, cats, dogs]).

A list consists of a head and a tail.  The tail, in turn, is another list with
a head and tail, continuing recursively until the tail is an empty list.  This

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

C<< $results->[0] >> is the name of the predicate, C<append>, and the next three
elements are the successive values generated by Prolog.

=head1 Problems with Prolog

This article wouldn't be complete without listing some of the issues that have
hampered Prolog.

Perhaps the most significant is the depth-first exhaustive search algorithm
used for deduction.  This is slow and due to the dynamically typed nature of
Prolog, many of the optimizations that have been applied to databases cannot be
applied to Prolog.  Many Prolog programmers, understanding how the language
works internally, use the "cut" operator, C<!> (an exclamation point), to prune
the search trees.  This leads to our next problem.

The "cut" operator is what is known as an "extra-logical" predicate.  This,
along with I/O and predicates that assert and retract facts in the database
are a subject of much controversy.  They cause issues because they frequently
cannot be backtracked over and the order of the clauses sometimes becomes
important when using them.  They can be very useful and simplify many problems,
but they are more imperative in nature than logical and can introduce subtle
bugs.

Math is also handled poorly in Prolog.  Consider the following program:

  convert(Celsius, Fahrenheit) :-
       Celsius is (Fahrenheit - 32) * 5 / 9.

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

=item * Scheduling systems

=item * And much more (it was even embedded in Windows NT) 

=back

At the present time, I would not recommend C<AI::Prolog> for production work.
Attempts to bring logic programming to Perl are still relatively recent and no
module (to this author's knowledge) is currently ready for widespread use.  The
C<AI::Prolog> distribution has a number of sample programs in the C<examples/>
directory and two complete, albeit small, games in the C<data/> directory.

=head1 References

=head2 Online Resources

=over 4

=item Adventure in Prolog

http://amzi.com/AdventureInProlog/index.htm

I highly recommend Amzi! Prolog's free online book "Adventure in Prolog."  It's
clearly written and by the time you're done, you've built Prolog programs for
an adventure game, a genealogical database, a customer order inventory
application and a simple expert system.

=item Building Expert Systems in Prolog

http://amzi.com/ExpertSystemsInProlog/xsipfrtop.htm

This free online book, also by Amzi!, walks you through building expert
systems.  It includes expert systems for solving Rubik's cube, tax preparation,
car diagnostics and many other examples.

=item Databases Vs AI

http://lsdis.cs.uga.edu/~cartic/SemWeb/DatabasesAI.ppt

This Powerpoint presentation discusses AI in Prolog and compares it to SQL
databases.

=item W-Prolog

http://goanna.cs.rmit.edu.au/~winikoff/wp/

Dr. Michael Winikoff kindly gave me permission to port this Java application to
Perl.  This formed the basis of the earliest versions.

=item X-Prolog

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

need to satisfy a goal once.  For example, if you wish to deny someone the
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

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/Cookbook.pod  view on Meta::CPAN


=head1 DESCRIPTION

Logic programming can take some time to get used to.  This document is intended
to provide solutions to common problems encountered in logic programming.  Many
of the predicates listed here will depend on other predicates defined here.  If
in doubt, see L<AI::Prolog::Builtins|AI::Prolog::Builtins> for which predicates
L<AI::Prolog|AI::Prolog> supports directly.

Like most predicates in Prolog, the following predicates can be reused in ways
to generate answers that a human could logically infer from the data presented.
However, many times those "answers" can result in infinite loops.  For example,
in the C<gather/3> predicate listed below, we can gather the items from a list
which match the supplied list of indices.

 gather([1,3], [a,b,c,d], Result). % Result is [a,c]

Or we can figure out which indices in a list match the resulting values:

 gather(Indices, [a,b,c,d], [a,d]). % Indices is [1,4]

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

        $self->dump_goal if $self->{_trace};
        $self->step      if $self->{_step_flag};

        unless ( $self->{_goal} ) {

            # we've succeeded.  return results
            if ( $self->formatted ) {
                return $self->_call->to_string;
            }
            else {
                my @results = $self->_call->to_data;
                return $self->raw_results
                    ? $results[1]
                    : $results[0];
            }
        }

        unless ( $self->{_goal} && $self->{_goal}{term} ) {
            croak("Engine->run fatal error.  goal->term is null!");
        }
        unless ( $self->{_goal}->{next_clause} ) {

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

    }
    return 1;
}

1;

__END__

=head1 NAME

AI::Prolog::Engine - Run queries against a Prolog database.

=head1 SYNOPSIS

 my $engine = AI::Prolog::Engine->new($query, $database).
 while (my $results = $engine->results) {
     print "$result\n";
 }

=head1 DESCRIPTION

C<AI::Prolog::Engine> is a Prolog engine implemented in Perl.

The C<new()> function actually bootstraps some Prolog code onto your program to
give you access to the built in predicates listed in the
L<AI::Prolog::Builtins|AI::Prolog::Builtins> documentation.

This documentation is provided for completeness.  You probably want to use
L<AI::Prolog|AI::Prolog>.

=head1 CLASS METHODS

=head2 C<new($query, $database)>

This creates a new Prolog engine.  The first argument must be of type
C<AI::Prolog::Term> and the second must be a database created by
C<AI::Prolog::Parser::consult>.

 my $database = Parser->consult($some_prolog_program);
 my $query    = Term->new('steals(badguy, X).');
 my $engine   = Engine->new($query, $database);
 Engine->formatted(1);
 while (my $results = $engine->results) {
    print $results, $/;
 }

The need to have a query at the same time you're instantiating the engine is a
bit of a drawback based upon the original W-Prolog work.  I will likely remove
this drawback in the future.

=head2 C<formatted([$boolean])>

The default value of C<formatted> is true.  This method, if passed a true
value, will cause C<results> to return a nicely formatted string representing
the output of the program.  This string will loosely correspond with the
expected output of a Prolog program.

If false, all calls to C<result> will return Perl data structures instead of
nicely formatted output.

If called with no arguments, this method returns the current C<formatted>
value.

 Engine->formatted(1); # turn on formatting
 Engine->formatted(0); # turn off formatting (default)
 
 if (Engine->formatted) {
     # test if formatting is enabled

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

 $logic->query($query_text);
 AI::Logic::Engine->formatted(1); # if you want formatted to true
 while (my $results = $logic->results) {
    print "$results\n";
 }

=head2 C<raw_results([$boolean])>

The default value of C<raw_results> is false.  Setting this property to a true
value automatically sets C<formatted> to false.  C<results> will return the raw
data structures generated by questions when this property is true.
 
 Engine->raw_results(1); # turn on raw results
 Engine->raw_results(0); # turn off raw results (default)
 
 if (Engine->raw_results) {
     # test if raw results is enabled
 }

=head2 C<trace($boolean)>

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

 }

=head2 C<query($query)>

If you already have an engine object instantiated, call the C<query()> method
for subsequent queries.  Internally, when calling C<new()>, the engine
bootstraps a set of Prolog predicates to provide the built ins.  However, this
process is slow.  Subsequent queries to the same engine with the C<query()>
method can double the speed of your program.
 
 my $engine   = Engine->new($query, $database);
 while (my $results = $engine->results) {
    print $results, $/;
 }
 $query = Term->new("steals(ovid, X).");
 $engine->query($query);
 while (my $results = $engine->results) {
    print $results, $/;
 }

=head1 BUGS

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

        continue {
            ++$cx;
        }
    }

    if ( not defined $function_ref ) {
	return FAIL;
    }

    # XXX What do to with the first arg?
    my ( undef, $results_ref ) = $term->getarg(1)->to_data;
    my @results = @{ $results_ref->[0] };

    eval {

        no strict 'refs';    ## no critic NoStrict
        $function_ref->(@results);
    };
    if ( my $e = $@ ) {
    
        # Extreme caution here.

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

        $head = $head->next_clause;
    }
}

1;

__END__

=head1 NAME

AI::Prolog::KnowledgeBase - The Prolog database.

=head1 SYNOPSIS

 my $kb = KnowledgeBase->new;

=head1 DESCRIPTION

There are no user-serviceable parts inside here.  See L<AI::Prolog|AI::Prolog>
for more information.  If you must know more, there are a few comments
sprinkled through the code.

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

1;

__END__

=head1 NAME

AI::Prolog::Parser - A simple Prolog parser.

=head1 SYNOPSIS

 my $database = Parser->consult($prolog_text).

=head1 DESCRIPTION

There are no user-serviceable parts inside here.  See L<AI::Prolog|AI::Prolog>
for more information.  If you must know more, there are a few comments
sprinkled through the code.

=head1 SEE ALSO

W-Prolog:  L<http://goanna.cs.rmit.edu.au/~winikoff/wp/>

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

use Hash::Util 'lock_keys';

use aliased 'AI::Prolog::Term::Cut';
use aliased 'AI::Prolog::Parser';

use aliased 'Hash::AsObject';

use constant NULL => 'null';

# Var is a type of term
# A term is a basic data structure in Prolog
# There are three types of terms:
#   1. Values     (i.e., have a functor and arguments)
#   2. Variables  (i.e., unbound)
#   3. References (bound to another variable)

my $VARNUM = 1;

# controls where occurcheck is used in unification.
# In early Java versions, the occurcheck was always performed
# which resulted in lower performance.

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

        }
    }

    # else unbound
    unless ( $term_aref->[ $self->{varid} ] ) {
        $term_aref->[ $self->{varid} ] = $self->new;
    }
    return $term_aref->[ $self->{varid} ];
}

sub to_data {
    my $self = shift;
    $self->{_results} = {};

    # @results is the full results, if we ever need it
    my @results = $self->_to_data($self);
    return AsObject->new( $self->{_results} ), \@results;
}

sub _to_data {
    my ( $self, $parent ) = @_;
    if ( defined $self->{varname} ) {

        # XXX here's where the [HEAD|TAIL] bug is.  The engine works fine,
        # but we can't bind TAIL to a result object and are forced to
        # switch to raw_results.
        my $varname = delete $self->{varname};
        ( $parent->{_results}{$varname} ) = $self->_to_data($parent);
        $self->{varname} = $varname;
    }
    if ( $self->{bound} ) {
        my $functor = $self->functor;
        my $arity   = $self->arity;
        return $self->ref->_to_data($parent) if $self->{deref};
        return [] if NULL eq $functor && !$arity;
        if ( "cons" eq $functor && 2 == $arity ) {
            my @result = $self->{args}[0]->_to_data($parent);
            my $term   = $self->{args}[1];

            while ( "cons" eq $term->getfunctor && 2 == $term->getarity ) {
                if ( $term->{varname} ) {
                  push @result => $term->_to_data($parent);
                } else {
                  push @result => $term->getarg(0)->_to_data($parent);
                }
                $term = $term->getarg(1);
            }

            # XXX Not really sure about this one
            push @result => $term->_to_data($parent)
                unless NULL eq $term->getfunctor && !$term->getarity;

            #    ? "]"
            #    : "|" . $term->_to_data($parent) . "]";
            return \@result;
        }
        else {
            my @results = $self->functor;
            if ( $self->arity ) {

                #push @results => [];
                my $arity = $self->arity;
                my @args  = @{ $self->args };
                if (@args) {
                    for my $i ( 0 .. $arity - 1 ) {
                        push @results => $args[$i]->_to_data($parent);
                    }

                    # I have no idea what the following line was doing.
                    #push @results => $args[$arity - 1]->_to_data($parent)
                }
            }
            return @results;
        }
    }    # else unbound;
    return undef;
}

my %varname_for;
my $varname = 'A';

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

        }
        return $string;
    }    # else unbound;
         # return "_" . $self->varid;
    my $var = $self->{varname} || $varname_for{ $self->varid } || $varname++;
    $varname_for{ $self->varid } = $var;
    return $var;
}

# ----------------------------------------------------------
#  Copy a term to put in the database
#    - with new variables (freshly renumbered)
# ----------------------------------------------------------

# XXX XProlog
my %CVDICT;
my $CVN;

sub clean_up {
    my $self = shift;
    %CVDICT = ();

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


=head1 DESCRIPTION

See L<AI::Prolog|AI::Prolog> for more information.  If you must know more,
there are plenty of comments sprinkled through the code.

=head1 BUGS

A query using C<[HEAD|TAIL]> syntax does not bind properly with the C<TAIL>
variable when returning a result object.  This bug can be found in the
C<_to_data> method of this class.

=head1 SEE ALSO

W-Prolog:  L<http://goanna.cs.rmit.edu.au/~winikoff/wp/>

Michael BartE<225>k's online guide to programming Prolog:
L<http://kti.ms.mff.cuni.cz/~bartak/prolog/index.html>

=head1 AUTHOR

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

    my $class = ref $proto || $proto;    # yes, I know what I'm doing
    return _new_from_term( $class, @_ ) if 1 == @_ && $_[0]->isa(Term);
    return _new_from_term_and_next( $class, @_ ) if 2 == @_;
    if (@_) {
        croak "Unknown arguments to TermList->new:  @_";
    }
    my $self = bless {
        term => undef,
        next => undef,
        next_clause =>
            undef,    # serves two purposes: either links clauses in database
                      # or points to defining clause for goals
        is_builtin => undef,

        varname  => undef,
        ID       => undef,
        _results => undef,
    } => $class;
    lock_keys %$self;
    return $self;
}

t/50engine.t  view on Meta::CPAN

    $CLASS = 'AI::Prolog::Engine';
    use_ok($CLASS) or die;
}

# I hate the fact that they're interdependent.  That brings a 
# chicken and egg problem to squashing bugs.
use aliased 'AI::Prolog::TermList';
use aliased 'AI::Prolog::Term';
use aliased 'AI::Prolog::Parser';

my $database = Parser->consult(<<'END_PROLOG');
append([], X, X).
append([W|X],Y,[W|Z]) :- append(X,Y,Z).
END_PROLOG
my @keys = sort keys %{$database->{ht}};
my @expected = qw{append/3};
is_deeply \@keys, \@expected,
    'A brand new database should only have the predicates listed in the query';

my $parser = Parser->new("append(X,Y,[a,b,c,d]).");
my $query  = $parser->_term;

can_ok $CLASS, 'new';
ok my $engine = $CLASS->new($query, $database),
    '... and calling new with a valid query and database should succeed';
isa_ok $engine, $CLASS, '... and the object it returns';

@expected = qw{
    !/0
    append/3
    assert/1
    call/1
    consult/1
    eq/2
    fail/0

t/50engine.t  view on Meta::CPAN

    retract/1
    trace/0
    true/0
    var/1
    wprologcase/3
    wprologtest/2
    write/1
    writeln/1
};

@keys = sort keys %{$database->ht};
is_deeply \@keys, \@expected,
    '... and the basic prolog terms should be bootstrapped';
can_ok $engine, 'results';
is $engine->results, 'append([], [a,b,c,d], [a,b,c,d])',
    '... calling it the first time should provide the first unification';
is $engine->results, 'append([a], [b,c,d], [a,b,c,d])',
    '... and then the second unification';
is $engine->results, 'append([a,b], [c,d], [a,b,c,d])',
    '... and then the third unification';
is $engine->results, 'append([a,b,c], [d], [a,b,c,d])',
    '... and then the fifth unification';
is $engine->results, 'append([a,b,c,d], [], [a,b,c,d])',
    '... and then the last unification unification';
ok ! defined $engine->results,
    '... and it should return undef when there are no more results';

my $bootstrapped_db = clone($database);

$query = Term->new('append(X,[d],[a,b,c,d]).');
can_ok $engine, 'query';
$engine->query($query);
is $engine->results,'append([a,b,c], [d], [a,b,c,d])',
    '... and it should let us issue a new query against the same db';
ok !$engine->results, '... and it should not return spurious results';

# this will eventually test data structures

can_ok $CLASS, 'formatted';

$engine->formatted(0);
$engine->query(Term->new('append(X,Y,[a,b,c,d])'));
my $result = $engine->results;
is_deeply $result->X, [], '... and the X result should be correct';
is_deeply $result->Y, [qw/a b c d/], '... and the Y result should be correct';

$result = $engine->results;

t/60aiprolog.t  view on Meta::CPAN


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

my $database = Parser->consult(<<'END_PROLOG');
append([], X, X).
append([W|X],Y,[W|Z]) :- append(X,Y,Z).
END_PROLOG

my $query  = Term->new("append(X,Y,[a,b,c,d]).");
my $engine = Engine->new($query,$database);

isa_ok $query,  Term,   '... and the Term shortcut';
isa_ok $engine, Engine, '... and the Engine shortcut';

my $prolog = AI::Prolog->new(<<'END_PROLOG');
member(X,[X|Xs]).
member(X,[_|Tail]) :- member(X,Tail).
END_PROLOG

$prolog->query('member(3, [1,2,3,4]).');

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


BEGIN
{
    chdir 't' if -d 't';
    unshift @INC => '../lib';
}
use aliased 'AI::Prolog';
use aliased 'AI::Prolog::Engine';
use aliased 'AI::Prolog::KnowledgeBase';

my $database = <<'END_PROLOG';
thief(badguy).
thief(thug).
steals(PERP,X) :-
  if(thief(PERP), eq(X,rubies), eq(X,nothing)).
p(X) :- call(steals(badguy,rubies)).
q(X) :- call(steals(badguy,X)).
valuable(gold).
valuable(rubies).
END_PROLOG
Engine->formatted(1);
my $prolog = Prolog->new($database);
$prolog->query("p(ovid)");
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->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 );
    return;
}
$prolog->query(q{perlcall2( "test_me", ["FIND ME","and me","also me", 42] ).});
ok $prolog->results, 'Called a perl function ok';
is_deeply \@test_me, ["find me","AND ME", "Also me", 42],

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

    '... and var(ovid) should evaluate to not true';

{
    my $faux_kb = Test::MockModule->new(KnowledgeBase);
    my @stdout;
    $faux_kb->mock(_print => sub { push @stdout => @_ });
    $prolog->query('listing.');
    $prolog->results;
    my $results = join ''=> @stdout;
    my $count = ($results =~ s/(\d+\.\s+\w+\/\d+:)//g);
    ok $count, 'listing should display a listing of the database';
}



( run in 0.389 second using v1.01-cache-2.11-cpan-8d75d55dd25 )