Source: ciao-async.js

/*
 *  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();
}