File Coverage

lib/AI/Prolog/Engine/Primitives.pm
Criterion Covered Total %
statement 75 126 59.5
branch 3 24 12.5
condition 0 3 0.0
subroutine 20 22 90.9
pod 0 1 0.0
total 98 176 55.6


line stmt bran cond sub pod time code
1             ## no critic (RcsKeywords,PodSections,InterpolationOfMetachars,EmptyQuotes,ConstantPragma,InitializationForLocalVars,LocalVars,PunctuationVars)
2             package AI::Prolog::Engine::Primitives;
3             $REVISION = '$Id: Primitives.pm,v 1.1 2005/08/06 23:28:40 ovid Exp $';
4             $VERSION = '0.3';
5 13     13   73 use strict;
  13         44  
  13         548  
6 13     13   78 use warnings;
  13         20  
  13         365  
7              
8 13     13   68 use base 'AI::Prolog::Engine';
  13         25  
  13         1287  
9 13     13   75 use Scalar::Util 'looks_like_number';
  13         22  
  13         6139  
10              
11 13     13   266 use aliased 'AI::Prolog::Term';
  13         47  
  13         257  
12 13     13   1779 use aliased 'AI::Prolog::Term::Cut';
  13         77  
  13         50  
13 13     13   1780 use aliased 'AI::Prolog::Term::Number';
  13         59  
  13         54  
14 13     13   1708 use aliased 'AI::Prolog::TermList';
  13         21  
  13         52  
15 13     13   1723 use aliased 'AI::Prolog::TermList::Step';
  13         24  
  13         71  
16 13     13   1831 use aliased 'AI::Prolog::ChoicePoint';
  13         34  
  13         47  
17              
18             my %DESCRIPTION_FOR;
19             my $LONGEST_PREDICATE = '';
20              
21             sub _load_builtins {
22 0 0   0   0 return if keys %DESCRIPTION_FOR;
23 0         0 require Pod::Simple::Text;
24 0         0 require Pod::Perldoc;
25 0         0 my $perldoc = Pod::Perldoc->new;
26 0         0 my $builtin_pod = 'AI::Prolog::Builtins';
27              
28 0 0       0 my ($found) = $perldoc->grand_search_init( [$builtin_pod] )
29             or die "Help failed. Cannot find documentation for $builtin_pod: $!";
30 0 0       0 open my $fh, '<', $found
31             or die "Cannot open $found for reading: ($!)";
32 0         0 my @lines = <$fh>;
33 0 0       0 close $fh or die "Cannot close $found: ($!)";
34              
35 0         0 while (@lines) {
36 0         0 my $line = shift @lines;
37 0         0 my $predicate;
38 0 0       0 if ( $line =~ /\A=item\s*(\S+)/mx ) {
39 0         0 $predicate = $1;
40 0 0       0 if ( $predicate =~ m{.*/\d+}mx ) {
41 0         0 my @pod = "=head1 $predicate";
42 0 0       0 if ( length $predicate > length $LONGEST_PREDICATE ) {
43 0         0 $LONGEST_PREDICATE = $predicate;
44             }
45 0         0 while ( $line = shift @lines ) {
46 0 0       0 if ( $line =~ /\A=(?:item|back)/mx ) {
47 0         0 unshift @lines => $line;
48 0         0 last;
49             }
50 0         0 push @pod => $line;
51             }
52 0         0 push @pod => '=cut';
53              
54             # XXX I hate instantiating this here, but there
55             # appears to be a bug in parsing if I don't :(
56 0         0 my $parser = Pod::Simple::Text->new;
57 0         0 my $output;
58 0         0 $parser->output_string( \$output );
59 0         0 $parser->parse_lines( @pod, undef );
60 0         0 $DESCRIPTION_FOR{$predicate} = $output;
61 0         0 $output = '';
62             }
63             }
64             }
65              
66 0         0 return;
67             }
68              
69             sub _remove_choices {
70              
71             # this implements the cut operator
72 1     1   2 my ( $self, $varid ) = @_;
73 1         1 my @stack;
74 1         4 my $i = @{ $self->{_stack} };
  1         4  
75 1         4 while ( $i > $varid ) {
76 3         4 my $o = pop @{ $self->{_stack} };
  3         6  
77 3 100       23 if ( not $o->isa(ChoicePoint) ) {
78 2         3 push @stack => $o;
79             }
80 3         7 $i--;
81             }
82 1         4 while (@stack) {
83 2         3 push @{ $self->{_stack} } => pop @stack;
  2         7  
84             }
85              
86 1         3 return;
87             }
88              
89             sub _splice_goal_list {
90 0     0   0 my ( $self, $term ) = @_;
91 0         0 my ( $t2, $p, $p1, $ptail );
92 0         0 my @vars;
93 0         0 my $i = 0;
94 0         0 $term = $term->getarg(0);
95 0   0     0 while ( $term && $term->getfunctor ne 'null' ) {
96 0         0 $t2 = $term->getarg(0);
97 0 0       0 if ( $t2 eq Term->CUT ) {
98 0         0 $p = TermList->new( Cut->new( scalar @{ $self->{_stack} } ) );
  0         0  
99             }
100             else {
101 0         0 $p = TermList->new($t2);
102             }
103 0 0       0 if ( $i++ == 0 ) {
104 0         0 $p1 = $ptail = $p;
105             }
106             else {
107 0         0 $ptail->next($p);
108 0         0 $ptail = $p;
109             }
110 0         0 $term = $term->getarg(1);
111             }
112 0         0 $ptail->next( $self->{_goal}->next );
113 0         0 $self->{_goal} = $p1;
114 0         0 $self->{_goal}->resolve( $self->{_db} );
115              
116 0         0 return;
117             }
118              
119 13     13   12635 use constant CONTINUE => 1;
  13         36  
  13         1182  
120 13     13   78 use constant RETURN => 2;
  13         22  
  13         596  
121 13     13   85 use constant FAIL => ();
  13         30  
  13         27914  
122             my @PRIMITIVES; # we'll fix this later
123              
124             $PRIMITIVES[1] = sub { # !/0 (cut)
125             my ( $self, $term, $c ) = @_;
126             _remove_choices( $self, $term->varid );
127             CONTINUE;
128             };
129              
130             $PRIMITIVES[2] = sub { # call/1
131             my ( $self, $term, $c ) = @_;
132             $self->{_goal} = TermList->new( $term->getarg(0), $self->{_goal}->next );
133             $self->{_goal}->resolve( $self->{_db} );
134             RETURN;
135             };
136              
137             $PRIMITIVES[3] = sub { # fail/0
138             FAIL;
139             };
140              
141             $PRIMITIVES[4] = sub { # consult/1
142             my ( $self, $term, $c ) = @_;
143             my $file = $term->getarg(0)->getfunctor;
144             if ( open my $fh, '<', $file ) {
145              
146             # Avoid do { local $/; <$fh> }. This triggers a bug where
147             # *two* copies of the string are made. Double space is
148             # required.
149             my $prolog;
150             {
151             local $/;
152             $prolog = <$fh>;
153             }
154             $self->{_db}->consult($prolog);
155             return CONTINUE;
156             }
157             else {
158             warn "Could not open ($file) for reading: $!";
159             return FAIL;
160             }
161             };
162              
163             $PRIMITIVES[5] = sub { # assert/1
164             my ( $self, $term, $c ) = @_;
165             $self->{_db}->assert( $term->getarg(0) );
166             return CONTINUE;
167             };
168              
169             $PRIMITIVES[7] = sub { # retract/1
170             my ( $self, $term, $c ) = @_;
171             if ( not $self->{_db}->retract( $term->getarg(0), $self->{_stack} ) ) {
172             $self->backtrack;
173             return FAIL;
174             }
175             $self->{_cp}->clause( $self->{_retract_clause} )
176             ; # if $self->{_cp}; # doesn't work
177             return CONTINUE;
178             };
179              
180             $PRIMITIVES[8] = sub { # listing/0
181             my $self = shift;
182             $self->{_db}->dump(0);
183             return CONTINUE;
184             };
185              
186             $PRIMITIVES[9] = sub { # listing/1
187             my ( $self, $term, $c ) = @_;
188             my $predicate = $term->getarg(0)->getfunctor;
189             $self->{_db}->list($predicate);
190             return CONTINUE;
191             };
192              
193             $PRIMITIVES[10] = sub { # print/1
194             my ( $self, $term, $c ) = @_;
195             AI::Prolog::Engine::_print( $term->getarg(0)->to_string );
196             return CONTINUE;
197             };
198              
199             $PRIMITIVES[11] = sub { # println/1
200             my ( $self, $term, $c ) = @_;
201             AI::Prolog::Engine::_print( $term->getarg(0)->to_string . "\n" );
202             return CONTINUE;
203             };
204              
205             $PRIMITIVES[12] = sub { AI::Prolog::Engine::_print("\n"); CONTINUE }; # nl
206              
207             $PRIMITIVES[13] = sub { # trace. notrace.
208             my ( $self, $term ) = @_;
209             $self->{_trace} = $term->getfunctor eq 'trace';
210             AI::Prolog::Engine::_print(
211             'Trace ' . ( $self->{_trace} ? 'ON' : 'OFF' ) );
212             return CONTINUE;
213             };
214              
215             $PRIMITIVES[15] = sub { # is/2
216             my ( $self, $term, $c ) = @_;
217             my $rhs = $term->getarg(0)->deref;
218             my $lhs = $term->getarg(1)->value;
219             if ( $rhs->is_bound ) {
220             my $value = $rhs->value;
221             if ( not looks_like_number($value) ) {
222             return FAIL;
223             }
224             return $value == $lhs;
225             }
226             $rhs->bind( Number->new($lhs) );
227             push @{ $self->{_stack} } => $rhs;
228             return CONTINUE;
229             };
230              
231             $PRIMITIVES[16] = sub { # gt/2
232             my ( $self, $term ) = @_;
233             return ( $term->getarg(0)->value > $term->getarg(1)->value )
234             ? CONTINUE
235             : FAIL;
236             };
237              
238             $PRIMITIVES[17] = sub { # lt/2
239             my ( $self, $term ) = @_;
240             return ( $term->getarg(0)->value < $term->getarg(1)->value )
241             ? CONTINUE
242             : FAIL;
243             };
244              
245             $PRIMITIVES[19] = sub { # ge/2
246             my ( $self, $term ) = @_;
247             return ( $term->getarg(0)->value >= $term->getarg(1)->value )
248             ? CONTINUE
249             : FAIL;
250             };
251              
252             $PRIMITIVES[20] = sub { # le/2
253             my ( $self, $term ) = @_;
254             return ( $term->getarg(0)->value <= $term->getarg(1)->value )
255             ? CONTINUE
256             : FAIL;
257             };
258              
259             $PRIMITIVES[22] = sub { # halt/0
260             my ( $self, $term ) = @_;
261             $self->halt(1);
262             CONTINUE;
263             };
264              
265             $PRIMITIVES[23] = sub { # var/1
266             my ( $self, $term, $c ) = @_;
267             return $term->getarg(0)->bound() ? FAIL : CONTINUE;
268             };
269              
270             # plus(X,Y) := 25.
271             # minux(X,Y) := 26.
272             # mult(X,Y) := 27.
273             # div(X,Y) := 28.
274             # mod(X,Y) := 29.
275              
276             $PRIMITIVES[30] = sub { # seq/1
277             my ( $self, $term, $c ) = @_;
278             $self->_splice_goal_list($term);
279             CONTINUE;
280             };
281              
282             my $HELP_OUTPUT;
283             $PRIMITIVES[31] = sub { # help/0
284             _load_builtins();
285             if ( not $HELP_OUTPUT ) {
286             $HELP_OUTPUT = "Help is available for the following builtins:\n\n";
287             my @predicates = sort keys %DESCRIPTION_FOR;
288             my $length = length $LONGEST_PREDICATE;
289             my $columns = 5;
290             my $format = join ' ' => ("%-${length}s") x $columns;
291             while (@predicates) {
292             my @row;
293             for ( 1 .. $columns ) {
294             push @row => @predicates
295             ? shift @predicates
296             : '';
297             }
298             $HELP_OUTPUT .= sprintf $format => @row;
299             $HELP_OUTPUT .= "\n";
300             }
301             $HELP_OUTPUT .= "\n";
302             }
303             AI::Prolog::Engine::_print($HELP_OUTPUT);
304             CONTINUE;
305             };
306              
307             $PRIMITIVES[32] = sub { # help/1
308             my ( $self, $term, $c ) = @_;
309             my $predicate = $term->getarg(0)->to_string;
310             _load_builtins();
311             if ( my $description = $DESCRIPTION_FOR{$predicate} ) {
312             AI::Prolog::Engine::_print($description);
313             }
314             else {
315             AI::Prolog::Engine::_print("No help available for ($predicate)\n\n");
316             $PRIMITIVES[31]->();
317             }
318             CONTINUE;
319             };
320              
321             my $gensym_int = 0;
322             $PRIMITIVES[33] = sub { # gemsym/1
323             my ( $self, $term, $c ) = @_;
324             my $t2 = Term->new( 'v' . $gensym_int++, 0 );
325             return $t2->unify( $term->getarg(0), $self->{_stack} )
326             ? CONTINUE
327             : FAIL;
328             };
329              
330 13         26 use constant UNDEFINED_SUBROUTINE_ERROR => do {
331 13         33 eval {
332 13     13   100 no strict 'refs'; ## no critic NoStrict
  13         27  
  13         1546  
333 13         33 &{'---'};
  13         272  
334             };
335 13         37 my $e = $@;
336              
337             # Undefined subroutine &main::--- called at .../Primitives.pm line 12.
338 13 50       394 my ($msg) = $e =~ / \A
339             (.+) # 'Undefined subroutine'
340             (?<=\s) # ' '
341             \S* # &main::
342             ---/mx
343             or die q[Perl's error message changed! Damn! Fix this regex.];
344              
345 13         3235 $msg;
346 13     13   71 };
  13         24  
347              
348             $PRIMITIVES[34] = sub { # perlcall2/2
349             my ( $self, $term ) = @_;
350              
351             # Get a function name...
352             my $function_term = $term->getarg(0);
353             if ( not $function_term->is_bound ) {
354             return FAIL;
355             }
356             my $function_name = $function_term->to_string;
357              
358             # Lookup a fully qualified function name...
359             my $function_ref;
360             if ( $function_name =~ /[:\']/mx ) {
361             $function_ref = $function_name;
362             }
363             elsif ( defined( my $package = $self->{_perlpackage} ) ) {
364             $function_name = "$package\::$function_name";
365             }
366              
367             # Search the call stack...
368             if ( not defined $function_ref ) {
369             my $cx = 1;
370             my %packages;
371             CX:
372             while ( my $package = caller $cx ) {
373              
374             # Don't retry packages...
375             next if exists $packages{$package};
376             $packages{$package} = undef;
377              
378             # AUTOLOAD using packages are expected to provide a
379             # ->can() that works. I don't know if that's a widely
380             # known expectation but it's what I'm going to go
381             # with. Hash::AsObject gets this wrong.
382             if (do {
383 13     13   87 no strict 'refs'; ## no critic NoStrict
  13         26  
  13         1784  
384             defined &{"$package\::$function_name"};
385             }
386             or $package->can($function_name)
387             )
388             {
389             $function_ref = "$package\::$function_name";
390             last CX;
391             }
392             }
393             continue {
394             ++$cx;
395             }
396             }
397              
398             # We got nuthin! Damn! I'll try for the first AUTOLOAD.
399             if ( not defined $function_ref ) {
400             my $cx = 1;
401             my %packages;
402             AUTOLOAD_CX:
403             while ( my ($package) = caller $cx ) {
404             next if exists $packages{$package};
405             $packages{$package} = undef;
406              
407             if (do {
408 13     13   69 no strict 'refs'; ## no critic NoStrict
  13         23  
  13         3695  
409             defined &{"$package\::AUTOLOAD"};
410             }
411             or $package->can('AUTOLOAD')
412             )
413             {
414             $function_ref = "$package\::$function_name";
415             last AUTOLOAD_CX;
416             }
417             }
418             continue {
419             ++$cx;
420             }
421             }
422              
423             if ( not defined $function_ref ) {
424             return FAIL;
425             }
426              
427             # XXX What do to with the first arg?
428             my ( undef, $results_ref ) = $term->getarg(1)->to_data;
429             my @results = @{ $results_ref->[0] };
430              
431             eval {
432              
433 13     13   72 no strict 'refs'; ## no critic NoStrict
  13         32  
  13         2487  
434             $function_ref->(@results);
435             };
436             if ( my $e = $@ ) {
437            
438             # Extreme caution here.
439             if ( $e =~ UNDEFINED_SUBROUTINE_ERROR ) {
440             return FAIL;
441             }
442             }
443              
444             return CONTINUE;
445             };
446              
447 49     49 0 380 sub find { return $PRIMITIVES[ $_[1] ] }
448              
449             1;
450              
451             __END__