JSLogProg is an implementation of Prolog-like logic programming in Javascript (JS). It implements Var, Clause, Rule and Query objects for the equivalent Prolog items and implements Prolog unification and goal resolution.
You can define and run a Prolog-like program in JSLogProg.
- A JSLogProg Var is equivalent to a Prolog variable. A JSLogProg atom can be a JS number, string, function, array or object. Arrays and objects can contain Vars as elements and properties, respectively, and may be nested.
- Unification is defined for Var and JS number, string, function, array and object.
- A JSLogProg program is created using rule and clause functions.
- A program is run by defining a query and providing it, along with an array of rules to a solve function, which returns on or more solutions. A solution is a binding of a query Var to an atom (JS number, string, function, array or object).
Here is an example JSLogProg program:
import {rule, clause, solve} from './jslogprog.mjs';
function g(){console.log('g');};
function h(){console.log('h');};
const rules = [
rule(clause('type1', g)),
rule(clause('type2', g)),
rule(clause('type2', h)),
rule(clause('type3', [1,2,3])),
rule(clause('type4', {key: 'value'})),
rule(clause('type5', 'abc'))
];
const query = clause('type2', 'X');
for (const solution of solve(query, rules)){
console.log(`solution: ${solution.toAnswerString()}`);
console.log(solution.X());
}
console.log ('No more solutions');
// solution: '{ X: Function g }'
// solution: '{ X: Function h }'
// No more solutions
JSLogProg can be used without rules, clauses and queries. For example:
import {vars} from './jslogprog.mjs';
let [W, X, Y, Z] = vars('W', 'X', 'Y', 'Z'); // define Variables W, X, Y and Z
try{
let o = [1,{a:W, b:15},3];
o.unify([1, {a:10, b:15}, 3}])
console.log(o.rewrite().toAnswerString()); // rewrite all Variables in o, i.e. 10 as W, and format for display
X.unify(Y);
Y.unify(Z);
Z.unify(X); // Circular Variable reference
Z.unify(5); // X=Y=Z=5
X.unify(function f(){}); // Throws "unification failed" error
} catch(e) {
e === "unification failed"
}
JSLogProg unification is straightforward:
- A Variable unifies with Variable, number, string, function, array, object
- A number unifies with the same number
- A string unifies with the same string or a function where function.name === string
- A function unifies with the same function
- An array unifies with an array of the same length and where the array elements unify
- An object unifies with an object with the same keys and where the object properties unify
git clone git@github.com:boblund/jslogprog.git
cd jslogprog
JSLogProg can be used in Node JS or a web site.
[TRACE=true] node jslogprog.mjs
Setting TRACE=true
will output the state stack (current list of goals and bindings) and rule/goal unification as the rules are searched for query solutions.
jslogprog.mjs is a module which can be imported in a web page. For example:
<html>
<body>
</body>
<script type="module">
import {vars, clause, rule, solve} from './jslogprog.mjs';
.
.
.
</script>
</html>
The file doing the import must be served by an https web site. localAwsApiGw is one option for setting up a such a server.
The web page jslogprog.html in this repo uses jslogprog.mjs to provide a playground for experimenting with JSLogProg. Load the jslogprog-browser-example and click run to see it in action.
JSLogProg has a limited set of builtin Prolog operations: '=' and '!='. These are implemented as native rules.
isEqual(X, Y)
notEqual(X, Y)
A rule can be defined that calls a JS function. This is useful for implementing builtin Prolog predicates, '=' for example:
rule(clause(
function isEqual([arg1, arg2]) {
return arg1.unify(arg2);
},
'X', 'Y'
))
This rule is referenced in a goal via the function's name, i.e. clause('isEual', X, 2))
. Clause arguments are referenced from an array. The JS function is expected to bind any appropriate variables and return a bindings object. Failure to unify must result in throwing a 'unification failed' error.
The JSLogProg API consists of:
clause
,rule
, andsolve
for creating and running JSLogProg programs.Var
andvars
for creating JSLogProg variables.- Common JSLogProg methods for JSLogProg terms (Var, number, string, function, array and object) for unification and output.
These are accessed via the jslogprog.mjs exports {vars, clause, rule, solve, Var, Bindings, assert}
.
The clause function returns an instance of Clause
with the name
and arguments
. It is a convenience function that replaces new Clause
. Variables in arguments
can be instances of Var
. Or, they can be denoted by strings that begin with '_' or an uppercase letter, i.e. the Prolog convention for variable names.
newClause
(Clause) instance of Clause
name
(string) clause name
arguments
(Var, number, string, function, array, object) clause arguments
The rule function returns an instance of Rule
with the head
and body
. It is a convenience function that replaces new Rule
.
newRule
(Rule) instance of Rule
head
(Clause) rule head
body
(Clause) rule body (one or more clauses)
The vars function returns new variable instances whose names are arguments
. A single new variable instance is returned if arguments is a single name. Otherwise an array of new variable instances is returned. It is a convenience function that replaces new Var
.
solve
is a generator that yields the next solution
(object) for query on every call. solution
keys are query Vars and properties are Var bindings, e.g. {X: 'abc', Y: 2}. It returns when no more solutions are found. The general structure of its use is:
for(const solution of solve(query, rules)) {
// do something with the solution
}
// no more solutions
query
(clause | [clause1, ...]) query clause(s).
rules
(Array) Array of Rules
The Var
class defines the behavior for JSLogProg variable.
let newVar = new Var(name)
where name
is a string.
name
Var instance name.
binding
Undefined if the Var instance is unbound or else it is a type of string or number or instance of Var, Function, Array or Object.
binding = newVar.unify(term)
Unifies this variable with term
which must be a type of string or number or instance of Var, Function, Array or Object. If unify succeeds binding
is an object with a key === newVar.name whose property is term. Otherwise a 'unification failed' error is thrown.
val = newVar.rewrite()
returns the newVar binding if it exists otherwise newVar.name
. If newVar is bound to an instance of Var then newVar.binding.rewrite()
is returned. This is applied recursively until a bound variable is found. A side effect is that all traversed variables will have a binding property equal to val
. If the chain of variable bindings is circular the val
is newVar.name
.
val = newVar.toAnswerString()
returns newVar.binding
if newVar is bound otherwise newVar.name
.
The Bindings
class defines the behavior for JSLogProg variable bindings.
let bindings = new Bindings(binds)
where bindings
is either an instance of Bindings
or an object of variable bindings, e.g {X:'a', Y:2}.
currentBindings
an object of variable bindings, e.g {X:'a', Y:2}.
bindings.add(moreBindings)
Adds moreBindings
, an object of variable bindings, e.g {X:'a', Y:2}, to this object's currentBindings property. Properties of duplicate keys in moreBindings
overwrite those in currentBindings.
assert
is called as part of unification. condition
is a boolean that if false ends this, and all subsequent, unification by throwing a 'unification failed' error.
String
, Number
and Function
prototypes are extended with unify
, rewrite
and toAnswerString
methods to integrate these types with JSLogProg.
let binding = <instance of [String|Number|Function]>.unify(term)
Unifies with an unbound variable or with the same instance otherwise throws a 'unification failed' error.
let val = <instance of [String|Number|Function]>.rewrite()
returns the string, number or function name, respectively.
let val = <instance of [String|Number|Function]>.toAnswerSAtring()
returns the string, number or function name, respectively.
The Array
prototype is extended with unify
, rewrite
and toAnswerString
methods to integrate these types with JSLogProg.
let binding = <instance of Array>.unify(term)
Unifies if term
is an array of the same length and elements of term
unify with elements of this array. Otherwise throws a 'unification failed' error.
let val = [<instance of Array>.rewrite()
returns a new array where each element in this array is rewrittem.
let val = <instance of Array>.toAnswerSAtring()
returns a string representation of this array and its elements, recursively.
The Object
prototype is extended with unify
, rewrite
and toAnswerString
methods to integrate these types with JSLogProg.
let binding = <instance of Object>.unify(term)
Unifies if term
is an object with the same keys, and properties of term
unify with the properties of this object. Otherwise throws a 'unification failed' error.
let val = [<instance of Object>.rewrite()
returns a new object where each property in this object is rewrittem.
let val = <instance of Object>.toAnswerSAtring()
returns a string representation of this object and its properties, recursively.