Files
mercury/samples/interpreter.m
Fergus Henderson 02751f887e Add calls to `io__flush_output', so that the input and output
Estimated hours taken: 0.1

samples/calculator.m:
samples/eliza.m:
samples/interpreter.m:
	Add calls to `io__flush_output', so that the input and output
	are interleaved properly when you run the programs with output
	redirected to a pipe, e.g. "./calculator | tee logfile".
1999-11-04 03:16:52 +00:00

375 lines
12 KiB
Mathematica

%-----------------------------------------------------------------------------%
% File: interpreter.m.
% Main author: fjh.
% This is an interpreter for definite logic programs
% (i.e. pure Prolog with no negation or if-then-else.)
%
% This is just intended as a demonstration of the use of the
% meta-programming library modules term, varset, and term_io.
% There are many extensions/improvements that could be made;
% they're left as an exercise for the reader.
% For a more efficient version (using backtrackable destructive update),
% see extras/trailed_update/samples/interpreter.m.
% This source file is hereby placed in the public domain. -fjh (the author).
%-----------------------------------------------------------------------------%
:- module interpreter.
:- interface.
:- import_module io.
:- pred main(io__state, io__state).
:- mode main(di, uo) is det.
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module list, string, term, varset, term_io, require, std_util.
main -->
io__write_string("Pure Prolog Interpreter.\n\n"),
io__command_line_arguments(Args),
( { Args = [] } ->
io__stderr_stream(StdErr),
io__write_string(StdErr, "Usage: interpreter filename ...\n"),
io__set_exit_status(1)
;
{ database_init(Database0) },
consult_list(Args, Database0, Database),
main_loop(Database)
).
:- pred main_loop(database, io__state, io__state).
:- mode main_loop(in, di, uo) is det.
main_loop(Database) -->
io__write_string("?- "),
io__flush_output,
term_io__read_term(ReadTerm),
main_loop_2(ReadTerm, Database).
:- pred main_loop_2(read_term, database, io__state, io__state).
:- mode main_loop_2(in, in, di, uo) is det.
main_loop_2(eof, _Database) --> [].
main_loop_2(error(ErrorMessage, LineNumber), Database) -->
io__write_string("Error reading term at line "),
io__write_int(LineNumber),
io__write_string(" of standard input: "),
io__write_string(ErrorMessage),
io__write_string("\n"),
main_loop(Database).
main_loop_2(term(VarSet0, Goal), Database) -->
%%% It would be a good idea to add some special commands
%%% with side-effects (such as `consult' and `listing');
%%% these could be identified and processed here.
{ solutions(solve(Database, Goal, VarSet0), Solutions) },
write_solutions(Solutions, Goal),
main_loop(Database).
:- pred write_solutions(list(varset), term, io__state, io__state).
:- mode write_solutions(in, in, di, uo) is det.
write_solutions(Solutions, Goal) -->
( { Solutions = [] } ->
io__write_string("No.\n")
;
write_solutions_2(Solutions, Goal),
io__write_string("Yes.\n")
).
:- pred write_solutions_2(list(varset), term, io__state, io__state).
:- mode write_solutions_2(in, in, di, uo) is det.
write_solutions_2([], _) --> [].
write_solutions_2([VarSet | VarSets], Goal) -->
term_io__write_term_nl(VarSet, Goal),
write_solutions_2(VarSets, Goal).
%-----------------------------------------------------------------------------%
:- pred consult_list(list(string), database, database, io__state, io__state).
:- mode consult_list(in, in, out, di, uo) is det.
consult_list([], Database, Database) --> [].
consult_list([File | Files], Database0, Database) -->
consult(File, Database0, Database1),
consult_list(Files, Database1, Database).
:- pred consult(string, database, database, io__state, io__state).
:- mode consult(in, in, out, di, uo) is det.
consult(File, Database0, Database) -->
io__write_string("Consulting file `"),
io__write_string(File),
io__write_string("'...\n"),
io__see(File, Result),
( { Result = ok } ->
consult_until_eof(Database0, Database),
io__seen
;
io__write_string("Error opening file `"),
io__write_string(File),
io__write_string("' for input.\n"),
{ Database = Database0 }
).
:- pred consult_until_eof(database, database, io__state, io__state).
:- mode consult_until_eof(in, out, di, uo) is det.
consult_until_eof(Database0, Database) -->
term_io__read_term(ReadTerm),
consult_until_eof_2(ReadTerm, Database0, Database).
:- pred consult_until_eof_2(read_term, database, database,
io__state, io__state).
:- mode consult_until_eof_2(in, in, out, di, uo) is det.
consult_until_eof_2(eof, Database, Database) --> [].
consult_until_eof_2(error(ErrorMessage, LineNumber), Database0, Database) -->
io__write_string("Error reading term at line "),
io__write_int(LineNumber),
io__write_string(" of standard input: "),
io__write_string(ErrorMessage),
io__write_string("\n"),
consult_until_eof(Database0, Database).
consult_until_eof_2(term(VarSet, Term), Database0, Database) -->
{ database_assert_clause(Database0, VarSet, Term, Database1) },
consult_until_eof(Database1, Database).
%-----------------------------------------------------------------------------%
% Solve takes a database of rules and facts, a goal to be solved,
% and a varset (which includes a supply of fresh vars, a substitution,
% and names for [some subset of] the variables). It updates
% the varset, producing a new substitution and perhaps introducing
% some new vars, and returns the result.
% Goals are stored just as terms.
% (It might be more efficient to parse them
% before storing them in the database. Currently we do
% this parsing work every time we interpret a clause.)
:- pred solve(database, term, varset, varset).
:- mode solve(in, in, in, out) is nondet.
solve(_Database, term__functor(term__atom("true"), [], _)) --> [].
solve(Database, term__functor(term__atom(","), [A, B], _)) -->
solve(Database, A),
solve(Database, B).
solve(Database, term__functor(term__atom(";"), [A, B], _)) -->
solve(Database, A)
;
solve(Database, B).
solve(_Database, term__functor(term__atom("="), [A, B], _)) -->
unify(A, B).
solve(Database, Goal) -->
{ database_lookup_clause(Database, Goal, ClauseVarSet, Head0, Body0) },
rename_apart(ClauseVarSet, [Head0, Body0], [Head, Body]),
unify(Goal, Head),
solve(Database, Body).
%-----------------------------------------------------------------------------%
:- pred rename_apart(varset, list(term), list(term), varset, varset).
:- mode rename_apart(in, in, out, in, out) is det.
rename_apart(NewVarSet, Terms0, Terms, VarSet0, VarSet) :-
varset__merge(VarSet0, NewVarSet, Terms0, VarSet, Terms).
%-----------------------------------------------------------------------------%
% The standard library module `term' contains routines for
% unifying terms based on separate substitutions, but we are
% using the substitutions that are contained in the `varset',
% so we can't use those versions.
:- pred unify(term, term, varset, varset).
:- mode unify(in, in, in, out) is semidet.
unify(term__variable(X), term__variable(Y), VarSet0, VarSet) :-
(
varset__search_var(VarSet0, X, BindingOfX)
->
(
varset__search_var(VarSet0, Y, BindingOfY)
->
% both X and Y already have bindings - just
% unify the terms they are bound to
unify(BindingOfX, BindingOfY, VarSet0, VarSet)
;
% Y is a variable which hasn't been bound yet
apply_rec_substitution(BindingOfX, VarSet0,
SubstBindingOfX),
( SubstBindingOfX = term__variable(Y) ->
VarSet = VarSet0
;
\+ occurs(SubstBindingOfX, Y, VarSet0),
varset__bind_var(VarSet0, Y, SubstBindingOfX,
VarSet)
)
)
;
(
varset__search_var(VarSet0, Y, BindingOfY2)
->
% X is a variable which hasn't been bound yet
apply_rec_substitution(BindingOfY2, VarSet0,
SubstBindingOfY2),
( SubstBindingOfY2 = term__variable(X) ->
VarSet = VarSet0
;
\+ occurs(SubstBindingOfY2, X, VarSet0),
varset__bind_var(VarSet0, X, SubstBindingOfY2,
VarSet)
)
;
% both X and Y are unbound variables -
% bind one to the other
( X = Y ->
VarSet = VarSet0
;
varset__bind_var(VarSet0, X, term__variable(Y),
VarSet)
)
)
).
unify(term__variable(X), term__functor(F, As, C), VarSet0, VarSet) :-
(
varset__search_var(VarSet0, X, BindingOfX)
->
unify(BindingOfX, term__functor(F, As, C), VarSet0,
VarSet)
;
\+ occurs_list(As, X, VarSet0),
varset__bind_var(VarSet0, X, term__functor(F, As, C), VarSet)
).
unify(term__functor(F, As, C), term__variable(X), VarSet0, VarSet) :-
(
varset__search_var(VarSet0, X, BindingOfX)
->
unify(term__functor(F, As, C), BindingOfX, VarSet0,
VarSet)
;
\+ occurs_list(As, X, VarSet0),
varset__bind_var(VarSet0, X, term__functor(F, As, C), VarSet)
).
unify(term__functor(F, AsX, _), term__functor(F, AsY, _)) -->
unify_list(AsX, AsY).
:- pred unify_list(list(term), list(term), varset, varset).
:- mode unify_list(in, in, in, out) is semidet.
unify_list([], []) --> [].
unify_list([X | Xs], [Y | Ys]) -->
unify(X, Y),
unify_list(Xs, Ys).
%-----------------------------------------------------------------------------%
% occurs(Term, Var, Subst) succeeds if Term contains Var,
% perhaps indirectly via the substitution. (The variable must
% not be mapped by the substitution.)
:- pred occurs(term, var, varset).
:- mode occurs(in, in, in) is semidet.
occurs(term__variable(X), Y, VarSet) :-
X = Y
;
varset__search_var(VarSet, X, BindingOfX),
occurs(BindingOfX, Y, VarSet).
occurs(term__functor(_F, As, _), Y, VarSet) :-
occurs_list(As, Y, VarSet).
:- pred occurs_list(list(term), var, varset).
:- mode occurs_list(in, in, in) is semidet.
occurs_list([Term | Terms], Y, VarSet) :-
occurs(Term, Y, VarSet)
;
occurs_list(Terms, Y, VarSet).
%-----------------------------------------------------------------------------%
% apply_rec_substitution(Term0, VarSet, Term) :
% recursively apply substitution to Term0 until
% no more substitions can be applied, and then
% return the result in Term.
:- pred apply_rec_substitution(term, varset, term).
:- mode apply_rec_substitution(in, in, out) is det.
apply_rec_substitution(term__variable(Var), VarSet, Term) :-
(
varset__search_var(VarSet, Var, Replacement)
->
% recursively apply the substition to the replacement
apply_rec_substitution(Replacement, VarSet, Term)
;
Term = term__variable(Var)
).
apply_rec_substitution(term__functor(Name, Args0, Context), VarSet,
term__functor(Name, Args, Context)) :-
apply_rec_substitution_to_list(Args0, VarSet, Args).
:- pred apply_rec_substitution_to_list(list(term), varset, list(term)).
:- mode apply_rec_substitution_to_list(in, in, out) is det.
apply_rec_substitution_to_list([], _VarSet, []).
apply_rec_substitution_to_list([Term0 | Terms0], VarSet,
[Term | Terms]) :-
apply_rec_substitution(Term0, VarSet, Term),
apply_rec_substitution_to_list(Terms0, VarSet, Terms).
%-----------------------------------------------------------------------------%
% We store the database just as a list of clauses.
% (It would be more realistic to index this on the predicate name/arity
% and subindex on the name/arity of the first argument.)
:- type database == list(clause).
:- type clause ---> clause(varset, term, term).
:- pred database_init(database).
:- mode database_init(out) is det.
database_init([]).
:- pred database_assert_clause(database, varset, term, database).
:- mode database_assert_clause(in, in, in, out) is det.
database_assert_clause(Database, VarSet, Term, [Clause | Database]) :-
( Term = term__functor(term__atom(":-"), [H, B], _) ->
Head = H,
Body = B
;
Head = Term,
term__context_init(Context),
Body = term__functor(term__atom("true"), [], Context)
),
Clause = clause(VarSet, Head, Body).
:- pred database_lookup_clause(database, term, varset, term, term).
:- mode database_lookup_clause(in, in, out, out, out) is nondet.
database_lookup_clause(Database, _Goal, VarSet, Head, Body) :-
list__member(Clause, Database),
Clause = clause(VarSet, Head, Body).
%-----------------------------------------------------------------------------%