Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for calling interpreted functions #201

Open
wants to merge 68 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
67356d9
Add methods for calling interpreted functions
Webifi Nov 10, 2020
a1122eb
Remove ES6
Webifi Nov 11, 2020
27cc696
Don't allow immediate callFunction when paused
Webifi Nov 11, 2020
c85abae
Remove debug
Webifi Nov 11, 2020
e028f9a
Simplify callback polyfill
Webifi Nov 11, 2020
8949d78
Add exports
Webifi Nov 11, 2020
b863237
Cleanup
Webifi Nov 11, 2020
4d01f8e
Fi issue with non-immediate callbacks
Webifi Nov 11, 2020
6f59cd6
Fix unwind issue with non-immediate callbacks
Webifi Nov 11, 2020
1930937
Remove need for callbacks
Webifi Nov 11, 2020
9024e3d
Remove unsused methods
Webifi Nov 11, 2020
b499bd2
Add callAsyncFunction and appendFunction methods
Webifi Nov 12, 2020
bc179e6
Add getter to async function call callback
Webifi Nov 12, 2020
3fa1672
Wrap value getter in pseudo if needed
Webifi Nov 12, 2020
2934a11
Add state.asyncWait_ and interpreter.strictAsync_
Webifi Nov 12, 2020
9e741a1
Expose runUntil and getStateStackSize methods
Webifi Nov 12, 2020
18c8543
Expose isPaused
Webifi Nov 12, 2020
04dec5c
Add ExpressionStatement before CallExpression
Webifi Nov 12, 2020
4834769
Change ExpressionStatement to EmptyStatement
Webifi Nov 12, 2020
d7a87a0
Add queueFunction method
Webifi Nov 12, 2020
8f98548
Wrap function calls in CallExpressionFunc_ node
Webifi Nov 14, 2020
e36756e
Executeimmediate functions in correct order
Webifi Nov 14, 2020
a513475
Fix index limit
Webifi Nov 14, 2020
32ca98d
No return
Webifi Nov 14, 2020
81c83d5
Rollback immediate call execution order
Webifi Nov 14, 2020
86bd209
Restart program on function queue
Webifi Nov 14, 2020
5911796
Fix bugs
Webifi Nov 14, 2020
151b898
Make pseudo fn callbacks use state machine
Webifi Nov 21, 2020
6a46a93
Merge pull request #1 from Webifi/dev2
Webifi Nov 21, 2020
b9e1b81
Remove unused exports
Webifi Nov 21, 2020
8a0ff21
Remove unused export
Webifi Nov 21, 2020
ca4f121
Rafactor NativeState to Callback
Webifi Nov 21, 2020
3f8aa11
Remove unnecessary callbackState_ = null
Webifi Nov 21, 2020
34f4f59
Use ternary operator
Webifi Nov 21, 2020
fb0fd76
Put pause back where it belongs
Webifi Nov 21, 2020
3bcc330
Move pause again
Webifi Nov 21, 2020
298fa89
Allow asyncFunctions to throw errors
Webifi Nov 21, 2020
fbfc8c0
Add .this(...) callback for queueFunction value
Webifi Nov 22, 2020
9be4aa0
Add catch/finally to callFunction/queueFunction
Webifi Nov 22, 2020
b4c8132
Don't call 'then' after 'catch'
Webifi Nov 22, 2020
8d42446
Fix typo
Webifi Nov 22, 2020
cd43aca
Simplify
Webifi Nov 22, 2020
170fc84
Rollback simplify
Webifi Nov 22, 2020
db7ee6c
Fix typo
Webifi Nov 22, 2020
2d8b51f
Allow catch to return value
Webifi Nov 22, 2020
bd7c067
Return proper value
Webifi Nov 22, 2020
f87d96c
Change where exception is thrown
Webifi Nov 22, 2020
46d9af9
Fix issue with async callbacks
Webifi Nov 23, 2020
e88a691
Refactor native catch, remove finally
Webifi Nov 23, 2020
fe24f3f
Cleanup
Webifi Nov 23, 2020
bba6491
Refactor callback catch
Webifi Nov 23, 2020
b322959
cleanup
Webifi Nov 23, 2020
8ea6fa5
Cleanup
Webifi Nov 23, 2020
392556e
Don't create state if not needed
Webifi Nov 23, 2020
d73e5df
Fix typos
Webifi Nov 23, 2020
2df2c2e
Cleanup
Webifi Nov 23, 2020
a9fafcb
Unify queued and immediate callback catch handlers
Webifi Nov 23, 2020
3c5a508
Allow queued call's then/catch to call-back
Webifi Nov 23, 2020
0da3a80
Allow chain of thens in callbacks
Webifi Nov 23, 2020
01d7355
fix result in then chain
Webifi Nov 23, 2020
2623269
Cleanup / comments
Webifi Nov 24, 2020
3d0499c
Fix case fallthrough
Webifi Nov 24, 2020
8acfcee
Fix throwing exception
Webifi Dec 2, 2020
79da820
Merge upstream changes
Webifi Sep 10, 2021
405d62d
Merge remote-tracking branch 'upstream/master'
Webifi Sep 10, 2021
cb7bf2c
Merge branch 'master' of https://github.com/NeilFraser/JS-Interpreter…
Webifi Sep 15, 2021
1502a58
Merge branch 'NeilFraser-master'
Webifi Sep 15, 2021
e714da8
Merge branch 'master' of https://github.com/NeilFraser/JS-Interpreter
Webifi Jun 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 266 additions & 3 deletions interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,59 @@ Interpreter.prototype.run = function() {
return this.paused_;
};

/**
* Queue a pseudo function for execution on next step
* @param {Interpreter.Object} func Interpreted function
* @param {Interpreter.Object} funcThis Interpreted Object to use as "this"
* @param {Interpreter.Object} var_args Interpreted Objects to pass as arguments
* @return {Interpreter.Callback} Object for running pseudo function callback
*/
Interpreter.prototype.callFunction = function (func, funcThis, var_args) {
var expNode = this.buildFunctionCaller_.apply(this, arguments);
return new Interpreter.Callback(expNode);
};

/**
* Queue a pseudo function for execution after all current instructions complete
* @param {Interpreter.Object} func Interpreted function
* @param {Interpreter.Object} funcThis Interpreted Object to use as "this"
* @param {Interpreter.Object} var_args Interpreted Objects to pass as arguments
* @return {Interpreter.Callback} Object for running pseudo function callback
*/
Interpreter.prototype.queueFunction = function (func, funcThis, var_args) {
var state = this.stateStack[0];
var expNode = this.buildFunctionCaller_.apply(this, arguments);
// Add function call to root Program state
state.node['body'].push(expNode);
state.done = false;
return new Interpreter.Callback(expNode, true); // Allows adding then/catch
};

/**
* Generate state objects for running pseudo function
* @param {Interpreter.Object} func Interpreted function
* @param {Interpreter.Object} funcThis Interpreted Object to use as "this"
* @param {Interpreter.Object} var_args Interpreted Objects to pass as arguments
* @return {nodeConstructor} node for running pseudo function
*/
Interpreter.prototype.buildFunctionCaller_ = function (func, funcThis, var_args) {
var thisInterpreter = this
var args = Array.prototype.slice.call(arguments, 2).map(function (arg) {
return arg instanceof Interpreter.Object ? arg : thisInterpreter.nativeToPseudo(arg)
});
// Create node for CallExpression with pre-embedded function and arguments
var scope = this.stateStack[this.stateStack.length - 1].scope; // This may be wrong
var ceNode = new this.nodeConstructor({options:{}});
ceNode['type'] = 'CallExpressionFunc_';
// Attach state settings to node, so we can retrieve them later.
// (Can only add a node to root program body.)
ceNode.funcThis_ = funcThis;
ceNode.func_ = func;
ceNode.arguments_ = args;
ceNode.scope_ = scope;
return ceNode;
};

/**
* Initialize the global object with buitin properties and functions.
* @param {!Interpreter.Object} globalObject Global object.
Expand Down Expand Up @@ -3142,6 +3195,48 @@ Interpreter.prototype.throwException = function(errorClass, opt_message) {
throw Interpreter.STEP_ERROR;
};

/**
* Return a Throwable object for use in native function return values
* @param {!Interpreter.Object|Interpreter.Value} errorClass Type of error
* (if message is provided) or the value to throw (if no message).
* @param {string=} opt_message Message being thrown.
*/
Interpreter.prototype.createThrowable = function(errorClass, opt_message) {
return new Interpreter.Throwable(errorClass, opt_message);
};

/**
* Handle result of native function call
* @param {Interpreter.State} state CallExpression state
* @param {!Interpreter.Scope} scope CallExpression scope.
* @param {Interpreter.Object|String|Number} value Values returned from native function
* @return {Interpreter.State} New callback state added, if any
*/
Interpreter.prototype.handleNativeResult_ = function(state, scope, value) {
if (value instanceof Interpreter.Callback) {
// We have a request for a pseudo function callback
value.pushState_(this, scope);
state.cb_ = value;
state.doneExec_ = false;
return value.state_;
} else if (value instanceof Interpreter.Throwable) {
// Result was an error
value.throw_(this);
} else {
// We have a final value
var cb = state.cb_;
if (cb) {
if (cb.stateless_) {
cb.value = value;
} else {
cb.state_.value = value;
}
}
state.value = value;
}
};


/**
* Unwind the stack to the innermost relevant enclosing TryStatement,
* For/ForIn/WhileStatement or Call/NewExpression. If this results in
Expand Down Expand Up @@ -3171,6 +3266,13 @@ Interpreter.prototype.unwind = function(type, value, label) {
throw Error('Unsynatctic break/continue not rejected by Acorn');
}
break;
case 'CallExpressionFunc_':
if (type === Interpreter.Completion.THROW && state.catch_) {
// Let stepCallExpressionFunc_ catch this throw
state.throw_ = value;
return;
}
continue;
case 'Program':
// Don't pop the stateStack.
// Leave the root scope on the tree in case the program is appended to.
Expand Down Expand Up @@ -3349,6 +3451,104 @@ Interpreter.Scope = function(parentScope, strict, object) {
this.object = object;
};

/**
* Class for allowing async function throws
* @param {!Interpreter.Object|Interpreter.Value} errorClass Type of error
* (if message is provided) or the value to throw (if no message).
* @param {string=} opt_message Message being thrown.
* @constructor
*/
Interpreter.Throwable = function(errorClass, opt_message) {
this.errorClass = errorClass;
this.opt_message = opt_message;
};

/**
* Class for allowing async function throws
* @param {Interpreter} interpreter Interpreter instance
* @constructor
*/
Interpreter.Throwable.prototype.throw_ = function(interpreter) {
interpreter.throwException(this.errorClass, this.opt_message);
};


/**
* Class for tracking native function states.
* @param {nodeConstructor} callFnState State that's being tracked
* @param {Boolean=} opt_queued Will this be queued or immediate
* @constructor
*/
Interpreter.Callback = function(callFnNode, opt_queued) {
this.node_ = callFnNode;
this.handlers_ = [];
this.catch_ = null;
this.node_.cb_ = this; // Attach callback to node so we can retrieve it later
this.queued_ = opt_queued;
};

/**
* Add handler for return of pseudo function's value
* @param {Function} handler Function to handle value
* @return {Interpreter.Callback} State object for running pseudo function
*/
Interpreter.Callback.prototype['then'] = function(handler) {
if (typeof handler !== 'function') {
throw new Error('Expected function for "then" handler');
}
this.handlers_.push(handler);
return this;
};

/**
* Add exception catch handler for return of pseudo function's call
* @param {Function} handler Function to handle value
* @return {Interpreter.Callback} State object for running pseudo function
*/
Interpreter.Callback.prototype['catch'] = function(handler) {
if (typeof handler !== 'function') {
throw new Error('Expected function for "catch" handler');
}
if (this.catch_) {
throw new Error('"catch" already defined');
}
// this.node_.skipThrow_ = true;
this.catch_ = handler;
return this;
};

/**
* Add pseudo function callback to statStack
* @param {Interpreter} interpreter Interpreter instance
* @param {Interpreter.Scope} scope Function's scope.
*/
Interpreter.Callback.prototype.pushState_ = function(interpreter, scope) {
if (this.stateless_) return; // Only uses handler_
if (!this.state_) {
this.state_ = new Interpreter.State(this.node_, scope);
}
interpreter.stateStack.push(this.state_);
};

/**
* Handle next step for native function callback
* @param {Interpreter.Object} value Object containing pseudo function callback's result.
* @param {Function} asyncCallback Function for asyncFunc callback
*/
Interpreter.Callback.prototype.doNext_ = function(asyncCallback) {
var handler = this.handlers_.shift();
if (handler) {
this.force_ = this.handlers_.length; // Continue to run if we have more
return handler(this.stateless_ ? this.value : this.state_.value, asyncCallback);
}
if (this.stateless_) return; // Callback does not have a state value
if(asyncCallback) {
asyncCallback(this.state_.value)
} else {
return this.state_.value
}
};

/**
* Class for an object.
* @param {Interpreter.Object} proto Prototype object or null.
Expand Down Expand Up @@ -3712,6 +3912,11 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
}
state.doneArgs_ = true;
}
if (state.cb_ && state.cb_.force_) {
// Callback wants to run again
state.doneExec_ = false;
state.cb_.force_ = false;
}
if (!state.doneExec_) {
state.doneExec_ = true;
if (!(func instanceof Interpreter.Object)) {
Expand Down Expand Up @@ -3774,19 +3979,27 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
if (!state.scope.strict) {
state.funcThis_ = this.boxThis_(state.funcThis_);
}
state.value = func.nativeFunc.apply(state.funcThis_, state.arguments_);
this.handleNativeResult_(state, scope, state.cb_
? state.cb_.doNext_()
: func.nativeFunc.apply(state.funcThis_, state.arguments_));
return;
} else if (func.asyncFunc) {
var thisInterpreter = this;
var callback = function(value) {
state.value = value;
thisInterpreter.paused_ = false;
thisInterpreter.handleNativeResult_(state, scope, value);
};
this.paused_ = true;
if (state.cb_) {
// Do next step of native async func
state.cb_.doNext_(callback);
return;
}
// Force the argument lengths to match, then append the callback.
var argLength = func.asyncFunc.length - 1;
var argsWithCallback = state.arguments_.concat(
new Array(argLength)).slice(0, argLength);
argsWithCallback.push(callback);
this.paused_ = true;
if (!state.scope.strict) {
state.funcThis_ = this.boxThis_(state.funcThis_);
}
Expand Down Expand Up @@ -3887,6 +4100,53 @@ Interpreter.prototype['stepEvalProgram_'] = function(stack, state, node) {
stack[stack.length - 1].value = this.value;
};

Interpreter.prototype['stepCallExpressionFunc_'] = function(stack, state, node) {
var cb = node.cb_;
var queued = cb.queued_;
if (!state.done_) {
state.done_ = true;
var ceNode = new this.nodeConstructor({options:{}});
ceNode['type'] = 'CallExpression';
var ceState = new Interpreter.State(ceNode, node.scope_ || state.scope);
ceState.doneCallee_ = true;
ceState.funcThis_ = node.funcThis_;
ceState.func_ = node.func_;
ceState.doneArgs_ = true;
ceState.arguments_ = node.arguments_;
state.catch_ = cb.catch_;
return ceState;
}
if (queued && cb.handlers_.length && !state.throw_) {
// Called via queued callback
// Callback a 'then' handler now (non-queued are called in setCallExpression)
var handler = cb.handlers_.shift();
this.handleNativeResult_(state, node.funcThis_, handler(state.value));
return;
}
if (state.catch_ && state.throw_) {
// Callback a 'catch' handler
if (queued) {
// Called via queued callback
// Just call the callback directly
this.handleNativeResult_(state, node.funcThis_, state.catch_(state.throw_));
state.catch_ = null;
return;
} else {
// Immediate callback from CallExpression
// Modify existing Callback object to execute catch steps
cb.stateless_ = true; // Callback can only use its handler for return value
cb.handlers_ = [state.catch_];
cb.value = state.throw_; // Set stateless value to pass to handler
cb.force_ = true; // Force callback to run again
}
}
stack.pop();
if (this.stateStack.length === 1) {
// Save value as return value if we were the last to be executed
this.value = state.value;
}
};

Interpreter.prototype['stepExpressionStatement'] = function(stack, state, node) {
if (!state.done_) {
this.value = undefined;
Expand Down Expand Up @@ -4536,3 +4796,6 @@ Interpreter.prototype['getGlobalScope'] = Interpreter.prototype.getGlobalScope;
Interpreter.prototype['getStateStack'] = Interpreter.prototype.getStateStack;
Interpreter.prototype['setStateStack'] = Interpreter.prototype.setStateStack;
Interpreter['VALUE_IN_DESCRIPTOR'] = Interpreter.VALUE_IN_DESCRIPTOR;
Interpreter.prototype['callFunction'] = Interpreter.prototype.callFunction;
Interpreter.prototype['queueFunction'] = Interpreter.prototype.queueFunction;
Interpreter.prototype['createThrowable'] = Interpreter.prototype.createThrowable;