view release on metacpan or search on metacpan
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)
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
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";
}