AI-Prolog

 view release on metacpan or  search on metacpan

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

package AI::Prolog;
$VERSION = '0.741';    ## no critic
use strict;
use Carp qw( confess carp croak );

use Hash::Util 'lock_keys';
use Exporter::Tidy shortcuts => [qw/Parser Term Engine/];

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

use Text::Quote;
use Regexp::Common;

# they don't want pretty printed strings if they're using this interface
Engine->formatted(0);

# Until (and unless) we figure out the weird bug that prevents some values
# binding in the external interface, we need to stick with this as the default
Engine->raw_results(1);

sub new {
    my ( $class, $program ) = @_;
    my $self = bless {
        _prog   => Parser->consult($program),
        _query  => undef,
        _engine => undef,
    } => $class;
    lock_keys %$self;
    return $self;
}

sub do {
    my ( $self, $query ) = @_;
    $self->query($query);
    1 while $self->results;
    $self;
}

sub query {
    my ( $self, $query ) = @_;

    # make that final period optional
    $query .= '.' unless $query =~ /\.$/;
    $self->{_query} = Term->new($query);
    unless ( defined $self->{_engine} ) {

        # prime the pump
        $self->{_engine} = Engine->new( @{$self}{qw/_query _prog/} );
    }
    $self->{_engine}->query( $self->{_query} );
    return $self;
}

sub results {
    my $self = shift;
    unless ( defined $self->{_query} ) {
        croak "You can't fetch results because you have not set a query";
    }
    $self->{_engine}->results;
}

sub trace {
    my $self = shift;
    if (@_) {
        $self->{_engine}->trace(shift);
        return $self;
    }
    return $self->{_engine}->trace;
}

sub raw_results {
    my $class = shift;
    if (@_) {
        Engine->raw_results(shift);
        return $class;
    }
    return Engine->raw_results;
}

my $QUOTER;

sub quote {
    my ( $proto, $string ) = @_;
    $QUOTER = Text::Quote->new unless $QUOTER;
    return $QUOTER->quote_simple($string);
}

sub list {
    my $proto = shift;
    return
        join ", " => map { /^$RE{num}{real}$/ ? $_ : $proto->quote($_) } @_;
}

sub continue {
    my $self = shift;
    return 1 unless $self->{_engine};    # we haven't started yet!
    !$self->{_engine}->halt;
}

1;

__END__

=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

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

=back

For quick examples of how that works, see the C<examples/> directory with this
distribution.  Feel free to contribute more.

=head2 Creating a logic program

This module is actually remarkable easy to use.  To create a Prolog program,
you simply pass the Prolog code as a string to the constructor:

 my $prolog = AI::Prolog->new(<<'END_PROLOG');
    steals(PERP, STUFF) :-
        thief(PERP),
        valuable(STUFF),
        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";
  }

=head1 BUILTINS

See L<AI::Prolog::Builtins|AI::Prolog::Builtins> for the built in predicates.

=head1 CLASS METHODS

=head2 C<new($program)>

This is the constructor.  It takes a string representing a Prolog program:

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

See L<AI::Prolog::Builtins|AI::Prolog::Builtins> and the C<examples/> directory
included with this distribution for more details on the program text.

Returns an C<AI::Prolog> object.

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

One can "trace" the program execution by setting this property to a true value
before fetching engine results:

 AI::Prolog->trace(1);
 while (my $result = $engine->results) {
     # do something with results
 }

This sends trace information to C<STDOUT> and allows you to see how the engine
is trying to satify your goals.  Naturally, this slows things down quite a bit.

Calling C<trace> without an argument returns the current C<trace> value.

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

You can get access to the full, raw results by setting C<raw_results> to true.
In this mode, the results are returned as an array reference with the functor
as the first element and an additional element for each term.  Lists are
represented as array references.

 AI::Prolog->raw_results(1);
 $prolog->query('steals(badguy, STUFF, VICTIM)');
 while (my $r = $prolog->results) {
     # do stuff with $r in the form:
     # ['steals', 'badguy', $STUFF, $VICTIM]
 }

Calling C<raw_results> without an argument returns the current C<raw_results>
value.

This is the default behavior.

=head2 C<quote($string)>.

This method quotes a Perl string to allow C<AI::Prolog> to treat it as a proper
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)).');

This is a shorthand for:

 $prolog->query('assert(loves(ovid,perl)).');
 1 while $prolog->results;



( run in 0.466 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )