scryer-prolog

A modern Prolog implementation written mostly in Rust.

Github stars Tracking Chart

Scryer Prolog

Scryer Prolog aims to become to ISO Prolog what GHC is to Haskell: an open
source industrial strength production environment that is also a
testbed for bleeding edge research in logic and constraint
programming, which is itself written in a high-level language.

Phase 1

Produce an implementation of the Warren Abstract Machine in Rust, done
according to the progression of languages in Warren's Abstract
Machine: A Tutorial
Reconstruction
.

Phase 1 has been completed in that Scryer Prolog implements in some form
all of the WAM book, including lists, cuts, Debray allocation, first
argument indexing, last call optimization and conjunctive queries.

Phase 2

Extend Scryer Prolog to include the following, among other features:

  • call/N as a built-in meta-predicate.
  • ISO Prolog compliant throw/catch.
  • Built-in and user-defined operators of all fixities, with custom
    associativity and precedence.
  • Bignum, rational number and floating point arithmetic.
  • Built-in control operators (,, ;, ->, etc.).
  • A revised, not-terrible module system.
  • Built-in predicates for list processing and top-level declarative
    control (setup_call_cleanup/3, call_with_inference_limit/3,
    etc.)
  • Default representation of strings as list of chars, using a packed
    internal representation.
  • term_expansion/2 and goal_expansion/2.
  • Definite Clause Grammars.
  • Attributed variables using the SICStus Prolog interface and
    semantics. Adding coroutines like dif/2, freeze/2, etc.
    is straightforward with attributed variables.
    • Support for verify_attributes/3
    • Support for attribute_goals/2 and project_attributes/2
    • call_residue_vars/2
  • if_ and related predicates, following the developments of the
    paper "Indexing dif/2".
  • All-solutions predicates (findall/{3,4}, bagof/3, setof/3, forall/2).
  • Clause creation and destruction (asserta/1, assertz/1,
    retract/1, abolish/1) with logical update semantics.
  • Backtrackable and non-backtrackable global variables via bb_get/2
    bb_put/2 (non-backtrackable) and bb_b_put/2
    (backtrackable).
  • Delimited continuations based on reset/3, shift/1 (documented in
    "Delimited Continuations for Prolog").
  • Tabling library based on delimited continuations
    (documented in "Tabling as a Library with Delimited Control").
  • A redone representation of strings as difference list of
    chars, using a packed internal representation (in progress).
  • clp(B) and clp(ℤ) as builtin libraries (in progress).
  • Streams and predicates for stream control (in progress).
  • An incremental compacting garbage collector satisfying the five
    properties of "Precise Garbage Collection in Prolog."
  • Mode declarations.

Phase 3

Use the WAM code produced by the completed code generator to get
JIT-compiled and -executed Prolog programs. The question of how to get
assembly from WAM code is something I'm still considering.

It's my hope to use Scryer Prolog as the logic engine of a low level (and
ideally, very fast) Shen implementation.

Nice to have features

There are no current plans to implement any of these, but they might be
nice to have in the future. They'd make a good project for anyone wanting
to contribute code to Scryer Prolog.

  1. Implement the global analysis techniques described in Peter van
    Roy's thesis, "Can Logic Programming Execute as Fast as Imperative
    Programming?"

  2. Add unum representation and arithmetic, using either an existing
    unum implementation or an ad hoc one. Unums are described in
    Gustafson's book "The End of Error."

  3. Add concurrent tables to manage shared references to atoms and
    strings.

  4. Add some form of JIT predicate indexing.

Installing Scryer Prolog

First, install the latest stable version of
Rust using your
preferred method. Then install Scryer Prolog with cargo,
like so:

$> cargo install scryer-prolog

cargo will download and install the libraries Scryer Prolog uses
automatically from crates.io. You can find the scryer-prolog
executable in ~/.cargo/bin.

Publishing Rust crates to crates.io and pushing to git are entirely
distinct, independent processes, so to be sure you have the latest
commit, it is recommended to clone directly from this git repository,
which can be done as follows:

$> git clone https://github.com/mthom/scryer-prolog
$> cd scryer-prolog
$> cargo run [--release]

The optional --release flag will perform various optimizations,
producing a faster executable.

Note on compatibility: Scryer Prolog should work on Linux, Mac OS X,
and BSD variants on which Rust runs. Windows support hinges on
rustyline and Termion being functional in that environment, which to
my knowledge is not presently the case.

Built-in predicates

The following predicates are built-in to Scryer.

  • Arithmetic support:
    • is/2 works for (+)/{1,2}, (-)/{1,2}, (*)/2, (//)/2, (**)/2,
      (^)/2, (div)/2, (/)/2, (rdiv)/2, (xor)/2, (rem)/2,
      (mod)/2, (/\)/2, (\/)/2, (>>)/2,(<<)/2, (\)/1,
      abs/1, sin/1, cos/1, tan/1, asin/1, acos/1,
      atan/1, atan2/2, log/1, exp/1, sqrt/1, float/1,
      truncate/1, round/1, floor/1, ceiling/1, pi/0,
      min/1, max/1, gcd/2, sign/1
    • Comparison operators: >, <, =<, >=, =:=, =\=.
  • (:)/2
  • (@>)/2
  • (@>=)/2
  • (@=<)/2
  • (@<)/2
  • (\+)/1
  • (==)/2
  • (\==)/2
  • (=)/2
  • (\=)/2
  • (=..)/2
  • (->)/2
  • (;)/2
  • abolish/1
  • acyclic_term/2
  • append/3
  • arg/3
  • asserta/1
  • assertz/1
  • atom/1
  • atomic/1
  • atom_chars/2
  • atom_codes/2
  • atom_concat/3
  • atom_length/2
  • bagof/3
  • bb_b_put/2
  • bb_get/2
  • bb_put/2
  • between/3
  • call/1..62
  • call_cleanup/2
  • call_with_inference_limit/3
  • call_residue_vars/2
  • can_be/2
  • catch/3
  • clause/2
  • compare/3
  • compound/1
  • copy_term/{2,3}
  • current_predicate/1
  • current_op/3
  • cyclic_term/1
  • dif/2
  • expand_goal/2
  • expand_term/2
  • fail/0
  • false/0
  • findall/{3,4}
  • float/1
  • foldl/{4,5}
  • forall/2
  • freeze/2
  • functor/3
  • gen_int/1
  • gen_nat/1
  • get_char/1
  • goal_expansion/2
  • ground/1
  • halt/0
  • integer/1
  • is_list/1
  • is_partial_string/1
  • keysort/2
  • length/2
  • maplist/2..9
  • member/2
  • memberchk/2
  • must_be/2
  • nl/0
  • nonvar/1
  • number_chars/2
  • number_codes/2
  • numbervars/2
  • numlist/{2,3}
  • once/1
  • op/3
  • partial_string/2
  • phrase/{2,3}
  • rational/1
  • read/1
  • repeat/{0,1}
  • retract/1
  • reverse/2
  • same_length/2
  • select/3
  • setof/3
  • setup_call_cleanup/3
  • sort/2
  • string/1
  • sub_atom/5
  • subsumes_term/2
  • sumlist/2
  • term_expansion/2
  • term_variables/2
  • throw/1
  • true/0
  • unify_with_occurs_check/2
  • use_module/{1,2}
  • user:goal_expansion/2
  • user:term_expansion/2
  • var/1
  • variant/2
  • wam_instructions/2
  • write/1
  • write_canonical/1
  • writeq/1
  • write_term/2

Tutorial

To enter a multi-clause predicate, the directive "[user]" is used.

For example,

?- [user].
(type Enter + Ctrl-D to terminate the stream when finished)
p(f(f(X)), h(W), Y) :- g(W), h(W), f(X).
p(X, Y, Z) :- h(Y), z(Z).
?- [user].
(type Enter + Ctrl-D to terminate the stream when finished)
h(x). h(y).
h(z).

In the example, Enter + Ctrl-D is used to terminate the standard
input stream. The instructive message is always printed.

Queries are issued as

?- p(X, Y, Z).

Pressing SPACE will backtrack through other possible answers, if any exist.
Pressing . will abort the search and return to the prompt.

Wildcards work as well:

?- [user].
(type Enter + Ctrl-D to terminate the stream when finished)
member(X, [X, _]).
member(X, [_, Xs]) :- member(X, Xs).
?- member(X, [a, b, c]).
true .
X = a ;
X = b ;
X = c ;
false.

and so do conjunctive queries:

?- [user].
(type Enter + Ctrl-D to terminate the stream when finished)
f(X) :- g(X).
g(x). g(y). g(z).
h(call(f, X)).
?- h(X), X.
true .
X = call(f, x) ;
X = call(f, y) ;
X = call(f, z).

Note that the values of variables belonging to successful queries are
printed out, on one line each. Uninstantiated variables are denoted by
a number preceded by an underscore (X = _0 in an example above).

To quit scryer-prolog, type

?- halt.

Dynamic operators

Scryer supports dynamic operators. Using the built-in
arithmetic operators with the usual precedences,

?- write_canonical(-5 + 3 - (2 * 4) // 8).
-(+(-(5), 3), //(*(2, 4), 8))
true.

New operators can be defined using the op declaration.

Partial strings

Scryer has two specialized, non-ISO builtin predicates for handling
so-called "partial strings". Partial strings imitate difference lists
of characters, but are much more space efficient. This efficiency
comes at the cost of full generality -- you cannot unify the tail
variables of two distinct partial strings, because their buffers will
always be distinct.

If X is a free variable, the query

?- partial_string("abc", X), X = [a, b, c, Y], is_partial_string(X), is_partial_string(Y).

will succeed. Further, if Y a free variable, unifying Y against
another string, "def" in this case, produces the equations

X = [a, b, c, d, e, f], Y = [d, e, f].

Modules

Scryer has a simple predicate-based module system. It provides a
way to separate units of code into distinct namespaces, for both
predicates and operators. See the files src/prolog/lib/*.pl for
examples.

At the time of this writing, several control and list processing
operators and predicates are hidden in their own modules that have not
been exported to the toplevel. To export them, write

?- use_module(library(lists)).

To load modules contained in files, the library functor can be
omitted, prompting Scryer to search for the file (specified as an
atom) from its working directory:

?- use_module('file.pl').

use_module directives can be qualified by adding a list of imports:

?- use_module(library(lists), [member/2]).

A qualified use_module can be used to remove imports from the
toplevel by calling it with an empty import list.

The (:)/2 operator resolves calls to predicates that might not be
imported to the current working namespace:

?- lists:member(X, Xs).

The [user] prompt can also be used to define modules inline at the
REPL:

?- [user].
(type Enter + Ctrl-D to terminate the stream when finished)
:- module(test, [local_member/2]).
:- use_module(library(lists)).

local_member(X, Xs) :- member(X, Xs).

The user listing can also be terminated by placing end_of_file. at
the end of the stream.

Main metrics

Overview
Name With Ownermthom/scryer-prolog
Primary LanguageRust
Program languageRust (Language Count: 6)
Platform
License:BSD 3-Clause "New" or "Revised" License
所有者活动
Created At2016-10-29 01:44:10
Pushed At2025-08-29 03:49:41
Last Commit At2025-08-28 20:49:41
Release Count17
Last Release Namev0.9.4 (Posted on 2024-02-29 10:08:26)
First Release Namev0.8.110 (Posted on )
用户参与
Stargazers Count2.3k
Watchers Count58
Fork Count153
Commits Count4.3k
Has Issues Enabled
Issues Count1828
Issue Open Count402
Pull Requests Count719
Pull Requests Open Count36
Pull Requests Close Count67
项目设置
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private