———#! perl -w
package
Language::Prolog::Interpreter;
our
$VERSION
=
"0.021"
;
=head1 NAME
Prolog Interpreter alpha 0.02
=head1 SYNOPSIS
Language::Prolog::Interpreter->readFile('E:/src/PROLOG/flamenco.pr');
or
$a = <<'EOPROLOG';
parent(john,sally).
parent(john,joe).
parent(mary,joe).
parent(phil,beau).
parent(jane,john).
grandparent(X,Z) :-parent(X,Y),parent(Y,Z).
EOPROLOG
;
while ($a) {
eval 'Language::Prolog::Interpreter->readStatement(\$a)';
$@ && die $@,$a,"\n";
$a=~s/^\s*//;
}
# Above is same as
# eval 'Language::Prolog::Interpreter->readFile($pathtomyfile)';
$a = '?- grandparent(GPARENT,GCHILD).';
print $a,"\n";
$Q = Language::Prolog::Interpreter->readStatement(\$a);
while($Q->query()) {
print "found solutions\n";
print 'GPARENT = ',$Q->variableResult('GPARENT'),"\n";
print 'GCHILD = ',$Q->variableResult('GCHILD'),"\n\n";
}
print "no more solutions\n\n";
$a = 'member(A,[A|_]).';
$b = 'member(A,[_|B]) :- member(A,B).'; #Classic member
Language::Prolog::Interpreter->readStatement(\$a);
Language::Prolog::Interpreter->readStatement(\$b);
$a = '?- member(c(V),[a(a),b(b),c(c),d(d),c(q)]).';
print $a,"\n";
$Q = Language::Prolog::Interpreter->readStatement(\$a);
while($Q->query()) {
print "found solutions\n";
print 'V = ',$Q->variableResult('V'),"\n\n";
}
print "no more solutions\n\n";
=head1 DESCRIPTION
A simple interpreter which doesn't allow infix operators (except for C<:-> and C<,>, both of which are built in).
=head2 SYNTAX
There are three possible statements:
=over 4
=item 1. Clauses
A single B<clause> ending in a statement terminator (C<.>).
This gets added to the database.
=item 2. Rules
A single B<rule> ending in a statement terminator (C<.>).
This gets added to the store.
=item 3. Queries
The he B<query> characters C<?->, followed by a comma separated list of clauses, ending in a statement terminator (C<.>).
This creates and returns a query.
=item Comments
Multi-line comments are Java-like, taking the form C</** ... **/>.
Single-line/end-of-line comments are donnated by C<%>.
=item Whitespace
Whitespace is ignored everywhere except in single quoted atoms
=back
=cut
our
$VARIABLE_REGEX
=
'[A-Z_]\w*'
;
our
$SIMPLE_ATOM_REGEX
=
'[a-z]\w*'
;
sub
readStatement {
my
(
$self
,
$string_ref
) =
@_
;
$$string_ref
=~ s/^\s*//;
return
undef
if
$$string_ref
eq
''
;
my
$statement
;
if
(
$$string_ref
=~ s/^\?\-//) {
return
$self
->readQuery(
$string_ref
);
}
else
{
$statement
=
$self
->readClauseOrRule(
$string_ref
);
$$string_ref
=~ s/^\s*//;
if
(
$$string_ref
=~ s/^\.//) {
$statement
->_addToStore();
return
undef
;
}
else
{
die
"Error - statement terminator is missing"
;
}
}
}
sub
readQuery {
my
(
$self
,
$string_ref
) =
@_
;
my
(
@clauses
,
$variables
);
$variables
= {};
for
(;;) {
push
(
@clauses
,
$self
->readClause(
$string_ref
,
$variables
));
if
(
$$string_ref
=~ s/\s*\,//) {
next
;
}
elsif
(
$$string_ref
=~ s/\s*\.//) {
return
Language::Prolog::Query->newQuery(
$variables
,
@clauses
);
}
else
{
die
"Error - statement terminator is missing"
;
}
}
}
=head2 TERMS
Terms are:-
=item Lists1:
Comma separated lists of terms enclosed in square brackets
e.g [Term1,Term2]
=item Lists2:
As List1, but final term is a variable separated by a '|'
e.g [Term1,Term2|Variable]
=item Atoms1:
sequence of characters/digits/underscore (i.e C<\w> character class) starting with a lower case character.
e.g. this_Is_An_Atom
=item Atoms1:
any sequence of characters enclosed in single quotes (')
e.g. 'This is another atom!'
=item Variables:
sequence of characters/digits/underscore (i.e C<\w> character class) starting with an upper case character or underscore
e.g. This_is_a_var, _and_this, _90
=item Clauses:
an Atom1 immediately followed by a left bracket, C<(>, followed by a comma separated list of terms, terminating in a right bracket.
e.g clause(one), clause2(a,hello,'More !',[a,b,c])
=item Rules:
A Clause, followed by optional whitespace, followed by C<:->, followed by optional whitespace, followed by a list of clauses separated by commas.
=cut
sub
readTerm {
my
(
$self
,
$string_ref
,
$variables
) =
@_
;
if
(!
defined
(
$variables
)) {
$variables
= {};}
my
(
$term
);
# Delete whitespace
$$string_ref
=~ s/\s*//;
if
(
$$string_ref
=~ m/^\[/) {
$term
=
$self
->readList(
$string_ref
,
$variables
);
}
elsif
(
$$string_ref
=~ s/^(
'[^'
]+
')//) { #'
$term
= Language::Prolog::Term->newAtom($1);
}
elsif
(
$$string_ref
=~ m/^
$SIMPLE_ATOM_REGEX
\(/o) {
$term
=
$self
->readClauseOrRule(
$string_ref
,
$variables
);
}
elsif
(
$$string_ref
=~ s/^(
$SIMPLE_ATOM_REGEX
)//o) {
$term
= Language::Prolog::Term->newAtom($1);
}
elsif
(
$$string_ref
=~ s/^(
$VARIABLE_REGEX
)//o) {
$term
=
$self
->variable(
$variables
,$1);
}
else
{
die
"Term not recognized"
;
}
# $$string_ref =~ s/^\s*\.// ||
# die "Statement terminator (.) expected but not found";
return
$term
;
}
sub
variable {
my
(
$self
,
$variables
,
$string
) =
@_
;
my
$new
;
$variables
= {}
if
not
defined
(
$variables
);
if
(!
$variables
->{
$string
}) {
$new
= Language::Prolog::Term->newVariable(
$string
);
$variables
->{
$string
} =
$new
;
}
else
{
$new
= Language::Prolog::Term->newVariable(
$string
);
$new
->unify(
$variables
->{
$string
}) ||
die
"Error - cannot specify variables to match recursively"
;
}
return
$new
;
}
sub
readList {
my
(
$self
,
$string_ref
,
$variables
) =
@_
;
my
(
@terms
);
(
$$string_ref
=~ s/^\s*\[//) ||
die
"Not a list"
;
return
Language::Prolog::Term->newList()
if
$$string_ref
=~ s/^\s*\]//;
for
(;;) {
$$string_ref
=~ s/^\s*//;
push
(
@terms
,
$self
->readTerm(
$string_ref
,
$variables
));
if
(
$$string_ref
=~ s/^\s*,//) {
next
;
}
elsif
(
$$string_ref
=~ s/^\s*\]//) {
return
Language::Prolog::Term->newList(
@terms
);
}
elsif
(
$$string_ref
=~ s/^\s*\|\s*(
$VARIABLE_REGEX
)\s*\]//o) {
return
Language::Prolog::Term->newVarList(
@terms
,
$self
->variable(
$variables
,$1));
}
else
{
die
"Term not recognized"
;
}
}
}
sub
readClauseOrRule {
my
(
$self
,
$string_ref
,
$variables
) =
@_
;
$variables
= {}
if
not
defined
(
$variables
);
my
$head
=
$self
->readClause(
$string_ref
,
$variables
);
if
(
$$string_ref
=~ s/^\s*:-//) {
my
(
@tail
);
for
(;;) {
$$string_ref
=~ s/^\s*//;
push
(
@tail
,
$self
->readClause(
$string_ref
,
$variables
));
if
(
$$string_ref
=~ s/^,//) {
next
;
}
else
{
return
Language::Prolog::Term->newRule(
$head
,
@tail
);
}
}
}
else
{
return
$head
;
}
}
sub
readClause {
my
(
$self
,
$string_ref
,
$variables
) =
@_
;
my
(
@terms
);
$$string_ref
=~ s/^\s*//;
if
(
$$string_ref
=~ s/^(
$SIMPLE_ATOM_REGEX
)\(//o) {
push
(
@terms
,Language::Prolog::Term->newAtom($1));
for
(;;) {
$$string_ref
=~ s/^\s*//;
push
(
@terms
,
$self
->readTerm(
$string_ref
,
$variables
));
if
(
$$string_ref
=~ s/^\s*,//) {
next
;
}
elsif
(
$$string_ref
=~ s/^\s*\)//) {
return
Language::Prolog::Term->newClause(
@terms
);
}
else
{
die
"Term not recognized"
;
}
}
}
elsif
(
$$string_ref
=~ s/^(
$SIMPLE_ATOM_REGEX
)\b//o) {
return
Language::Prolog::Term->newClause(
Language::Prolog::Term->newAtom($1)
);
}
else
{
warn
"Not a clause:- \n>>\n$$string_ref\n<<"
;
confess;
}
}
#
# This is one of Lee's subs.
#
sub
readFile {
my
(
$self
,
$path
)=(
shift
,
shift
);
die
"readFile requires a file path to read from."
if
not
defined
$path
;
warn
"No such file at <$path>"
and
return
undef
if
not -e
$path
;
open
IN,
$path
or
die
"Couldn't open path <$path>:\n$!"
;
@_
= <IN>;
close
IN;
my
$file
=
join
"\n"
,
@_
;
#
# Strip comments
#
$file
=~ s| \Q/**\E .*? \*?\Q*/\E ||sgx;
# Remove multiline comments. /**..**/ or /**..*/
$file
=~ s|\%.*?\n||g;
# Remove single-line comments
$file
=~ s|\n||sg;
#
# Make the file into lines of clauses (terminated with a full-stop) for processing.
# Will not terminate with brackets [] or () or single-quotes, ''.
# Any character escaped with \ is ignored.
#
my
(
$c
,
$q
,
$clauses
);
my
@clauses
;
my
$b
=0;
for
(
my
$i
=0;
$i
<
length
(
$file
);
$i
++){
my
$c
=
substr
(
$file
,
$i
,1);
# Does this increase speed?
if
(
$c
eq
'\\'
){
$clauses
.=
$c
;
next
}
# Don't set quote flag if escaped quote
if
(
$c
eq
"'"
) {
$q
= not
$q
}
# Invert quote flag
if
(
$c
=~ /^[(\[]$/){ ++
$b
}
# Stack of open brackets
if
(
$c
=~ /^[)\]]$/){ --
$b
}
# Stack of closed brackets
if
(
$c
eq
"."
and not
$q
and
$b
==0) {
$clauses
.=
"$c\n"
;
# Add \n to .
push
@clauses
,
$clauses
;
next
;
}
$clauses
.=
$c
;
# Store result
}
foreach
(
@clauses
) {
eval
'Language::Prolog::Interpreter->readStatement(\$file)'
;
$@ &&
die
$@,
$file
,
"\n"
;
$file
=~s/^\s*//;
}
}
1;
=head1 AUTHOR
Jack Shirazi.
Since Mr Shirzai seems to have vanished, updated by Lee Goddard <lgoddard@cpan.org> to support file parsing, single- and multi-line comments, and multi-ilne clauses.
=head1 COPYRIGHT
Copyright (C) 1995, Jack Shirazi. All Rights Reserved.
Updates Copyright (C) 2001, Lee Goddard. All Rights Reserved.
Usage is under the same terms as for Perl itself.
=cut