File Coverage

lib/AI/Prolog/Engine.pm
Criterion Covered Total %
statement 155 186 83.3
branch 52 74 70.2
condition 8 12 66.6
subroutine 27 35 77.1
pod 6 10 60.0
total 248 317 78.2


line stmt bran cond sub pod time code
1             package AI::Prolog::Engine;
2             $REVISION = '$Id: Engine.pm,v 1.13 2005/08/06 23:28:40 ovid Exp $';
3             $VERSION = '0.4';
4 13     13   37776 use strict;
  13         28  
  13         511  
5 13     13   77 use warnings;
  13         22  
  13         573  
6 13     13   73 use Carp qw( confess carp );
  13         26  
  13         725  
7              
8 13     13   78 use Scalar::Util qw/looks_like_number/;
  13         24  
  13         748  
9 13     13   1056 use Hash::Util 'lock_keys';
  13         8032  
  13         118  
10              
11 13     13   1440 use aliased 'AI::Prolog::Term';
  13         785  
  13         106  
12 13     13   1644 use aliased 'AI::Prolog::Term::Cut';
  13         25  
  13         75  
13 13     13   1712 use aliased 'AI::Prolog::Term::Number';
  13         26  
  13         72  
14 13     13   1002 use aliased 'AI::Prolog::TermList';
  13         26  
  13         59  
15 13     13   1242 use aliased 'AI::Prolog::TermList::Step';
  13         25  
  13         64  
16 13     13   935 use aliased 'AI::Prolog::TermList::Primitive';
  13         26  
  13         207  
17 13     13   1517 use aliased 'AI::Prolog::KnowledgeBase';
  13         26  
  13         54  
18 13     13   1048 use aliased 'AI::Prolog::Parser';
  13         24  
  13         57  
19 13     13   1562 use aliased 'AI::Prolog::ChoicePoint';
  13         26  
  13         67  
20              
21 13     13   7192 use AI::Prolog::Engine::Primitives;
  13         35  
  13         497  
22              
23 13     13   84 use constant OnceMark => 'OnceMark';
  13         23  
  13         22463  
24              
25             # The engine is what executes prolog queries.
26             # Author emeritus: Dr. Michael Winikoff
27             # Translation to Perl: Curtis "Ovid" Poe
28              
29             # $prog An initial program - this will be extended
30             # $term The query to be executed
31              
32             # This governs whether tracing is done
33             sub trace {
34 0     0 1 0 my $self = shift;
35 0 0       0 if (@_) {
36 0         0 $self->{_trace} = shift;
37 0         0 return $self;
38             }
39 0         0 return $self->{_trace};
40             }
41              
42             sub halt {
43 0     0 0 0 my $self = shift;
44 0 0       0 if (@_) {
45 0         0 $self->{_halt} = shift;
46 0         0 return $self;
47             }
48 0         0 return $self->{_halt};
49             }
50              
51             my $FORMATTED = 1;
52              
53             sub formatted {
54 63     63 1 123 my $self = shift;
55 63 100       165 if (@_) {
56 15         25 $FORMATTED = shift;
57 15         39 return $self;
58             }
59 48         352 return $FORMATTED;
60             }
61              
62             my $RAW_RESULTS;
63              
64             sub raw_results {
65 22     22 1 46 my $self = shift;
66 22 100       77 if (@_) {
67 7         17 $RAW_RESULTS = shift;
68 7 100       28 if ($RAW_RESULTS) {
69 6         24 $self->formatted(0);
70             }
71 7         21 return $self;
72             }
73 15         195 return $RAW_RESULTS;
74             }
75              
76             my $BUILTIN = 0;
77              
78             sub _adding_builtins {
79 412     412   656 my $self = shift;
80 412 100       868 if (@_) {
81 18         28 $BUILTIN = shift;
82 18         49 return $self;
83             }
84 394         753 return $BUILTIN;
85             }
86              
87             sub new {
88 9     9 1 792 my ( $class, $term, $prog ) = @_;
89 9         59 my $self = bless {
90              
91             # The stack holds choicepoints and a list of variables
92             # which need to be un-bound upon backtracking.
93             _stack => [],
94             _db => KnowledgeBase->new,
95             _goal => TermList->new( $term, undef ), # TermList
96             _call => $term, # Term
97             _run_called => undef,
98             _cp => undef,
99             _retract_clause => undef,
100             _trace => 0, # whether or not tracing is done
101             _halt => 0, # will stop the aiprolog shell
102             _perlpackage => undef,
103             _step_flag => undef,
104             } => $class;
105 9         63 lock_keys %$self;
106              
107             # to add a new primitive, use the binding operator (:=) to assign a unique
108             # index to the primitive and add the corresponding definition to
109             # @PRIMITIVES.
110 9         71 eval {
111 9         41 $self->_adding_builtins(1);
112 9         45 $self->{_db} = Parser->consult( <<' END_PROG', $prog );
113             ne(X, Y) :- not(eq(X,Y)).
114             if(X,Y,Z) :- once(wprologtest(X,R)) , wprologcase(R,Y,Z).
115             wprologtest(X,yes) :- call(X). wprologtest(X,no).
116             wprologcase(yes,X,Y) :- call(X).
117             wprologcase(no,X,Y) :- call(Y).
118             not(X) :- if(X,fail,true).
119             or(X,Y) :- call(X).
120             or(X,Y) :- call(Y).
121             true.
122             % the following are handled internally. Don't use the
123             % := operator. Eventually, I'll make this a fatal error.
124             % See AI::Prolog::Engine::Builtins to see the code for these
125             ! := 1.
126             call(X) := 2.
127             fail := 3.
128             consult(X) := 4.
129             assert(X) := 5.
130             retract(X) := 7.
131             retract(X) :- retract(X).
132             listing := 8.
133             listing(X) := 9.
134             print(X) := 10.
135             write(X) := 10.
136             println(X) := 11.
137             writeln(X) := 11.
138             nl := 12.
139             trace := 13.
140             notrace := 13.
141             is(X,Y) := 15.
142             gt(X,Y) := 16.
143             lt(X,Y) := 17.
144             ge(X,Y) := 19.
145             le(X,Y) := 20.
146             halt := 22.
147             var(X) := 23.
148             %seq(X) := 30.
149             help := 31.
150             help(X) := 32.
151             gensym(X) := 33.
152             perlcall2(X,Y) := 34.
153             eq(X,X).
154             not(X) :- X, !, fail.
155             not(X).
156             %if(X, Yes, _ ) :- seq(X), !, seq(Yes).
157             %if(X, _ , No) :- seq(No).
158             %if(X, Yes) :- seq(X), !, seq(Yes).
159             %if(X, _ ).
160             %or(X,Y) :- seq(X).
161             %or(X,Y) :- seq(Y).
162             once(X) :- X , !.
163             END_PROG
164 9         156 $self->_adding_builtins(0);
165             };
166 9 50       52 if ($@) {
167 0         0 croak("Engine->new failed. Cannot parse default program: $@");
168             }
169 9         67 $self->{_retract_clause} = $self->{_db}->get("retract/1");
170 9         60 $self->{_goal}->resolve( $self->{_db} );
171 9         81 return $self;
172             }
173              
174             sub query {
175 40     40 1 1007 my ( $self, $query ) = @_;
176 40         95 $self->{_stack} = [];
177 40         150 $self->{_run_called} = undef;
178 40         189 $self->{_goal} = TermList->new($query);
179 40         97 $self->{_call} = $query;
180 40         405 $self->{_goal}->resolve( $self->{_db} );
181 40         84 return $self;
182             }
183              
184 0     0   0 sub _stack { shift->{_stack} }
185 0     0   0 sub _db { shift->{_db} }
186 0     0   0 sub _goal { shift->{_goal} }
187 48     48   184 sub _call { shift->{_call} }
188              
189             sub dump_goal {
190 0     0 0 0 my ($self) = @_;
191 0 0       0 if ( $self->{_goal} ) {
192 0         0 _print( "\n= Goals: " . $self->{_goal}->to_string );
193 0 0       0 _print(
194             "\n==> Try: " . $self->{_goal}->next_clause->to_string . "\n" )
195             if $self->{_goal}->next_clause;
196             }
197             else {
198 0         0 _print("\n= Goals: null\n");
199             }
200             }
201              
202             sub results {
203 64     64 1 9589 my $self = shift;
204 64 100       181 if ( $self->{_run_called} ) {
205 23 100       85 return unless $self->backtrack;
206             }
207             else {
208 41         71 $self->{_run_called} = 1;
209             }
210 63         181 $self->_run;
211             }
212              
213             sub _run {
214 63     63   94 my ($self) = @_;
215 63         82 my $stackTop = 0;
216              
217 63         92 while (1) {
218 174         240 $stackTop = @{ $self->{_stack} };
  174         310  
219              
220 174 50 66     1336 if ( $self->{_goal} && $self->{_goal}->isa(Step) ) {
221 0         0 $self->{_goal} = $self->{_goal}->next;
222 0 0       0 if ( $self->{_goal} ) {
223 0         0 $self->{_goal}->resolve( $self->{_db} );
224             }
225 0         0 $self->{_step_flag} = 1;
226 0         0 $self->trace(1);
227             }
228 174 50       375 $self->dump_goal if $self->{_trace};
229 174 50       397 $self->step if $self->{_step_flag};
230              
231 174 100       371 unless ( $self->{_goal} ) {
232              
233             # we've succeeded. return results
234 48 100       146 if ( $self->formatted ) {
235 33         83 return $self->_call->to_string;
236             }
237             else {
238 15         282 my @results = $self->_call->to_data;
239 15 100       455 return $self->raw_results
240             ? $results[1]
241             : $results[0];
242             }
243             }
244              
245 126 50 33     654 unless ( $self->{_goal} && $self->{_goal}{term} ) {
246 0         0 croak("Engine->run fatal error. goal->term is null!");
247             }
248 126 50       311 unless ( $self->{_goal}->{next_clause} ) {
249 0         0 my $predicate = $self->{_goal}{term}->predicate;
250 0         0 _warn("WARNING: undefined predicate ($predicate)\n");
251 0 0       0 next if $self->backtrack; # if we backtracked, try again
252 0         0 return; # otherwise, we failed
253             }
254              
255 126         208 my $clause = $self->{_goal}->{next_clause};
256 126 100       312 if ( my $next_clause = $clause->{next_clause} ) {
257 37         63 push @{ $self->{_stack} } => $self->{_cp}
  37         262  
258             = ChoicePoint->new( $self->{_goal}, $next_clause, );
259             }
260 126         366 my $vars = [];
261 126         442 my $curr_term = $clause->{term}->refresh($vars);
262 126 100       548 if ( $curr_term->unify( $self->{_goal}->term, $self->{_stack} ) ) {
263 114         373 $clause = $clause->{next};
264 114 100 100     1024 if ( $clause && $clause->isa(Primitive) ) {
    100          
265 49 100 66     149 if ( !$self->do_primitive( $self->{_goal}->{term}, $clause )
266             && !$self->backtrack )
267             {
268 9         82 return;
269             }
270             }
271             elsif ( !$clause ) { # matching against fact
272 29         74 $self->{_goal} = $self->{_goal}->{next};
273 29 100       175 if ( $self->{_goal} ) {
274 4         14 $self->{_goal}->resolve( $self->{_db} );
275             }
276             }
277             else { # replace goal by clause body
278 36         52 my ( $p, $p1, $ptail ); # termlists
279 36         152 for ( my $i = 1; $clause; $i++ ) {
280              
281             # will there only be one CUT?
282 59 100       251 if ( $clause->{term} eq Term->CUT ) {
283 2         16 $p = TermList->new( Cut->new($stackTop) );
284             }
285             else {
286 57         166 $p = TermList->new( $clause->{term}->refresh($vars) );
287             }
288              
289 59 100       146 if ( $i == 1 ) {
290 36         97 $p1 = $ptail = $p;
291             }
292             else {
293 23         60 $ptail->next($p);
294 23         25 $ptail = $p; # XXX ?
295             }
296 59         264 $clause = $clause->{next};
297             }
298 36         147 $ptail->next( $self->{_goal}->{next} );
299 36         60 $self->{_goal} = $p1;
300 36         134 $self->{_goal}->resolve( $self->{_db} );
301             }
302             }
303             else { # unify failed. Must backtrack
304 12 100       37 return unless $self->backtrack;
305             }
306             }
307             }
308              
309             sub backtrack {
310 45     45 0 82 my $self = shift;
311 45 50       337 _print(" <<== Backtrack: \n") if $self->{_trace};
312 45         71 while ( @{ $self->{_stack} } ) {
  246         648  
313 231         282 my $o = pop @{ $self->{_stack} };
  231         393  
314              
315 231 100       895 if ( UNIVERSAL::isa( $o, Term ) ) {
    50          
316 201         532 $o->unbind;
317             }
318             elsif ( UNIVERSAL::isa( $o, ChoicePoint ) ) {
319 30         79 $self->{_goal} = $o->{goal};
320              
321             # XXX This could be very dangerous if we accidentally try
322             # to assign a term to itself! See ChoicePoint->next_clause
323 30         2502 $self->{_goal}->next_clause( $o->{clause} );
324 30         3589 return 1;
325             }
326             }
327 15         118 return;
328             }
329              
330             sub _print { # convenient testing hook
331 0     0   0 print @_;
332             }
333              
334             sub _warn { # convenient testing hook
335 0     0   0 warn @_;
336             }
337              
338 13     13   92 use constant RETURN => 2;
  13         25  
  13         2813  
339              
340             sub do_primitive { # returns false if fails
341 49     49 0 73 my ( $self, $term, $c ) = @_;
342 49 50       322 my $primitive = AI::Prolog::Engine::Primitives->find( $c->ID )
343             or die sprintf "Cannot find primitive for %s (ID: %d)\n",
344             $term->to_string, $c->ID;
345 49 100       137 return unless my $result = $primitive->( $self, $term, $c );
346 38 50       88 return 1 if RETURN == $result;
347 38         121 $self->{_goal} = $self->{_goal}->next;
348 38 100       132 if ( $self->{_goal} ) {
349 15         44 $self->{_goal}->resolve( $self->{_db} );
350             }
351 38         250 return 1;
352             }
353              
354             1;
355              
356             __END__