JavaScript "machine"

         
/**
* Modern Albufeira Prolog Interpreter
*
* Warranty & Liability
* To the extent permitted by applicable law and unless explicitly
* otherwise agreed upon, XLOG Technologies AG makes no warranties
* regarding the provided information. XLOG Technologies AG assumes
* no liability that any problems might be solved with the information
* provided by XLOG Technologies AG.
*
* Rights & License
* All industrial property rights regarding the information - copyright
* and patent rights in particular - are the sole property of XLOG
* Technologies AG. If the company was not the originator of some
* excerpts, XLOG Technologies AG has at least obtained the right to
* reproduce, change and translate the information.
*
* Reproduction is restricted to the whole unaltered document. Reproduction
* of the information is only allowed for non-commercial uses. Selling,
* giving away or letting of the execution of the library is prohibited.
* The library can be distributed as part of your applications and libraries
* for execution provided this comment remains unchanged.
*
* Restrictions
* Only to be distributed with programs that add significant and primary
* functionality to the library. Not to be distributed with additional
* software intended to replace any components of the library.
*
* Trademarks
* Jekejeke is a registered trademark of XLOG Technologies AG.
*/
import {
is_logical, snapshot_peek, pred_link, is_clause,
is_cache, resolve_link, MASK_PRED_ARITH,
snapshot_rope, defined_pred, is_stick, is_element,
MASK_PRED_TEST, MASK_PRED_SPECIAL, is_goal, Structure,
engine, set_engine, Engine, Variable, is_variable,
Compound, is_structure, is_skeleton, is_place, deref,
VAR_MASK_ODD, VAR_MASK_STATE, VAR_MASK_SERNO,
stack_push, stack_peek, stack_pop, is_compound, Item
} from "./store.mjs";
export const VOID_ARGS = [];
export let fs;
export let url;
export let path;
export let os;
if (typeof window === 'undefined') {
fs = await import("node:fs");
url = await import("node:url");
path = await import("node:path");
os = await import("node:os");
} else {
fs = undefined;
url = undefined;
path = undefined;
os = undefined;
}
export let trail = null;
export let redo = null;
export let call = "[]";
/**
* Create an error term from a message.
*
* @param beta The message.
* @return Compound The error term.
*/
export function make_error(beta) {
return new Compound("error", [beta, fetch_stack(ctx)]);
}
/**
* Retrieve the current stack.
*
* @param buf The context.
* @return {string | Compound} The current stack.
*/
function fetch_stack(buf) {
let temp = pred_link("sys_including", 3);
if (temp === undefined || (!is_logical(temp.func) &&
!is_stick(temp.func)))
return "[]";
let data = snapshot_peek(temp.func);
let back = null;
let res = null;
for (let i = 0; i < data.length; i++) {
let clause = data[i];
let display;
if (clause.size !== 0) {
display = new Array(clause.size);
} else {
display = null;
}
let elem = exec_frame(clause.functor, clause.head, display);
if (elem.args[1] !== buf)
continue;
elem = new Compound("sys_including", [elem.args[0], elem.args[2]]);
elem = new Compound(".", [elem, undefined]);
if (back === null) {
res = elem;
} else {
back.args[1] = elem;
}
back = elem;
}
if (back === null) {
res = "[]";
} else {
back.args[1] = "[]";
}
return res;
}
export function exec_frame(functor, head, display) {
if (head.length > 0) {
temp_display = display;
let args = new Array(head.length);
for (let i = 0; i < args.length; i++)
args[i] = exec_build(head[i]);
temp_display = null;
return new Compound(functor, args);
} else {
return functor;
}
}
/**
* Create a Prolog compound indicator.
*
* @param functor The functor.
* @param arity The arity.
* @return Compound The Prolog compound indicator.
*/
export function make_indicator(functor, arity) {
if (is_cache(functor))
functor = functor.name;
return new Compound("/", [functor, arity]);
}
/**************************************************************/
/* Frozen Terms */
/**************************************************************/
/**
* Create a Prolog frozen.
*
* @param functor The functor.
* @param args The arguments.
* @constructor The new frozen.
*/
export class Frozen extends Structure {
constructor(functor, args) {
super(functor, args);
this.hash = object_hash_code(functor);
for (let i = 0; i < args.length; i++) {
let obj = args[i];
if (is_frozen(obj)) {
this.hash = this.hash * 31 + obj.hash;
} else {
this.hash = this.hash * 31 + object_hash_code(obj);
}
}
}
}
/**
* Check whether an object is a Prolog frozen.
*
* @param obj The object.
* @return boolean True if the object is a frozen, otherwise false.
*/
export function is_frozen(obj) {
return obj instanceof Frozen;
}
/**
* Compute the hash code of an object
*
* @param alpha The object.
* @return The hash code, a signed 32-bit integer
*/
export function object_hash_code(alpha) {
if (is_atom(alpha)) {
let res = 0;
for (let i = 0; i < alpha.length; i++)
res = (res * 31 + alpha.charCodeAt(i)) | 0;
return res;
} else if (is_number(alpha)) {
if (is_integer(alpha)) {
if (!is_bigint(alpha)) {
return alpha;
} else {
let sign;
if (alpha < 0) {
sign = -1;
alpha = -alpha;
} else {
sign = 1;
}
alpha = alpha.toString(16);
let res = 0;
let i = alpha.length % 8;
if (i > 0)
res = parseInt(alpha.substring(0, i), 16) | 0;
for (; i < alpha.length; i += 8)
res = (res * 31 + parseInt(alpha.substring(i, i + 8), 16)) | 0;
return (sign > 0 ? res : -res);
}
} else {
let beta = new Float64Array(1);
beta[0] = Number(alpha);
let gamma = new Int32Array(beta.buffer);
return gamma[0] ^ gamma[1];
}
} else if (alpha === null) {
return 0;
} else if (alpha === true) {
return 1231;
} else if (alpha === false) {
return 1237;
} else {
return 2467;
}
}
/**************************************************************/
/* Garbage Collection */
/**************************************************************/
export let gc_maxinfs = 128000;
export let gc_mask = VAR_MASK_ODD;
export let gc_last = 0.0;
export let gc_time = 0.0;
export let gc_enter = 0;
let gc_tick = gc_maxinfs;
let gc_tack = 60*gc_maxinfs;
let gc_tock = 3600*gc_maxinfs;
/**
* Retrieve the real time.
*
* @return The real time.
*/
export function real_time() {
if (fs !== undefined) {
return (Number(process.hrtime.bigint()) / 1000000.0);
} else {
return performance.now();
}
}
/**************************************************************/
/* Major Marking */
/**************************************************************/
/**
* Perform major garbage collection.
*/
export function gc_major() {
gc_time -= real_time();
gc_mask ^= VAR_MASK_STATE;
engine.low = 0;
engine.high = engine.serno;
engine.serno = 0;
mark_redo();
adjust_redo();
sweep_trail(null);
gc_time += real_time();
}
/**
* Major mark a term.
*
* @param first The term.
*/
function mark_term(first) {
let stack = null;
for (;;) {
if (is_variable(first)) {
if ((first.flags & VAR_MASK_STATE) !== gc_mask) {
first.flags = (first.flags & ~VAR_MASK_STATE) | gc_mask;
let val = first.flags & VAR_MASK_SERNO;
if (val !== VAR_MASK_SERNO) {
if (val > engine.serno)
engine.serno = val + 1;
if (engine.low <= val && val < engine.high) {
if (val - engine.low > engine.high - val) {
engine.high = val;
} else {
engine.low = val + 1;
}
}
}
if (first.instantiated !== undefined) {
let peek = first.instantiated;
first.instantiated = stack;
stack = first;
first = peek;
continue;
}
}
} else if (is_compound(first)) {
if ((first.walk & VAR_MASK_STATE) !== gc_mask) {
first.walk = (first.walk & ~VAR_MASK_STATE) | gc_mask;
let peek = first.args[0];
first.args[0] = stack;
first.walk &= ~VAR_MASK_SERNO;
stack = first;
first = peek;
continue;
}
}
while (stack !== null && (is_variable(stack) ||
(stack.walk & VAR_MASK_SERNO) === stack.args.length - 1)) {
if (is_variable(stack)) {
let peek = stack.instantiated;
stack.instantiated = first;
first = stack;
stack = peek;
} else {
let peek = stack.args[stack.walk & VAR_MASK_SERNO];
stack.args[stack.walk & VAR_MASK_SERNO] = first;
first = stack;
stack = peek;
}
}
if (stack === null) {
return;
} else {
let peek = stack.args[stack.walk & VAR_MASK_SERNO];
stack.args[stack.walk & VAR_MASK_SERNO] = first;
stack.walk++;
first = stack.args[stack.walk & VAR_MASK_SERNO];
stack.args[stack.walk & VAR_MASK_SERNO] = peek;
}
}
}
/**
* Major mark the continuations.
*/
function mark_redo() {
let term = call;
let last = redo;
for (;;) {
mark_term(term);
if (last === null)
break;
term = last.cont;
last = last.tail;
}
}
/**************************************************************/
/* Minor Marking */
/**************************************************************/
/**
* Perform minor garbage collection.
*/
function gc_minor() {
gc_time -= real_time();
mark2_redo();
mark2_trail(engine.backtrail);
adjust_redo();
sweep_trail(engine.backtrail);
let gc_now = real_time();
gc_time += gc_now;
let val = gc_now - gc_last;
gc_last = gc_now;
val = (gc_maxinfs * 1000) / val;
gc_maxinfs = Math.round((3 * gc_maxinfs + val) / 4);
}
/**
* Minor mark a term.
*
* @param first The term.
*/
export function mark2_term(first) {
let stack = null;
for (;;) {
if (is_variable(first)) {
if ((first.flags & VAR_MASK_STATE) !== gc_mask) {
first.flags = (first.flags & ~VAR_MASK_STATE) | gc_mask;
if (first.instantiated !== undefined) {
let peek = first.instantiated;
first.instantiated = stack;
stack = first;
first = peek;
continue;
}
}
} else if (is_compound(first)) {
if ((first.walk & VAR_MASK_STATE) !== gc_mask) {
first.walk = (first.walk & ~VAR_MASK_STATE) | gc_mask;
let peek = first.args[0];
first.args[0] = stack;
first.walk &= ~VAR_MASK_SERNO;
stack = first;
first = peek;
continue;
}
}
while (stack !== null && (is_variable(stack) ||
(stack.walk & VAR_MASK_SERNO) === stack.args.length - 1)) {
if (is_variable(stack)) {
let peek = stack.instantiated;
stack.instantiated = first;
first = stack;
stack = peek;
} else {
let peek = stack.args[stack.walk & VAR_MASK_SERNO];
stack.args[stack.walk & VAR_MASK_SERNO] = first;
first = stack;
stack = peek;
}
}
if (stack === null) {
return;
} else {
let peek = stack.args[stack.walk & VAR_MASK_SERNO];
stack.args[stack.walk & VAR_MASK_SERNO] = first;
stack.walk++;
first = stack.args[stack.walk & VAR_MASK_SERNO];
stack.args[stack.walk & VAR_MASK_SERNO] = peek;
}
}
}
/**
* Minor mark the continuations.
*/
function mark2_redo() {
let term = call;
let last = redo;
for (;;) {
mark2_term(term);
if (last === null)
break;
term = last.cont;
last = last.tail;
}
}
/**************************************************************/
/* Variable Sweep */
/**************************************************************/
/**
* Adjust the markers into the trail.
*/
function adjust_redo() {
let last = redo;
while (last !== null) {
let temp = last.mark;
while (temp !== null) {
if ((temp.flags & VAR_MASK_STATE) === gc_mask) {
break;
} else {
temp = temp.tail;
}
}
last.mark = temp;
last = last.tail;
}
}
/**
* Minor mark the trail
*
* @param stop The stop.
*/
function mark2_trail(stop) {
let temp = trail;
while (temp !== stop) {
if ((temp.flags & VAR_MASK_STATE) === VAR_MASK_STATE)
mark2_term(temp);
temp = temp.tail;
}
}
/**
* Sweep the trail.
*
* @param stop The stop.
*/
function sweep_trail(stop) {
let temp = trail;
let back = null;
while (temp !== stop) {
let term = temp;
temp = term.tail;
if ((term.flags & VAR_MASK_STATE) === gc_mask) {
if (back !== null) {
back.tail = term;
} else {
trail = term;
}
back = term;
} else {
term.instantiated = undefined;
term.tail = null;
}
}
if (back !== null) {
back.tail = stop;
} else {
trail = stop;
}
engine.backtrail = trail;
}
/**************************************************************/
/* Signal Handling */
/**************************************************************/
/**
* Check the signal message.
*/
export function solve_signal(rope,at,choice) {
if (engine.signal !== undefined) {
let message = engine.signal;
engine.signal = undefined;
throw make_error(message);
}
return true;
}
/**************************************************************/
/* Clause Loops */
/**************************************************************/
export const SYS_MASK_ASYNC_MODE = 0x00000001;
export const SYS_MASK_ALLOW_YIELD = 0x00000008;
export const SYS_MASK_FULL_ASYNC = SYS_MASK_ASYNC_MODE |
SYS_MASK_ALLOW_YIELD;
/**
* Set the continuation.
*
* @param term The continuation.
*/
export function cont(term) {
call = term;
}
/**
* Solve Prolog goals.
*
* @param snap The choice point boundary.
* @param found True for call, and false for redo.
* @return True if execution succeeds, otherwise false.
*/
export function solve(snap, found) {
for (; ;) {
if (found === true) {
if (gc_enter >= gc_tick) {
if (gc_enter >= gc_tock) {
gc_major();
gc_tock += 3600*gc_maxinfs;
} else if (gc_enter >= gc_tack) {
gc_minor();
gc_tack += 60*gc_maxinfs;
}
gc_tick += gc_maxinfs;
if ((engine.flags & SYS_MASK_ASYNC_MODE) !== 0) {
more(new Choice(solve_signal, null, 0, trail));
return immediate_promise();
}
}
let goal = call;
if (is_structure(goal)) {
gc_enter++;
goal = goal.args[0];
let peek = lookup_pred(goal);
if (peek === undefined || (peek.flags & MASK_PRED_ARITH) !== 0)
throw make_error(new Compound("existence_error",
["procedure", make_indicator_term(goal)]));
if (is_structure(goal)) {
goal = goal.args;
} else {
goal = VOID_ARGS;
}
if ((peek.flags & MASK_PRED_TEST) !== 0) {
if (!peek.func(goal)) {
found = false;
} else {
cont(call.args[1]);
found = true;
}
} else if ((peek.flags & MASK_PRED_SPECIAL) !== 0) {
found = peek.func(goal);
} else {
peek = defined_pred(peek, goal);
found = solve2_rope(goal, snapshot_rope(peek), 0, null);
}
} else {
break;
}
} else if (found === false) {
if (redo !== snap) {
let choice = redo;
redo = choice.tail;
unbind(choice.mark);
call = choice.cont;
found = choice.func(choice.data, choice.at, choice);
} else {
break;
}
} else {
break;
}
}
return found;
}
function immediate_promise() {
return new Promise(resolve => setDelay(resolve, 0));
}
/******************************************************************/
/* Linked Provables */
/******************************************************************/
/**
* Lookup a predicate.
*
* @param goal The goal.
* @return Provable The predicate or undefined.
*/
export function lookup_pred(goal) {
let functor;
let arity;
if (is_element(goal)) {
functor = goal.functor;
arity = goal.args.length;
} else {
functor = goal;
arity = 0;
}
return resolve_link(functor, arity);
}
/**
* Lookup an evaluable function.
*
* @param expr The arithmetic expression.
* @return Provable The evaluable function or undefined.
*/
function lookup_eval(expr) {
let functor;
let arity;
if (is_element(expr)) {
functor = expr.functor;
arity = expr.args.length;
} else {
functor = expr;
arity = 0;
}
return resolve_link(functor, arity + 1);
}
export function make_indicator_term(goal) {
let functor;
let arity;
if (is_structure(goal)) {
functor = goal.functor;
arity = goal.args.length;
} else {
functor = goal;
arity = 0;
}
return make_indicator(functor, arity);
}
function solve_rope(rope, at, choice) {
let goal = deref(call.args[0]);
if (is_structure(goal)) {
goal = goal.args;
} else {
goal = VOID_ARGS;
}
return solve2_rope(goal, rope, at, choice);
}
/**
* Search a Prolog clause and add it to the continuation.
*
* @param paras The called goal.
* @param rope The clause list.
* @param at The clause index.
* @param choice The choice point for reuse or null.
* @return boolean True if search succeeds, otherwise false.
*/
function solve2_rope(paras, rope,at,choice) {
let mark = trail;
while (at < rope.length) {
let clause = rope[at++];
let display;
if (clause.size !== 0) {
display = new Array(clause.size);
} else {
display = null;
}
if (exec_head(clause.head, display, paras)) {
let peek = clause.cutvar;
if (peek !== -1)
display[peek] = redo;
if (at < rope.length) {
if (choice === null) {
choice = new Choice(solve_rope, rope, at, mark);
} else {
choice.at = at;
}
more(choice);
if (exec_check(clause.body, display))
return true;
if (redo !== choice)
return false;
more(choice.tail);
} else {
return exec_check(clause.body, display);
}
}
unbind(mark);
}
return false;
}
/**
* Sets the choice point.
*
* @param choice The new choice point.
*/
export function more(choice) {
redo = choice;
}
/**
* Create a choice point.
*
* @param func The choice point handler.
* @param data The choice point data.
* @param at The choice point index.
* @param mark The trail mark.
* @constructor The choice point.
*/
export function Choice(func, data, at, mark) {
this.func = func;
this.data = data;
this.at = at;
this.mark = mark;
this.cont = call;
this.tail = redo;
}
/**************************************************************/
/* Directives */
/**************************************************************/
export let CTX_MAIN = "main";
/**
* Run a compiled goal once. The goal is run with auto-yield
* disabled and promises are not accepted.
*
* @param goal The compiled goal.
* @throw If false.
*/
export function run(goal) {
if (!launch(goal, CTX_MAIN, VOID_ARGS))
throw make_error(new Compound("syntax_error", ["directive_failed"]));
}
export function snap_setup() {
redo = new Choice(solve_setup, null, 0, trail);
return redo;
}
function solve_setup(rope,at,choice) {
return false;
}
export function snap_cleanup(snap) {
more(snap.tail);
unbind(snap.mark);
call = snap.cont;
}
/**************************************************************/
/* Terms */
/**************************************************************/
/**
* Check whether an object is an atom.
*
* @param obj The object.
* @return boolean True if the object is an atom, otherwise false.
*/
export function is_atom(obj) {
return typeof obj === "string";
}
/**
* Check whether an object is a number.
*
* @param obj The object.
* @return boolean True if the object is a number, otherwise false.
*/
export function is_number(obj) {
if (typeof obj === "number")
return true;
if (typeof obj === "bigint")
return true;
return false;
}
/**
* Check whether an object is an integer.
*
* @param obj The object.
* @return boolean True if the object is an integer, otherwise false.
*/
export function is_integer(obj) {
if (typeof obj === "number" &&
Number.isInteger(obj) &&
((-94906266 <= obj) && (obj <= 94906266)))
return true;
if (typeof obj === "bigint" &&
!((-94906266 <= obj) && (obj <= 94906266)))
return true;
return false;
}
/**
* Check whether an object is a float.
*
* @param obj The object.
* @return boolean True if the object is a float, otherwise false.
*/
export function is_float(obj) {
if (typeof obj === "number" &&
(!Number.isInteger(obj) ||
!((-94906266 <= obj) && (obj <= 94906266))))
return true;
if (typeof obj === "bigint" &&
((-94906266 <= obj) && (obj <= 94906266)))
return true;
return false;
}
/**
* Check whether an object is a bigint.
*
* @param alpha The object.
* @return boolean True if the object is a bignum, otherwise false.
*/
export function is_bigint(alpha) {
return typeof alpha === "bigint";
}
/**
* Check whether an object is a special value.
*
* @param alpha The object.
* @return boolean True if the object is a special value, otherwise false.
*/
export function is_special(alpha) {
return (typeof alpha === "number" &&
!Number.isFinite(alpha));
}
/**************************************************************/
/* Albufeira Lazy */
/**************************************************************/
let temp_display = null;
export function set_temp_display(display) {
temp_display = display;
}
export function is_pending(template) {
if (template === undefined) {
return true;
} else if (is_place(template)) {
return true;
} else {
return false;
}
}
export function exec_deref(template) {
if (template === undefined) {
return template;
} else if (is_place(template)) {
let index = template.index;
let peek = temp_display[index];
if (peek === undefined) {
return template;
} else {
return deref(peek);
}
} else {
return deref(template);
}
}
/**************************************************************/
/* Albufeira Modes */
/**************************************************************/
export function exec_build(template) {
let back = null;
for (; ;) {
if (template === undefined) {
template = new Variable();
break;
} else if (is_place(template)) {
let index = template.index;
template = temp_display[index];
if (template === undefined) {
template = new Variable();
temp_display[index] = template;
} else {
template = deref(template);
}
break;
} else if (is_skeleton(template)) {
let args = new Array(template.args.length);
args[args.length - 1] = back;
back = new Compound(template.functor, args);
template = template.args;
let i = 0;
for (; i < args.length - 1; i++)
args[i] = exec_build(template[i]);
template = template[i];
} else {
template = deref(template);
break;
}
}
while (back !== null) {
let peek = back.args[back.args.length - 1];
back.args[back.args.length - 1] = template;
template = back;
back = peek;
}
return template;
}
export function exec_unify(template, alpha) {
for (; ;) {
if (template === undefined) {
return true;
} else if (is_place(template)) {
let index = template.index;
template = temp_display[index];
if (template === undefined) {
temp_display[index] = deref(alpha);
return true;
} else {
return unify(alpha, template);
}
} else if (is_skeleton(template)) {
alpha = deref(alpha);
if (is_variable(alpha)) {
let args = new Array(template.args.length);
bind(new Compound(template.functor, args), alpha);
template = template.args;
for (let i = 0; i < args.length; i++)
args[i] = exec_build(template[i]);
return true;
} else if (is_structure(alpha)) {
if (template.args.length !== alpha.args.length)
return false;
if (template.functor !== alpha.functor)
return false;
template = template.args;
alpha = alpha.args;
let i = 0;
for (; i < template.length - 1; i++)
if (!exec_unify(template[i], alpha[i]))
return false;
template = template[i];
alpha = alpha[i];
} else {
return false;
}
} else {
return unify(template, alpha);
}
}
}
/**************************************************************/
/* Albufeira Clauses */
/**************************************************************/
export function exec_body(code, display) {
temp_display = display;
let back = null;
let res = null;
for (let i = 0; i < code.length; i++) {
let goal = exec_build(code[i]);
let temp = new Compound(".", [goal, undefined]);
if (back === null) {
res = temp;
} else {
back.args[1] = temp;
}
back = temp;
}
if (back === null) {
res = "[]";
} else {
back.args[1] = "[]";
}
temp_display = null;
return res;
}
export function exec_head(code, display, aux) {
if (aux.length !== code.length)
return false;
temp_display = display;
for (let i = 0; i < code.length; i++) {
if (!exec_unify(code[i], aux[i])) {
temp_display = null;
return false;
}
}
temp_display = null;
return true;
}
/**************************************************************/
/* Head Check */
/**************************************************************/
export function exec_eval(template) {
if (template === undefined) {
throw make_error("instantiation_error");
} else if (is_place(template)) {
let index = template.index;
template = temp_display[index];
if (template === undefined)
throw make_error("instantiation_error");
}
template = deref(template);
if (is_number(template))
return template;
let peek = lookup_eval(template);
if (peek === undefined || (peek.flags & MASK_PRED_ARITH) === 0)
throw make_error(new Compound("type_error",
["evaluable", make_indicator_term(template)]));
if (is_element(template)) {
template = template.args;
} else {
template = VOID_ARGS;
}
return peek.func(template);
}
function exec_test(template) {
let peek = lookup_pred(template);
if (peek === undefined || (peek.flags & MASK_PRED_TEST) === 0) {
return exec_build(template);
} else {
if (is_element(template)) {
template = template.args;
} else {
template = VOID_ARGS;
}
gc_enter++;
return peek.func(template);
}
}
export function exec_check(code, display) {
temp_display = display;
let check = true;
let back = null;
let res = null;
for (let i = 0; i < code.length; i++) {
let goal = (check ? exec_test(code[i]) : exec_build(code[i]));
if (true === goal)
continue;
if (false === goal) {
temp_display = null;
return false;
}
goal = new Compound(".", [goal, undefined]);
if (back === null) {
res = goal;
} else {
back.args[1] = goal;
}
back = goal;
check = false;
}
if (back === null) {
res = call.args[1];
} else {
back.args[1] = call.args[1];
}
temp_display = null;
cont(res);
return true;
}
/**************************************************************/
/* Unification */
/**************************************************************/
/**
* Determine whether two terms unify.
* As a side effect the trail is extended, even if unification fails.
* Can handle cyclic terms and deep recursion.
*
* @param first The first term.
* @param second The second term.
* @return boolean True if the two terms unify, otherwise false.
*/
export function unify(first, second) {
let stack = null;
let log = null;
try {
for (; ;) {
first = deref(first);
second = deref(second);
if (is_variable(first)) {
if (!is_variable(second) || first !== second)
bind(second, first);
} else if (is_variable(second)) {
bind(first, second);
} else if (!is_structure(first)) {
if (!Object.is(first, second))
break;
} else if (!is_structure(second)) {
break;
} else if (first.args.length !== second.args.length) {
break;
} else {
first = union_find(first);
second = union_find(second);
if (first !== second) {
if (is_frozen(first) && is_frozen(second) &&
first.hash !== second.hash)
break;
if (first.functor !== second.functor)
break;
log = union_add(log, first, second);
if (0 !== first.args.length - 1) {
let item2 = new Item(first, second, 0);
stack = stack_push(stack, item2);
}
first = first.args[0];
second = second.args[0];
continue;
}
}
let item = stack_peek(stack);
if (item === null) {
return true;
} else {
item.idx++;
first = item.first.args[item.idx];
second = item.second.args[item.idx];
if (item.idx === item.first.args.length - 1)
stack_pop(stack);
}
}
return false;
} finally {
union_undo(log);
}
}
/**
* The function returns the representative of a structure.
*
* @param obj The structure.
* @return The representative.
*/
export function union_find(obj) {
while (is_structure(obj.functor))
obj = obj.functor;
return obj;
}
/**
* The function merges respresentatives and returns a new log.
*
* @param log The log.
* @param from The first representative.
* @param to The second representative.
* @return The new log.
*/
export function union_add(log, from, to) {
from.functor = to;
if (log === null)
log = new Array(0);
log.push(from);
return log;
}
/**
* The routine uses the log L to undo modifications.
*
* @param log The log.
*/
export function union_undo(log) {
if (log === null)
return;
for (let i = log.length - 1; i >= 0; i--) {
let elem = log[i];
elem.functor = elem.functor.functor;
}
}
/**
* Bind a variable to a term.
*
* @param source The Prolog term.
* @param term The variable.
*/
export function bind(source, term) {
term.instantiated = source;
term.tail = trail;
if ((term.flags & VAR_MASK_STATE) === gc_mask)
term.flags |= VAR_MASK_STATE;
trail = term;
}
/**
* Unbind variable binds.
*
* @param mark The trail mark.
*/
export function unbind(mark) {
while (mark !== trail) {
let term = trail;
if (engine.backtrail === term)
engine.backtrail = term.tail;
trail = term.tail;
term.instantiated = undefined;
term.tail = null;
}
}
/**************************************************************/
/* Context */
/**************************************************************/
export let tasks = new Array(0);
let notify = () => {};
export function Context() {
this.trail = null;
this.redo = null;
this.call = "[]";
this.gc_mask = VAR_MASK_ODD;
this.engine = new Engine();
this.engine.text_output = engine.text_output;
this.engine.text_error = engine.text_error;
this.engine.text_input = engine.text_input;
tasks.push(this);
}
function ctx_ended(buf) {
let k = tasks.lastIndexOf(buf);
if (k !== -1)
tasks.splice(k, 1);
notify();
}
/**************************************************************/
/* Group */
/**************************************************************/
export function group_teardown() {
for (let i = 0; i < tasks.length; i++) {
let buf = tasks[i];
let msg = new Compound("system_error", ["user_exit"]);
register_signal(buf, msg);
invoke_interrupt(buf);
}
}
export async function group_gather_async() {
while (tasks.length > 0) {
solve_signal(null, 0, null);
let prom = wait_promise(CTX_MAIN);
await prom;
}
solve_signal(null, 0, null);
}
function wait_promise(buf) {
return new Promise(resolve => {
function handler() {
notify = () => {};
register_interrupt(buf, () => {});
resolve();
}
notify = handler;
register_interrupt(buf, () => {
notify = () => {};
register_interrupt(buf, () => {});
resolve();
});
});
}
/**************************************************************/
/* Switching */
/**************************************************************/
export let ctx = CTX_MAIN;
/**
* Set the task context.
*
* @param buf The context.
*/
export function ctx_set(buf) {
ctx = buf;
}
/**
* Switch the task context.
*
* @param buf The context.
*/
export function ctx_switch(buf) {
let temp = trail;
trail = buf.trail;
buf.trail = temp;
temp = redo;
redo = buf.redo;
buf.redo = temp;
temp = call;
call = buf.call;
buf.call = temp;
temp = gc_mask;
gc_mask = buf.gc_mask;
buf.gc_mask = temp;
temp = engine;
set_engine(buf.engine);
buf.engine = temp;
}
/**************************************************************/
/* Callback */
/**************************************************************/
/**
* Run a callback once, i.e. no choice point or trailing left
* behind. Callbacks are run with auto-yield disabled and
* promises are not accepted, i.e. run "stackless" on top of the
* given main stack or side stack. "stackless" because completion,
* i.e. return or exception by the callback, is the only context switch.
*
* @param form The goal or closure.
* @param buf The context or "main".
* @param paras The actual parameters.
* @return boolean True or false.
*/
export function launch(form, buf, paras) {
if (buf !== CTX_MAIN) {
ctx_set(buf);
ctx_switch(buf);
}
let back = engine.flags & SYS_MASK_FULL_ASYNC;
engine.flags &= ~SYS_MASK_FULL_ASYNC;
let snap = snap_setup();
if (is_clause(form)) {
call = melt_clause(form, paras);
} else if (is_goal(form)) {
call = melt_directive(form);
} else {
call = form;
}
let found;
try {
found = solve(snap, true);
} finally {
snap_cleanup(snap);
engine.flags &= ~SYS_MASK_FULL_ASYNC;
engine.flags |= back;
if (buf !== CTX_MAIN) {
ctx_switch(buf);
ctx_set(CTX_MAIN);
}
}
return found;
}
export function callback(form, buf, paras) {
try {
return launch(form, buf, paras);
} catch (err) {
return false;
}
}
export function melt_directive(goal) {
let display;
if (goal.size !== 0) {
display = new Array(goal.size);
} else {
display = null;
}
let peek = goal.cutvar;
if (peek !== -1)
display[peek] = redo;
return exec_body(goal.body, display);
}
export function melt_clause(clause, paras) {
let display;
if (clause.size !== 0) {
display = new Array(clause.size);
} else {
display = null;
}
if (exec_head(clause.head, display, paras)) {
let peek = clause.cutvar;
if (peek !== -1)
display[peek] = redo;
return exec_body(clause.body, display);
} else {
return "[]";
}
}
/**************************************************************/
/* Task */
/**************************************************************/
/**
* Run a task once, i.e. no choice point or trailing left
* behind. Tasks are run with auto-yield enabled and promises are
* accepted, i.e. run "stackfull" on top of the given main stack
* or side stack. "stackfull" because not only completion, i.e.
* return or exception by the task, cause a context switch, but
* also await of an auto-yield or promise.
*
* @param form The goal or closure.
* @param buf The context or "main".
* @param paras The actual parameters.
* @return True or false.
*/
export async function launch_async(form, buf, paras) {
gc_last += real_time();
if (buf !== CTX_MAIN) {
ctx_set(buf);
ctx_switch(buf);
}
let back = engine.flags & SYS_MASK_FULL_ASYNC;
engine.flags |= SYS_MASK_FULL_ASYNC;
let found = true;
let snap = snap_setup();
if (is_clause(form)) {
call = melt_clause(form, paras);
} else if (is_goal(form)) {
call = melt_directive(form);
} else {
call = form;
}
try {
for (;;) {
found = solve(snap, found);
if (found === false) {
break;
} else if (found !== true) {
if (buf !== CTX_MAIN) {
ctx_switch(buf);
ctx_set(CTX_MAIN);
}
gc_last -= real_time();
await found;
gc_last += real_time();
if (buf !== CTX_MAIN) {
ctx_set(buf);
ctx_switch(buf);
}
found = false;
} else {
break;
}
}
} finally {
snap_cleanup(snap);
engine.flags &= ~SYS_MASK_FULL_ASYNC;
engine.flags |= back;
if (buf !== CTX_MAIN) {
ctx_switch(buf);
ctx_set(CTX_MAIN);
ctx_ended(buf);
}
}
gc_last -= real_time();
return found;
}
export async function task_async(form, buf, paras) {
try {
await launch_async(form, buf, paras);
} catch (err) {
/* */
}
}
/**
* Delay a coroutine by some milliseconds.
*
* @param proc The coroutine.
* @param delay The milliseconds.
* @return The timer.
*/
export function setDelay(proc, delay) {
if (delay === 0 && fs !== undefined) {
return setImmediate(proc);
} else {
return setTimeout(proc, delay);
}
}
/**
* Register an abort function in a context.
*
* @param buf The context.
* @param func The function.
*/
export function register_interrupt(buf, func) {
let en = determine_engine(buf);
en.abort = func;
}
/**
* Register a signal in a context.
*
* @param buf The context.
* @param msg The signal.
*/
export function register_signal(buf, msg) {
let en = determine_engine(buf);
en.signal = msg;
}
/**
* Invoke the abort handler of a context.
*
* @param buf The context.
*/
export function invoke_interrupt(buf) {
let en = determine_engine(buf);
en.abort();
}
function determine_engine(buf) {
if (buf !== CTX_MAIN) {
if (buf !== ctx) {
return buf.engine;
} else {
return engine;
}
} else {
if (ctx !== CTX_MAIN) {
return ctx.engine;
} else {
return engine;
}
}
}

Use Privacy (c) 2005-2026 XLOG Technologies AG