AI-Prolog

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


0.72  Sun July 31, 2005
      Move builtins to their own class, AI::Prolog::Engine::Builtins
      Non-existent predicates now issue a warning even if trace is off.
      Added new predicates:
        halt/0 -- currently a no-op outside of the aiprolog shell
        help/0 -- list all builtin predicates for which help is available
        help/1 -- list help for builtin predicate
        write/1
        writeln/1
      Added ** operator (pow/1) to math preprocessor.  Leaving it out
        earlier was an oversight.

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)

Changes  view on Meta::CPAN


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.
      Modified listing/0 to not show builtins.
      Added math preprocessor.  This transforms proper Prolog math
        into internal predicates the parser recognizes:

        X is N + 1. % is(X, plus(N, 1)).
        
      Pushed all parsing back to the Parser class.  No more parsing
        in TermList and Term :)
      Added anonymous variables:  foo(bar, _, baz).  I don't have
        any efficiency gains from this (yet) because they're just
        variables with hidden names, but they work.
      Added Test::Exception to the PREREQ_PM (thanks to rjray for

MANIFEST  view on Meta::CPAN

t/25number.t
t/30termlist.t
t/35clause.t
t/35primitive.t
t/35step.t
t/40parser.t
t/50engine.t
t/60aiprolog.t
t/70builtins.t
t/80math.t
t/80preprocessor.t
t/80preprocessor_math.t
t/90results.t
t/99regression.t

examples/monkey.pl  view on Meta::CPAN


getfood(state(_,_,_,has)).

getfood(S1) :- perform(Act, S1, S2),
              nl, print('In '), print(S1), print(' try '), print(Act), nl,
              getfood(S2).
END_PROLOG

$prolog->query("getfood(state(atdoor,atwindow,onfloor,hasnot)).");
$prolog->results; # note that everything is done internally.
                  # there's no need to process the results

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

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
logical terms and "unify" them.  Imagine you have the following two lists:

 ( 1, 2, undef, undef,     5 )
 ( 1, 2,     3,     4, undef )

Imagine that undef means "unknown". We can unify those two lists because every
element that is known corresponds in the two lists. This leaves us with a list

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

Note that we can then force the engine to backtrack and by continuously
following this procedure, we can determine who all the fathers are.

And that's how logic programming works. Simple, eh? (Well, individual items can
be lists or other rules, but you get the idea).

=head2 Executing Prolog in Perl

Getting back to Perl, how would we implement that in a Perl program?

The basic process for using C<AI::Prolog> looks something like this:

 use AI::Prolog;
 my $prolog = AI::Prolog->new($prolog_code);

Create a new C<AI::Prolog> object, passing Prolog code as the argument.  If you
prefer, you can wrap the constructor in a C<BEGIN> block:

 my $prolog;
 BEGIN {
   $prolog = AI::Prolog->new(<<'  END_PROLOG');

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

fact, many common uses for Prolog are heavily used in the AI arena:

=over 4

=item * Agent-based programming

=item * Expert Systems

=item * Fraud prevention (via inductive logic programming)

=item * Natural language processing

=item * Rules-based systems

=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.

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

 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)>

Set this to a true value to turn on tracing.  This will trace through the
engine's goal satisfaction process while it's running.  This is very slow.

 Engine->trace(1); # turn on tracing
 Engine->trace(0); # turn off tracing

=head1 INSTANCE METHODS

=head2 C<results()>

This method will return the results from the last run query, one result at a
time.  It will return false when there are no more results.  If C<formatted> is

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

 while (my $r = $engine->results) {
    # do stuff with $r in the form:
    # ['steals', 'badguy', $STUFF, $VICTIM]
 }

=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, $/;

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

use aliased 'AI::Prolog::TermList::Clause';
use aliased 'AI::Prolog::TermList::Primitive';

my $ATOM = qr/[[:alpha:]][[:alnum:]_]*/;

use constant NULL => 'null';

sub new {
    my ( $class, $string ) = @_;
    my $self = bless {
        _str      => PreProcessor->process($string),
        _posn     => 0,
        _start    => 0,
        _varnum   => 0,
        _internal => 0,
        _vardict  => {},
    } => $class;
    lock_keys %$self;
    return $self;
}

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

package AI::Prolog::Parser::PreProcessor;
$REVISION = '$Id: PreProcessor.pm,v 1.2 2005/08/06 23:28:40 ovid Exp $';

$VERSION = '0.01';
use strict;
use warnings;

use aliased 'AI::Prolog::Parser::PreProcessor::Math';

sub process {
    my ($class, $prolog) = @_;
    # why the abstraction?  Because I want DCGs in here, too.  Maybe 
    # other stuff ...
    $prolog = Math->process($prolog);
    return $prolog;
}

1;

__END__

=head1 NAME

AI::Prolog::Parser::PreProcessor - The AI::Prolog Preprocessor

=head1 SYNOPSIS

 my $program = AI::Prolog::Parser::Preprocessor->process($prolog_text).

=head1 DESCRIPTION

This code reads in the Prolog text and rewrites it to a for that is suitable
for the L<AI::Prolog::Parser|AI::Prolog::Parser> to read.  Users of
L<AI::Prolog||AI::Prolog> should never need to know about this.

=head1 AUTHOR

Curtis "Ovid" Poe, E<lt>moc tod oohay ta eop_divo_sitrucE<gt>

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

        **    pow
        <     lt
        <=    le
        >     gt
        >=    ge
        ==    eq
        \=    ne
        }
);

sub process {
    my ( $class, $prolog ) = @_;
    while ( $prolog =~ $expression ) {
        my ( $old_expression, $lhs, $comp, $rhs ) = ( $1, $2, $3, $4 );
        my $new_rhs        = $class->_parse( $class->_lex($rhs) );
        my $new_expression = sprintf
            "%s(%s, %s)" => $convert{$comp},
            $lhs, $new_rhs;
        $prolog =~ s/\Q$old_expression\E/$new_expression/g;
    }
    return $prolog;

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

            next unless $token;
            if ( "(" eq _as_string($token) ) {
                $first = $i;
            }
            if ( ")" eq _as_string($token) ) {
                unless ( defined $first ) {

                # XXX I should probably cache the string and show it.
                # XXX But it doesn't matter because that shouldn't happen here
                    croak(
                        "Parse error in math pre-processor.  Mismatched parens"
                    );
                }
                $last = $i;
                $tokens->[$first] = $class->_parse_group(
                    [ @{$tokens}[ $first + 1 .. $last - 1 ] ] );
                undef $tokens->[$_] for $first + 1 .. $last;
                @$tokens = grep $_ => @$tokens;
                undef $first;
                undef $last;
                redo REDUCE;

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

1;

__END__

=head1 NAME

AI::Prolog::Parser::PreProcessor::Math - The AI::Prolog math macro

=head1 SYNOPSIS

 my $program = AI::Prolog::Parser::PreProcessor::Math->process($prolog_text).

=head1 DESCRIPTION

This code reads in the Prolog text and rewrites it to a for that is suitable
for the L<AI::Prolog::Parser|AI::Prolog::Parser> to read.  Users of
L<AI::Prolog||AI::Prolog> should never need to know about this.

=head1 TODO

Constant folding for performance improvment.  No need to internally have

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

#!/usr/bin/perl
# '$Id: 80preprocessor.t,v 1.2 2005/06/20 07:36:48 ovid Exp $';
use warnings;
use strict;
use Test::More tests => 8;
#use Test::More qw/no_plan/;
use aliased 'AI::Prolog';

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

my $expected = <<END_PROLOG;
factorial(N,F) :-
    gt(N, 0),
    is(N1, minus(N, 1)),
    factorial(N1,F1), 
    is(F, mult(N, F1)).
factorial(0,1). 
END_PROLOG
is $CLASS->process($prolog), $expected,
    '... and math expressions should be transformed correctly';

my $prog = Prolog->new($prolog);
$prog->query('factorial(5,X).');
my $result = $prog->results;
is $result->[2], 120, '... and we can use pre-processed programs';

$prolog   = 'bar(X) :- 3 \= X.';
$expected = 'bar(X) :- ne(3, X).';
is $CLASS->process($prolog), $expected,
    '... and math expressions should be transformed correctly';
$prog = Prolog->new($prolog);
$prog->query('bar(2).');
$result = $prog->results;
is $result->[1], 2, '... and we can use pre-processed programs';

$prog->query('bar(3).');
$result = $prog->results;
ok ! $result, '... and we can use pre-processed programs';

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

#!/usr/bin/perl
# '$Id: 80preprocessor_math.t,v 1.3 2005/08/06 23:28:40 ovid Exp $';
use warnings;
use strict;
use Test::More tests => 140;
#use Test::More qw/no_plan/;

my $CLASS;
BEGIN
{
    chdir 't' if -d 't';
    unshift @INC => '../lib';

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

is $CLASS->_parse([
    [qw/ ATOM   5 /],
    [qw/ OP     * /],
    [qw/ LPAREN ( /],
    [qw/ ATOM   4 /],
    [qw/ OP     + /],
    [qw/ ATOM 7.2 /],
    [qw/ RPAREN ) /],
]), 'mult(5, plus(4, 7.2))', '... and parentheses should group properly';

is $CLASS->process('X is 3 + 2.'), 'is(X, plus(3, 2)).',
    '... and it should be able to transform full math expressions.';

my @expressions = (
    'X is 3.',
    'is(X, 3).',

    'Answer = A + B.',
    'eq(Answer, plus(A, B)).',

    'Answer = A + B + C.',

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

    'is(Answer, plus(div(9, mod(plus(3, plus(4, 7)), ModValue)), div(2, plus(3, 7)))).',
    
    'X \= 9 / (3 + (4+7) % ModValue) + 2 / (3+7).',
    'ne(X, plus(div(9, mod(plus(3, plus(4, 7)), ModValue)), div(2, plus(3, 7)))).',

    '-1 is E ** (PI * I).',
    'is(-1, pow(E, mult(PI, I))).',
);

while (my ($before, $after) = splice @expressions, 0, 2) {
    is $CLASS->process($before), $after,
        "$before : should transform correctly";
}



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