The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

#! perl -w
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<<";
use Carp;
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