diff --git a/demos/callback.html b/demos/callback.html new file mode 100644 index 00000000..f376bf30 --- /dev/null +++ b/demos/callback.html @@ -0,0 +1,131 @@ + + +
+ +A native function can take a callback as an argument, and call it later
+ to create real asynchronous scripts. The example below (see this page's
+ source) creates a getXhr
function that fetches the URL and
+ calls the given callback, returning the content.
This function is defined using createNativeFunction
during
+ initialization. When called, the interpreter passes the provided callback
+ function as a pseudo object representing all the data needed to execute
+ it later. The Xhr is then intialized and waits for the targeted URL's content.
+ The execution of the interpreter's program continues normally. When the
+ Xhr responds, the callback is loaded in the interpeter, and all it's arguments
+ are provided using queueCall
. Then the interpreter is re-started
+ by calling run
, and the callback function is executed within the
+ interpreter.
Click Parse, then either click Step repeatedly, + or click Run once. Open your browser's console for errors.
+ +
+
+
+
+
Back to the JS-Interpreter documentation.
+ + + + diff --git a/docs.html b/docs.html index 94e7c325..ec7fe96c 100644 --- a/docs.html +++ b/docs.html @@ -140,6 +140,28 @@For a working example, see the async demo.
+Alternatively, a callback can be provided to make asychronous calls. The
+ getXhr(url)
function can therefore be written like this :
+ var wrapper = function(href, callback) { + href = href ? href.toString() : ''; + var req = new XMLHttpRequest(); + req.open('GET', href, true); + req.onreadystatechange = function() { + if (req.readyState == 4 && req.status == 200) { + interpreter.queueCall(callback, [interpreter.createPrimitive(req.responseText)]); + interpreter.run(); + } + }; + req.send(null); + }; + interpreter.setProperty(scope, 'getXhr', + interpreter.createNativeFunction(wrapper)); ++ +
See the callback demo.
+The version of JavaScript implemented by the interpreter has a few diff --git a/interpreter.js b/interpreter.js index e6dcb056..a62471c7 100644 --- a/interpreter.js +++ b/interpreter.js @@ -143,6 +143,40 @@ Interpreter.prototype.appendCode = function(code) { state.done = false; }; +/** +* Shifts the given function at the bottom of the state stack, delaying the call. +* @param {Interpreter.Object} func Pseudo function to call. +* @param {Interpreter.Object[]} args Arguments to provide to the function. +*/ +Interpreter.prototype.queueCall = function(func, args) { + var state = this.stateStack[0]; + var interpreter = this; + if (!state || state.node.type != 'Program') { + throw Error('Expecting original AST to start with a Program node.'); + } + state.done = false; + var scope = this.createScope(func.node.body, func.parentScope); + func.node.params.forEach(function(p, i) { + interpreter.setProperty(scope, interpreter.createPrimitive(p.name), args[i]); + }) + var argsList = this.createObject(this.ARRAY); + args.forEach(function(arg, i) { + interpreter.setProperty(argsList, interpreter.createPrimitive(i), arg); + }) + this.setProperty(scope, 'arguments', argsList); + var last = func.node.body.body[func.node.body.body.length - 1]; + if(last.type == 'ReturnStatement') { + last.type = 'ExpressionStatement'; + last.expression = last.argument; + delete last.argument; + } + this.stateStack.splice(1, 0, { + node: func.node.body, + scope: scope, + value: this.getScope().strict ? this.UNDEFINED : this.global + }); +}; + /** * Execute one step of the interpreter. * @return {boolean} True if a step was executed, false if no more instructions. @@ -3738,3 +3772,4 @@ Interpreter.prototype['createAsyncFunction'] = Interpreter.prototype.createAsyncFunction; Interpreter.prototype['step'] = Interpreter.prototype.step; Interpreter.prototype['run'] = Interpreter.prototype.run; +Interpreter.prototype['queueCall'] = Interpreter.prototype.queueCall;