/* Copyright (c) 2014, Torbjörn Lager All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /******************************* * CONSTRUCTOR * *******************************/ /** * Create a Prolog engine (Pengine). The constructor is handed an object * that specifies the behaviour. Defaults for this object may be * specified by adding properties to `Pengine.options`. For example * `Pengine.options.chunk = 1000;` * * @param {Object} options * @param {String} [options.format="json"] specifies the response * format. * @param {String} [options.ask] specifies the initial Prolog query. If * omitted, the user must provide an `oncreate` handler and use * `.ask()` from the create handle. * @param {Boolean} [options.destroy=true] If `true` destroy the pengine * if initial query has finished. * @param {Int} [options.chunk=1] Provide answers in chunks of this * size. * @param {String} [options.applicatio="pengine_sandbox"] Application in * which to execute the query. * @param {Function} [options.oncreate] * @param {Function} [options.onsuccess] * @param {Function} [options.ondata] * @param {Function} [options.onfailure] * @param {Function} [options.onerror] * @param {Function} [options.onprompt] * @param {Function} [options.onoutput] * @param {Function} [options.ondebug] * @param {Function} [options.onping] * @param {Function} [options.onabort] * @param {Function} [options.ondetach] * @param {Function} [options.ondestroy] * Callback functions. The callback function is called with an object * argument. This object always has a property `pengine` that refers * to the JavaScript `Pengine` instance. All callbacks are optional, * although creating a Pengine without `onsuccess` or `ondata` is * typically meaningless. * * On success, first `options.onsuccess` is called. If this does not * return `false` and `options.ondata` is defined, this function is * called for each answer in the answer set with `this` refering to * the data object as a whole while the first argument is an object * holding a single answer. If more answers are available, `next()` * is called on the Pengine. */ function Pengine(options) { var that = this; // private functions function fillDefaultOptions(options) { for(var k in Pengine.options) { if ( Pengine.options.hasOwnProperty(k) && options[k] === undefined ) options[k] = Pengine.options[k]; } return options; } function copyOptions(to, from, list) { for(var i=0; i]*>/, "") .replace(/<\/body>/, ""); var plain; if (typeof $ === 'undefined') { plain = msg.replace(/(<([^>]+)>)/ig, ''); } else { plain = $("
").html(msg).text(); } obj.data = plain; obj.dataHTML = msg; } else if ( textStatus ) { obj.data = "Server status: " + textStatus; } else if ( errorThrown ) { obj.data = "Server error: " + errorThrown; } if ( !obj.dataHTML && obj.data ) obj.dataHTML = ''+obj.data+''; unregisterPengine(this); /* see above */ this.process_response(obj); } Pengine.prototype.report = function(level, data) { if ( console !== undefined ) console[level](data); else if ( level === 'error' ) alert(data); }; /******************************* * HANDLE EVENTS * *******************************/ Pengine.onresponse = { create: function(obj) { this.id = obj.id; Pengine.alive.push(this); if ( Pengine.alive.length > obj.slave_limit ) { this.destroy(); obj.data = "Attempt to create too many pengines. "+ "The limit is: " + obj.slave_limit; obj.code = "too_many_pengines"; if ( !this.callback('onerror', obj) ) this.report('error', obj.data); } else { this.callback('oncreate', obj); if ( obj.answer ) this.process_response(obj.answer); } }, stop: function(obj) { this.callback('onstop', obj); }, failure: function(obj) { this.callback('onfailure', obj); }, prompt: function(obj) { this.callback('onprompt', obj); }, success: function(obj) { if ( this.callback('onsuccess', obj) != false && this.options.ondata ) { for(i=0; i -1 ) Pengine.alive.splice(index, 1); if ( !this.detached ) pengine.died = true; } /** * Turn an object into a Prolog option list. The option values * must be valid Prolog syntax. Use Pengine.stringify() when * applicable. */ function options_to_list(options) { var opts = "["; for (var name in options) { if ( opts !== "[" ) opts += ","; if ( options.hasOwnProperty(name) ) { opts += name + "(" + options[name] + ")"; } } return opts + "]"; } })(); /** * Serialize JavaScript data as a Prolog term. The serialization * is performed according to the rules below: * * - A number is serialized trivially * - A string is serialized into a Prolog string unless * `option.string == "atom"` * - `true`, `false`, `null` and `undefined` are serialized as * atoms. * - An array is serialized into a Prolog list * - An object is serialized into a Prolog dict. Keys that are * integers are mapped to Prolog integers, other keys are mapped * to Prolog atoms. Note that in JavaScript {1:42} and {"1":42} * are equavalent and both can be retrieved as either * obj[1] or obj["1"] * * @param {any} data is the data to be serialized * @param {Object} [options] * @param {String} [options.string] If `"atom"`, translate * JavaScript strings into Prolog objects. * @return {String|undefined} is the Prolog serialization of `data` */ Pengine.stringify = function(data, options) { var msg = ""; var strq = options && options.string == "atom" ? "'" : '"'; function serialize(data) { function stringEscape(s, q) { function dec2unicode(i) { var r; if (i >= 0 && i <= 15) { r = "\\u000" + i.toString(16); } else if (i >= 16 && i <= 255) { r = "\\u00" + i.toString(16); } else if (i >= 256 && i <= 4095) { r = "\\u0" + i.toString(16); } else if (i >= 4096 && i <= 65535) { r = "\\u" + i.toString(16); } return r } var result = q; for(var i=0; i= ' ' ) { if ( c == '\\' ) result += "\\\\"; else if ( c == q ) result += "\\"+q; else result += c; } else { if ( c == '\n' ) result += "\\n"; else if ( c == '\r' ) result += "\\r"; else if ( c == '\t' ) result += "\\t"; else if ( c == '\b' ) result += "\\b"; else if ( c == '\f' ) result += "\\f"; else result += dec2unicode(c.charCodeAt(0)); } } return result+q; } function serializeKey(k) { if ( k.match(/^\d+$/) ) { msg += k; } else { msg += stringEscape(k, "'"); } return true; } switch ( typeof(data) ) { case "boolean": msg += data ? "true" : "false"; break; case "undefined": msg += "undefined"; break; case "number": msg += data; break; case "string": msg += stringEscape(data, strq); break; case "object": if ( data == null ) { msg += "null"; } else if ( Array.isArray(data) ) { msg += "["; for(var i=0; i 0 ) { var servers = {}; for(var i=0; i