Admin User, created Apr 22. 2025
/**
* 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 {
add, clear, Compound, engine, is_variable, set_stage, stage
} from "./store.mjs";
import {
call, callback, cont, Context, copy_term, ctx, deref, exec_build,
exec_unify, fs, invoke_interrupt, launch, launch_async,
make_error, register_interrupt, register_signal, setDelay,
task_async, teardown, unify, VOID_ARGS
} from "./machine.mjs";
import {
check_atom, check_integer, check_number, make_check, make_special,
narrow_float, norm_float, set_to_list
} from "./special.mjs";
import {
check_goal, put_atom, Sink, Source, stream_close,
stream_flush, MASK_SRC_AREAD
} from "./runtime.mjs";
/**************************************************************/
/* Main API */
/**************************************************************/
/**
* Post a message to the Prolog interpreter.
*
* @param msg The message.
*/
export function post(msg) {
register_signal("main", msg);
invoke_interrupt("main");
}
/**
* Run a Prolog term once. The goal is run with auto-yield
* disabled and promises are not accepted.
*
* @param goal The Prolog term.
* @return boolean True or false.
*/
export function perform(goal) {
goal = new Compound(".", [goal, "[]"]);
return launch(goal, "main", VOID_ARGS);
}
/**
* Run a Prolog term once. The goal is run with auto-yield
* enabled and promises are accepted.
*
* @param goal The Prolog term.
* @return boolean True or false.
*/
export async function perform_async(goal) {
goal = new Compound(".", [goal, "[]"]);
return await launch_async(goal, "main", VOID_ARGS);
}
/**************************************************************/
/* Register API */
/**************************************************************/
/**
* The flag indicates a non-void dispatch foreign function.
*/
export const FFI_FUNC = 0x00000100;
/**
* Add a JavaScript function to the knowledge base. The given
* function is invoked with rather slow argument copying and
* spreading. Also the function is wrapped and registered as a
* special, therefore not eligible to neck optimization.
*
* @param functor The functor.
* @param arity The arity.
* @param func The JavaScript function.
* @param flags The flags.
*/
export function register(functor, arity, func, flags) {
let res;
if ((flags & FFI_FUNC) !== 0) {
res = make_special(args => invoke_func(func, args));
} else {
res = make_special(args => invoke(func, args));
}
add(functor, arity, res);
}
function invoke(func, args) {
let temp = new Array(args.length);
for (let i = 0; i < temp.length; i++)
temp[i] = deref(args[i]);
func(...temp);
cont(call.args[1]);
return true;
}
function invoke_func(func, args) {
let temp = new Array(args.length-1);
for (let i = 0; i < temp.length; i++)
temp[i] = deref(args[i]);
let res = func(...temp);
if (!unify(args[args.length-1], res))
return false;
cont(call.args[1]);
return true;
}
/*********************************************************************/
/* Fiddle Console */
/*********************************************************************/
let html_encoder;
if (fs === undefined) {
html_encoder = new XMLSerializer();
} else {
html_encoder = undefined;
}
export function html_send(data, buf) {
let text = html_encoder.serializeToString(new Text(buf));
put_atom(data, text);
return data;
}
export function html_alert(data, buf) {
let text = "<span style='color: #A52A2A'>";
text += html_encoder.serializeToString(new Text(buf));
text += "</span>";
put_atom(data, text);
return data;
}
export function fiddle_out(data, buf) {
if (data.hasAttribute("cell-clear")) {
data.innerHTML = "";
data.removeAttribute("cell-clear");
}
let depth = 0;
let k = buf.indexOf("<");
let i = 0;
while (k !== -1) {
let k2 = buf.indexOf(">", k+1);
if (k2 === -1)
throw make_error(new Compound("syntax_error",
["missing_angle"]));
if (buf.charCodeAt(k+1) === 47) { // '/'
if (depth > 0) {
depth--;
} else {
data.insertAdjacentHTML("beforeend", buf.substring(i, k));
data = data.parentElement;
i = k2 + 1;
}
} else if (buf.charCodeAt(k2-1) !== 47) { // '/'
depth++;
}
k = buf.indexOf("<", k2+1);
}
data.insertAdjacentHTML("beforeend", buf.substring(i));
while (depth > 0) {
data = data.lastElementChild;
depth--;
}
return data;
}
export function fiddle_in_promise(buf, stream) {
return new Promise(resolve => {
function keydown_handler(event) {
if (event.key === "Enter") {
event.preventDefault();
let elem2 = stream.data;
elem2.setAttribute("contenteditable", "false");
stream.buf = elem2.innerText+"\n";
stream.pos = 0;
elem2.insertAdjacentHTML("afterend", "<span contenteditable='false'>\n</span>");
elem2 = elem2.nextElementSibling;
engine.text_output.data.data = elem2;
engine.text_error.data.data = elem2;
elem2.insertAdjacentHTML("afterend", "<span style='color: #2AA52A'></span>");
elem2 = elem2.nextElementSibling;
engine.text_input.data = elem2;
elem2 = stream.data.parentElement;
elem2.removeEventListener("keydown", keydown_handler);
resolve();
}
}
let elem2 = stream.data;
window.getSelection().setPosition(elem2, 0);
elem2.scrollIntoView();
elem2 = stream.data.parentElement;
elem2.addEventListener("keydown", keydown_handler);
register_interrupt(buf, () => {
elem2.removeEventListener("keydown", keydown_handler);
resolve();
});
}).then(() => register_interrupt(buf, () => {}));
}
/****************************************************************/
/* Input/Output */
/****************************************************************/
export function set_cursor(elem) {
let dom = new Sink();
dom.data = elem;
dom.send = fiddle_out;
let dst = new Sink();
dst.data = dom;
dst.send = html_send;
dst.notify = stream_flush;
dst.release = stream_close;
dst.buf = null;
engine.text_output = dst;
dom = new Sink();
dom.data = elem;
dom.send = fiddle_out;
dst = new Sink();
dst.data = dom;
dst.send = html_alert;
dst.notify = stream_flush;
dst.release = stream_close;
dst.buf = null;
engine.text_error = dst;
}
export function set_caret(elem) {
let src = new Source();
src.data = elem;
src.receive = fiddle_in_promise;
src.flags |= MASK_SRC_AREAD;
engine.text_input = src;
}
export function set_input(text) {
let src = new Source();
src.buf = text;
engine.text_input = src;
}
/****************************************************************/
/* Notebook Execution */
/****************************************************************/
/**
* Disable/enable all the buttons.
*
* @param flag Enable if true, other disable.
*/
function shield_buttons(flag) {
let pres = document.getElementsByClassName("nb_box");
for (let num = 0; num<pres.length; num++) {
let btn = pres[num].previousElementSibling;
btn.disabled = flag;
}
}
/**
* Rollback the Prolog interpreter to a stage.
*
* @param num The desired stage.
*/
function rollback_stage(num) {
let pres = document.getElementsByClassName("nb_box");
while (num < stage) {
set_stage(stage-1);
let out = pres[stage].getElementsByClassName("nb_out")[0];
if (num === stage) {
out.setAttribute("cell-clear", "true");
} else {
out.innerHTML = "";
}
teardown();
clear();
}
}
/**
* Advance the Prolog interpreter to a stage.
*
* @param num The desired stage.
*/
async function advance_async(num) {
let pres = document.getElementsByClassName("nb_box");
while (num >= stage) {
let btn = pres[stage].previousElementSibling;
btn.disabled = false;
btn.innerText = "⏹";
btn.style.color = "red";
let out = pres[stage].getElementsByClassName("nb_out")[0];
set_cursor(out);
let txt = pres[stage].getElementsByClassName("nb_txt")[0];
set_input(txt.innerText);
try {
await perform_async(new Compound("ensure_loaded", ["user"]));
perform("flush_output");
} catch (err) {
perform(new Compound("sys_print_error", [err]));
}
set_stage(stage+1);
btn.disabled = true;
btn.innerText = "⏵";
btn.style.removeProperty("color");
}
}
/**
* Asynchronously run or abort the Prolog interpreter.
*
* @param form The form.
*/
export async function goto_async(form) {
let pres = document.getElementsByClassName("nb_box");
let num = [].indexOf.call(pres,form);
let btn = form.previousElementSibling;
if (btn.innerText === "⏵") {
shield_buttons(true);
rollback_stage(num);
await advance_async(num);
shield_buttons(false);
} else {
post(new Compound("system_error", ["user_abort"]));
}
}
/*****************************************************************/
/* ir_object_new/1 and ir_object_current/3 */
/*****************************************************************/
/**
* ir_object_new(O):
* The predicate succeeds in O with a new JavaScript object.
*/
function test_ir_object_new(args) {
let res = {};
return exec_unify(args[0], res);
}
/**
* ir_object_current(O, K, V):
* The predicate succeeds in V with the value of the key K
* in the JavaScript object O.
*/
function test_ir_object_current(args) {
let obj = deref(exec_build(args[0]));
let key = deref(exec_build(args[1]));
check_atom(key);
let res;
try {
res = obj[key];
} catch (err) {
res = undefined;
}
if (res === undefined)
return false;
return exec_unify(args[2], res);
}
/*****************************************************************/
/* ir_object_set/3 and ir_object_keys/2 */
/*****************************************************************/
/**
* ir_object_set(O, K, V):
* The predicate sets the value of the key K in the
* JavaScript object O to V.
*/
function test_ir_object_set(args) {
let obj = deref(exec_build(args[0]));
let key = deref(exec_build(args[1]));
check_atom(key);
let value = deref(exec_build(args[2]));
try {
obj[key] = value;
} catch (err) {
throw make_error(new Compound("permission_error",
["field", "modify", key]));
}
return true;
}
/**
* ir_object_keys(O, L):
* The predicate succeeds in L with the keys of
* the JavaScript object O
*/
function test_ir_object_keys(args) {
let obj = deref(exec_build(args[0]));
let res = Object.keys(obj);
res = set_to_list(res);
return exec_unify(args[1], res);
}
/*********************************************************************/
/* ir_float_value/2 */
/*********************************************************************/
/**
* ir_float_value(P, H):
* If P is instantiated, then the predicate succeeds in H with the
* JavaScript float of P, otherwise in P with the Prolog float of H.
*/
function test_ir_float_value(args) {
let alpha = deref(exec_build(args[0]));
if (is_variable(alpha)) {
let beta = deref(exec_build(args[1]));
check_number(beta);
let res = norm_float(narrow_float(beta));
return unify(alpha, res);
} else {
check_number(alpha);
let res = narrow_float(alpha);
return exec_unify(args[1], res);
}
}
/*********************************************************************/
/* os_call_later/3 and os_call_cancel/1 */
/*********************************************************************/
/**
* os_call_later(N, D, T): internal only
* The predicate succeeds in T with a new timer. As a side
* effect it schedules the compiled goal N to be executed
* after D milliseconds.
*/
function test_os_call_later(args) {
let goal = deref(exec_build(args[0]));
check_goal(goal);
let delay = deref(exec_build(args[1]));
check_integer(delay);
delay = narrow_float(delay);
let buf = ctx;
let tid = setDelay(() => callback(goal, buf, VOID_ARGS), delay);
return exec_unify(args[2], tid);
}
/**
* os_call_cancel(T): internal only
* The predicate succeeds. As a side effect it cancels the timer T.
*/
function test_os_call_cancel(args) {
let tid = deref(exec_build(args[0]));
clearTimeout(tid);
return true;
}
/*********************************************************************/
/* os_task_current/1, os_task_abort/2 and os_task_create/2 */
/*********************************************************************/
/**
* os_task_current(E): internal only
* The predicate succeeds in E with the current task.
*/
function test_os_task_current(args) {
return exec_unify(args[0], ctx);
}
/**
* os_task_abort(E, M): internal only
* The predicate succeeds. As a side effect the task E gets
* the message M signalled.
*/
function test_os_task_abort(args) {
let buf = deref(exec_build(args[0]));
let msg = exec_build(args[1]);
msg = copy_term(msg);
register_signal(buf, msg);
invoke_interrupt(buf);
return true;
}
/**
* os_task_create(N, E): internal only
* The predicate succeeds in E with a new task for the compiled
* goal N. The task is scheduled to execute immediately.
*/
function test_os_task_create(args) {
let goal = deref(exec_build(args[0]));
check_goal(goal);
let buf = new Context();
setDelay(async () => await task_async(goal, buf, VOID_ARGS), 0);
return exec_unify(args[1], buf);
}
/*********************************************************************/
/* Theatre Init */
/*********************************************************************/
// object specials
add("ir_object_new", 1, make_check(test_ir_object_new));
add("ir_object_current", 3, make_check(test_ir_object_current));
add("ir_object_set", 3, make_check(test_ir_object_set));
add("ir_object_keys", 2, make_check(test_ir_object_keys));
// scalar specials
add("ir_float_value", 2, make_check(test_ir_float_value));
// system specials, coroutines, internal only
add("os_call_later", 3, make_check(test_os_call_later));
add("os_call_cancel", 1, make_check(test_os_call_cancel));
add("os_task_current", 1, make_check(test_os_task_current));
add("os_task_abort", 2, make_check(test_os_task_abort));
add("os_task_create", 2, make_check(test_os_task_create));