/*
* ciao-async.js
*
* Async version of ciao.js (as a webworker)
*
* Copyright (C) 2017 Jose F. Morales
*/
/* --------------------------------------------------------------------------- */
/* TODO: similar to (:- block) implementation */
function CiaoPromiseProxy() {
this.hasValue = false;
this.value = null;
this.handler = null;
this.next = null;
}
function dummyPromise(v) {
var p = new CiaoPromiseProxy();
p.setValue(undefined); /* no value */
return p;
}
CiaoPromiseProxy.prototype.setValue = function(value) {
if (typeof this.handler === 'function') { /* have both! call handler */
var next = this.handler(value);
if (typeof next === 'undefined') {
this.next.setValue(undefined); /* no value */
} else { /* assume it is a promise */
var this_next = this.next;
next.then(function(r) {
this_next.setValue(r); /* propagate value */
});
}
} else { /* otherwise save the value */
this.value = value;
this.hasValue = true;
}
}
/* Note: assume that handler returns undefined or a promise */
CiaoPromiseProxy.prototype.then = function(handler) {
if (this.hasValue === true) { /* have both! call handler */
var next = handler(this.value);
if (typeof next === 'undefined') { /* create a dummy promise */
next = dummyPromise(undefined);
}
return next;
} else { /* otherwise save the handler */
var next = new CiaoPromiseProxy(); /* create temporary promise */
this.next = next;
this.handler = handler;
return next;
}
}
/* --------------------------------------------------------------------------- */
/**
* Class for the Ciao Worker, able to run the Ciao queries, like `use_bundle`,
* `use_module_from_string` and run queries.
* @class
* @constructor
* @param {string} ciao_root_URL
*/
function CiaoWorker(ciao_root_URL) {
var listeners = [];
this.pending_queue = [];
this.pending_loadEng = true;
this.pending_boot = true;
this.ciao_root_URL = ciao_root_URL;
this.listeners = listeners;
this.w = new Worker(ciao_root_URL + "build/bin/ciao-worker.js");
this.w.onmessage = function(event) {
if (event.data.isLog) { // TODO: debug only?
console.log(event.data.msg);
} else {
listeners[event.data.id].setValue(event.data.ret);
delete listeners[event.data.id]; /* undefine this array element */
}
};
}
/**
* In charge of communications with Worker by messages. It sends the function
* and its arguments to be executed by the Ciao Worker.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} Ciao promise with the result of the call.
*/
CiaoWorker.prototype.async_ = function() {
/* TODO: cleaner way? */
if (this.pending_queue.length > 0) { /* call queued calls before */
var f = this.pending_queue.shift();
var self = this;
var my_args = arguments;
return f(self).then(function(r) {
return self.async_.apply(self, my_args);
});
}
var proxy = new CiaoPromiseProxy();
/* set in listeners (get next free id or push) */
var id = 0;
for (var id = 0; id < this.listeners.length; id++) {
if (this.listeners[id] === undefined) break;
}
if (id === this.listeners.length) {
this.listeners.push(proxy);
} else {
this.listeners[id] = proxy;
}
this.w.postMessage({
id: id, /* id of message in listener array */
cmd: arguments[0],
args: Array.prototype.slice.call(arguments, 1)
});
return proxy;
};
/**
* Ensure the engine is loaded correctly.
* @memberof CiaoWorker
*/
CiaoWorker.prototype.ensure_loadEng = function() {
if (this.pending_loadEng) {
this.pending_loadEng = false;
this.pending_queue.push(function(self) {
return self.async_('loadEng', self.ciao_root_URL);
});
}
}
/**
* Ensure boot.
* @memberof CiaoWorker
*/
CiaoWorker.prototype.ensure_boot = function() {
this.ensure_loadEng();
if (this.pending_boot) {
this.pending_boot = false;
this.pending_queue.push(function(self) {
return self.async_('boot');
});
}
}
/**
* Use the bundle passed as a parameter, loading the engine if needed.
* @param {string} name - Name of the bundle.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.use_bundle = function(name) {
this.ensure_loadEng();
return this.async_('useBundle', name);
};
/**
* Get the Ciao root path.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.get_ciao_root = function() {
this.ensure_loadEng();
return this.async_('get_ciao_root');
};
/**
* Get the stats of the latest call.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.get_stats = function() {
this.ensure_loadEng();
return this.async_('get_stats');
};
/**
* Show boot information in a JS console (this forces boot).
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.bootInfo = function() {
this.ensure_boot();
return this.async_('bootInfo');
};
/**
* Run the query passed as a parameter and obtain all solutions.
* @param {string} template - Template term for solutions (null if same as goal).
* @param {string} goal - Query to be executed.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.query = function(template, goal) {
this.ensure_boot();
return this.async_('query', template, goal);
};
/**
* Begins the query passed as parameter and obtains one solution. The
* decision tree stays awake and waits for user's input.
* @param {string} template - Template term for solutions (null if same as goal).
* @param {string} goal - Query to be launched.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.query_one_begin = function(template, goal) {
this.ensure_boot();
return this.async_('query_one_begin', template, goal);
}
/**
* Tells whether the query has started correctly and has more solutions
* or not.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.query_ok = function() {
return this.async_('query_ok');
}
/**
* Obtain the next solution for the query previously launched.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.query_one_next = function() {
return this.async_('query_one_next');
}
/**
* End the query process stopping its decision tree.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.query_end = function() {
return this.async_('query_end');
}
/**
* Use the module defined by the string passed a parameter, saving it into
* a file. This allows for modules written by the user to be used in a Ciao
* playground.
* @param {string} path - Path to save the contents.
* @param {string} contents - Contents of the module.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.use_module_from_string = function(path, contents) {
this.ensure_loadEng();
return this.async_('use_module_from_string', path, contents);
};
/**
* Read file specified in path parameter.
* @param {string} path - Path of the file to read.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.readFile = function(path) {
this.ensure_boot();
return this.async_('readFile', path);
};
/**
* Write a string in a file.
* @param {string} path - Path of the file to write.
* @param {string} contents - String of the contents to write in the file.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.writeFile = function(path, contents) {
this.ensure_boot();
return this.async_('writeFile', path, contents);
};
/**
* Capture the output of the most recent query/ies.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.readStdout = function() {
return this.async_('readStdout');
};
/**
* Capture the standard error of the most recent query/ies.
* @memberof CiaoWorker
* @return {CiaoPromiseProxy} - Result of the call to the worker.
*/
CiaoWorker.prototype.readStderr = function() {
return this.async_('readStderr');
};
/**
* Terminate the worker `w`. It does not finish its operations; it is
* stopped at once.
* @returns undefined
*/
CiaoWorker.prototype.terminate = function() {
return this.w.terminate();
}