JavaScript "machine"

Admin User, created May 02. 2024
         
/**
* 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 {
Clause, is_logical, snapshot_data, pred_link,
is_provable, is_cache, ensure_link, MASK_PRED_ARITHMETIC,
MASK_PRED_CHECK, MASK_PRED_SPECIAL,
engine, set_engine, Engine, Variable, is_variable,
Compound, is_compound, is_skeleton, is_place
} from "./store.mjs";
export const VOID_ARGS = [];
export let fs;
export let url;
export let path;
export let http;
if (typeof window === 'undefined') {
fs = await import("node:fs");
url = await import("node:url");
path = await import("node:path");
http = await import("node:http");
} else {
fs = undefined
url = undefined
path = undefined
http = undefined
}
export let trail = null;
export let redo = null;
export let call = "[]";
export let count = 0;
/**
* 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()]);
}
/**
* Retrieve the current stack.
*
* @return {string | Compound} The current stack.
*/
function fetch_stack() {
let temp = pred_link("sys_including", 3);
if (temp === undefined || !is_logical(temp.rope))
return "[]";
let data = snapshot_data(temp.rope);
let back = null;
let res = null;
for (let i = 0; i < data.length; i++) {
let elem = fetch_frame(data[i]);
if (elem.args[1] !== ctx)
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;
}
function fetch_frame(clause) {
let display;
if (clause.size !== 0) {
display = new Array(clause.size);
} else {
display = null;
}
temp_display = display;
let args = new Array(clause.head.length);
for (let i = 0; i < args.length; i++)
args[i] = exec_build(clause.head[i]);
temp_display = null;
return new Compound("sys_including", args);
}
/**
* Create an indicator from a functor and an arity.
*
* @param functor The functor.
* @param arity The arity.
* @return Compound The indicator.
*/
export function make_indicator(functor, arity) {
return new Compound("/", [functor, arity]);
}
/**************************************************************/
/* Garbage Collection */
/**************************************************************/
const VAR_MASK_EVEN = 0x40000000;
const VAR_MASK_ODD = 0x20000000;
export const VAR_MASK_STATE = VAR_MASK_EVEN | VAR_MASK_ODD;
export const GC_MASK_ASYNC_MODE = 0x00000001;
export const GC_MASK_ALLOW_YIELD = 0x00000008;
export const GC_MASK_IMPORT_ASYNC = 0x00000010;
export const GC_MASK_PROP_ASYNC = 0x00000020;
export const GC_MASK_READ_ASYNC = 0x00000040;
export const GC_MASK_FULL_ASYNC = GC_MASK_ASYNC_MODE | GC_MASK_ALLOW_YIELD;
const GC_MAX_TRAIL = 3000000;
let GC_MAX_INFERS;
let GC_MAX_DIRTY;
if (fs !== undefined) {
/* LIPS / 60 */
GC_MAX_INFERS = 92000;
/* LIPS * 60 */
GC_MAX_DIRTY = 331200000;
} else {
/* LIPS / 60 */
GC_MAX_INFERS = 61000;
/* LIPS * 60 */
GC_MAX_DIRTY = 219600000;
}
export let gc_flags = VAR_MASK_ODD |
GC_MASK_IMPORT_ASYNC | GC_MASK_PROP_ASYNC | GC_MASK_READ_ASYNC;
export let gc_time = 0;
export let gc_enter = 0;
let gc_tick = GC_MAX_INFERS;
let gc_tock = GC_MAX_DIRTY;
/**
* Retrieve the real time.
*
* @return The real time.
*/
export function real_time() {
if (fs !== undefined) {
return Number((process.hrtime.bigint()+500000n) / 1000000n);
} else {
return Math.round(performance.now());
}
}
/**
* Set the garbage collector flags.
*
* @param flags The garbage collector flags.
*/
export function set_gc_flags(flags) {
gc_flags = flags;
}
/**************************************************************/
/* Major Marking */
/**************************************************************/
/**
* Perform major garbage collection.
*/
function gc_major() {
gc_time -= real_time();
gc_flags ^= VAR_MASK_STATE;
/* mark phase */
engine.low = 0;
engine.high = engine.serno;
engine.serno = 0;
mark_call(call);
let last = redo;
while (last !== null) {
mark_call(last.cont);
last = last.tail;
}
/* sweep phase */
last = redo;
while (last !== null) {
last.mark = adjust_mark(last.mark);
last = last.tail;
}
sweep_trail(null);
gc_time += real_time();
}
/**
* Major mark a term.
*
* @param term The term.
*/
function mark_term(term) {
for (; ;) {
if (is_variable(term)) {
let val = term.flags;
if ((val & VAR_MASK_STATE) === (gc_flags & VAR_MASK_STATE))
break;
val = val & ~VAR_MASK_STATE;
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;
}
}
term.flags = val | (gc_flags & VAR_MASK_STATE);
if (term.instantiated !== undefined) {
term = term.instantiated;
} else {
break;
}
} else if (is_compound(term)) {
term = term.args;
let i = 0;
for (; i < term.length - 1; i++)
mark_term(term[i]);
term = term[i];
} else {
break;
}
}
}
/**
* Major mark a continuation.
*
* @param term The continuation.
*/
function mark_call(term) {
let gc_color = ((gc_flags & VAR_MASK_EVEN) !== 0) ? "E" : "O";
while (is_compound(term) && term.functor !== gc_color) {
term.functor = gc_color;
mark_term(term.args[0]);
term = term.args[1];
}
}
/**************************************************************/
/* Minor Marking */
/**************************************************************/
/**
* Perform minor garbage collection.
*/
function gc_minor() {
gc_time -= real_time();
/* mark phase */
mark2_call(call);
let last = redo;
while (last !== null) {
mark2_call(last.cont);
last = last.tail;
}
mark2_trail(engine.backtrail);
/* sweep phase */
last = redo;
while (last !== null) {
last.mark = adjust_mark(last.mark);
last = last.tail;
}
sweep_trail(engine.backtrail);
gc_time += real_time();
}
/**
* Minor mark a term.
*
* @param term The term.
*/
export function mark2_term(term) {
for (; ;) {
if (is_variable(term)) {
let val = term.flags;
if ((val & VAR_MASK_STATE) === (gc_flags & VAR_MASK_STATE))
break;
val = val & ~VAR_MASK_STATE;
term.flags = val | (gc_flags & VAR_MASK_STATE);
if (term.instantiated !== undefined) {
term = term.instantiated;
} else {
break;
}
} else if (is_compound(term)) {
term = term.args;
let i = 0;
for (; i < term.length - 1; i++)
mark2_term(term[i]);
term = term[i];
} else {
break;
}
}
}
/**
* Minor mark a continuation.
*
* @param term The continuation.
*/
function mark2_call(term) {
let gc_color = ((gc_flags & VAR_MASK_EVEN) !== 0) ? "E" : "O";
while (is_compound(term) && term.functor !== gc_color) {
term.functor = gc_color;
mark2_term(term.args[0]);
term = term.args[1];
}
}
/**
* 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;
}
}
/**************************************************************/
/* Variable Sweep */
/**************************************************************/
/**
* Adjust a marker into the trail.
*
* @param temp The trail.
* @return Variable The adjusted trail.
*/
function adjust_mark(temp) {
while (temp !== null) {
if ((temp.flags & VAR_MASK_STATE) === (gc_flags & VAR_MASK_STATE)) {
return temp;
} else {
temp = temp.tail;
}
}
return null;
}
/**
* 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_flags & VAR_MASK_STATE)) {
if (back !== null) {
back.tail = term;
} else {
trail = term;
}
back = term;
} else {
count--;
term.instantiated = undefined;
term.tail = null;
}
}
if (back !== null) {
back.tail = stop;
} else {
trail = stop;
}
engine.backtrail = trail;
engine.backcount = count;
}
/**************************************************************/
/* 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;
}
/**
* Copy a term.
*
* @param alpha The term.
* @return any The copy.
*/
export function copy_term(alpha) {
let assoc = null;
function copy_term2(alpha2) {
let back = null;
for (; ;) {
alpha2 = deref(alpha2);
if (is_variable(alpha2)) {
let peek;
if (assoc === null) {
assoc = new Map();
peek = undefined;
} else {
peek = assoc.get(alpha2);
}
if (peek === undefined) {
peek = new Variable();
assoc.set(alpha2, peek);
}
alpha2 = peek;
break;
} else if (is_compound(alpha2)) {
let t1 = alpha2.args;
let args = new Array(t1.length);
alpha2 = new Compound(alpha2.functor, args);
let i = 0;
for (; i < args.length - 1; i++)
args[i] = copy_term2(t1[i]);
args[i] = back;
back = alpha2;
alpha2 = t1[i];
} else {
break;
}
}
while (back !== null) {
let peek = back.args[back.args.length - 1];
back.args[back.args.length - 1] = alpha2;
alpha2 = back;
back = peek;
}
return alpha2;
}
return copy_term2(alpha);
}
/**************************************************************/
/* Clause Loops */
/**************************************************************/
/**
* 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) {
gc_tick += GC_MAX_INFERS;
if (count > GC_MAX_TRAIL) {
gc_major();
if (count > GC_MAX_TRAIL)
throw make_error(new Compound("system_error",
["stack_overflow"]));
} else if (gc_enter >= gc_tock) {
gc_tock += GC_MAX_DIRTY;
gc_major();
} else if (3 * (count - engine.backcount) > GC_MAX_TRAIL &&
3 * engine.backcount > GC_MAX_TRAIL) {
gc_minor();
}
if ((gc_flags & GC_MASK_ASYNC_MODE) !== 0) {
more(new Choice(solve_signal, null, null, trail));
return immediate_promise();
}
}
let goal = call;
if (is_compound(goal)) {
gc_enter++;
goal = deref(goal.args[0]);
let peek = lookup_pred(goal);
if (peek === undefined || (peek.flags & MASK_PRED_ARITHMETIC) !== 0)
throw make_error(new Compound("existence_error",
["procedure", make_indicator_term(goal)]));
if (is_compound(goal)) {
goal = goal.args;
} else {
goal = VOID_ARGS;
}
if ((peek.flags & MASK_PRED_CHECK) !== 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_clauses(peek, goal);
found = solve2_rope(goal, snapshot_data(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));
}
/**
* Lookup a predicate.
*
* @param goal The goal.
* @return Provable The predicate or undefined.
*/
export function lookup_pred(goal) {
let functor;
let arity;
if (is_compound(goal)) {
functor = goal.functor;
arity = goal.args.length;
} else {
functor = goal;
arity = 0;
}
return resolve_functor(functor, arity);
}
function resolve_functor(functor, arity) {
if (is_cache(functor)) {
return ensure_link(functor, arity);
} else if (is_atom(functor)) {
return pred_link(functor, arity);
} else if (is_provable(functor)) {
return functor;
} else {
check_callable(functor);
return undefined;
}
}
export function make_indicator_term(goal) {
let functor;
let arity;
if (is_compound(goal)) {
functor = goal.functor;
arity = goal.args.length;
} else {
functor = goal;
arity = 0;
}
if (is_cache(functor))
functor = functor.name;
return make_indicator(functor, arity);
}
function solve_rope(rope, at, choice) {
let goal = deref(call.args[0]);
if (is_compound(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 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(args, 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, args)) {
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;
}
/**
* Remove choice points.
*
* @param last The last choice point.
*/
export function cut(last) {
redo = last;
}
/**
* Advance the choice points.
*
* @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;
}
/**
* Create a goal.
*
* @param size The number of variables.
* @param body The build Albufeira code.
*/
export function Goal(size, body) {
this.size = size;
this.body = body;
}
/**************************************************************/
/* Clauses Retrieval */
/**************************************************************/
/**
* Find the logical for a call.
*
* @param pred The predicate.
* @param args The arguments.
* @return Logical The logical.
*/
export function defined_clauses(pred, args) {
let peek = pred.rope;
if (peek.count < 2)
return peek;
if (args.length > 0 && pred.idxmap.size > 0) {
let key = key_value(args[0]);
if (key === undefined) {
return peek;
} else {
peek = pred.idxmap.get(key);
if (peek === undefined) {
return pred.nonguard;
} else {
return peek;
}
}
} else {
return peek;
}
}
function key_value(term) {
term = deref(term);
if (is_variable(term)) {
return undefined;
} else if (is_compound(term)) {
return term.functor;
} else {
return term;
}
}
/**************************************************************/
/* Directives */
/**************************************************************/
/**
* 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, "main", VOID_ARGS))
throw make_error(new Compound("syntax_error", ["directive_failed"]));
}
export function snap_setup() {
redo = new Choice(solve_setup, null, null, trail);
return redo;
}
function solve_setup(rope,at,choice) {
return false;
}
export function snap_cleanup(snap) {
cut(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;
}
/**************************************************************/
/* Albufeira code */
/**************************************************************/
let temp_display = null;
export function exec_build(template) {
let back = null;
for (; ;) {
if (is_place(template)) {
let index = template.index;
if (index === -1) {
template = new Variable();
} else if (index < -1) {
template = new Variable();
temp_display[(-index) - 2] = template;
} else {
template = temp_display[index];
}
break;
} else if (is_skeleton(template)) {
let t1 = template.args;
let args = new Array(t1.length);
template = new Compound(template.functor, args);
let i = 0;
for (; i < args.length - 1; i++)
args[i] = exec_build(t1[i]);
args[i] = back;
back = template;
template = t1[i];
} else {
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 (is_place(template)) {
template = template.index;
if (template === -1) {
return true;
} else if (template < -1) {
temp_display[(-template)-2] = deref(alpha);
return true;
} else {
return unify(temp_display[template], alpha);
}
} else if (is_skeleton(template)) {
alpha = deref(alpha);
if (is_variable(alpha)) {
let t1 = template.args;
let args = new Array(t1.length);
template = new Compound(template.functor, args);
for (let i = 0; i < args.length; i++)
args[i] = exec_build(t1[i]);
bind(template, alpha);
return true;
} else if (is_compound(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);
}
}
}
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) {
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 (is_skeleton(template)) {
let peek = resolve_functor(template.functor, template.args.length + 1);
if (peek === undefined || (peek.flags & MASK_PRED_ARITHMETIC) === 0)
throw make_error(new Compound("type_error",
["evaluable", make_indicator(template.functor, template.args.length)]));
return peek.func(template.args);
} else {
if (is_place(template)) {
template = template.index;
if (template < 0) {
throw make_error("instantiation_error");
} else {
template = temp_display[template];
}
}
template = deref(template);
if (is_number(template))
return template;
let peek = lookup_eval(template);
if (peek === undefined || (peek.flags & MASK_PRED_ARITHMETIC) === 0)
throw make_error(new Compound("type_error",
["evaluable", make_indicator_term(template)]));
if (is_compound(template)) {
template = template.args;
} else {
template = VOID_ARGS;
}
return peek.func(template);
}
}
function exec_test(template) {
if (is_skeleton(template)) {
let peek = resolve_functor(template.functor, template.args.length);
if (peek === undefined || (peek.flags & MASK_PRED_CHECK) === 0) {
let args = new Array(template.args.length);
for (let i = 0; i < args.length; i++)
args[i] = exec_build(template.args[i]);
if (peek === undefined || (peek.flags & MASK_PRED_ARITHMETIC) !== 0) {
return new Compound(template.functor, args);
} else {
return new Compound(peek, args);
}
} else {
gc_enter++;
return peek.func(template.args);
}
} else {
if (is_place(template)) {
template = template.index;
if (template < 0) {
throw make_error("instantiation_error");
} else {
template = temp_display[template];
}
}
template = deref(template);
let peek = lookup_pred(template);
if (peek === undefined || (peek.flags & MASK_PRED_CHECK) === 0) {
return template;
} else {
gc_enter++;
if (is_compound(template)) {
template = template.args;
} else {
template = VOID_ARGS;
}
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;
}
let temp = new Compound(".", [goal, undefined]);
if (back === null) {
res = temp;
} else {
back.args[1] = temp;
}
back = temp;
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.
* Tail recursive solution.
*
* @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) {
for (; ;) {
first = deref(first);
second = deref(second);
if (is_variable(first)) {
if (is_variable(second) && first === second)
return true;
bind(second, first);
return true;
}
if (is_variable(second)) {
bind(first, second);
return true;
}
if (!is_compound(first))
return (first === second);
if (!is_compound(second))
return false;
if (first.args.length !== second.args.length)
return false;
if (first.functor !== second.functor)
return false;
first = first.args;
second = second.args;
let i = 0;
for (; i < first.length - 1; i++) {
if (!unify(first[i], second[i]))
return false;
}
first = first[i];
second = second[i];
}
}
/**
* Dereference a Prolog term.
*
* @param term The Prolog term.
* @return any The dereferenced Prolog term.
*/
export function deref(term) {
while (is_variable(term) && term.instantiated !== undefined)
term = term.instantiated;
return term;
}
/**
* 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_flags & VAR_MASK_STATE))
term.flags |= VAR_MASK_STATE;
trail = term;
count++;
}
/**
* 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;
engine.backcount--;
}
trail = term.tail;
count--;
term.instantiated = undefined;
term.tail = null;
}
}
/**************************************************************/
/* Eval Service */
/**************************************************************/
/**
* Assure that the object is a nonvar.
*
* @param beta The object.
*/
export function check_nonvar(beta) {
if (is_variable(beta))
throw make_error("instantiation_error");
}
/**
* Assure that the object is a callable.
*
* @param beta The object.
*/
export function check_callable(beta) {
if (is_variable(beta) || is_number(beta)) {
check_nonvar(beta);
beta = copy_term(beta);
throw make_error(new Compound("type_error", ["callable", beta]));
}
}
/**
* 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_compound(expr)) {
functor = expr.functor;
arity = expr.args.length;
} else {
functor = expr;
arity = 0;
}
return resolve_functor(functor, arity + 1);
}
/**************************************************************/
/* Context */
/**************************************************************/
/**
* Create a task context.
*
* @constructor The context.
*/
export function Context() {
this.trail = null;
this.redo = null;
this.call = "[]";
this.count = 0;
this.gc_flags = VAR_MASK_ODD |
GC_MASK_IMPORT_ASYNC | GC_MASK_PROP_ASYNC | GC_MASK_READ_ASYNC;
this.engine = new Engine();
}
export let 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 = count;
count = buf.count;
buf.count = temp;
temp = gc_flags;
gc_flags = buf.gc_flags;
buf.gc_flags = 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 params The actual parameters.
* @return boolean True or false.
*/
export function launch(form, buf, params) {
if (buf !== "main") {
ctx_set(buf);
ctx_switch(buf);
}
let back = gc_flags & GC_MASK_FULL_ASYNC;
set_gc_flags(gc_flags & ~GC_MASK_FULL_ASYNC);
let snap = snap_setup();
if (form instanceof Clause) {
call = melt_clause(form, params);
} else if (form instanceof Goal) {
call = melt_directive(form);
} else {
call = form;
}
let found;
try {
found = solve(snap, true);
} finally {
snap_cleanup(snap);
set_gc_flags((gc_flags & ~GC_MASK_FULL_ASYNC) | back);
if (buf !== "main") {
ctx_switch(buf);
ctx_set("main");
}
}
return found;
}
export function melt_directive(goal) {
let display;
if (goal.size !== 0) {
display = new Array(goal.size);
} else {
display = null;
}
return exec_body(goal.body, display);
}
export function melt_clause(clause, params) {
let display;
if (clause.size !== 0) {
display = new Array(clause.size);
} else {
display = null;
}
if (exec_head(clause.head, display, params)) {
let temp = clause.cutvar;
if (temp !== -1)
display[temp] = 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 params The actual parameters.
* @return True or false.
*/
export async function launch_async(form, buf, params) {
if (buf !== "main") {
ctx_set(buf);
ctx_switch(buf);
}
let back = gc_flags & GC_MASK_FULL_ASYNC;
set_gc_flags(gc_flags | GC_MASK_FULL_ASYNC);
let found = true;
let snap = snap_setup();
if (form instanceof Clause) {
call = melt_clause(form, params);
} else if (form instanceof Goal) {
call = melt_directive(form);
} else {
call = form;
}
try {
for (;;) {
found = solve(snap, found);
if (found === false) {
break;
} else if (found !== true) {
if (buf !== "main") {
ctx_switch(buf);
ctx_set("main");
}
await found;
if (buf !== "main") {
ctx_set(buf);
ctx_switch(buf);
}
found = false;
} else {
break;
}
}
} finally {
snap_cleanup(snap);
set_gc_flags((gc_flags & ~GC_MASK_FULL_ASYNC) | back);
if (buf !== "main") {
ctx_switch(buf);
ctx_set("main");
}
}
return found;
}
/**
* 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 interrupt function in a context.
*
* @param buf The context.
* @param func The function.
*/
export function register_interrupt(buf, func) {
let en = determine_engine(buf);
en.interrupt = 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 interrupt handler of a context.
*
* @param buf The context.
*/
export function invoke_interrupt(buf) {
let en = determine_engine(buf);
en.interrupt();
}
function determine_engine(buf) {
if (buf !== "main") {
if (buf !== ctx) {
return buf.engine;
} else {
return engine;
}
} else {
if (ctx !== "main") {
return ctx.engine;
} else {
return engine;
}
}
}