File Coverage

blib/lib/Language/Prolog/Interpreter.pm
Criterion Covered Total %
statement 3 118 2.5
branch 0 70 0.0
condition 0 8 0.0
subroutine 1 9 11.1
pod 0 8 0.0
total 4 213 1.8


line stmt bran cond sub pod time code
1             #! perl -w
2             package Language::Prolog::Interpreter;
3             our $VERSION = "0.021";
4            
5             =head1 NAME
6            
7             Prolog Interpreter alpha 0.02
8            
9             =head1 SYNOPSIS
10            
11             Language::Prolog::Interpreter->readFile('E:/src/PROLOG/flamenco.pr');
12            
13             or
14            
15             $a = <<'EOPROLOG';
16             parent(john,sally).
17             parent(john,joe).
18             parent(mary,joe).
19             parent(phil,beau).
20             parent(jane,john).
21             grandparent(X,Z) :-parent(X,Y),parent(Y,Z).
22             EOPROLOG
23             ;
24             while ($a) {
25             eval 'Language::Prolog::Interpreter->readStatement(\$a)';
26             $@ && die $@,$a,"\n";
27             $a=~s/^\s*//;
28             }
29            
30             # Above is same as
31             # eval 'Language::Prolog::Interpreter->readFile($pathtomyfile)';
32            
33             $a = '?- grandparent(GPARENT,GCHILD).';
34             print $a,"\n";
35             $Q = Language::Prolog::Interpreter->readStatement(\$a);
36             while($Q->query()) {
37             print "found solutions\n";
38             print 'GPARENT = ',$Q->variableResult('GPARENT'),"\n";
39             print 'GCHILD = ',$Q->variableResult('GCHILD'),"\n\n";
40             }
41             print "no more solutions\n\n";
42            
43             $a = 'member(A,[A|_]).';
44             $b = 'member(A,[_|B]) :- member(A,B).'; #Classic member
45             Language::Prolog::Interpreter->readStatement(\$a);
46             Language::Prolog::Interpreter->readStatement(\$b);
47            
48             $a = '?- member(c(V),[a(a),b(b),c(c),d(d),c(q)]).';
49             print $a,"\n";
50             $Q = Language::Prolog::Interpreter->readStatement(\$a);
51             while($Q->query()) {
52             print "found solutions\n";
53             print 'V = ',$Q->variableResult('V'),"\n\n";
54             }
55             print "no more solutions\n\n";
56            
57             =head1 DESCRIPTION
58            
59             A simple interpreter which doesn't allow infix operators (except for C<:-> and C<,>, both of which are built in).
60            
61             =head2 SYNTAX
62            
63             There are three possible statements:
64            
65             =over 4
66            
67             =item 1. Clauses
68            
69             A single B ending in a statement terminator (C<.>).
70            
71             This gets added to the database.
72            
73             =item 2. Rules
74            
75             A single B ending in a statement terminator (C<.>).
76            
77             This gets added to the store.
78            
79             =item 3. Queries
80            
81             The he B characters C, followed by a comma separated list of clauses, ending in a statement terminator (C<.>).
82            
83             This creates and returns a query.
84            
85             =item Comments
86            
87             Multi-line comments are Java-like, taking the form C.
88            
89             Single-line/end-of-line comments are donnated by C<%>.
90            
91             =item Whitespace
92            
93             Whitespace is ignored everywhere except in single quoted atoms
94            
95             =back
96            
97             =cut
98            
99            
100             our $VARIABLE_REGEX = '[A-Z_]\w*';
101             our $SIMPLE_ATOM_REGEX = '[a-z]\w*';
102            
103            
104 0     0 0   sub readStatement { my($self,$string_ref) = @_;
105 0           $$string_ref =~ s/^\s*//;
106 0 0         return undef if $$string_ref eq '';
107 0           my $statement;
108            
109 0 0         if ($$string_ref =~ s/^\?\-//) {
110 0           return $self->readQuery($string_ref);
111             } else {
112 0           $statement = $self->readClauseOrRule($string_ref);
113 0           $$string_ref =~ s/^\s*//;
114 0 0         if ($$string_ref =~ s/^\.//) {
115 0           $statement->_addToStore();
116 0           return undef;
117             } else {
118 0           die "Error - statement terminator is missing";
119             }
120             }
121             }
122            
123             sub readQuery {
124 0     0 0   my($self,$string_ref) = @_;
125 0           my(@clauses,$variables);
126 0           $variables = {};
127            
128 0           for(;;) {
129 0           push(@clauses,$self->readClause($string_ref,$variables));
130 0 0         if ($$string_ref =~ s/\s*\,//) {
    0          
131 0           next;
132             } elsif ($$string_ref =~ s/\s*\.//) {
133 0           return Language::Prolog::Query->newQuery($variables,@clauses);
134             } else {
135 0           die "Error - statement terminator is missing";
136             }
137             }
138             }
139            
140            
141             =head2 TERMS
142            
143             Terms are:-
144            
145             =item Lists1:
146            
147             Comma separated lists of terms enclosed in square brackets
148            
149             e.g [Term1,Term2]
150            
151             =item Lists2:
152            
153             As List1, but final term is a variable separated by a '|'
154            
155             e.g [Term1,Term2|Variable]
156            
157             =item Atoms1:
158            
159             sequence of characters/digits/underscore (i.e C<\w> character class) starting with a lower case character.
160            
161             e.g. this_Is_An_Atom
162            
163             =item Atoms1:
164            
165             any sequence of characters enclosed in single quotes (')
166            
167             e.g. 'This is another atom!'
168            
169             =item Variables:
170            
171             sequence of characters/digits/underscore (i.e C<\w> character class) starting with an upper case character or underscore
172            
173             e.g. This_is_a_var, _and_this, _90
174            
175             =item Clauses:
176            
177             an Atom1 immediately followed by a left bracket, C<(>, followed by a comma separated list of terms, terminating in a right bracket.
178            
179             e.g clause(one), clause2(a,hello,'More !',[a,b,c])
180            
181             =item Rules:
182            
183             A Clause, followed by optional whitespace, followed by C<:->, followed by optional whitespace, followed by a list of clauses separated by commas.
184            
185             =cut
186            
187             sub readTerm {
188 0     0 0   my($self,$string_ref,$variables) = @_;
189 0 0         if(!defined($variables)) {$variables = {};}
  0            
190 0           my($term);
191            
192             # Delete whitespace
193 0           $$string_ref =~ s/\s*//;
194            
195 0 0         if ($$string_ref =~ m/^\[/) {
    0          
    0          
    0          
    0          
196 0           $term = $self->readList($string_ref,$variables);
197             } elsif ($$string_ref =~ s/^('[^']+')//) { #'
198 0           $term = Language::Prolog::Term->newAtom($1);
199             } elsif ($$string_ref =~ m/^$SIMPLE_ATOM_REGEX\(/o) {
200 0           $term = $self->readClauseOrRule($string_ref,$variables);
201             } elsif ($$string_ref =~ s/^($SIMPLE_ATOM_REGEX)//o) {
202 0           $term = Language::Prolog::Term->newAtom($1);
203             } elsif ($$string_ref =~ s/^($VARIABLE_REGEX)//o) {
204 0           $term = $self->variable($variables,$1);
205             } else {
206 0           die "Term not recognized";
207             }
208            
209             # $$string_ref =~ s/^\s*\.// ||
210             # die "Statement terminator (.) expected but not found";
211 0           return $term;
212             }
213            
214             sub variable {
215 0     0 0   my($self,$variables,$string) = @_;
216 0           my $new;
217 0 0         $variables = {} if not defined($variables);
218 0 0         if (!$variables->{$string}) {
219 0           $new = Language::Prolog::Term->newVariable($string);
220 0           $variables->{$string} = $new;
221             } else {
222 0           $new = Language::Prolog::Term->newVariable($string);
223 0 0         $new->unify($variables->{$string}) ||
224             die "Error - cannot specify variables to match recursively";
225             }
226 0           return $new;
227             }
228            
229            
230             sub readList {
231 0     0 0   my($self,$string_ref,$variables) = @_;
232 0           my(@terms);
233            
234 0 0         ($$string_ref =~ s/^\s*\[//) || die "Not a list";
235            
236 0 0         return Language::Prolog::Term->newList() if $$string_ref =~ s/^\s*\]//;
237            
238 0           for (;;) {
239 0           $$string_ref =~ s/^\s*//;
240 0           push(@terms,$self->readTerm($string_ref,$variables));
241 0 0         if ($$string_ref =~ s/^\s*,//) {
    0          
    0          
242 0           next;
243             } elsif ($$string_ref =~ s/^\s*\]//) {
244 0           return Language::Prolog::Term->newList(@terms);
245             } elsif ($$string_ref =~ s/^\s*\|\s*($VARIABLE_REGEX)\s*\]//o) {
246 0           return Language::Prolog::Term->newVarList(@terms,
247             $self->variable($variables,$1));
248             } else {
249 0           die "Term not recognized";
250             }
251             }
252             }
253            
254             sub readClauseOrRule {
255 0     0 0   my($self,$string_ref,$variables) = @_;
256            
257 0 0         $variables = {} if not defined($variables);
258            
259 0           my $head = $self->readClause($string_ref,$variables);
260 0 0         if ($$string_ref =~ s/^\s*:-//) {
261 0           my(@tail);
262 0           for (;;) {
263 0           $$string_ref =~ s/^\s*//;
264            
265 0           push(@tail,$self->readClause($string_ref,$variables));
266            
267 0 0         if ($$string_ref =~ s/^,//) {
268 0           next;
269             } else {
270 0           return Language::Prolog::Term->newRule($head,@tail);
271             }
272             }
273             } else {
274 0           return $head;
275             }
276             }
277            
278             sub readClause {
279 0     0 0   my($self,$string_ref,$variables) = @_;
280 0           my(@terms);
281            
282 0           $$string_ref =~ s/^\s*//;
283            
284 0 0         if ($$string_ref =~ s/^($SIMPLE_ATOM_REGEX)\(//o) {
    0          
285 0           push(@terms,Language::Prolog::Term->newAtom($1));
286 0           for (;;) {
287 0           $$string_ref =~ s/^\s*//;
288            
289 0           push(@terms,$self->readTerm($string_ref,$variables));
290            
291 0 0         if ($$string_ref =~ s/^\s*,//) {
    0          
292 0           next;
293             } elsif ($$string_ref =~ s/^\s*\)//) {
294 0           return Language::Prolog::Term->newClause(@terms);
295             } else {
296 0           die "Term not recognized";
297             }
298             }
299             } elsif ($$string_ref =~ s/^($SIMPLE_ATOM_REGEX)\b//o) {
300 0           return Language::Prolog::Term->newClause(
301             Language::Prolog::Term->newAtom($1)
302             );
303             } else {
304 0           warn "Not a clause:- \n>>\n$$string_ref\n<<";
305 1     1   2922 use Carp;
  1         2  
  1         858  
306 0           confess;
307             }
308             }
309            
310            
311             #
312             # This is one of Lee's subs.
313             #
314 0     0 0   sub readFile { my ($self,$path)=(shift,shift);
315 0 0         die "readFile requires a file path to read from." if not defined $path;
316 0 0 0       warn "No such file at <$path>" and return undef if not -e $path;
317            
318 0 0         open IN,$path or die "Couldn't open path <$path>:\n$!";
319 0           @_ = ;
320 0           close IN;
321 0           my $file = join "\n",@_;
322            
323             #
324             # Strip comments
325             #
326 0           $file =~ s| \Q/**\E .*? \*?\Q*/\E ||sgx; # Remove multiline comments. /**..**/ or /**..*/
327 0           $file =~ s|\%.*?\n||g; # Remove single-line comments
328 0           $file =~ s|\n||sg;
329            
330             #
331             # Make the file into lines of clauses (terminated with a full-stop) for processing.
332             # Will not terminate with brackets [] or () or single-quotes, ''.
333             # Any character escaped with \ is ignored.
334             #
335 0           my ($c,$q,$clauses);
336 0           my @clauses;
337 0           my $b=0;
338 0           for (my $i=0; $i
339 0           my $c = substr($file,$i,1); # Does this increase speed?
340 0 0         if ($c eq '\\'){ $clauses .= $c; next } # Don't set quote flag if escaped quote
  0            
  0            
341 0 0         if ($c eq "'" ) { $q = not $q } # Invert quote flag
  0            
342 0 0         if ($c =~ /^[(\[]$/){ ++$b } # Stack of open brackets
  0            
343 0 0         if ($c =~ /^[)\]]$/){ --$b } # Stack of closed brackets
  0            
344 0 0 0       if ($c eq "." and not $q and $b==0) {
      0        
345 0           $clauses .= "$c\n"; # Add \n to .
346 0           push @clauses,$clauses;
347 0           next;
348             }
349 0           $clauses .= $c; # Store result
350             }
351            
352 0           foreach (@clauses) {
353 0           eval 'Language::Prolog::Interpreter->readStatement(\$file)';
354 0 0         $@ && die $@,$file,"\n";
355 0           $file=~s/^\s*//;
356             }
357            
358             }
359            
360             1;
361            
362             =head1 AUTHOR
363            
364             Jack Shirazi.
365            
366             Since Mr Shirzai seems to have vanished, updated by Lee Goddard to support file parsing, single- and multi-line comments, and multi-ilne clauses.
367            
368             =head1 COPYRIGHT
369            
370             Copyright (C) 1995, Jack Shirazi. All Rights Reserved.
371            
372             Updates Copyright (C) 2001, Lee Goddard. All Rights Reserved.
373            
374             Usage is under the same terms as for Perl itself.
375            
376             =cut