Trealla Prolog bindings for JS
npm install @gaussylean/treallahttps://esm.sh/trealla@X.Y.Z.
js
import { load, Prolog } from 'https://esm.sh/trealla';
import { load, Prolog } from 'https://esm.run/trealla';
import { load, Prolog } from 'https://unpkg.com/trealla';
import { load, Prolog } from 'https://cdn.skypack.dev/trealla';
`
$3
This package is available on NPM as trealla.
`bash
npm install trealla
`
`js
import { load, Prolog } from 'trealla';
`
Example
$3
`html
`
`javascript
{
"result": "success",
"answer": {"X": 2, "Y": 4},
"stdout": "(2,4)\n"
}
// ...
`
$3
Experimental. With great power comes great responsibility π€
The JS host will evaluate the expression you give it and marshal it to JSON.
You can use js_eval_json/2 to grab the result.
Note that JSON does not handle all types such as undefined.
`prolog
greet :-
js_eval_json("return prompt('Name?');", Name),
format("Greetings, ~s.", [Name]).
here(URL) :-
js_eval_json("return location.href;", URL).
% URL = "https://php.energy/trealla.html"
`
If your evaluated code returns a promise, Prolog will yield to the host to evaluate the promise.
Hopefully this should be transparent to the user.
`prolog
?- js_eval_json("fetch('http://example.com').then(x => x.text());", Src).
Src = "Example page..."
`
js_eval/2 works the same but does not attempt to parse the JSON.
If your JS expression returns a Uint8Array, it will be returned as-is instead of JSON-encoded.
`prolog
?- js_eval("return new TextEncoder().encode('arbitrary text');", Result)
Result = "arbitrary text".
`
$3
Multiple queries can be run concurrently. If you'd like to kill a query early, use the return() method on the generator returned from query().
This is not necessary if you iterate through until it is finished.
$3
You can change the output format with the format option in queries.
The format is "json" by default which goes through library(js) and returns JSON objects.
$3
You can get pure text output with the "prolog" format.
The output is the same as Trealla's regular toplevel, but full terms (with a dot) are printed.
`javascript
for await (const answer of pl.query(dif(A, B) ; dif(C, D)., {format: "prolog"})) {
console.log(answer);
};
// "dif:dif(A,B)."
// "dif:dif(C,D)."
`
$3
Each Prolog interpreter instance has its own virtual filesystem you can read and write to.
For details, check out the wasmer-js docs.
`js
const pl = new Prolog();
// create a file in the virtual filesystem
pl.fs.open("/greeting.pl", { write: true, create: true }).writeString(
);
// consult file
await pl.consult("/greeting.pl");
// use the file we added
const query = pl.query("use_module(greeting), hello(X)");
for await (const answer of query) {
console.log(answer); // X = world, X = δΈη
}
`
Javascript API
Approaching stability.
`typescript
declare module 'trealla' {
// Call this first to load the runtime.
// Must be called before any interpreters are constructed.
function load(): Promise;
// Prolog interpreter.
// Each interpreter is independent, having its own knowledgebase and virtual filesystem.
// Multiple queries can be run against one interpreter simultaneously.
class Prolog {
constructor(options?: PrologOptions);
// Run a query. This is an asynchronous generator function.
// Use a for await loop to easily iterate through results.
// Exiting the loop will automatically destroy the query and reclaim memory.
// If manually iterating with next(), call the return() method of the generator to kill it early.
// Runtimes that support finalizers will make a best effort attempt to kill live but garbage-collected queries.
public query(goal: string, options?: QueryOptions): AsyncGenerator;
// Runs a query and returns a single solution, ignoring others.
public queryOnce(goal: string, options?: QueryOptions): Promise;
// Consult (load) a Prolog file with the given filename.
public consult(filename: string): Promise;
// Consult (load) a Prolog file with the given text content.
public consultText(text: string | Uint8Array): Promise;
// Use fs to manipulate the virtual filesystem.
public readonly fs: any; // wasmer-js filesystem
}
interface PrologOptions {
// Library files path (default: "/library")
// This is for use_module(library(...)).
library?: string;
// Environment variables.
// Accessible with the predicate getenv/2.
env?: Record;
// Quiet mode. Disables warnings printed to stderr if true.
quiet?: boolean;
// Manually specify module instead of the default.
module?: WebAssembly.Module;
}
interface QueryOptions {
// Mapping of variables to bind in the query.
bind?: Substitution;
// Prolog program text to evaluate before the query.
program?: string | Uint8Array;
// Answer format. This changes the return type of the query generator.
// "json" (default) returns Javascript objects.
// "prolog" returns the standard Prolog toplevel output as strings.
// You can add custom formats to the global FORMATS object.
// You can also pass in a Toplevel object directly.
format?: keyof typeof FORMATS | Toplevel;
// Encoding options for "json" or custom formats.
encode?: EncodingOptions;
}
type EncodingOptions = JSONEncodingOptions | PrologEncodingOptions | Record;
interface JSONEncodingOptions {
// Encoding for Prolog atoms. Default is "object".
atoms?: "string" | "object";
// Encoding for Prolog strings. Default is "string".
strings?: "string" | "list";
// Functor for compounds of arity 1 to be converted to booleans/null/undefined.
// e.g. "{}" to turn {true} into true ala Tau, "@" for SWI-ish behavior.
booleans?: string;
nulls?: string;
undefineds?: string;
}
interface PrologEncodingOptions {
// Include the fullstop "." in results.
// True by default.
dot?: boolean;
}
// Answer for the "json" format.
interface Answer {
result: "success" | "failure" | "error";
answer?: Substitution;
error?: Term;
stdout?: string; // standard output text (user_output stream in Prolog)
stderr?: string; // standard error text (user_error stream in Prolog)
}
// Mapping of variable name β Term substitutions.
type Substitution = Record;
/*
Default encoding (in order of priority):
string(X) β string
is_list(X) β List
atom(X) β Atom
compound(X) β Compound
integer(X) β BigInt if necessary
number(X) β number
var(X) β Variable
*/
type Term = Atom | Compound | Variable | List | string | number | BigInt;
type List = Term[];
class Atom {
functor: string;
readonly pi: string; // predicate indicator ("foo/0")
toProlog(): string;
}
// String template literal for making atoms: atom foo = 'foo'.
function atom([functor]): Atom;
class Compound {
functor: string;
args: List;
readonly pi: string; // predicate indicator ("foo/N")
toProlog(): string;
}
class Variable {
var: string; // variable name
attr?: List; // residual goals
toProlog(): string;
}
// Convert Term objects to their Prolog text representation.
function toProlog(object: Term): string;
// Parse JSON representations of terms.
function fromJSON(json: string, options?: JSONEncodingOptions): Term;
// Convert Term objects to JSON text.
function toJSON(term: Term, indent?: string): string;
const FORMATS: {
json: Toplevel,
prolog: Toplevel,
// add your own!
// [name: string]: Toplevel
};
interface Toplevel {
// Prepare query string, returns goal to execute.
query(pl: Prolog, goal: string, bind?: Substitution, options?: Options): string;
// Parse stdout and return an answer.
parse(pl: Prolog, status: boolean, stdout: Uint8Array, stderr: Uint8Array, options?: Options): T;
// Yield simple truth value, when output is blank.
// For queries such as true. and 1=2..
// Return null to bail early and yield no values.
truth(pl: Prolog, status: boolean, stderr: Uint8Array, options?: Options): T | null;
}
}
`
Predicate reference
library(js)
Module library(js) is autoloaded. It provides predicates for calling into the host.
$3
Load Prolog code from URL.
`prolog
%! http_consult(+URL) is det.
% Downloads Prolog code from URL, which must be a string, and consults it.
http_consult(URL).
`
$3
Fetch content from a URL.
`prolog
%! js_fetch(+URL, +Options, -Content) is det.
% Fetch URL (string) and unify the result with Content.
% This is a friendly wrapper around Javascript's fetch API.
% Options is a list of options:
% - as(string): Content will be unified with the text of the result as a string
% - as(json): Content will be parsed as JSON and unified with a JSON term
% - headers(["key"-"value", ...]): HTTP headers to send
% - body(Cs): body to send (Cs is string)
js_fetch(URL, Options, Content).
`
$3
Evaluate a string of Javascript code.
`prolog
%! js_eval_json(+Code, -JSON) is det.
% Evaluate Code, which must be a string of valid Javascript code.
% Returning a promise will cause the query to yield to the host. The host will await the promise and resume the query.
% Return values are encoded to JSON and returned as a JSON term (see pseudojson:json_value/2).
js_eval_json(Code, JSON).
`
$3
Low-level predicate for evaluating JS code.
`prolog
%! js_eval(+Code, -Cs) is det.
% Low-level predicate that functions the same as js_eval_json/2 but without the JSON decoding.
% Returning a Uint8Array in your JS code will bypass the host's default JSON encoding.
% Combined with this, you can customize the host->guest API.
js_eval(Code, Cs).
`
library(pseudojson)
Module library(pseudojson) is preloaded. It provides very fast predicates for encoding and decoding JSON.
Its One Crazy Trick is using regular Prolog terms such as {"foo":"bar"} for reading/writing.
This means that it accepts invalid JSON that is a valid Prolog term.
The predicate json_value/2 converts between the same representation of JSON values as library(json), to ensure future compatibility.
You are free to use library(json) which provides a JSON DCG that properly validates (but is slow for certain inputs).
$3
Encoding and decoding of JSON strings.
`prolog
%! json_chars(?JSON, ?Cs) is det.
% JSON is a Prolog term representing the JSON.
% Cs is a JSON string.
json_chars(JSON, Cs).
`
$3
Relates JSON terms and friendlier Value terms that are compatible with library(json).
- strings: string("abc")
- numbers: number(123)
- booleans: boolean(true)
- objects: pairs([string("key")-Value, ...])
- arrays: list([...])
`prolog
%! json_value(?JSON, ?Value) is det.
% Unifies JSON and Value with their library(pseudojson) and library(json) counterparts.
% Can be used to convert between JSON terms and friendlier Value terms.
json_value(JSON, Value).
`
Implementation Details
Currently uses the WASM build from guregu/trealla.
Output goes through the js module.
$3
Make sure you can build Trealla.
`bash
install deps
npm install
build wasm
npm run compile
build js
npm run build
"test"
node examples/node.mjs
``