mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-11 02:03:58 +01:00
3933 lines
125 KiB
JavaScript
3933 lines
125 KiB
JavaScript
import * as acorn from "acorn";
|
|
/**
|
|
* @license
|
|
* JavaScript Interpreter
|
|
*
|
|
* Copyright 2013 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Interpreting JavaScript in JavaScript.
|
|
* @author fraser@google.com (Neil Fraser)
|
|
*/
|
|
("use strict");
|
|
|
|
/**
|
|
* Create a new interpreter.
|
|
* @param {string|!Object} code Raw JavaScript text or AST.
|
|
* @param {Function=} opt_initFunc Optional initialization function. Used to
|
|
* define APIs. When called it is passed the interpreter object and the
|
|
* global scope object.
|
|
* @param {Number} Bitburner-specific number used for determining exception line numbers
|
|
* @constructor
|
|
*/
|
|
var Interpreter = function (code, opt_initFunc, lineOffset = 0) {
|
|
this.sourceCode = code;
|
|
this.sourceCodeLineOffset = lineOffset;
|
|
if (typeof code === "string") {
|
|
code = acorn.parse(code, Interpreter.PARSE_OPTIONS);
|
|
}
|
|
this.ast = code;
|
|
this.initFunc_ = opt_initFunc;
|
|
this.paused_ = false;
|
|
this.polyfills_ = [];
|
|
// Unique identifier for native functions. Used in serialization.
|
|
this.functionCounter_ = 0;
|
|
// Map node types to our step function names; a property lookup is faster
|
|
// than string concatenation with "step" prefix.
|
|
this.stepFunctions_ = Object.create(null);
|
|
var stepMatch = /^step([A-Z]\w*)$/;
|
|
var m;
|
|
for (var methodName in this) {
|
|
if (typeof this[methodName] === "function" && (m = methodName.match(stepMatch))) {
|
|
this.stepFunctions_[m[1]] = this[methodName].bind(this);
|
|
}
|
|
}
|
|
// Create and initialize the global scope.
|
|
this.global = this.createScope(this.ast, null);
|
|
// Run the polyfills.
|
|
this.ast = acorn.parse(this.polyfills_.join("\n"), Interpreter.PARSE_OPTIONS);
|
|
this.polyfills_ = undefined; // Allow polyfill strings to garbage collect.
|
|
this.stripLocations_(this.ast, undefined, undefined);
|
|
var state = new Interpreter.State(this.ast, this.global);
|
|
state.done = false;
|
|
this.stateStack = [state];
|
|
this.run();
|
|
this.value = undefined;
|
|
// Point at the main program.
|
|
this.ast = code;
|
|
var state = new Interpreter.State(this.ast, this.global);
|
|
state.done = false;
|
|
this.stateStack.length = 0;
|
|
this.stateStack[0] = state;
|
|
// Get a handle on Acorn's node_t object. It's tricky to access.
|
|
this.nodeConstructor = state.node.constructor;
|
|
// Preserve publicly properties from being pruned/renamed by JS compilers.
|
|
// Add others as needed.
|
|
this["stateStack"] = this.stateStack;
|
|
};
|
|
|
|
/**
|
|
* @const {!Object} Configuration used for all Acorn parsing.
|
|
*/
|
|
Interpreter.PARSE_OPTIONS = {
|
|
ecmaVersion: 5,
|
|
locations: true,
|
|
};
|
|
|
|
/**
|
|
* Property descriptor of readonly properties.
|
|
*/
|
|
Interpreter.READONLY_DESCRIPTOR = {
|
|
configurable: true,
|
|
enumerable: true,
|
|
writable: false,
|
|
};
|
|
|
|
/**
|
|
* Property descriptor of non-enumerable properties.
|
|
*/
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR = {
|
|
configurable: true,
|
|
enumerable: false,
|
|
writable: true,
|
|
};
|
|
|
|
/**
|
|
* Property descriptor of readonly, non-enumerable properties.
|
|
*/
|
|
Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR = {
|
|
configurable: true,
|
|
enumerable: false,
|
|
writable: false,
|
|
};
|
|
|
|
/**
|
|
* Property descriptor of variables.
|
|
*/
|
|
Interpreter.VARIABLE_DESCRIPTOR = {
|
|
configurable: false,
|
|
enumerable: true,
|
|
writable: true,
|
|
};
|
|
|
|
/**
|
|
* Unique symbol for indicating that a step has encountered an error, has
|
|
* added it to the stack, and will be thrown within the user's program.
|
|
* When STEP_ERROR is thrown in the JS-Interpreter, the error can be ignored.
|
|
*/
|
|
Interpreter.STEP_ERROR = {};
|
|
|
|
/**
|
|
* Unique symbol for indicating that a reference is a variable on the scope,
|
|
* not an object property.
|
|
*/
|
|
Interpreter.SCOPE_REFERENCE = {};
|
|
|
|
/**
|
|
* Unique symbol for indicating, when used as the value of the value
|
|
* parameter in calls to setProperty and friends, that the value
|
|
* should be taken from the property descriptor instead.
|
|
*/
|
|
Interpreter.VALUE_IN_DESCRIPTOR = {};
|
|
|
|
/**
|
|
* For cycle detection in array to string and error conversion;
|
|
* see spec bug github.com/tc39/ecma262/issues/289
|
|
* Since this is for atomic actions only, it can be a class property.
|
|
*/
|
|
Interpreter.toStringCycles_ = [];
|
|
|
|
/**
|
|
* Determine error/exception line number in Bitburner source code
|
|
* @param {Object} AST Node that causes Error/Exception
|
|
*/
|
|
Interpreter.prototype.getErrorLineNumber = function (node) {
|
|
var code = this.sourceCode;
|
|
if (node == null || node.start == null) {
|
|
return NaN;
|
|
}
|
|
try {
|
|
code = code.substring(0, node.start);
|
|
return (code.match(/\n/g) || []).length + 1 - this.sourceCodeLineOffset;
|
|
} catch (e) {
|
|
return NaN;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generate the appropriate line number error message for Bitburner
|
|
* @param {Number} lineNumber
|
|
*/
|
|
Interpreter.prototype.getErrorLineNumberMessage = function (lineNumber) {
|
|
if (isNaN(lineNumber)) {
|
|
return " (Unknown line number)";
|
|
} else if (lineNumber <= 0) {
|
|
return " (Error occurred in an imported function)";
|
|
} else {
|
|
return (
|
|
" (Line Number " +
|
|
lineNumber +
|
|
". This line number is probably incorrect " +
|
|
"if your script is importing any functions. This is being worked on)"
|
|
);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add more code to the interpreter.
|
|
* @param {string|!Object} code Raw JavaScript text or AST.
|
|
*/
|
|
Interpreter.prototype.appendCode = function (code) {
|
|
var state = this.stateStack[0];
|
|
if (!state || state.node["type"] !== "Program") {
|
|
throw Error("Expecting original AST to start with a Program node.");
|
|
}
|
|
if (typeof code === "string") {
|
|
code = acorn.parse(code, Interpreter.PARSE_OPTIONS);
|
|
}
|
|
if (!code || code["type"] !== "Program") {
|
|
throw Error("Expecting new AST to start with a Program node.");
|
|
}
|
|
this.populateScope_(code, state.scope);
|
|
// Append the new program to the old one.
|
|
for (var i = 0, node; (node = code["body"][i]); i++) {
|
|
state.node["body"].push(node);
|
|
}
|
|
state.done = false;
|
|
};
|
|
|
|
/**
|
|
* Execute one step of the interpreter.
|
|
* @return {boolean} True if a step was executed, false if no more instructions.
|
|
*/
|
|
Interpreter.prototype.step = function () {
|
|
var stack = this.stateStack;
|
|
var state = stack[stack.length - 1];
|
|
if (!state) {
|
|
return false;
|
|
}
|
|
var node = state.node,
|
|
type = node["type"];
|
|
if (type === "Program" && state.done) {
|
|
return false;
|
|
} else if (this.paused_) {
|
|
return true;
|
|
}
|
|
try {
|
|
var nextState = this.stepFunctions_[type](stack, state, node);
|
|
} catch (e) {
|
|
// Eat any step errors. They have been thrown on the stack.
|
|
if (e !== Interpreter.STEP_ERROR) {
|
|
// Uh oh. This is a real error in the JS-Interpreter. Rethrow.
|
|
throw e;
|
|
}
|
|
}
|
|
if (nextState) {
|
|
stack.push(nextState);
|
|
}
|
|
if (!node["end"]) {
|
|
// This is polyfill code. Keep executing until we arrive at user code.
|
|
return this.step();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Execute the interpreter to program completion. Vulnerable to infinite loops.
|
|
* @return {boolean} True if a execution is asynchronously blocked,
|
|
* false if no more instructions.
|
|
*/
|
|
Interpreter.prototype.run = function () {
|
|
while (!this.paused_ && this.step()) {}
|
|
return this.paused_;
|
|
};
|
|
|
|
/**
|
|
* Initialize the global scope with buitin properties and functions.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initGlobalScope = function (scope) {
|
|
// Initialize uneditable global properties.
|
|
this.setProperty(scope, "NaN", NaN, Interpreter.READONLY_DESCRIPTOR);
|
|
this.setProperty(scope, "Infinity", Infinity, Interpreter.READONLY_DESCRIPTOR);
|
|
this.setProperty(scope, "undefined", undefined, Interpreter.READONLY_DESCRIPTOR);
|
|
this.setProperty(scope, "window", scope, Interpreter.READONLY_DESCRIPTOR);
|
|
this.setProperty(scope, "this", scope, Interpreter.READONLY_DESCRIPTOR);
|
|
this.setProperty(scope, "self", scope); // Editable.
|
|
|
|
// Create the objects which will become Object.prototype and
|
|
// Function.prototype, which are needed to bootstrap everything else.
|
|
this.OBJECT_PROTO = new Interpreter.Object(null);
|
|
this.FUNCTION_PROTO = new Interpreter.Object(this.OBJECT_PROTO);
|
|
// Initialize global objects.
|
|
this.initFunction(scope);
|
|
this.initObject(scope);
|
|
// Unable to set scope's parent prior (OBJECT did not exist).
|
|
// Note that in a browser this would be 'Window', whereas in Node.js it would
|
|
// be 'Object'. This interpreter is closer to Node in that it has no DOM.
|
|
scope.proto = this.OBJECT_PROTO;
|
|
this.setProperty(scope, "constructor", this.OBJECT, Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
this.initArray(scope);
|
|
this.initString(scope);
|
|
this.initBoolean(scope);
|
|
this.initNumber(scope);
|
|
this.initDate(scope);
|
|
this.initRegExp(scope);
|
|
this.initError(scope);
|
|
this.initMath(scope);
|
|
this.initJSON(scope);
|
|
|
|
// Initialize global functions.
|
|
var thisInterpreter = this;
|
|
var func = this.createNativeFunction(function (x) {
|
|
throw EvalError("Can't happen");
|
|
}, false);
|
|
func.eval = true;
|
|
this.setProperty(scope, "eval", func);
|
|
|
|
this.setProperty(scope, "parseInt", this.createNativeFunction(parseInt, false));
|
|
this.setProperty(scope, "parseFloat", this.createNativeFunction(parseFloat, false));
|
|
|
|
this.setProperty(scope, "isNaN", this.createNativeFunction(isNaN, false));
|
|
|
|
this.setProperty(scope, "isFinite", this.createNativeFunction(isFinite, false));
|
|
|
|
var strFunctions = [
|
|
[escape, "escape"],
|
|
[unescape, "unescape"],
|
|
[decodeURI, "decodeURI"],
|
|
[decodeURIComponent, "decodeURIComponent"],
|
|
[encodeURI, "encodeURI"],
|
|
[encodeURIComponent, "encodeURIComponent"],
|
|
];
|
|
for (var i = 0; i < strFunctions.length; i++) {
|
|
var wrapper = (function (nativeFunc) {
|
|
return function (str) {
|
|
try {
|
|
return nativeFunc(str);
|
|
} catch (e) {
|
|
// decodeURI('%xy') will throw an error. Catch and rethrow.
|
|
thisInterpreter.throwException(thisInterpreter.URI_ERROR, e.message);
|
|
}
|
|
};
|
|
})(strFunctions[i][0]);
|
|
this.setProperty(
|
|
scope,
|
|
strFunctions[i][1],
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
}
|
|
// Preserve publicly properties from being pruned/renamed by JS compilers.
|
|
// Add others as needed.
|
|
this["OBJECT"] = this.OBJECT;
|
|
this["OBJECT_PROTO"] = this.OBJECT_PROTO;
|
|
this["FUNCTION"] = this.FUNCTION;
|
|
this["FUNCTION_PROTO"] = this.FUNCTION_PROTO;
|
|
this["ARRAY"] = this.ARRAY;
|
|
this["ARRAY_PROTO"] = this.ARRAY_PROTO;
|
|
this["REGEXP"] = this.REGEXP;
|
|
this["REGEXP_PROTO"] = this.REGEXP_PROTO;
|
|
this["DATE"] = this.DATE;
|
|
this["DATE_PROTO"] = this.DATE_PROTO;
|
|
// The following properties are obsolete. Do not use.
|
|
this["UNDEFINED"] = undefined;
|
|
this["NULL"] = null;
|
|
this["NAN"] = NaN;
|
|
this["TRUE"] = true;
|
|
this["FALSE"] = false;
|
|
this["STRING_EMPTY"] = "";
|
|
this["NUMBER_ZERO"] = 0;
|
|
this["NUMBER_ONE"] = 1;
|
|
|
|
// Run any user-provided initialization.
|
|
if (this.initFunc_) {
|
|
this.initFunc_(this, scope);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialize the Function class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initFunction = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
var identifierRegexp = /^[A-Za-z_$][\w$]*$/;
|
|
// Function constructor.
|
|
wrapper = function (var_args) {
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new Function().
|
|
var newFunc = this;
|
|
} else {
|
|
// Called as Function().
|
|
var newFunc = thisInterpreter.createObjectProto(thisInterpreter.FUNCTION_PROTO);
|
|
}
|
|
if (arguments.length) {
|
|
var code = String(arguments[arguments.length - 1]);
|
|
} else {
|
|
var code = "";
|
|
}
|
|
var argsStr = Array.prototype.slice.call(arguments, 0, -1).join(",").trim();
|
|
if (argsStr) {
|
|
var args = argsStr.split(/\s*,\s*/);
|
|
for (var i = 0; i < args.length; i++) {
|
|
var name = args[i];
|
|
if (!identifierRegexp.test(name)) {
|
|
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, "Invalid function argument: " + name);
|
|
}
|
|
}
|
|
argsStr = args.join(", ");
|
|
}
|
|
// Interestingly, the scope for constructed functions is the global scope,
|
|
// even if they were constructed in some other scope.
|
|
newFunc.parentScope = thisInterpreter.global;
|
|
// Acorn needs to parse code in the context of a function or else 'return'
|
|
// statements will be syntax errors.
|
|
try {
|
|
var ast = acorn.parse("(function(" + argsStr + ") {" + code + "})", Interpreter.PARSE_OPTIONS);
|
|
} catch (e) {
|
|
// Acorn threw a SyntaxError. Rethrow as a trappable error.
|
|
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, "Invalid code: " + e.message);
|
|
}
|
|
if (ast["body"].length !== 1) {
|
|
// Function('a', 'return a + 6;}; {alert(1);');
|
|
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, "Invalid code in function body.");
|
|
}
|
|
newFunc.node = ast["body"][0]["expression"];
|
|
thisInterpreter.setProperty(newFunc, "length", newFunc.node["length"], Interpreter.READONLY_DESCRIPTOR);
|
|
return newFunc;
|
|
};
|
|
wrapper.id = this.functionCounter_++;
|
|
this.FUNCTION = this.createObjectProto(this.FUNCTION_PROTO);
|
|
|
|
this.setProperty(scope, "Function", this.FUNCTION);
|
|
// Manually setup type and prototype because createObj doesn't recognize
|
|
// this object as a function (this.FUNCTION did not exist).
|
|
this.setProperty(this.FUNCTION, "prototype", this.FUNCTION_PROTO);
|
|
this.FUNCTION.nativeFunc = wrapper;
|
|
|
|
// Configure Function.prototype.
|
|
this.setProperty(this.FUNCTION_PROTO, "constructor", this.FUNCTION, Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
this.FUNCTION_PROTO.nativeFunc = function () {};
|
|
this.FUNCTION_PROTO.nativeFunc.id = this.functionCounter_++;
|
|
this.setProperty(this.FUNCTION_PROTO, "length", 0, Interpreter.READONLY_DESCRIPTOR);
|
|
|
|
var boxThis = function (value) {
|
|
// In non-strict mode 'this' must be an object.
|
|
if ((!value || !value.isObject) && !thisInterpreter.getScope().strict) {
|
|
if (value === undefined || value === null) {
|
|
// 'Undefined' and 'null' are changed to global object.
|
|
value = thisInterpreter.global;
|
|
} else {
|
|
// Primitives must be boxed in non-strict mode.
|
|
var box = thisInterpreter.createObjectProto(thisInterpreter.getPrototype(value));
|
|
box.data = value;
|
|
value = box;
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
|
|
wrapper = function (thisArg, args) {
|
|
var state = thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1];
|
|
// Rewrite the current 'CallExpression' to apply a different function.
|
|
state.func_ = this;
|
|
// Assign the 'this' object.
|
|
state.funcThis_ = boxThis(thisArg);
|
|
// Bind any provided arguments.
|
|
state.arguments_ = [];
|
|
if (args !== null && args !== undefined) {
|
|
if (args.isObject) {
|
|
state.arguments_ = thisInterpreter.arrayPseudoToNative(args);
|
|
} else {
|
|
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, "CreateListFromArrayLike called on non-object");
|
|
}
|
|
}
|
|
state.doneExec_ = false;
|
|
};
|
|
this.setNativeFunctionPrototype(this.FUNCTION, "apply", wrapper);
|
|
|
|
wrapper = function (thisArg /*, var_args */) {
|
|
var state = thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1];
|
|
// Rewrite the current 'CallExpression' to call a different function.
|
|
state.func_ = this;
|
|
// Assign the 'this' object.
|
|
state.funcThis_ = boxThis(thisArg);
|
|
// Bind any provided arguments.
|
|
state.arguments_ = [];
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
state.arguments_.push(arguments[i]);
|
|
}
|
|
state.doneExec_ = false;
|
|
};
|
|
this.setNativeFunctionPrototype(this.FUNCTION, "call", wrapper);
|
|
|
|
this.polyfills_.push(
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
|
|
"Object.defineProperty(Function.prototype, 'bind',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(oThis) {",
|
|
"if (typeof this !== 'function') {",
|
|
"throw TypeError('What is trying to be bound is not callable');",
|
|
"}",
|
|
"var aArgs = Array.prototype.slice.call(arguments, 1),",
|
|
"fToBind = this,",
|
|
"fNOP = function() {},",
|
|
"fBound = function() {",
|
|
"return fToBind.apply(this instanceof fNOP",
|
|
"? this",
|
|
": oThis,",
|
|
"aArgs.concat(Array.prototype.slice.call(arguments)));",
|
|
"};",
|
|
"if (this.prototype) {",
|
|
"fNOP.prototype = this.prototype;",
|
|
"}",
|
|
"fBound.prototype = new fNOP();",
|
|
"return fBound;",
|
|
"}",
|
|
"});",
|
|
"",
|
|
);
|
|
|
|
// Function has no parent to inherit from, so it needs its own mandatory
|
|
// toString and valueOf functions.
|
|
wrapper = function () {
|
|
return this.toString();
|
|
};
|
|
this.setNativeFunctionPrototype(this.FUNCTION, "toString", wrapper);
|
|
this.setProperty(
|
|
this.FUNCTION,
|
|
"toString",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
wrapper = function () {
|
|
return this.valueOf();
|
|
};
|
|
this.setNativeFunctionPrototype(this.FUNCTION, "valueOf", wrapper);
|
|
this.setProperty(
|
|
this.FUNCTION,
|
|
"valueOf",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Initialize the Object class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initObject = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
// Object constructor.
|
|
wrapper = function (value) {
|
|
if (value === undefined || value === null) {
|
|
// Create a new object.
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new Object().
|
|
return this;
|
|
} else {
|
|
// Called as Object().
|
|
return thisInterpreter.createObjectProto(thisInterpreter.OBJECT_PROTO);
|
|
}
|
|
}
|
|
if (!value.isObject) {
|
|
// Wrap the value as an object.
|
|
var box = thisInterpreter.createObjectProto(thisInterpreter.getPrototype(value));
|
|
box.data = value;
|
|
return box;
|
|
}
|
|
// Return the provided object.
|
|
return value;
|
|
};
|
|
this.OBJECT = this.createNativeFunction(wrapper, true);
|
|
// Throw away the created prototype and use the root prototype.
|
|
this.setProperty(this.OBJECT, "prototype", this.OBJECT_PROTO);
|
|
this.setProperty(this.OBJECT_PROTO, "constructor", this.OBJECT, Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
this.setProperty(scope, "Object", this.OBJECT);
|
|
|
|
/**
|
|
* Checks if the provided value is null or undefined.
|
|
* If so, then throw an error in the call stack.
|
|
* @param {Interpreter.Value} value Value to check.
|
|
*/
|
|
var throwIfNullUndefined = function (value) {
|
|
if (value === undefined || value === null) {
|
|
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, "Cannot convert '" + value + "' to object");
|
|
}
|
|
};
|
|
|
|
// Static methods on Object.
|
|
wrapper = function (obj) {
|
|
throwIfNullUndefined(obj);
|
|
var props = obj.isObject ? obj.properties : obj;
|
|
return thisInterpreter.arrayNativeToPseudo(Object.getOwnPropertyNames(props));
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"getOwnPropertyNames",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
wrapper = function (obj) {
|
|
throwIfNullUndefined(obj);
|
|
if (obj.isObject) {
|
|
obj = obj.properties;
|
|
}
|
|
return thisInterpreter.arrayNativeToPseudo(Object.keys(obj));
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"keys",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
wrapper = function (proto) {
|
|
// Support for the second argument is the responsibility of a polyfill.
|
|
if (proto === null) {
|
|
return thisInterpreter.createObjectProto(null);
|
|
}
|
|
if (proto === undefined || !proto.isObject) {
|
|
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, "Object prototype may only be an Object or null");
|
|
}
|
|
return thisInterpreter.createObjectProto(proto);
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"create",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
// Add a polyfill to handle create's second argument.
|
|
this.polyfills_.push(
|
|
"(function() {",
|
|
"var create_ = Object.create;",
|
|
"Object.create = function(proto, props) {",
|
|
"var obj = create_(proto);",
|
|
"props && Object.defineProperties(obj, props);",
|
|
"return obj;",
|
|
"};",
|
|
"})();",
|
|
"",
|
|
);
|
|
|
|
wrapper = function (obj, prop, descriptor) {
|
|
prop = String(prop);
|
|
if (!obj || !obj.isObject) {
|
|
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, "Object.defineProperty called on non-object");
|
|
}
|
|
if (!descriptor || !descriptor.isObject) {
|
|
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, "Property description must be an object");
|
|
}
|
|
if (!obj.properties[prop] && obj.preventExtensions) {
|
|
thisInterpreter.throwException(
|
|
thisInterpreter.TYPE_ERROR,
|
|
"Can't define property '" + prop + "', object is not extensible",
|
|
);
|
|
}
|
|
// The polyfill guarantees no inheritance and no getter functions.
|
|
// Therefore the descriptor properties map is the native object needed.
|
|
thisInterpreter.setProperty(obj, prop, Interpreter.VALUE_IN_DESCRIPTOR, descriptor.properties);
|
|
return obj;
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"defineProperty",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
this.polyfills_.push(
|
|
// Flatten the descriptor to remove any inheritance or getter functions.
|
|
"(function() {",
|
|
"var defineProperty_ = Object.defineProperty;",
|
|
"Object.defineProperty = function(obj, prop, d1) {",
|
|
"var d2 = {};",
|
|
"if ('configurable' in d1) d2.configurable = d1.configurable;",
|
|
"if ('enumerable' in d1) d2.enumerable = d1.enumerable;",
|
|
"if ('writable' in d1) d2.writable = d1.writable;",
|
|
"if ('value' in d1) d2.value = d1.value;",
|
|
"if ('get' in d1) d2.get = d1.get;",
|
|
"if ('set' in d1) d2.set = d1.set;",
|
|
"return defineProperty_(obj, prop, d2);",
|
|
"};",
|
|
"})();",
|
|
|
|
"Object.defineProperty(Object, 'defineProperties',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(obj, props) {",
|
|
"var keys = Object.keys(props);",
|
|
"for (var i = 0; i < keys.length; i++) {",
|
|
"Object.defineProperty(obj, keys[i], props[keys[i]]);",
|
|
"}",
|
|
"return obj;",
|
|
"}",
|
|
"});",
|
|
"",
|
|
);
|
|
|
|
wrapper = function (obj, prop) {
|
|
if (!obj || !obj.isObject) {
|
|
thisInterpreter.throwException(
|
|
thisInterpreter.TYPE_ERROR,
|
|
"Object.getOwnPropertyDescriptor called on non-object",
|
|
);
|
|
}
|
|
prop = String(prop);
|
|
if (!(prop in obj.properties)) {
|
|
return undefined;
|
|
}
|
|
var descriptor = Object.getOwnPropertyDescriptor(obj.properties, prop);
|
|
var getter = obj.getter[prop];
|
|
var setter = obj.setter[prop];
|
|
|
|
if (getter || setter) {
|
|
descriptor.get = getter;
|
|
descriptor.set = setter;
|
|
delete descriptor.value;
|
|
delete descriptor.writable;
|
|
}
|
|
// Preserve value, but remove it for the nativeToPseudo call.
|
|
var value = descriptor.value;
|
|
var hasValue = "value" in descriptor;
|
|
delete descriptor.value;
|
|
var pseudoDescriptor = thisInterpreter.nativeToPseudo(descriptor);
|
|
if (hasValue) {
|
|
thisInterpreter.setProperty(pseudoDescriptor, "value", value);
|
|
}
|
|
return pseudoDescriptor;
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"getOwnPropertyDescriptor",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
wrapper = function (obj) {
|
|
throwIfNullUndefined(obj);
|
|
return thisInterpreter.getPrototype(obj);
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"getPrototypeOf",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
wrapper = function (obj) {
|
|
return Boolean(obj) && !obj.preventExtensions;
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"isExtensible",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
wrapper = function (obj) {
|
|
if (obj && obj.isObject) {
|
|
obj.preventExtensions = true;
|
|
}
|
|
return obj;
|
|
};
|
|
this.setProperty(
|
|
this.OBJECT,
|
|
"preventExtensions",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
// Instance methods on Object.
|
|
this.setNativeFunctionPrototype(this.OBJECT, "toString", Interpreter.Object.prototype.toString);
|
|
this.setNativeFunctionPrototype(this.OBJECT, "toLocaleString", Interpreter.Object.prototype.toString);
|
|
this.setNativeFunctionPrototype(this.OBJECT, "valueOf", Interpreter.Object.prototype.valueOf);
|
|
|
|
wrapper = function (prop) {
|
|
throwIfNullUndefined(this);
|
|
if (!this.isObject) {
|
|
return this.hasOwnProperty(prop);
|
|
}
|
|
return String(prop) in this.properties;
|
|
};
|
|
this.setNativeFunctionPrototype(this.OBJECT, "hasOwnProperty", wrapper);
|
|
|
|
wrapper = function (prop) {
|
|
throwIfNullUndefined(this);
|
|
if (!this.isObject) {
|
|
return this.propertyIsEnumerable(prop);
|
|
}
|
|
return Object.prototype.propertyIsEnumerable.call(this.properties, prop);
|
|
};
|
|
this.setNativeFunctionPrototype(this.OBJECT, "propertyIsEnumerable", wrapper);
|
|
|
|
wrapper = function (obj) {
|
|
while (true) {
|
|
// Note, circular loops shouldn't be possible.
|
|
obj = thisInterpreter.getPrototype(obj);
|
|
if (!obj) {
|
|
// No parent; reached the top.
|
|
return false;
|
|
}
|
|
if (obj === this) {
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
this.setNativeFunctionPrototype(this.OBJECT, "isPrototypeOf", wrapper);
|
|
};
|
|
|
|
/**
|
|
* Initialize the Array class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initArray = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
// Array constructor.
|
|
wrapper = function (var_args) {
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new Array().
|
|
var newArray = this;
|
|
} else {
|
|
// Called as Array().
|
|
var newArray = thisInterpreter.createObjectProto(thisInterpreter.ARRAY_PROTO);
|
|
}
|
|
var first = arguments[0];
|
|
if (arguments.length === 1 && typeof first === "number") {
|
|
if (isNaN(Interpreter.legalArrayLength(first))) {
|
|
thisInterpreter.throwException(thisInterpreter.RANGE_ERROR, "Invalid array length");
|
|
}
|
|
newArray.properties.length = first;
|
|
} else {
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
newArray.properties[i] = arguments[i];
|
|
}
|
|
newArray.properties.length = i;
|
|
}
|
|
return newArray;
|
|
};
|
|
this.ARRAY = this.createNativeFunction(wrapper, true);
|
|
this.ARRAY_PROTO = this.ARRAY.properties["prototype"];
|
|
this.setProperty(scope, "Array", this.ARRAY);
|
|
|
|
// Static methods on Array.
|
|
wrapper = function (obj) {
|
|
return obj && obj.class === "Array";
|
|
};
|
|
this.setProperty(
|
|
this.ARRAY,
|
|
"isArray",
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
// Instance methods on Array.
|
|
wrapper = function () {
|
|
return Array.prototype.pop.call(this.properties);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "pop", wrapper);
|
|
|
|
wrapper = function (var_args) {
|
|
return Array.prototype.push.apply(this.properties, arguments);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "push", wrapper);
|
|
|
|
wrapper = function () {
|
|
return Array.prototype.shift.call(this.properties);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "shift", wrapper);
|
|
|
|
wrapper = function (var_args) {
|
|
return Array.prototype.unshift.apply(this.properties, arguments);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "unshift", wrapper);
|
|
|
|
wrapper = function () {
|
|
Array.prototype.reverse.call(this.properties);
|
|
return this;
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "reverse", wrapper);
|
|
|
|
wrapper = function (index, howmany /*, var_args*/) {
|
|
var list = Array.prototype.splice.apply(this.properties, arguments);
|
|
return thisInterpreter.arrayNativeToPseudo(list);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "splice", wrapper);
|
|
|
|
wrapper = function (opt_begin, opt_end) {
|
|
var list = Array.prototype.slice.call(this.properties, opt_begin, opt_end);
|
|
return thisInterpreter.arrayNativeToPseudo(list);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "slice", wrapper);
|
|
|
|
wrapper = function (opt_separator) {
|
|
return Array.prototype.join.call(this.properties, opt_separator);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "join", wrapper);
|
|
|
|
wrapper = function (var_args) {
|
|
var list = [];
|
|
var length = 0;
|
|
// Start by copying the current array.
|
|
var iLength = thisInterpreter.getProperty(this, "length");
|
|
for (var i = 0; i < iLength; i++) {
|
|
if (thisInterpreter.hasProperty(this, i)) {
|
|
var element = thisInterpreter.getProperty(this, i);
|
|
list[length] = element;
|
|
}
|
|
length++;
|
|
}
|
|
// Loop through all arguments and copy them in.
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
var value = arguments[i];
|
|
if (thisInterpreter.isa(value, thisInterpreter.ARRAY)) {
|
|
var jLength = thisInterpreter.getProperty(value, "length");
|
|
for (var j = 0; j < jLength; j++) {
|
|
if (thisInterpreter.hasProperty(value, j)) {
|
|
list[length] = thisInterpreter.getProperty(value, j);
|
|
}
|
|
length++;
|
|
}
|
|
} else {
|
|
list[length] = value;
|
|
}
|
|
}
|
|
return thisInterpreter.arrayNativeToPseudo(list);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "concat", wrapper);
|
|
|
|
wrapper = function (searchElement, opt_fromIndex) {
|
|
return Array.prototype.indexOf.apply(this.properties, arguments);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "indexOf", wrapper);
|
|
|
|
wrapper = function (searchElement, opt_fromIndex) {
|
|
return Array.prototype.lastIndexOf.apply(this.properties, arguments);
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "lastIndexOf", wrapper);
|
|
|
|
wrapper = function () {
|
|
Array.prototype.sort.call(this.properties);
|
|
return this;
|
|
};
|
|
this.setNativeFunctionPrototype(this.ARRAY, "sort", wrapper);
|
|
|
|
this.polyfills_.push(
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/every
|
|
"Object.defineProperty(Array.prototype, 'every',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(callbackfn, thisArg) {",
|
|
"if (!this || typeof callbackfn !== 'function') throw TypeError();",
|
|
"var T, k;",
|
|
"var O = Object(this);",
|
|
"var len = O.length >>> 0;",
|
|
"if (arguments.length > 1) T = thisArg;",
|
|
"k = 0;",
|
|
"while (k < len) {",
|
|
"if (k in O && !callbackfn.call(T, O[k], k, O)) return false;",
|
|
"k++;",
|
|
"}",
|
|
"return true;",
|
|
"}",
|
|
"});",
|
|
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
|
|
"Object.defineProperty(Array.prototype, 'filter',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(fun/*, thisArg*/) {",
|
|
"if (this === void 0 || this === null || typeof fun !== 'function') throw TypeError();",
|
|
"var t = Object(this);",
|
|
"var len = t.length >>> 0;",
|
|
"var res = [];",
|
|
"var thisArg = arguments.length >= 2 ? arguments[1] : void 0;",
|
|
"for (var i = 0; i < len; i++) {",
|
|
"if (i in t) {",
|
|
"var val = t[i];",
|
|
"if (fun.call(thisArg, val, i, t)) res.push(val);",
|
|
"}",
|
|
"}",
|
|
"return res;",
|
|
"}",
|
|
"});",
|
|
|
|
// Polyfill copied from:
|
|
// https://tc39.github.io/ecma262/#sec-array.prototype.find
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
|
|
"if (!Array.prototype.find) {",
|
|
"Object.defineProperty(Array.prototype, 'find', {",
|
|
"value: function(predicate) {",
|
|
"if (this == null) {",
|
|
"throw new TypeError('\"this\" is null or not defined');",
|
|
"}",
|
|
"var o = Object(this);",
|
|
"var len = o.length >>> 0;",
|
|
"if (typeof predicate !== 'function') {",
|
|
"throw new TypeError('predicate must be a function');",
|
|
"}",
|
|
"var thisArg = arguments[1];",
|
|
"var k = 0;",
|
|
"while (k < len) {",
|
|
"var kValue = o[k];",
|
|
"if (predicate.call(thisArg, kValue, k, o)) {",
|
|
"return kValue;",
|
|
"}",
|
|
"k++;",
|
|
"}",
|
|
"return undefined;",
|
|
"},",
|
|
"configurable: true,",
|
|
"writable: true",
|
|
"});",
|
|
"}",
|
|
|
|
// Poly fill copied from:
|
|
// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
|
|
"if (!Array.prototype.findIndex) {",
|
|
"Object.defineProperty(Array.prototype, 'findIndex', {",
|
|
"value: function(predicate) {",
|
|
"if (this == null) {",
|
|
"throw new TypeError('\"this\" is null or not defined');",
|
|
"}",
|
|
"var o = Object(this);",
|
|
"var len = o.length >>> 0;",
|
|
"if (typeof predicate !== 'function') {",
|
|
"throw new TypeError('predicate must be a function');",
|
|
"}",
|
|
"var thisArg = arguments[1];",
|
|
"var k = 0;",
|
|
"while (k < len) {",
|
|
"var kValue = o[k];",
|
|
"if (predicate.call(thisArg, kValue, k, o)) {",
|
|
"return k;",
|
|
"}",
|
|
"k++;",
|
|
"}",
|
|
"return -1;",
|
|
"},",
|
|
"configurable: true,",
|
|
"writable: true",
|
|
"});",
|
|
"}",
|
|
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
|
|
"Object.defineProperty(Array.prototype, 'forEach',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(callback, thisArg) {",
|
|
"if (!this || typeof callback !== 'function') throw TypeError();",
|
|
"var T, k;",
|
|
"var O = Object(this);",
|
|
"var len = O.length >>> 0;",
|
|
"if (arguments.length > 1) T = thisArg;",
|
|
"k = 0;",
|
|
"while (k < len) {",
|
|
"if (k in O) callback.call(T, O[k], k, O);",
|
|
"k++;",
|
|
"}",
|
|
"}",
|
|
"});",
|
|
|
|
// Polyfill copied from:
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill
|
|
"Object.defineProperty(Array.prototype, 'includes', {",
|
|
"value: function(searchElement, fromIndex) {",
|
|
"if (this == null) {",
|
|
"throw new TypeError('\"this\" is null or not defined');",
|
|
"}",
|
|
"// 1. Let O be ? ToObject(this value).",
|
|
"var o = Object(this);",
|
|
'// 2. Let len be ? ToLength(? Get(O, "length")).',
|
|
"var len = o.length >>> 0;",
|
|
"// 3. If len is 0, return false.",
|
|
"if (len === 0) {",
|
|
"return false;",
|
|
"}",
|
|
"// 4. Let n be ? ToInteger(fromIndex).",
|
|
"// (If fromIndex is undefined, this step produces the value 0.)",
|
|
"var n = fromIndex | 0;",
|
|
"// 5. If n ≥ 0, then",
|
|
"// a. Let k be n.",
|
|
"// 6. Else n < 0,",
|
|
"// a. Let k be len + n.",
|
|
"// b. If k < 0, let k be 0.",
|
|
"var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);",
|
|
"function sameValueZero(x, y) {",
|
|
"return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));",
|
|
"}",
|
|
"// 7. Repeat, while k < len",
|
|
"while (k < len) {",
|
|
"// a. Let elementK be the result of ? Get(O, ! ToString(k)).",
|
|
"// b. If SameValueZero(searchElement, elementK) is true, return true.",
|
|
"if (sameValueZero(o[k], searchElement)) {",
|
|
"return true;",
|
|
"}",
|
|
"// c. Increase k by 1. ",
|
|
"k++;",
|
|
"}",
|
|
"// 8. Return false",
|
|
"return false;",
|
|
"}",
|
|
"});",
|
|
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map
|
|
"Object.defineProperty(Array.prototype, 'map',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(callback, thisArg) {",
|
|
"if (!this || typeof callback !== 'function') new TypeError;",
|
|
"var T, A, k;",
|
|
"var O = Object(this);",
|
|
"var len = O.length >>> 0;",
|
|
"if (arguments.length > 1) T = thisArg;",
|
|
"A = new Array(len);",
|
|
"k = 0;",
|
|
"while (k < len) {",
|
|
"if (k in O) A[k] = callback.call(T, O[k], k, O);",
|
|
"k++;",
|
|
"}",
|
|
"return A;",
|
|
"}",
|
|
"});",
|
|
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
|
|
"Object.defineProperty(Array.prototype, 'reduce',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(callback /*, initialValue*/) {",
|
|
"if (!this || typeof callback !== 'function') throw TypeError();",
|
|
"var t = Object(this), len = t.length >>> 0, k = 0, value;",
|
|
"if (arguments.length === 2) {",
|
|
"value = arguments[1];",
|
|
"} else {",
|
|
"while (k < len && !(k in t)) k++;",
|
|
"if (k >= len) {",
|
|
"throw TypeError('Reduce of empty array with no initial value');",
|
|
"}",
|
|
"value = t[k++];",
|
|
"}",
|
|
"for (; k < len; k++) {",
|
|
"if (k in t) value = callback(value, t[k], k, t);",
|
|
"}",
|
|
"return value;",
|
|
"}",
|
|
"});",
|
|
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight
|
|
"Object.defineProperty(Array.prototype, 'reduceRight',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(callback /*, initialValue*/) {",
|
|
"if (null === this || 'undefined' === typeof this || 'function' !== typeof callback) throw TypeError();",
|
|
"var t = Object(this), len = t.length >>> 0, k = len - 1, value;",
|
|
"if (arguments.length >= 2) {",
|
|
"value = arguments[1];",
|
|
"} else {",
|
|
"while (k >= 0 && !(k in t)) k--;",
|
|
"if (k < 0) {",
|
|
"throw TypeError('Reduce of empty array with no initial value');",
|
|
"}",
|
|
"value = t[k--];",
|
|
"}",
|
|
"for (; k >= 0; k--) {",
|
|
"if (k in t) value = callback(value, t[k], k, t);",
|
|
"}",
|
|
"return value;",
|
|
"}",
|
|
"});",
|
|
|
|
// Polyfill copied from:
|
|
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/some
|
|
"Object.defineProperty(Array.prototype, 'some',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function(fun/*, thisArg*/) {",
|
|
"if (!this || typeof fun !== 'function') throw TypeError();",
|
|
"var t = Object(this);",
|
|
"var len = t.length >>> 0;",
|
|
"var thisArg = arguments.length >= 2 ? arguments[1] : void 0;",
|
|
"for (var i = 0; i < len; i++) {",
|
|
"if (i in t && fun.call(thisArg, t[i], i, t)) {",
|
|
"return true;",
|
|
"}",
|
|
"}",
|
|
"return false;",
|
|
"}",
|
|
"});",
|
|
|
|
"(function() {",
|
|
"var sort_ = Array.prototype.sort;",
|
|
"Array.prototype.sort = function(opt_comp) {",
|
|
// Fast native sort.
|
|
"if (typeof opt_comp !== 'function') {",
|
|
"return sort_.call(this);",
|
|
"}",
|
|
// Slow bubble sort.
|
|
"for (var i = 0; i < this.length; i++) {",
|
|
"var changes = 0;",
|
|
"for (var j = 0; j < this.length - i - 1; j++) {",
|
|
"if (opt_comp(this[j], this[j + 1]) > 0) {",
|
|
"var swap = this[j];",
|
|
"this[j] = this[j + 1];",
|
|
"this[j + 1] = swap;",
|
|
"changes++;",
|
|
"}",
|
|
"}",
|
|
"if (!changes) break;",
|
|
"}",
|
|
"return this;",
|
|
"};",
|
|
"})();",
|
|
|
|
"Object.defineProperty(Array.prototype, 'toLocaleString',",
|
|
"{configurable: true, writable: true, value:",
|
|
"function() {",
|
|
"var out = [];",
|
|
"for (var i = 0; i < this.length; i++) {",
|
|
"out[i] = (this[i] === null || this[i] === undefined) ? '' : this[i].toLocaleString();",
|
|
"}",
|
|
"return out.join(',');",
|
|
"}",
|
|
"});",
|
|
"",
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Initialize the String class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initString = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
// String constructor.
|
|
wrapper = function (value) {
|
|
value = String(value);
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new String().
|
|
this.data = value;
|
|
return this;
|
|
} else {
|
|
// Called as String().
|
|
return value;
|
|
}
|
|
};
|
|
this.STRING = this.createNativeFunction(wrapper, true);
|
|
this.setProperty(scope, "String", this.STRING);
|
|
|
|
// Static methods on String.
|
|
this.setProperty(
|
|
this.STRING,
|
|
"fromCharCode",
|
|
this.createNativeFunction(String.fromCharCode, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
// Instance methods on String.
|
|
// Methods with exclusively primitive arguments.
|
|
var functions = [
|
|
"charAt",
|
|
"charCodeAt",
|
|
"concat",
|
|
"indexOf",
|
|
"lastIndexOf",
|
|
"slice",
|
|
"substr",
|
|
"substring",
|
|
"toLocaleLowerCase",
|
|
"toLocaleUpperCase",
|
|
"toLowerCase",
|
|
"toUpperCase",
|
|
"trim",
|
|
];
|
|
for (var i = 0; i < functions.length; i++) {
|
|
this.setNativeFunctionPrototype(this.STRING, functions[i], String.prototype[functions[i]]);
|
|
}
|
|
|
|
wrapper = function (compareString, locales, options) {
|
|
locales = locales ? thisInterpreter.pseudoToNative(locales) : undefined;
|
|
options = options ? thisInterpreter.pseudoToNative(options) : undefined;
|
|
return String(this).localeCompare(compareString, locales, options);
|
|
};
|
|
this.setNativeFunctionPrototype(this.STRING, "localeCompare", wrapper);
|
|
|
|
wrapper = function (separator, limit) {
|
|
if (thisInterpreter.isa(separator, thisInterpreter.REGEXP)) {
|
|
separator = separator.data;
|
|
}
|
|
var jsList = String(this).split(separator, limit);
|
|
return thisInterpreter.arrayNativeToPseudo(jsList);
|
|
};
|
|
this.setNativeFunctionPrototype(this.STRING, "split", wrapper);
|
|
|
|
wrapper = function (regexp) {
|
|
if (thisInterpreter.isa(regexp, thisInterpreter.REGEXP)) {
|
|
regexp = regexp.data;
|
|
}
|
|
var m = String(this).match(regexp);
|
|
return m && thisInterpreter.arrayNativeToPseudo(m);
|
|
};
|
|
this.setNativeFunctionPrototype(this.STRING, "match", wrapper);
|
|
|
|
wrapper = function (regexp) {
|
|
if (thisInterpreter.isa(regexp, thisInterpreter.REGEXP)) {
|
|
regexp = regexp.data;
|
|
}
|
|
return String(this).search(regexp);
|
|
};
|
|
this.setNativeFunctionPrototype(this.STRING, "search", wrapper);
|
|
|
|
wrapper = function (substr, newSubstr) {
|
|
// Support for function replacements is the responsibility of a polyfill.
|
|
if (thisInterpreter.isa(substr, thisInterpreter.REGEXP)) {
|
|
substr = substr.data;
|
|
}
|
|
return String(this).replace(substr, newSubstr);
|
|
};
|
|
this.setNativeFunctionPrototype(this.STRING, "replace", wrapper);
|
|
// Add a polyfill to handle replace's second argument being a function.
|
|
this.polyfills_.push(
|
|
"(function() {",
|
|
"var replace_ = String.prototype.replace;",
|
|
"String.prototype.replace = function(substr, newSubstr) {",
|
|
"if (typeof newSubstr !== 'function') {",
|
|
// string.replace(string|regexp, string)
|
|
"return replace_.call(this, substr, newSubstr);",
|
|
"}",
|
|
"var str = this;",
|
|
"if (substr instanceof RegExp) {", // string.replace(regexp, function)
|
|
"var subs = [];",
|
|
"var m = substr.exec(str);",
|
|
"while (m) {",
|
|
"m.push(m.index, str);",
|
|
"var inject = newSubstr.apply(null, m);",
|
|
"subs.push([m.index, m[0].length, inject]);",
|
|
"m = substr.global ? substr.exec(str) : null;",
|
|
"}",
|
|
"for (var i = subs.length - 1; i >= 0; i--) {",
|
|
"str = str.substring(0, subs[i][0]) + subs[i][2] + " + "str.substring(subs[i][0] + subs[i][1]);",
|
|
"}",
|
|
"} else {", // string.replace(string, function)
|
|
"var i = str.indexOf(substr);",
|
|
"if (i !== -1) {",
|
|
"var inject = newSubstr(str.substr(i, substr.length), i, str);",
|
|
"str = str.substring(0, i) + inject + " + "str.substring(i + substr.length);",
|
|
"}",
|
|
"}",
|
|
"return str;",
|
|
"};",
|
|
"})();",
|
|
|
|
// Polyfill copied from:
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
|
|
"if (!String.prototype.endsWith) {",
|
|
"String.prototype.endsWith = function(search, this_len) {",
|
|
"if (this_len === undefined || this_len > this.length) {",
|
|
"this_len = this.length;",
|
|
"}",
|
|
"return this.substring(this_len - search.length, this_len) === search;",
|
|
"};",
|
|
"}",
|
|
|
|
//Polyfill copied from:
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
|
|
"if (!String.prototype.includes) {",
|
|
"String.prototype.includes = function(search, start) {",
|
|
"'use strict';",
|
|
"if (typeof start !== 'number') {",
|
|
"start = 0;",
|
|
"}",
|
|
" ",
|
|
"if (start + search.length > this.length) {",
|
|
"return false;",
|
|
"} else {",
|
|
"return this.indexOf(search, start) !== -1;",
|
|
"}",
|
|
"};",
|
|
"}",
|
|
|
|
// Polyfill copied from:
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
|
|
"if (!String.prototype.startsWith) {",
|
|
"String.prototype.startsWith = function(search, pos) {",
|
|
"return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;",
|
|
"};",
|
|
"}",
|
|
|
|
"",
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Initialize the Boolean class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initBoolean = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
// Boolean constructor.
|
|
wrapper = function (value) {
|
|
value = Boolean(value);
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new Boolean().
|
|
this.data = value;
|
|
return this;
|
|
} else {
|
|
// Called as Boolean().
|
|
return value;
|
|
}
|
|
};
|
|
this.BOOLEAN = this.createNativeFunction(wrapper, true);
|
|
this.setProperty(scope, "Boolean", this.BOOLEAN);
|
|
};
|
|
|
|
/**
|
|
* Initialize the Number class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initNumber = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
// Number constructor.
|
|
wrapper = function (value) {
|
|
value = Number(value);
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new Number().
|
|
this.data = value;
|
|
return this;
|
|
} else {
|
|
// Called as Number().
|
|
return value;
|
|
}
|
|
};
|
|
this.NUMBER = this.createNativeFunction(wrapper, true);
|
|
this.setProperty(scope, "Number", this.NUMBER);
|
|
|
|
var numConsts = ["MAX_VALUE", "MIN_VALUE", "NaN", "NEGATIVE_INFINITY", "POSITIVE_INFINITY"];
|
|
for (var i = 0; i < numConsts.length; i++) {
|
|
this.setProperty(this.NUMBER, numConsts[i], Number[numConsts[i]], Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
|
|
}
|
|
|
|
// Instance methods on Number.
|
|
wrapper = function (fractionDigits) {
|
|
try {
|
|
return Number(this).toExponential(fractionDigits);
|
|
} catch (e) {
|
|
// Throws if fractionDigits isn't within 0-20.
|
|
thisInterpreter.throwException(thisInterpreter.ERROR, e.message);
|
|
}
|
|
};
|
|
this.setNativeFunctionPrototype(this.NUMBER, "toExponential", wrapper);
|
|
|
|
wrapper = function (digits) {
|
|
try {
|
|
return Number(this).toFixed(digits);
|
|
} catch (e) {
|
|
// Throws if digits isn't within 0-20.
|
|
thisInterpreter.throwException(thisInterpreter.ERROR, e.message);
|
|
}
|
|
};
|
|
this.setNativeFunctionPrototype(this.NUMBER, "toFixed", wrapper);
|
|
|
|
wrapper = function (precision) {
|
|
try {
|
|
return Number(this).toPrecision(precision);
|
|
} catch (e) {
|
|
// Throws if precision isn't within range (depends on implementation).
|
|
thisInterpreter.throwException(thisInterpreter.ERROR, e.message);
|
|
}
|
|
};
|
|
this.setNativeFunctionPrototype(this.NUMBER, "toPrecision", wrapper);
|
|
|
|
wrapper = function (radix) {
|
|
try {
|
|
return Number(this).toString(radix);
|
|
} catch (e) {
|
|
// Throws if radix isn't within 2-36.
|
|
thisInterpreter.throwException(thisInterpreter.ERROR, e.message);
|
|
}
|
|
};
|
|
this.setNativeFunctionPrototype(this.NUMBER, "toString", wrapper);
|
|
|
|
wrapper = function (locales, options) {
|
|
locales = locales ? thisInterpreter.pseudoToNative(locales) : undefined;
|
|
options = options ? thisInterpreter.pseudoToNative(options) : undefined;
|
|
return Number(this).toLocaleString(locales, options);
|
|
};
|
|
this.setNativeFunctionPrototype(this.NUMBER, "toLocaleString", wrapper);
|
|
};
|
|
|
|
/**
|
|
* Initialize the Date class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initDate = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
// Date constructor.
|
|
wrapper = function (value, var_args) {
|
|
if (!thisInterpreter.calledWithNew()) {
|
|
// Called as Date().
|
|
// Calling Date() as a function returns a string, no arguments are heeded.
|
|
return Date();
|
|
}
|
|
// Called as new Date().
|
|
var args = [null].concat(Array.from(arguments));
|
|
this.data = new (Function.prototype.bind.apply(Date, args))();
|
|
return this;
|
|
};
|
|
this.DATE = this.createNativeFunction(wrapper, true);
|
|
this.DATE_PROTO = this.DATE.properties["prototype"];
|
|
this.setProperty(scope, "Date", this.DATE);
|
|
|
|
// Static methods on Date.
|
|
this.setProperty(this.DATE, "now", this.createNativeFunction(Date.now, false), Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
|
|
this.setProperty(
|
|
this.DATE,
|
|
"parse",
|
|
this.createNativeFunction(Date.parse, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
this.setProperty(this.DATE, "UTC", this.createNativeFunction(Date.UTC, false), Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
|
|
// Instance methods on Date.
|
|
var functions = [
|
|
"getDate",
|
|
"getDay",
|
|
"getFullYear",
|
|
"getHours",
|
|
"getMilliseconds",
|
|
"getMinutes",
|
|
"getMonth",
|
|
"getSeconds",
|
|
"getTime",
|
|
"getTimezoneOffset",
|
|
"getUTCDate",
|
|
"getUTCDay",
|
|
"getUTCFullYear",
|
|
"getUTCHours",
|
|
"getUTCMilliseconds",
|
|
"getUTCMinutes",
|
|
"getUTCMonth",
|
|
"getUTCSeconds",
|
|
"getYear",
|
|
"setDate",
|
|
"setFullYear",
|
|
"setHours",
|
|
"setMilliseconds",
|
|
"setMinutes",
|
|
"setMonth",
|
|
"setSeconds",
|
|
"setTime",
|
|
"setUTCDate",
|
|
"setUTCFullYear",
|
|
"setUTCHours",
|
|
"setUTCMilliseconds",
|
|
"setUTCMinutes",
|
|
"setUTCMonth",
|
|
"setUTCSeconds",
|
|
"setYear",
|
|
"toDateString",
|
|
"toISOString",
|
|
"toJSON",
|
|
"toGMTString",
|
|
"toLocaleDateString",
|
|
"toLocaleString",
|
|
"toLocaleTimeString",
|
|
"toTimeString",
|
|
"toUTCString",
|
|
];
|
|
for (var i = 0; i < functions.length; i++) {
|
|
wrapper = (function (nativeFunc) {
|
|
return function (var_args) {
|
|
var args = [];
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
args[i] = thisInterpreter.pseudoToNative(arguments[i]);
|
|
}
|
|
return this.data[nativeFunc].apply(this.data, args);
|
|
};
|
|
})(functions[i]);
|
|
this.setNativeFunctionPrototype(this.DATE, functions[i], wrapper);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialize Regular Expression object.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initRegExp = function (scope) {
|
|
var thisInterpreter = this;
|
|
var wrapper;
|
|
// RegExp constructor.
|
|
wrapper = function (pattern, flags) {
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new RegExp().
|
|
var rgx = this;
|
|
} else {
|
|
// Called as RegExp().
|
|
var rgx = thisInterpreter.createObjectProto(thisInterpreter.REGEXP_PROTO);
|
|
}
|
|
pattern = pattern ? pattern.toString() : "";
|
|
flags = flags ? flags.toString() : "";
|
|
thisInterpreter.populateRegExp(rgx, new RegExp(pattern, flags));
|
|
return rgx;
|
|
};
|
|
this.REGEXP = this.createNativeFunction(wrapper, true);
|
|
this.REGEXP_PROTO = this.REGEXP.properties["prototype"];
|
|
this.setProperty(scope, "RegExp", this.REGEXP);
|
|
|
|
this.setProperty(
|
|
this.REGEXP.properties["prototype"],
|
|
"global",
|
|
undefined,
|
|
Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
this.setProperty(
|
|
this.REGEXP.properties["prototype"],
|
|
"ignoreCase",
|
|
undefined,
|
|
Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
this.setProperty(
|
|
this.REGEXP.properties["prototype"],
|
|
"multiline",
|
|
undefined,
|
|
Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
this.setProperty(
|
|
this.REGEXP.properties["prototype"],
|
|
"source",
|
|
"(?:)",
|
|
Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
|
|
wrapper = function (str) {
|
|
return this.data.test(str);
|
|
};
|
|
this.setNativeFunctionPrototype(this.REGEXP, "test", wrapper);
|
|
|
|
wrapper = function (str) {
|
|
str = str.toString();
|
|
// Get lastIndex from wrapped regex, since this is settable.
|
|
this.data.lastIndex = Number(thisInterpreter.getProperty(this, "lastIndex"));
|
|
var match = this.data.exec(str);
|
|
thisInterpreter.setProperty(this, "lastIndex", this.data.lastIndex);
|
|
|
|
if (match) {
|
|
var result = thisInterpreter.createObjectProto(thisInterpreter.ARRAY_PROTO);
|
|
for (var i = 0; i < match.length; i++) {
|
|
thisInterpreter.setProperty(result, i, match[i]);
|
|
}
|
|
// match has additional properties.
|
|
thisInterpreter.setProperty(result, "index", match.index);
|
|
thisInterpreter.setProperty(result, "input", match.input);
|
|
return result;
|
|
}
|
|
return null;
|
|
};
|
|
this.setNativeFunctionPrototype(this.REGEXP, "exec", wrapper);
|
|
};
|
|
|
|
/**
|
|
* Initialize the Error class.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initError = function (scope) {
|
|
var thisInterpreter = this;
|
|
// Error constructor.
|
|
this.ERROR = this.createNativeFunction(function (opt_message) {
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new Error().
|
|
var newError = this;
|
|
} else {
|
|
// Called as Error().
|
|
var newError = thisInterpreter.createObject(thisInterpreter.ERROR);
|
|
}
|
|
if (opt_message) {
|
|
thisInterpreter.setProperty(newError, "message", String(opt_message), Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
}
|
|
return newError;
|
|
}, true);
|
|
this.setProperty(scope, "Error", this.ERROR);
|
|
this.setProperty(this.ERROR.properties["prototype"], "message", "", Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
this.setProperty(this.ERROR.properties["prototype"], "name", "Error", Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
|
|
var createErrorSubclass = function (name) {
|
|
var constructor = thisInterpreter.createNativeFunction(function (opt_message) {
|
|
if (thisInterpreter.calledWithNew()) {
|
|
// Called as new XyzError().
|
|
var newError = this;
|
|
} else {
|
|
// Called as XyzError().
|
|
var newError = thisInterpreter.createObject(constructor);
|
|
}
|
|
if (opt_message) {
|
|
thisInterpreter.setProperty(newError, "message", String(opt_message), Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
}
|
|
return newError;
|
|
}, true);
|
|
thisInterpreter.setProperty(constructor, "prototype", thisInterpreter.createObject(thisInterpreter.ERROR));
|
|
thisInterpreter.setProperty(
|
|
constructor.properties["prototype"],
|
|
"name",
|
|
name,
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
thisInterpreter.setProperty(scope, name, constructor);
|
|
|
|
return constructor;
|
|
};
|
|
|
|
this.EVAL_ERROR = createErrorSubclass("EvalError");
|
|
this.RANGE_ERROR = createErrorSubclass("RangeError");
|
|
this.REFERENCE_ERROR = createErrorSubclass("ReferenceError");
|
|
this.SYNTAX_ERROR = createErrorSubclass("SyntaxError");
|
|
this.TYPE_ERROR = createErrorSubclass("TypeError");
|
|
this.URI_ERROR = createErrorSubclass("URIError");
|
|
};
|
|
|
|
/**
|
|
* Initialize Math object.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initMath = function (scope) {
|
|
var thisInterpreter = this;
|
|
var myMath = this.createObjectProto(this.OBJECT_PROTO);
|
|
this.setProperty(scope, "Math", myMath);
|
|
var mathConsts = ["E", "LN2", "LN10", "LOG2E", "LOG10E", "PI", "SQRT1_2", "SQRT2"];
|
|
for (var i = 0; i < mathConsts.length; i++) {
|
|
this.setProperty(myMath, mathConsts[i], Math[mathConsts[i]], Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
|
|
}
|
|
var numFunctions = [
|
|
"abs",
|
|
"acos",
|
|
"asin",
|
|
"atan",
|
|
"atan2",
|
|
"ceil",
|
|
"cos",
|
|
"exp",
|
|
"floor",
|
|
"log",
|
|
"max",
|
|
"min",
|
|
"pow",
|
|
"random",
|
|
"round",
|
|
"sin",
|
|
"sqrt",
|
|
"tan",
|
|
];
|
|
for (var i = 0; i < numFunctions.length; i++) {
|
|
this.setProperty(
|
|
myMath,
|
|
numFunctions[i],
|
|
this.createNativeFunction(Math[numFunctions[i]], false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialize JSON object.
|
|
* @param {!Interpreter.Object} scope Global scope.
|
|
*/
|
|
Interpreter.prototype.initJSON = function (scope) {
|
|
var thisInterpreter = this;
|
|
var myJSON = thisInterpreter.createObjectProto(this.OBJECT_PROTO);
|
|
this.setProperty(scope, "JSON", myJSON);
|
|
|
|
var wrapper = function (text) {
|
|
try {
|
|
var nativeObj = JSON.parse(text.toString());
|
|
} catch (e) {
|
|
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, e.message);
|
|
}
|
|
return thisInterpreter.nativeToPseudo(nativeObj);
|
|
};
|
|
this.setProperty(myJSON, "parse", this.createNativeFunction(wrapper, false));
|
|
|
|
wrapper = function (value) {
|
|
var nativeObj = thisInterpreter.pseudoToNative(value);
|
|
try {
|
|
var str = JSON.stringify(nativeObj);
|
|
} catch (e) {
|
|
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, e.message);
|
|
}
|
|
return str;
|
|
};
|
|
this.setProperty(myJSON, "stringify", this.createNativeFunction(wrapper, false));
|
|
};
|
|
|
|
/**
|
|
* Is an object of a certain class?
|
|
* @param {Interpreter.Value} child Object to check.
|
|
* @param {Interpreter.Object} constructor Constructor of object.
|
|
* @return {boolean} True if object is the class or inherits from it.
|
|
* False otherwise.
|
|
*/
|
|
Interpreter.prototype.isa = function (child, constructor) {
|
|
if (child === null || child === undefined || !constructor) {
|
|
return false;
|
|
}
|
|
var proto = constructor.properties["prototype"];
|
|
if (child === proto) {
|
|
return true;
|
|
}
|
|
// The first step up the prototype chain is harder since the child might be
|
|
// a primitive value. Subsequent steps can just follow the .proto property.
|
|
child = this.getPrototype(child);
|
|
while (child) {
|
|
if (child === proto) {
|
|
return true;
|
|
}
|
|
child = child.proto;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Is a value a legal integer for an array length?
|
|
* @param {Interpreter.Value} x Value to check.
|
|
* @return {number} Zero, or a positive integer if the value can be
|
|
* converted to such. NaN otherwise.
|
|
*/
|
|
Interpreter.legalArrayLength = function (x) {
|
|
var n = x >>> 0;
|
|
// Array length must be between 0 and 2^32-1 (inclusive).
|
|
return n === Number(x) ? n : NaN;
|
|
};
|
|
|
|
/**
|
|
* Is a value a legal integer for an array index?
|
|
* @param {Interpreter.Value} x Value to check.
|
|
* @return {number} Zero, or a positive integer if the value can be
|
|
* converted to such. NaN otherwise.
|
|
*/
|
|
Interpreter.legalArrayIndex = function (x) {
|
|
var n = x >>> 0;
|
|
// Array index cannot be 2^32-1, otherwise length would be 2^32.
|
|
// 0xffffffff is 2^32-1.
|
|
return String(n) === String(x) && n !== 0xffffffff ? n : NaN;
|
|
};
|
|
|
|
/**
|
|
* Typedef for JS values.
|
|
* @typedef {!Interpreter.Object|boolean|number|string|undefined|null}
|
|
*/
|
|
Interpreter.Value;
|
|
|
|
/**
|
|
* Class for an object.
|
|
* @param {Interpreter.Object} proto Prototype object or null.
|
|
* @constructor
|
|
*/
|
|
Interpreter.Object = function (proto) {
|
|
this.getter = Object.create(null);
|
|
this.setter = Object.create(null);
|
|
this.properties = Object.create(null);
|
|
this.proto = proto;
|
|
};
|
|
|
|
/** @type {Interpreter.Object} */
|
|
Interpreter.Object.prototype.proto = null;
|
|
|
|
/** @type {boolean} */
|
|
Interpreter.Object.prototype.isObject = true;
|
|
|
|
/** @type {string} */
|
|
Interpreter.Object.prototype.class = "Object";
|
|
|
|
/** @type {Date|RegExp|boolean|number|string|undefined|null} */
|
|
Interpreter.Object.prototype.data = null;
|
|
|
|
/**
|
|
* Convert this object into a string.
|
|
* @return {string} String value.
|
|
* @override
|
|
*/
|
|
Interpreter.Object.prototype.toString = function () {
|
|
if (this.class === "Array") {
|
|
// Array
|
|
var cycles = Interpreter.toStringCycles_;
|
|
cycles.push(this);
|
|
try {
|
|
var strs = [];
|
|
for (var i = 0; i < this.properties.length; i++) {
|
|
var value = this.properties[i];
|
|
strs[i] = value && value.isObject && cycles.indexOf(value) !== -1 ? "..." : value;
|
|
}
|
|
} finally {
|
|
cycles.pop();
|
|
}
|
|
return strs.join(",");
|
|
}
|
|
if (this.class === "Error") {
|
|
var cycles = Interpreter.toStringCycles_;
|
|
if (cycles.indexOf(this) !== -1) {
|
|
return "[object Error]";
|
|
}
|
|
var name, message;
|
|
// Bug: Does not support getters and setters for name or message.
|
|
var obj = this;
|
|
do {
|
|
if ("name" in obj.properties) {
|
|
name = obj.properties["name"];
|
|
break;
|
|
}
|
|
} while ((obj = obj.proto));
|
|
var obj = this;
|
|
do {
|
|
if ("message" in obj.properties) {
|
|
message = obj.properties["message"];
|
|
break;
|
|
}
|
|
} while ((obj = obj.proto));
|
|
cycles.push(this);
|
|
try {
|
|
name = name && name.toString();
|
|
message = message && message.toString();
|
|
} finally {
|
|
cycles.pop();
|
|
}
|
|
return message ? name + ": " + message : String(name);
|
|
}
|
|
|
|
// RegExp, Date, and boxed primitives.
|
|
if (this.data !== null) {
|
|
return String(this.data);
|
|
}
|
|
|
|
return "[object " + this.class + "]";
|
|
};
|
|
|
|
/**
|
|
* Return the object's value.
|
|
* @return {Interpreter.Value} Value.
|
|
* @override
|
|
*/
|
|
Interpreter.Object.prototype.valueOf = function () {
|
|
if (this.data === undefined || this.data === null || this.data instanceof RegExp) {
|
|
return this; // An Object.
|
|
}
|
|
if (this.data instanceof Date) {
|
|
return this.data.valueOf(); // Milliseconds.
|
|
}
|
|
return /** @type {(boolean|number|string)} */ (this.data); // Boxed primitive.
|
|
};
|
|
|
|
/**
|
|
* Create a new data object based on a constructor's prototype.
|
|
* @param {Interpreter.Object} constructor Parent constructor function,
|
|
* or null if scope object.
|
|
* @return {!Interpreter.Object} New data object.
|
|
*/
|
|
Interpreter.prototype.createObject = function (constructor) {
|
|
return this.createObjectProto(constructor && constructor.properties["prototype"]);
|
|
};
|
|
|
|
/**
|
|
* Create a new data object based on a prototype.
|
|
* @param {Interpreter.Object} proto Prototype object.
|
|
* @return {!Interpreter.Object} New data object.
|
|
*/
|
|
Interpreter.prototype.createObjectProto = function (proto) {
|
|
if (typeof proto !== "object") {
|
|
throw Error("Non object prototype");
|
|
}
|
|
var obj = new Interpreter.Object(proto);
|
|
// Functions have prototype objects.
|
|
if (this.isa(obj, this.FUNCTION)) {
|
|
this.setProperty(obj, "prototype", this.createObjectProto(this.OBJECT_PROTO || null));
|
|
obj.class = "Function";
|
|
}
|
|
// Arrays have length.
|
|
if (this.isa(obj, this.ARRAY)) {
|
|
this.setProperty(obj, "length", 0, {
|
|
configurable: false,
|
|
enumerable: false,
|
|
writable: true,
|
|
});
|
|
obj.class = "Array";
|
|
}
|
|
if (this.isa(obj, this.ERROR)) {
|
|
obj.class = "Error";
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
/**
|
|
* Initialize a pseudo regular expression object based on a native regular
|
|
* expression object.
|
|
* @param {!Interpreter.Object} pseudoRegexp The existing object to set.
|
|
* @param {!RegExp} nativeRegexp The native regular expression.
|
|
*/
|
|
Interpreter.prototype.populateRegExp = function (pseudoRegexp, nativeRegexp) {
|
|
pseudoRegexp.data = nativeRegexp;
|
|
// lastIndex is settable, all others are read-only attributes
|
|
this.setProperty(pseudoRegexp, "lastIndex", nativeRegexp.lastIndex, Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
this.setProperty(pseudoRegexp, "source", nativeRegexp.source, Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
|
|
this.setProperty(pseudoRegexp, "global", nativeRegexp.global, Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
|
|
this.setProperty(pseudoRegexp, "ignoreCase", nativeRegexp.ignoreCase, Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
|
|
this.setProperty(pseudoRegexp, "multiline", nativeRegexp.multiline, Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
|
|
};
|
|
|
|
/**
|
|
* Create a new function.
|
|
* @param {!Object} node AST node defining the function.
|
|
* @param {!Object} scope Parent scope.
|
|
* @return {!Interpreter.Object} New function.
|
|
*/
|
|
Interpreter.prototype.createFunction = function (node, scope) {
|
|
var func = this.createObjectProto(this.FUNCTION_PROTO);
|
|
func.parentScope = scope;
|
|
func.node = node;
|
|
this.setProperty(func, "length", func.node["params"].length, Interpreter.READONLY_DESCRIPTOR);
|
|
return func;
|
|
};
|
|
|
|
/**
|
|
* Create a new native function.
|
|
* @param {!Function} nativeFunc JavaScript function.
|
|
* @param {boolean=} opt_constructor If true, the function's
|
|
* prototype will have its constructor property set to the function.
|
|
* If false, the function cannot be called as a constructor (e.g. escape).
|
|
* Defaults to undefined.
|
|
* @return {!Interpreter.Object} New function.
|
|
*/
|
|
Interpreter.prototype.createNativeFunction = function (nativeFunc, opt_constructor) {
|
|
var func = this.createObjectProto(this.FUNCTION_PROTO);
|
|
func.nativeFunc = nativeFunc;
|
|
nativeFunc.id = this.functionCounter_++;
|
|
this.setProperty(func, "length", nativeFunc.length, Interpreter.READONLY_DESCRIPTOR);
|
|
if (opt_constructor) {
|
|
this.setProperty(func.properties["prototype"], "constructor", func, Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
} else if (opt_constructor === false) {
|
|
func.illegalConstructor = true;
|
|
this.setProperty(func, "prototype", undefined);
|
|
}
|
|
return func;
|
|
};
|
|
|
|
/**
|
|
* Create a new native asynchronous function.
|
|
* @param {!Function} asyncFunc JavaScript function.
|
|
* @return {!Interpreter.Object} New function.
|
|
*/
|
|
Interpreter.prototype.createAsyncFunction = function (asyncFunc) {
|
|
var func = this.createObjectProto(this.FUNCTION_PROTO);
|
|
func.asyncFunc = asyncFunc;
|
|
asyncFunc.id = this.functionCounter_++;
|
|
this.setProperty(func, "length", asyncFunc.length, Interpreter.READONLY_DESCRIPTOR);
|
|
return func;
|
|
};
|
|
|
|
/**
|
|
* Converts from a native JS object or value to a JS interpreter object.
|
|
* Can handle JSON-style values, does NOT handle cycles.
|
|
* @param {*} nativeObj The native JS object to be converted.
|
|
* @return {Interpreter.Value} The equivalent JS interpreter object.
|
|
*/
|
|
Interpreter.prototype.nativeToPseudo = function (nativeObj) {
|
|
if ((typeof nativeObj !== "object" && typeof nativeObj !== "function") || nativeObj === null) {
|
|
return nativeObj;
|
|
}
|
|
|
|
if (nativeObj instanceof RegExp) {
|
|
var pseudoRegexp = this.createObjectProto(this.REGEXP_PROTO);
|
|
this.populateRegExp(pseudoRegexp, nativeObj);
|
|
return pseudoRegexp;
|
|
}
|
|
|
|
if (nativeObj instanceof Date) {
|
|
var pseudoDate = this.createObjectProto(this.DATE_PROTO);
|
|
pseudoDate.data = nativeObj;
|
|
return pseudoDate;
|
|
}
|
|
|
|
if (nativeObj instanceof Function) {
|
|
var interpreter = this;
|
|
var wrapper = function () {
|
|
return interpreter.nativeToPseudo(
|
|
nativeObj.apply(
|
|
interpreter,
|
|
Array.prototype.slice.call(arguments).map(function (i) {
|
|
return interpreter.pseudoToNative(i);
|
|
}),
|
|
),
|
|
);
|
|
};
|
|
return this.createNativeFunction(wrapper, undefined);
|
|
}
|
|
|
|
var pseudoObj;
|
|
if (Array.isArray(nativeObj)) {
|
|
// Array.
|
|
pseudoObj = this.createObjectProto(this.ARRAY_PROTO);
|
|
for (var i = 0; i < nativeObj.length; i++) {
|
|
if (i in nativeObj) {
|
|
this.setProperty(pseudoObj, i, this.nativeToPseudo(nativeObj[i]));
|
|
}
|
|
}
|
|
} else {
|
|
// Object.
|
|
pseudoObj = this.createObjectProto(this.OBJECT_PROTO);
|
|
for (var key in nativeObj) {
|
|
this.setProperty(pseudoObj, key, this.nativeToPseudo(nativeObj[key]));
|
|
}
|
|
}
|
|
return pseudoObj;
|
|
};
|
|
|
|
/**
|
|
* Converts from a JS interpreter object to native JS object.
|
|
* Can handle JSON-style values, plus cycles.
|
|
* @param {Interpreter.Value} pseudoObj The JS interpreter object to be
|
|
* converted.
|
|
* @param {Object=} opt_cycles Cycle detection (used in recursive calls).
|
|
* @return {*} The equivalent native JS object or value.
|
|
*/
|
|
Interpreter.prototype.pseudoToNative = function (pseudoObj, opt_cycles) {
|
|
if ((typeof pseudoObj !== "object" && typeof pseudoObj !== "function") || pseudoObj === null) {
|
|
return pseudoObj;
|
|
}
|
|
|
|
if (this.isa(pseudoObj, this.REGEXP)) {
|
|
// Regular expression.
|
|
return pseudoObj.data;
|
|
}
|
|
|
|
if (this.isa(pseudoObj, this.DATE)) {
|
|
// Date.
|
|
return pseudoObj.data;
|
|
}
|
|
|
|
var cycles = opt_cycles || {
|
|
pseudo: [],
|
|
native: [],
|
|
};
|
|
var i = cycles.pseudo.indexOf(pseudoObj);
|
|
if (i !== -1) {
|
|
return cycles.native[i];
|
|
}
|
|
cycles.pseudo.push(pseudoObj);
|
|
var nativeObj;
|
|
if (this.isa(pseudoObj, this.ARRAY)) {
|
|
// Array.
|
|
nativeObj = [];
|
|
cycles.native.push(nativeObj);
|
|
var length = this.getProperty(pseudoObj, "length");
|
|
for (var i = 0; i < length; i++) {
|
|
if (this.hasProperty(pseudoObj, i)) {
|
|
nativeObj[i] = this.pseudoToNative(this.getProperty(pseudoObj, i), cycles);
|
|
}
|
|
}
|
|
} else {
|
|
// Object.
|
|
nativeObj = {};
|
|
cycles.native.push(nativeObj);
|
|
var val;
|
|
for (var key in pseudoObj.properties) {
|
|
val = pseudoObj.properties[key];
|
|
nativeObj[key] = this.pseudoToNative(val, cycles);
|
|
}
|
|
}
|
|
cycles.pseudo.pop();
|
|
cycles.native.pop();
|
|
return nativeObj;
|
|
};
|
|
|
|
/**
|
|
* Converts from a native JS array to a JS interpreter array.
|
|
* Does handle non-numeric properties (like str.match's index prop).
|
|
* Does NOT recurse into the array's contents.
|
|
* @param {!Array} nativeArray The JS array to be converted.
|
|
* @return {!Interpreter.Object} The equivalent JS interpreter array.
|
|
*/
|
|
Interpreter.prototype.arrayNativeToPseudo = function (nativeArray) {
|
|
var pseudoArray = this.createObjectProto(this.ARRAY_PROTO);
|
|
var props = Object.getOwnPropertyNames(nativeArray);
|
|
for (var i = 0; i < props.length; i++) {
|
|
this.setProperty(pseudoArray, props[i], nativeArray[props[i]]);
|
|
}
|
|
return pseudoArray;
|
|
};
|
|
|
|
/**
|
|
* Converts from a JS interpreter array to native JS array.
|
|
* Does handle non-numeric properties (like str.match's index prop).
|
|
* Does NOT recurse into the array's contents.
|
|
* @param {!Interpreter.Object} pseudoArray The JS interpreter array,
|
|
* or JS interpreter object pretending to be an array.
|
|
* @return {!Array} The equivalent native JS array.
|
|
*/
|
|
Interpreter.prototype.arrayPseudoToNative = function (pseudoArray) {
|
|
var nativeArray = [];
|
|
for (var key in pseudoArray.properties) {
|
|
nativeArray[key] = this.getProperty(pseudoArray, key);
|
|
}
|
|
// pseudoArray might be an object pretending to be an array. In this case
|
|
// it's possible that length is non-existent, invalid, or smaller than the
|
|
// largest defined numeric property. Set length explicitly here.
|
|
nativeArray.length = Interpreter.legalArrayLength(this.getProperty(pseudoArray, "length")) || 0;
|
|
return nativeArray;
|
|
};
|
|
|
|
/**
|
|
* Look up the prototype for this value.
|
|
* @param {Interpreter.Value} value Data object.
|
|
* @return {Interpreter.Object} Prototype object, null if none.
|
|
*/
|
|
Interpreter.prototype.getPrototype = function (value) {
|
|
switch (typeof value) {
|
|
case "number":
|
|
return this.NUMBER.properties["prototype"];
|
|
case "boolean":
|
|
return this.BOOLEAN.properties["prototype"];
|
|
case "string":
|
|
return this.STRING.properties["prototype"];
|
|
}
|
|
if (value) {
|
|
return value.proto;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Fetch a property value from a data object.
|
|
* @param {Interpreter.Value} obj Data object.
|
|
* @param {Interpreter.Value} name Name of property.
|
|
* @param {Acorn AST Node} node Node that triggered this function. Used by Bitburner for getting error line numbers
|
|
* @return {Interpreter.Value} Property value (may be undefined).
|
|
*/
|
|
Interpreter.prototype.getProperty = function (obj, name, node) {
|
|
name = String(name);
|
|
if (obj === undefined || obj === null) {
|
|
let lineNum;
|
|
if (node != null && node.loc != null && node.loc.start != null) {
|
|
lineNum = node.loc.start.line;
|
|
}
|
|
this.throwException(this.TYPE_ERROR, "Cannot read property '" + name + "' of " + obj, lineNum);
|
|
}
|
|
if (name === "length") {
|
|
// Special cases for magic length property.
|
|
if (this.isa(obj, this.STRING)) {
|
|
return String(obj).length;
|
|
}
|
|
} else if (name.charCodeAt(0) < 0x40) {
|
|
// Might have numbers in there?
|
|
// Special cases for string array indexing
|
|
if (this.isa(obj, this.STRING)) {
|
|
var n = Interpreter.legalArrayIndex(name);
|
|
if (!isNaN(n) && n < String(obj).length) {
|
|
return String(obj)[n];
|
|
}
|
|
}
|
|
}
|
|
do {
|
|
if (obj.properties && name in obj.properties) {
|
|
var getter = obj.getter[name];
|
|
if (getter) {
|
|
// Flag this function as being a getter and thus needing immediate
|
|
// execution (rather than being the value of the property).
|
|
getter.isGetter = true;
|
|
return getter;
|
|
}
|
|
return obj.properties[name];
|
|
}
|
|
} while ((obj = this.getPrototype(obj)));
|
|
return undefined;
|
|
};
|
|
|
|
/**
|
|
* Does the named property exist on a data object.
|
|
* @param {Interpreter.Value} obj Data object.
|
|
* @param {Interpreter.Value} name Name of property.
|
|
* @return {boolean} True if property exists.
|
|
*/
|
|
Interpreter.prototype.hasProperty = function (obj, name) {
|
|
if (!obj.isObject) {
|
|
throw TypeError("Primitive data type has no properties");
|
|
}
|
|
name = String(name);
|
|
if (name === "length" && this.isa(obj, this.STRING)) {
|
|
return true;
|
|
}
|
|
if (this.isa(obj, this.STRING)) {
|
|
var n = Interpreter.legalArrayIndex(name);
|
|
if (!isNaN(n) && n < String(obj).length) {
|
|
return true;
|
|
}
|
|
}
|
|
do {
|
|
if (obj.properties && name in obj.properties) {
|
|
return true;
|
|
}
|
|
} while ((obj = this.getPrototype(obj)));
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Set a property value on a data object.
|
|
* @param {!Interpreter.Object} obj Data object.
|
|
* @param {Interpreter.Value} name Name of property.
|
|
* @param {Interpreter.Value} value New property value.
|
|
* Use Interpreter.VALUE_IN_DESCRIPTOR if value is handled by
|
|
* descriptor instead.
|
|
* @param {Object=} opt_descriptor Optional descriptor object.
|
|
* @return {!Interpreter.Object|undefined} Returns a setter function if one
|
|
* needs to be called, otherwise undefined.
|
|
*/
|
|
Interpreter.prototype.setProperty = function (obj, name, value, opt_descriptor) {
|
|
name = String(name);
|
|
if (obj === undefined || obj === null) {
|
|
this.throwException(this.TYPE_ERROR, "Cannot set property '" + name + "' of " + obj);
|
|
}
|
|
if (
|
|
opt_descriptor &&
|
|
("get" in opt_descriptor || "set" in opt_descriptor) &&
|
|
("value" in opt_descriptor || "writable" in opt_descriptor)
|
|
) {
|
|
this.throwException(
|
|
this.TYPE_ERROR,
|
|
"Invalid property descriptor. " + "Cannot both specify accessors and a value or writable attribute",
|
|
);
|
|
}
|
|
var strict = !this.stateStack || this.getScope().strict;
|
|
if (!obj.isObject) {
|
|
if (strict) {
|
|
this.throwException(this.TYPE_ERROR, "Can't create property '" + name + "' on '" + obj + "'");
|
|
}
|
|
return;
|
|
}
|
|
if (this.isa(obj, this.STRING)) {
|
|
var n = Interpreter.legalArrayIndex(name);
|
|
if (name === "length" || (!isNaN(n) && n < String(obj).length)) {
|
|
// Can't set length or letters on String objects.
|
|
if (strict) {
|
|
this.throwException(
|
|
this.TYPE_ERROR,
|
|
"Cannot assign to read only " + "property '" + name + "' of String '" + obj.data + "'",
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (obj.class === "Array") {
|
|
// Arrays have a magic length variable that is bound to the elements.
|
|
var length = obj.properties.length;
|
|
var i;
|
|
if (name === "length") {
|
|
// Delete elements if length is smaller.
|
|
if (opt_descriptor) {
|
|
if (!("value" in opt_descriptor)) {
|
|
return;
|
|
}
|
|
value = opt_descriptor.value;
|
|
}
|
|
value = Interpreter.legalArrayLength(value);
|
|
if (isNaN(value)) {
|
|
this.throwException(this.RANGE_ERROR, "Invalid array length");
|
|
}
|
|
if (value < length) {
|
|
for (i in obj.properties) {
|
|
i = Interpreter.legalArrayIndex(i);
|
|
if (!isNaN(i) && value <= i) {
|
|
delete obj.properties[i];
|
|
}
|
|
}
|
|
}
|
|
} else if (!isNaN((i = Interpreter.legalArrayIndex(name)))) {
|
|
// Increase length if this index is larger.
|
|
obj.properties.length = Math.max(length, i + 1);
|
|
}
|
|
}
|
|
if (obj.preventExtensions && !(name in obj.properties)) {
|
|
if (strict) {
|
|
this.throwException(this.TYPE_ERROR, "Can't add property '" + name + "', object is not extensible");
|
|
}
|
|
return;
|
|
}
|
|
if (opt_descriptor) {
|
|
// Define the property.
|
|
if ("get" in opt_descriptor) {
|
|
if (opt_descriptor.get) {
|
|
obj.getter[name] = opt_descriptor.get;
|
|
} else {
|
|
delete obj.getter[name];
|
|
}
|
|
}
|
|
if ("set" in opt_descriptor) {
|
|
if (opt_descriptor.set) {
|
|
obj.setter[name] = opt_descriptor.set;
|
|
} else {
|
|
delete obj.setter[name];
|
|
}
|
|
}
|
|
var descriptor = {};
|
|
if ("configurable" in opt_descriptor) {
|
|
descriptor.configurable = opt_descriptor.configurable;
|
|
}
|
|
if ("enumerable" in opt_descriptor) {
|
|
descriptor.enumerable = opt_descriptor.enumerable;
|
|
}
|
|
if ("writable" in opt_descriptor) {
|
|
descriptor.writable = opt_descriptor.writable;
|
|
delete obj.getter[name];
|
|
delete obj.setter[name];
|
|
}
|
|
if ("value" in opt_descriptor) {
|
|
descriptor.value = opt_descriptor.value;
|
|
delete obj.getter[name];
|
|
delete obj.setter[name];
|
|
} else if (value !== Interpreter.VALUE_IN_DESCRIPTOR) {
|
|
descriptor.value = value;
|
|
delete obj.getter[name];
|
|
delete obj.setter[name];
|
|
}
|
|
try {
|
|
Object.defineProperty(obj.properties, name, descriptor);
|
|
} catch (e) {
|
|
this.throwException(this.TYPE_ERROR, "Cannot redefine property: " + name);
|
|
}
|
|
} else {
|
|
// Set the property.
|
|
if (value === Interpreter.VALUE_IN_DESCRIPTOR) {
|
|
throw ReferenceError("Value not specified.");
|
|
}
|
|
// Determine the parent (possibly self) where the property is defined.
|
|
var defObj = obj;
|
|
while (!(name in defObj.properties)) {
|
|
defObj = this.getPrototype(defObj);
|
|
if (!defObj) {
|
|
// This is a new property.
|
|
defObj = obj;
|
|
break;
|
|
}
|
|
}
|
|
if (defObj.setter && defObj.setter[name]) {
|
|
return defObj.setter[name];
|
|
}
|
|
if (defObj.getter && defObj.getter[name]) {
|
|
if (strict) {
|
|
this.throwException(
|
|
this.TYPE_ERROR,
|
|
"Cannot set property '" + name + "' of object '" + obj + "' which only has a getter",
|
|
);
|
|
}
|
|
} else {
|
|
// No setter, simple assignment.
|
|
try {
|
|
obj.properties[name] = value;
|
|
} catch (e) {
|
|
if (strict) {
|
|
this.throwException(
|
|
this.TYPE_ERROR,
|
|
"Cannot assign to read only " + "property '" + name + "' of object '" + obj + "'",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Convenience method for adding a native function as a non-enumerable property
|
|
* onto an object's prototype.
|
|
* @param {!Interpreter.Object} obj Data object.
|
|
* @param {Interpreter.Value} name Name of property.
|
|
* @param {!Function} wrapper Function object.
|
|
*/
|
|
Interpreter.prototype.setNativeFunctionPrototype = function (obj, name, wrapper) {
|
|
this.setProperty(
|
|
obj.properties["prototype"],
|
|
name,
|
|
this.createNativeFunction(wrapper, false),
|
|
Interpreter.NONENUMERABLE_DESCRIPTOR,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns the current scope from the stateStack.
|
|
* @return {!Interpreter.Object} Current scope dictionary.
|
|
*/
|
|
Interpreter.prototype.getScope = function () {
|
|
var scope = this.stateStack[this.stateStack.length - 1].scope;
|
|
if (!scope) {
|
|
throw Error("No scope found.");
|
|
}
|
|
return scope;
|
|
};
|
|
|
|
/**
|
|
* Create a new scope dictionary.
|
|
* @param {!Object} node AST node defining the scope container
|
|
* (e.g. a function).
|
|
* @param {Interpreter.Object} parentScope Scope to link to.
|
|
* @return {!Interpreter.Object} New scope.
|
|
*/
|
|
Interpreter.prototype.createScope = function (node, parentScope) {
|
|
var scope = this.createObjectProto(null);
|
|
scope.parentScope = parentScope;
|
|
if (!parentScope) {
|
|
this.initGlobalScope(scope);
|
|
}
|
|
this.populateScope_(node, scope);
|
|
|
|
// Determine if this scope starts with 'use strict'.
|
|
scope.strict = false;
|
|
if (parentScope && parentScope.strict) {
|
|
scope.strict = true;
|
|
} else {
|
|
var firstNode = node["body"] && node["body"][0];
|
|
if (
|
|
firstNode &&
|
|
firstNode.expression &&
|
|
firstNode.expression["type"] === "Literal" &&
|
|
firstNode.expression.value === "use strict"
|
|
) {
|
|
scope.strict = true;
|
|
}
|
|
}
|
|
return scope;
|
|
};
|
|
|
|
/**
|
|
* Create a new special scope dictionary. Similar to createScope(), but
|
|
* doesn't assume that the scope is for a function body.
|
|
* This is used for 'catch' clauses and 'with' statements.
|
|
* @param {!Interpreter.Object} parentScope Scope to link to.
|
|
* @param {Interpreter.Object=} opt_scope Optional object to transform into
|
|
* scope.
|
|
* @return {!Interpreter.Object} New scope.
|
|
*/
|
|
Interpreter.prototype.createSpecialScope = function (parentScope, opt_scope) {
|
|
if (!parentScope) {
|
|
throw Error("parentScope required");
|
|
}
|
|
var scope = opt_scope || this.createObjectProto(null);
|
|
scope.parentScope = parentScope;
|
|
scope.strict = parentScope.strict;
|
|
return scope;
|
|
};
|
|
|
|
/**
|
|
* Retrieves a value from the scope chain.
|
|
* @param {string} name Name of variable.
|
|
* @param {Acorn AST Node} node Node that triggered this function. Used by Bitburner for getting error line number
|
|
* @return {Interpreter.Value} Any value.
|
|
* May be flagged as being a getter and thus needing immediate execution
|
|
* (rather than being the value of the property).
|
|
*/
|
|
Interpreter.prototype.getValueFromScope = function (name, node) {
|
|
var scope = this.getScope();
|
|
while (scope && scope !== this.global) {
|
|
if (name in scope.properties) {
|
|
return scope.properties[name];
|
|
}
|
|
scope = scope.parentScope;
|
|
}
|
|
// The root scope is also an object which has inherited properties and
|
|
// could also have getters.
|
|
if (scope === this.global && this.hasProperty(scope, name)) {
|
|
return this.getProperty(scope, name);
|
|
}
|
|
// Typeof operator is unique: it can safely look at non-defined variables.
|
|
var prevNode = this.stateStack[this.stateStack.length - 1].node;
|
|
if (prevNode["type"] === "UnaryExpression" && prevNode["operator"] === "typeof") {
|
|
return undefined;
|
|
}
|
|
|
|
var lineNum;
|
|
if (node != null && node.loc != null && node.loc.start != null) {
|
|
lineNum = node.loc.start.line;
|
|
}
|
|
this.throwException(this.REFERENCE_ERROR, name + " is not defined", lineNum);
|
|
};
|
|
|
|
/**
|
|
* Sets a value to the current scope.
|
|
* @param {string} name Name of variable.
|
|
* @param {Interpreter.Value} value Value.
|
|
* @return {!Interpreter.Object|undefined} Returns a setter function if one
|
|
* needs to be called, otherwise undefined.
|
|
*/
|
|
Interpreter.prototype.setValueToScope = function (name, value) {
|
|
var scope = this.getScope();
|
|
var strict = scope.strict;
|
|
while (scope && scope !== this.global) {
|
|
if (name in scope.properties) {
|
|
scope.properties[name] = value;
|
|
return undefined;
|
|
}
|
|
scope = scope.parentScope;
|
|
}
|
|
// The root scope is also an object which has readonly properties and
|
|
// could also have setters.
|
|
if (scope === this.global && (!strict || this.hasProperty(scope, name))) {
|
|
return this.setProperty(scope, name, value);
|
|
}
|
|
this.throwException(this.REFERENCE_ERROR, name + " is not defined");
|
|
};
|
|
|
|
/**
|
|
* Create a new scope for the given node.
|
|
* @param {!Object} node AST node (program or function).
|
|
* @param {!Interpreter.Object} scope Scope dictionary to populate.
|
|
* @private
|
|
*/
|
|
Interpreter.prototype.populateScope_ = function (node, scope) {
|
|
if (node["type"] === "VariableDeclaration") {
|
|
for (var i = 0; i < node["declarations"].length; i++) {
|
|
this.setProperty(scope, node["declarations"][i]["id"]["name"], undefined, Interpreter.VARIABLE_DESCRIPTOR);
|
|
}
|
|
} else if (node["type"] === "FunctionDeclaration") {
|
|
this.setProperty(scope, node["id"]["name"], this.createFunction(node, scope), Interpreter.VARIABLE_DESCRIPTOR);
|
|
return; // Do not recurse into function.
|
|
} else if (node["type"] === "FunctionExpression") {
|
|
return; // Do not recurse into function.
|
|
} else if (node["type"] === "ExpressionStatement") {
|
|
return; // Expressions can't contain variable/function declarations.
|
|
}
|
|
var nodeClass = node["constructor"];
|
|
for (var name in node) {
|
|
var prop = node[name];
|
|
if (prop && typeof prop === "object") {
|
|
if (Array.isArray(prop)) {
|
|
for (var i = 0; i < prop.length; i++) {
|
|
if (prop[i] && prop[i].constructor === nodeClass) {
|
|
this.populateScope_(prop[i], scope);
|
|
}
|
|
}
|
|
} else {
|
|
if (prop.constructor === nodeClass) {
|
|
this.populateScope_(prop, scope);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove start and end values from AST, or set start and end values to a
|
|
* constant value. Used to remove highlighting from polyfills and to set
|
|
* highlighting in an eval to cover the entire eval expression.
|
|
* @param {!Object} node AST node.
|
|
* @param {number=} start Starting character of all nodes, or undefined.
|
|
* @param {number=} end Ending character of all nodes, or undefined.
|
|
* @private
|
|
*/
|
|
Interpreter.prototype.stripLocations_ = function (node, start, end) {
|
|
if (start) {
|
|
node["start"] = start;
|
|
} else {
|
|
delete node["start"];
|
|
}
|
|
if (end) {
|
|
node["end"] = end;
|
|
} else {
|
|
delete node["end"];
|
|
}
|
|
for (var name in node) {
|
|
if (node.hasOwnProperty(name)) {
|
|
var prop = node[name];
|
|
if (prop && typeof prop === "object") {
|
|
this.stripLocations_(prop, start, end);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Is the current state directly being called with as a construction with 'new'.
|
|
* @return {boolean} True if 'new foo()', false if 'foo()'.
|
|
*/
|
|
Interpreter.prototype.calledWithNew = function () {
|
|
return this.stateStack[this.stateStack.length - 1].isConstructor;
|
|
};
|
|
|
|
/**
|
|
* Gets a value from the scope chain or from an object property.
|
|
* @param {!Array} ref Name of variable or object/propname tuple.
|
|
* @param {Acorn AST Node} node Node that triggered this function. Used by Bitburner for getting error line number
|
|
* @return {Interpreter.Value} Any value.
|
|
* May be flagged as being a getter and thus needing immediate execution
|
|
* (rather than being the value of the property).
|
|
*/
|
|
Interpreter.prototype.getValue = function (ref, node) {
|
|
if (ref[0] === Interpreter.SCOPE_REFERENCE) {
|
|
// A null/varname variable lookup.
|
|
return this.getValueFromScope(ref[1], node);
|
|
} else {
|
|
// An obj/prop components tuple (foo.bar).
|
|
return this.getProperty(ref[0], ref[1], node);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets a value to the scope chain or to an object property.
|
|
* @param {!Array} ref Name of variable or object/propname tuple.
|
|
* @param {Interpreter.Value} value Value.
|
|
* @return {!Interpreter.Object|undefined} Returns a setter function if one
|
|
* needs to be called, otherwise undefined.
|
|
*/
|
|
Interpreter.prototype.setValue = function (ref, value) {
|
|
if (ref[0] === Interpreter.SCOPE_REFERENCE) {
|
|
// A null/varname variable lookup.
|
|
return this.setValueToScope(ref[1], value);
|
|
} else {
|
|
// An obj/prop components tuple (foo.bar).
|
|
return this.setProperty(ref[0], ref[1], value);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Completion Value Types.
|
|
* @enum {number}
|
|
*/
|
|
Interpreter.Completion = {
|
|
NORMAL: 0,
|
|
BREAK: 1,
|
|
CONTINUE: 2,
|
|
RETURN: 3,
|
|
THROW: 4,
|
|
};
|
|
|
|
/**
|
|
* Throw an exception in the interpreter that can be handled by an
|
|
* interpreter try/catch statement. If unhandled, a real exception will
|
|
* be thrown. Can be called with either an error class and a message, or
|
|
* with an actual object to be thrown.
|
|
* @param {!Interpreter.Object} 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.throwException = function (errorClass, opt_message, lineNumber) {
|
|
if (opt_message === undefined) {
|
|
var error = errorClass; // This is a value to throw, not an error class.
|
|
} else {
|
|
var error = this.createObject(errorClass);
|
|
this.setProperty(error, "message", opt_message, Interpreter.NONENUMERABLE_DESCRIPTOR);
|
|
}
|
|
var lineNumErrorMsg;
|
|
if (lineNumber != null) {
|
|
lineNumErrorMsg = this.getErrorLineNumberMessage(lineNumber);
|
|
}
|
|
this.unwind(Interpreter.Completion.THROW, error, undefined, lineNumErrorMsg);
|
|
// Abort anything related to the current step.
|
|
throw Interpreter.STEP_ERROR;
|
|
};
|
|
|
|
/**
|
|
* Unwind the stack to the innermost relevant enclosing TryStatement,
|
|
* For/ForIn/WhileStatement or Call/NewExpression. If this results in
|
|
* the stack being completely unwound the thread will be terminated
|
|
* and the appropriate error being thrown.
|
|
* @param {Interpreter.Completion} type Completion type.
|
|
* @param {Interpreter.Value=} value Value computed, returned or thrown.
|
|
* @param {string=} label Target label for break or return.
|
|
*/
|
|
Interpreter.prototype.unwind = function (type, value, label, lineNumberMsg = "") {
|
|
if (type === Interpreter.Completion.NORMAL) {
|
|
throw TypeError("Should not unwind for NORMAL completions");
|
|
}
|
|
|
|
for (var stack = this.stateStack; stack.length > 0; stack.pop()) {
|
|
var state = stack[stack.length - 1];
|
|
switch (state.node["type"]) {
|
|
case "TryStatement":
|
|
state.cv = { type: type, value: value, label: label };
|
|
return;
|
|
case "CallExpression":
|
|
case "NewExpression":
|
|
if (type === Interpreter.Completion.RETURN) {
|
|
state.value = value;
|
|
return;
|
|
} else if (type !== Interpreter.Completion.THROW) {
|
|
throw Error("Unsynatctic break/continue not rejected by Acorn");
|
|
}
|
|
}
|
|
if (type === Interpreter.Completion.BREAK) {
|
|
if (label ? state.labels && state.labels.indexOf(label) !== -1 : state.isLoop || state.isSwitch) {
|
|
stack.pop();
|
|
return;
|
|
}
|
|
} else if (type === Interpreter.Completion.CONTINUE) {
|
|
if (label ? state.labels && state.labels.indexOf(label) !== -1 : state.isLoop) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unhandled completion. Throw a real error.
|
|
var realError;
|
|
if (this.isa(value, this.ERROR)) {
|
|
var errorTable = {
|
|
EvalError: EvalError,
|
|
RangeError: RangeError,
|
|
ReferenceError: ReferenceError,
|
|
SyntaxError: SyntaxError,
|
|
TypeError: TypeError,
|
|
URIError: URIError,
|
|
};
|
|
var name = this.getProperty(value, "name").toString();
|
|
var message = this.getProperty(value, "message").valueOf();
|
|
var type = errorTable[name] || Error;
|
|
realError = type(message + lineNumberMsg);
|
|
} else {
|
|
realError = String(value) + lineNumberMsg;
|
|
}
|
|
throw realError;
|
|
};
|
|
|
|
/**
|
|
* Create a call to a getter function.
|
|
* @param {!Interpreter.Object} func Function to execute.
|
|
* @param {!Interpreter.Object|!Array} left
|
|
* Name of variable or object/propname tuple.
|
|
* @private
|
|
*/
|
|
Interpreter.prototype.createGetter_ = function (func, left) {
|
|
// Normally 'this' will be specified as the object component (o.x).
|
|
// Sometimes 'this' is explicitly provided (o).
|
|
var funcThis = Array.isArray(left) ? left[0] : left;
|
|
var node = new this.nodeConstructor();
|
|
node["type"] = "CallExpression";
|
|
var state = new Interpreter.State(node, this.stateStack[this.stateStack.length - 1].scope);
|
|
state.doneCallee_ = true;
|
|
state.funcThis_ = funcThis;
|
|
state.func_ = func;
|
|
state.doneArgs_ = true;
|
|
state.arguments_ = [];
|
|
return state;
|
|
};
|
|
|
|
/**
|
|
* Create a call to a setter function.
|
|
* @param {!Interpreter.Object} func Function to execute.
|
|
* @param {!Interpreter.Object|!Array} left
|
|
* Name of variable or object/propname tuple.
|
|
* @param {Interpreter.Value} value Value to set.
|
|
* @private
|
|
*/
|
|
Interpreter.prototype.createSetter_ = function (func, left, value) {
|
|
// Normally 'this' will be specified as the object component (o.x).
|
|
// Sometimes 'this' is implicitly the global object (x).
|
|
var funcThis = Array.isArray(left) ? left[0] : this.global;
|
|
var node = new this.nodeConstructor();
|
|
node["type"] = "CallExpression";
|
|
var state = new Interpreter.State(node, this.stateStack[this.stateStack.length - 1].scope);
|
|
state.doneCallee_ = true;
|
|
state.funcThis_ = funcThis;
|
|
state.func_ = func;
|
|
state.doneArgs_ = true;
|
|
state.arguments_ = [value];
|
|
return state;
|
|
};
|
|
|
|
/**
|
|
* Class for a state.
|
|
* @param {!Object} node AST node for the state.
|
|
* @param {!Interpreter.Object} scope Scope object for the state.
|
|
* @constructor
|
|
*/
|
|
Interpreter.State = function (node, scope) {
|
|
this.node = node;
|
|
this.scope = scope;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Functions to handle each node type.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
Interpreter.prototype["stepArrayExpression"] = function (stack, state, node) {
|
|
var elements = node["elements"];
|
|
var n = state.n_ || 0;
|
|
if (!state.array_) {
|
|
state.array_ = this.createObjectProto(this.ARRAY_PROTO);
|
|
state.array_.properties.length = elements.length;
|
|
} else {
|
|
this.setProperty(state.array_, n, state.value);
|
|
n++;
|
|
}
|
|
while (n < elements.length) {
|
|
// Skip missing elements - they're not defined, not undefined.
|
|
if (elements[n]) {
|
|
state.n_ = n;
|
|
return new Interpreter.State(elements[n], state.scope);
|
|
}
|
|
n++;
|
|
}
|
|
stack.pop();
|
|
stack[stack.length - 1].value = state.array_;
|
|
};
|
|
|
|
Interpreter.prototype["stepAssignmentExpression"] = function (stack, state, node) {
|
|
if (!state.doneLeft_) {
|
|
state.doneLeft_ = true;
|
|
var nextState = new Interpreter.State(node["left"], state.scope);
|
|
nextState.components = true;
|
|
return nextState;
|
|
}
|
|
if (!state.doneRight_) {
|
|
if (!state.leftReference_) {
|
|
state.leftReference_ = state.value;
|
|
}
|
|
if (state.doneGetter_) {
|
|
state.leftValue_ = state.value;
|
|
}
|
|
if (!state.doneGetter_ && node["operator"] !== "=") {
|
|
var leftValue = this.getValue(state.leftReference_, node);
|
|
state.leftValue_ = leftValue;
|
|
if (leftValue && typeof leftValue === "object" && leftValue.isGetter) {
|
|
// Clear the getter flag and call the getter function.
|
|
leftValue.isGetter = false;
|
|
state.doneGetter_ = true;
|
|
var func = /** @type {!Interpreter.Object} */ (leftValue);
|
|
return this.createGetter_(func, state.leftReference_);
|
|
}
|
|
}
|
|
state.doneRight_ = true;
|
|
return new Interpreter.State(node["right"], state.scope);
|
|
}
|
|
if (state.doneSetter_) {
|
|
// Return if setter function.
|
|
// Setter method on property has completed.
|
|
// Ignore its return value, and use the original set value instead.
|
|
stack.pop();
|
|
stack[stack.length - 1].value = state.setterValue_;
|
|
return;
|
|
}
|
|
var value = state.leftValue_;
|
|
var rightValue = state.value;
|
|
switch (node["operator"]) {
|
|
case "=":
|
|
value = rightValue;
|
|
break;
|
|
case "+=":
|
|
value += rightValue;
|
|
break;
|
|
case "-=":
|
|
value -= rightValue;
|
|
break;
|
|
case "*=":
|
|
value *= rightValue;
|
|
break;
|
|
case "/=":
|
|
value /= rightValue;
|
|
break;
|
|
case "%=":
|
|
value %= rightValue;
|
|
break;
|
|
case "<<=":
|
|
value <<= rightValue;
|
|
break;
|
|
case ">>=":
|
|
value >>= rightValue;
|
|
break;
|
|
case ">>>=":
|
|
value >>>= rightValue;
|
|
break;
|
|
case "&=":
|
|
value &= rightValue;
|
|
break;
|
|
case "^=":
|
|
value ^= rightValue;
|
|
break;
|
|
case "|=":
|
|
value |= rightValue;
|
|
break;
|
|
default:
|
|
throw SyntaxError("Unknown assignment expression: " + node["operator"]);
|
|
}
|
|
var setter = this.setValue(state.leftReference_, value);
|
|
if (setter) {
|
|
state.doneSetter_ = true;
|
|
state.setterValue_ = value;
|
|
return this.createSetter_(setter, state.leftReference_, value);
|
|
}
|
|
// Return if no setter function.
|
|
stack.pop();
|
|
stack[stack.length - 1].value = value;
|
|
};
|
|
|
|
Interpreter.prototype["stepBinaryExpression"] = function (stack, state, node) {
|
|
if (!state.doneLeft_) {
|
|
state.doneLeft_ = true;
|
|
return new Interpreter.State(node["left"], state.scope);
|
|
}
|
|
if (!state.doneRight_) {
|
|
state.doneRight_ = true;
|
|
state.leftValue_ = state.value;
|
|
return new Interpreter.State(node["right"], state.scope);
|
|
}
|
|
stack.pop();
|
|
var leftValue = state.leftValue_;
|
|
var rightValue = state.value;
|
|
var value;
|
|
switch (node["operator"]) {
|
|
case "==":
|
|
value = leftValue == rightValue;
|
|
break;
|
|
case "!=":
|
|
value = leftValue != rightValue;
|
|
break;
|
|
case "===":
|
|
value = leftValue === rightValue;
|
|
break;
|
|
case "!==":
|
|
value = leftValue !== rightValue;
|
|
break;
|
|
case ">":
|
|
value = leftValue > rightValue;
|
|
break;
|
|
case ">=":
|
|
value = leftValue >= rightValue;
|
|
break;
|
|
case "<":
|
|
value = leftValue < rightValue;
|
|
break;
|
|
case "<=":
|
|
value = leftValue <= rightValue;
|
|
break;
|
|
case "+":
|
|
value = leftValue + rightValue;
|
|
break;
|
|
case "-":
|
|
value = leftValue - rightValue;
|
|
break;
|
|
case "*":
|
|
value = leftValue * rightValue;
|
|
break;
|
|
case "/":
|
|
value = leftValue / rightValue;
|
|
break;
|
|
case "%":
|
|
value = leftValue % rightValue;
|
|
break;
|
|
case "&":
|
|
value = leftValue & rightValue;
|
|
break;
|
|
case "|":
|
|
value = leftValue | rightValue;
|
|
break;
|
|
case "^":
|
|
value = leftValue ^ rightValue;
|
|
break;
|
|
case "<<":
|
|
value = leftValue << rightValue;
|
|
break;
|
|
case ">>":
|
|
value = leftValue >> rightValue;
|
|
break;
|
|
case ">>>":
|
|
value = leftValue >>> rightValue;
|
|
break;
|
|
case "in":
|
|
if (!rightValue || !rightValue.isObject) {
|
|
let lineNum = this.getErrorLineNumber(node);
|
|
this.throwException(this.TYPE_ERROR, "'in' expects an object, not '" + rightValue + "'", lineNum);
|
|
}
|
|
value = this.hasProperty(rightValue, leftValue);
|
|
break;
|
|
case "instanceof":
|
|
if (!this.isa(rightValue, this.FUNCTION)) {
|
|
let lineNum = this.getErrorLineNumber(node);
|
|
this.throwException(this.TYPE_ERROR, "Right-hand side of instanceof is not an object", lineNum);
|
|
}
|
|
value = leftValue.isObject ? this.isa(leftValue, rightValue) : false;
|
|
break;
|
|
default:
|
|
throw SyntaxError("Unknown binary operator: " + node["operator"]);
|
|
}
|
|
stack[stack.length - 1].value = value;
|
|
};
|
|
|
|
Interpreter.prototype["stepBlockStatement"] = function (stack, state, node) {
|
|
var n = state.n_ || 0;
|
|
var expression = node["body"][n];
|
|
if (expression) {
|
|
state.n_ = n + 1;
|
|
return new Interpreter.State(expression, state.scope);
|
|
}
|
|
stack.pop();
|
|
};
|
|
|
|
Interpreter.prototype["stepBreakStatement"] = function (stack, state, node) {
|
|
var label = node["label"] && node["label"]["name"];
|
|
this.unwind(Interpreter.Completion.BREAK, undefined, label);
|
|
};
|
|
|
|
Interpreter.prototype["stepCallExpression"] = function (stack, state, node) {
|
|
if (!state.doneCallee_) {
|
|
state.doneCallee_ = 1;
|
|
// Components needed to determine value of 'this'.
|
|
var nextState = new Interpreter.State(node["callee"], state.scope);
|
|
nextState.components = true;
|
|
return nextState;
|
|
}
|
|
if (state.doneCallee_ === 1) {
|
|
// Determine value of the function.
|
|
state.doneCallee_ = 2;
|
|
var func = state.value;
|
|
if (Array.isArray(func)) {
|
|
state.func_ = this.getValue(func, node);
|
|
if (func[0] === Interpreter.SCOPE_REFERENCE) {
|
|
// (Globally or locally) named function. Is it named 'eval'?
|
|
state.directEval_ = func[1] === "eval";
|
|
} else {
|
|
// Method function, 'this' is object (ignored if invoked as 'new').
|
|
state.funcThis_ = func[0];
|
|
}
|
|
func = state.func_;
|
|
if (func && typeof func === "object" && func.isGetter) {
|
|
// Clear the getter flag and call the getter function.
|
|
func.isGetter = false;
|
|
state.doneCallee_ = 1;
|
|
return this.createGetter_(/** @type {!Interpreter.Object} */ (func), state.value);
|
|
}
|
|
} else {
|
|
// Already evaluated function: (function(){...})();
|
|
state.func_ = func;
|
|
}
|
|
state.arguments_ = [];
|
|
state.n_ = 0;
|
|
}
|
|
var func = state.func_;
|
|
if (!state.doneArgs_) {
|
|
if (state.n_ !== 0) {
|
|
state.arguments_.push(state.value);
|
|
}
|
|
if (node["arguments"][state.n_]) {
|
|
return new Interpreter.State(node["arguments"][state.n_++], state.scope);
|
|
}
|
|
// Determine value of 'this' in function.
|
|
if (node["type"] === "NewExpression") {
|
|
if (func.illegalConstructor) {
|
|
// Illegal: new escape();
|
|
let lineNum = this.getErrorLineNumber(node);
|
|
this.throwException(this.TYPE_ERROR, func + " is not a constructor", lineNum);
|
|
}
|
|
// Constructor, 'this' is new object.
|
|
var proto = func.properties["prototype"];
|
|
if (typeof proto !== "object" || proto === null) {
|
|
// Non-object prototypes default to Object.prototype.
|
|
proto = this.OBJECT_PROTO;
|
|
}
|
|
state.funcThis_ = this.createObjectProto(proto);
|
|
state.isConstructor = true;
|
|
} else if (state.funcThis_ === undefined) {
|
|
// Global function, 'this' is global object (or 'undefined' if strict).
|
|
state.funcThis_ = state.scope.strict ? undefined : this.global;
|
|
}
|
|
state.doneArgs_ = true;
|
|
}
|
|
if (!state.doneExec_) {
|
|
state.doneExec_ = true;
|
|
if (!func || !func.isObject) {
|
|
let lineNum = this.getErrorLineNumber(node);
|
|
this.throwException(this.TYPE_ERROR, func + " is not a function", lineNum);
|
|
}
|
|
var funcNode = func.node;
|
|
if (funcNode) {
|
|
var scope = this.createScope(funcNode["body"], func.parentScope);
|
|
// Add all arguments.
|
|
for (var i = 0; i < funcNode["params"].length; i++) {
|
|
var paramName = funcNode["params"][i]["name"];
|
|
var paramValue = state.arguments_.length > i ? state.arguments_[i] : undefined;
|
|
this.setProperty(scope, paramName, paramValue);
|
|
}
|
|
// Build arguments variable.
|
|
var argsList = this.createObjectProto(this.ARRAY_PROTO);
|
|
for (var i = 0; i < state.arguments_.length; i++) {
|
|
this.setProperty(argsList, i, state.arguments_[i]);
|
|
}
|
|
this.setProperty(scope, "arguments", argsList);
|
|
// Add the function's name (var x = function foo(){};)
|
|
var name = funcNode["id"] && funcNode["id"]["name"];
|
|
if (name) {
|
|
this.setProperty(scope, name, func);
|
|
}
|
|
this.setProperty(scope, "this", state.funcThis_, Interpreter.READONLY_DESCRIPTOR);
|
|
state.value = undefined; // Default value if no explicit return.
|
|
return new Interpreter.State(funcNode["body"], scope);
|
|
} else if (func.eval) {
|
|
var code = state.arguments_[0];
|
|
if (typeof code !== "string") {
|
|
// JS does not parse String objects:
|
|
// eval(new String('1 + 1')) -> '1 + 1'
|
|
state.value = code;
|
|
} else {
|
|
try {
|
|
var ast = acorn.parse(code.toString(), Interpreter.PARSE_OPTIONS);
|
|
} catch (e) {
|
|
// Acorn threw a SyntaxError. Rethrow as a trappable error.
|
|
let lineNum = this.getErrorLineNumber(node);
|
|
this.throwException(this.SYNTAX_ERROR, "Invalid code: " + e.message, lineNum);
|
|
}
|
|
var evalNode = new this.nodeConstructor();
|
|
evalNode["type"] = "EvalProgram_";
|
|
evalNode["body"] = ast["body"];
|
|
this.stripLocations_(evalNode, node["start"], node["end"]);
|
|
// Create new scope and update it with definitions in eval().
|
|
var scope = state.directEval_ ? state.scope : this.global;
|
|
if (scope.strict) {
|
|
// Strict mode get its own scope in eval.
|
|
scope = this.createScope(ast, scope);
|
|
} else {
|
|
// Non-strict mode pollutes the current scope.
|
|
this.populateScope_(ast, scope);
|
|
}
|
|
this.value = undefined; // Default value if no code.
|
|
return new Interpreter.State(evalNode, scope);
|
|
}
|
|
} else if (func.nativeFunc) {
|
|
state.value = func.nativeFunc.apply(state.funcThis_, state.arguments_);
|
|
} else if (func.asyncFunc) {
|
|
var thisInterpreter = this;
|
|
var callback = function (value) {
|
|
state.value = value;
|
|
thisInterpreter.paused_ = false;
|
|
};
|
|
var argsWithCallback = state.arguments_.concat(callback);
|
|
this.paused_ = true;
|
|
func.asyncFunc.apply(state.funcThis_, argsWithCallback);
|
|
return;
|
|
} else {
|
|
/* A child of a function is a function but is not callable. For example:
|
|
var F = function() {};
|
|
F.prototype = escape;
|
|
var f = new F();
|
|
f();
|
|
*/
|
|
let lineNum = this.getErrorLineNumber(node);
|
|
this.throwException(this.TYPE_ERROR, func.class + " is not a function", lineNum);
|
|
}
|
|
} else {
|
|
// Execution complete. Put the return value on the stack.
|
|
stack.pop();
|
|
if (state.isConstructor && typeof state.value !== "object") {
|
|
stack[stack.length - 1].value = state.funcThis_;
|
|
} else {
|
|
stack[stack.length - 1].value = state.value;
|
|
}
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepCatchClause"] = function (stack, state, node) {
|
|
if (!state.done_) {
|
|
state.done_ = true;
|
|
// Create an empty scope.
|
|
var scope = this.createSpecialScope(state.scope);
|
|
// Add the argument.
|
|
this.setProperty(scope, node["param"]["name"], state.throwValue);
|
|
// Execute catch clause.
|
|
return new Interpreter.State(node["body"], scope);
|
|
} else {
|
|
stack.pop();
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepConditionalExpression"] = function (stack, state, node) {
|
|
var mode = state.mode_ || 0;
|
|
if (mode === 0) {
|
|
state.mode_ = 1;
|
|
return new Interpreter.State(node["test"], state.scope);
|
|
}
|
|
if (mode === 1) {
|
|
state.mode_ = 2;
|
|
var value = Boolean(state.value);
|
|
if (value && node["consequent"]) {
|
|
// Execute 'if' block.
|
|
return new Interpreter.State(node["consequent"], state.scope);
|
|
} else if (!value && node["alternate"]) {
|
|
// Execute 'else' block.
|
|
return new Interpreter.State(node["alternate"], state.scope);
|
|
}
|
|
// eval('1;if(false){2}') -> undefined
|
|
this.value = undefined;
|
|
}
|
|
stack.pop();
|
|
if (node["type"] === "ConditionalExpression") {
|
|
stack[stack.length - 1].value = state.value;
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepContinueStatement"] = function (stack, state, node) {
|
|
var label = node["label"] && node["label"]["name"];
|
|
this.unwind(Interpreter.Completion.CONTINUE, undefined, label);
|
|
};
|
|
|
|
Interpreter.prototype["stepDebuggerStatement"] = function (stack, state, node) {
|
|
// Do nothing. May be overridden by developers.
|
|
stack.pop();
|
|
};
|
|
|
|
Interpreter.prototype["stepDoWhileStatement"] = function (stack, state, node) {
|
|
if (node["type"] === "DoWhileStatement" && state.test_ === undefined) {
|
|
// First iteration of do/while executes without checking test.
|
|
state.value = true;
|
|
state.test_ = true;
|
|
}
|
|
if (!state.test_) {
|
|
state.test_ = true;
|
|
return new Interpreter.State(node["test"], state.scope);
|
|
}
|
|
if (!state.value) {
|
|
// Done, exit loop.
|
|
stack.pop();
|
|
} else if (node["body"]) {
|
|
// Execute the body.
|
|
state.test_ = false;
|
|
state.isLoop = true;
|
|
return new Interpreter.State(node["body"], state.scope);
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepEmptyStatement"] = function (stack, state, node) {
|
|
stack.pop();
|
|
};
|
|
|
|
Interpreter.prototype["stepEvalProgram_"] = function (stack, state, node) {
|
|
var n = state.n_ || 0;
|
|
var expression = node["body"][n];
|
|
if (expression) {
|
|
state.n_ = n + 1;
|
|
return new Interpreter.State(expression, state.scope);
|
|
}
|
|
stack.pop();
|
|
stack[stack.length - 1].value = this.value;
|
|
};
|
|
|
|
Interpreter.prototype["stepExpressionStatement"] = function (stack, state, node) {
|
|
if (!state.done_) {
|
|
state.done_ = true;
|
|
return new Interpreter.State(node["expression"], state.scope);
|
|
}
|
|
stack.pop();
|
|
// Save this value to interpreter.value for use as a return value if
|
|
// this code is inside an eval function.
|
|
this.value = state.value;
|
|
};
|
|
|
|
Interpreter.prototype["stepForInStatement"] = function (stack, state, node) {
|
|
// First, initialize a variable if exists. Only do so once, ever.
|
|
if (!state.doneInit_) {
|
|
state.doneInit_ = true;
|
|
if (node["left"]["declarations"] && node["left"]["declarations"][0]["init"]) {
|
|
if (state.scope.strict) {
|
|
let lineNum = this.getErrorLineNumber(node);
|
|
this.throwException(
|
|
this.SYNTAX_ERROR,
|
|
"for-in loop variable declaration may not have an initializer.",
|
|
lineNum,
|
|
);
|
|
}
|
|
// Variable initialization: for (var x = 4 in y)
|
|
return new Interpreter.State(node["left"], state.scope);
|
|
}
|
|
}
|
|
// Second, look up the object. Only do so once, ever.
|
|
if (!state.doneObject_) {
|
|
state.doneObject_ = true;
|
|
if (!state.variable_) {
|
|
state.variable_ = state.value;
|
|
}
|
|
return new Interpreter.State(node["right"], state.scope);
|
|
}
|
|
if (!state.isLoop) {
|
|
// First iteration.
|
|
state.isLoop = true;
|
|
state.object_ = state.value;
|
|
state.visited_ = Object.create(null);
|
|
}
|
|
// Third, find the property name for this iteration.
|
|
if (state.name_ === undefined) {
|
|
gotPropName: while (true) {
|
|
if (state.object_ && state.object_.isObject) {
|
|
if (!state.props_) {
|
|
state.props_ = Object.getOwnPropertyNames(state.object_.properties);
|
|
}
|
|
while (true) {
|
|
var prop = state.props_.shift();
|
|
if (prop === undefined) {
|
|
break; // Reached end of this object's properties.
|
|
}
|
|
if (!Object.prototype.hasOwnProperty.call(state.object_.properties, prop)) {
|
|
continue; // Property has been deleted in the loop.
|
|
}
|
|
if (state.visited_[prop]) {
|
|
continue; // Already seen this property on a child.
|
|
}
|
|
state.visited_[prop] = true;
|
|
if (!Object.prototype.propertyIsEnumerable.call(state.object_.properties, prop)) {
|
|
continue; // Skip non-enumerable property.
|
|
}
|
|
state.name_ = prop;
|
|
break gotPropName;
|
|
}
|
|
} else if (state.object_ !== null && state.object_ !== undefined) {
|
|
// Primitive value (other than null or undefined).
|
|
if (!state.props_) {
|
|
state.props_ = Object.getOwnPropertyNames(state.object_);
|
|
}
|
|
while (true) {
|
|
var prop = state.props_.shift();
|
|
if (prop === undefined) {
|
|
break; // Reached end of this value's properties.
|
|
}
|
|
state.visited_[prop] = true;
|
|
if (!Object.prototype.propertyIsEnumerable.call(state.object_, prop)) {
|
|
continue; // Skip non-enumerable property.
|
|
}
|
|
state.name_ = prop;
|
|
break gotPropName;
|
|
}
|
|
}
|
|
state.object_ = this.getPrototype(state.object_);
|
|
state.props_ = null;
|
|
if (state.object_ === null) {
|
|
// Done, exit loop.
|
|
stack.pop();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// Fourth, find the variable
|
|
if (!state.doneVariable_) {
|
|
state.doneVariable_ = true;
|
|
var left = node["left"];
|
|
if (left["type"] === "VariableDeclaration") {
|
|
// Inline variable declaration: for (var x in y)
|
|
state.variable_ = [Interpreter.SCOPE_REFERENCE, left["declarations"][0]["id"]["name"]];
|
|
} else {
|
|
// Arbitrary left side: for (foo().bar in y)
|
|
state.variable_ = null;
|
|
var nextState = new Interpreter.State(left, state.scope);
|
|
nextState.components = true;
|
|
return nextState;
|
|
}
|
|
}
|
|
if (!state.variable_) {
|
|
state.variable_ = state.value;
|
|
}
|
|
// Fifth, set the variable.
|
|
if (!state.doneSetter_) {
|
|
state.doneSetter_ = true;
|
|
var value = state.name_;
|
|
var setter = this.setValue(state.variable_, value);
|
|
if (setter) {
|
|
return this.createSetter_(setter, state.variable_, value);
|
|
}
|
|
}
|
|
// Next step will be step three.
|
|
state.name_ = undefined;
|
|
// Reevaluate the variable since it could be a setter on the global object.
|
|
state.doneVariable_ = false;
|
|
state.doneSetter_ = false;
|
|
// Sixth and finally, execute the body if there was one. this.
|
|
if (node["body"]) {
|
|
return new Interpreter.State(node["body"], state.scope);
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepForStatement"] = function (stack, state, node) {
|
|
var mode = state.mode_ || 0;
|
|
if (mode === 0) {
|
|
state.mode_ = 1;
|
|
if (node["init"]) {
|
|
return new Interpreter.State(node["init"], state.scope);
|
|
}
|
|
} else if (mode === 1) {
|
|
state.mode_ = 2;
|
|
if (node["test"]) {
|
|
return new Interpreter.State(node["test"], state.scope);
|
|
}
|
|
} else if (mode === 2) {
|
|
state.mode_ = 3;
|
|
if (node["test"] && !state.value) {
|
|
// Done, exit loop.
|
|
stack.pop();
|
|
} else {
|
|
// Execute the body.
|
|
state.isLoop = true;
|
|
return new Interpreter.State(node["body"], state.scope);
|
|
}
|
|
} else if (mode === 3) {
|
|
state.mode_ = 1;
|
|
if (node["update"]) {
|
|
return new Interpreter.State(node["update"], state.scope);
|
|
}
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepFunctionDeclaration"] = function (stack, state, node) {
|
|
// This was found and handled when the scope was populated.
|
|
stack.pop();
|
|
};
|
|
|
|
Interpreter.prototype["stepFunctionExpression"] = function (stack, state, node) {
|
|
stack.pop();
|
|
stack[stack.length - 1].value = this.createFunction(node, state.scope);
|
|
};
|
|
|
|
Interpreter.prototype["stepIdentifier"] = function (stack, state, node) {
|
|
stack.pop();
|
|
if (state.components) {
|
|
stack[stack.length - 1].value = [Interpreter.SCOPE_REFERENCE, node["name"]];
|
|
return;
|
|
}
|
|
var value = this.getValueFromScope(node["name"], node);
|
|
// An identifier could be a getter if it's a property on the global object.
|
|
if (value && typeof value === "object" && value.isGetter) {
|
|
// Clear the getter flag and call the getter function.
|
|
value.isGetter = false;
|
|
var scope = state.scope;
|
|
while (!this.hasProperty(scope, node["name"])) {
|
|
scope = scope.parentScope;
|
|
}
|
|
var func = /** @type {!Interpreter.Object} */ (value);
|
|
return this.createGetter_(func, this.global);
|
|
}
|
|
stack[stack.length - 1].value = value;
|
|
};
|
|
|
|
Interpreter.prototype["stepIfStatement"] = Interpreter.prototype["stepConditionalExpression"];
|
|
|
|
Interpreter.prototype["stepLabeledStatement"] = function (stack, state, node) {
|
|
// No need to hit this node again on the way back up the stack.
|
|
stack.pop();
|
|
// Note that a statement might have multiple labels.
|
|
var labels = state.labels || [];
|
|
labels.push(node["label"]["name"]);
|
|
var nextState = new Interpreter.State(node["body"], state.scope);
|
|
nextState.labels = labels;
|
|
return nextState;
|
|
};
|
|
|
|
Interpreter.prototype["stepLiteral"] = function (stack, state, node) {
|
|
stack.pop();
|
|
var value = node["value"];
|
|
if (value instanceof RegExp) {
|
|
var pseudoRegexp = this.createObjectProto(this.REGEXP_PROTO);
|
|
this.populateRegExp(pseudoRegexp, value);
|
|
value = pseudoRegexp;
|
|
}
|
|
stack[stack.length - 1].value = value;
|
|
};
|
|
|
|
Interpreter.prototype["stepLogicalExpression"] = function (stack, state, node) {
|
|
if (node["operator"] !== "&&" && node["operator"] !== "||") {
|
|
throw SyntaxError("Unknown logical operator: " + node["operator"]);
|
|
}
|
|
if (!state.doneLeft_) {
|
|
state.doneLeft_ = true;
|
|
return new Interpreter.State(node["left"], state.scope);
|
|
}
|
|
if (!state.doneRight_) {
|
|
if ((node["operator"] === "&&" && !state.value) || (node["operator"] === "||" && state.value)) {
|
|
// Shortcut evaluation.
|
|
stack.pop();
|
|
stack[stack.length - 1].value = state.value;
|
|
} else {
|
|
state.doneRight_ = true;
|
|
return new Interpreter.State(node["right"], state.scope);
|
|
}
|
|
} else {
|
|
stack.pop();
|
|
stack[stack.length - 1].value = state.value;
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepMemberExpression"] = function (stack, state, node) {
|
|
if (!state.doneObject_) {
|
|
state.doneObject_ = true;
|
|
return new Interpreter.State(node["object"], state.scope);
|
|
}
|
|
var propName;
|
|
if (!node["computed"]) {
|
|
state.object_ = state.value;
|
|
// obj.foo -- Just access 'foo' directly.
|
|
propName = node["property"]["name"];
|
|
} else if (!state.doneProperty_) {
|
|
state.object_ = state.value;
|
|
// obj[foo] -- Compute value of 'foo'.
|
|
state.doneProperty_ = true;
|
|
return new Interpreter.State(node["property"], state.scope);
|
|
} else {
|
|
propName = state.value;
|
|
}
|
|
stack.pop();
|
|
if (state.components) {
|
|
stack[stack.length - 1].value = [state.object_, propName];
|
|
} else {
|
|
var value = this.getProperty(state.object_, propName);
|
|
if (value && typeof value === "object" && value.isGetter) {
|
|
// Clear the getter flag and call the getter function.
|
|
value.isGetter = false;
|
|
var func = /** @type {!Interpreter.Object} */ (value);
|
|
return this.createGetter_(func, state.object_);
|
|
}
|
|
stack[stack.length - 1].value = value;
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepNewExpression"] = Interpreter.prototype["stepCallExpression"];
|
|
|
|
Interpreter.prototype["stepObjectExpression"] = function (stack, state, node) {
|
|
var n = state.n_ || 0;
|
|
var property = node["properties"][n];
|
|
if (!state.object_) {
|
|
// First execution.
|
|
state.object_ = this.createObjectProto(this.OBJECT_PROTO);
|
|
state.properties_ = Object.create(null);
|
|
} else {
|
|
// Determine property name.
|
|
var key = property["key"];
|
|
if (key["type"] === "Identifier") {
|
|
var propName = key["name"];
|
|
} else if (key["type"] === "Literal") {
|
|
var propName = key["value"];
|
|
} else {
|
|
throw SyntaxError("Unknown object structure: " + key["type"]);
|
|
}
|
|
// Set the property computed in the previous execution.
|
|
if (!state.properties_[propName]) {
|
|
// Create temp object to collect value, getter, and/or setter.
|
|
state.properties_[propName] = {};
|
|
}
|
|
state.properties_[propName][property["kind"]] = state.value;
|
|
state.n_ = ++n;
|
|
property = node["properties"][n];
|
|
}
|
|
if (property) {
|
|
return new Interpreter.State(property["value"], state.scope);
|
|
}
|
|
for (var key in state.properties_) {
|
|
var kinds = state.properties_[key];
|
|
if ("get" in kinds || "set" in kinds) {
|
|
// Set a property with a getter or setter.
|
|
var descriptor = {
|
|
configurable: true,
|
|
enumerable: true,
|
|
get: kinds["get"],
|
|
set: kinds["set"],
|
|
};
|
|
this.setProperty(state.object_, key, null, descriptor);
|
|
} else {
|
|
// Set a normal property with a value.
|
|
this.setProperty(state.object_, key, kinds["init"]);
|
|
}
|
|
}
|
|
stack.pop();
|
|
stack[stack.length - 1].value = state.object_;
|
|
};
|
|
|
|
Interpreter.prototype["stepProgram"] = function (stack, state, node) {
|
|
var expression = node["body"].shift();
|
|
if (expression) {
|
|
state.done = false;
|
|
return new Interpreter.State(expression, state.scope);
|
|
}
|
|
state.done = true;
|
|
// Don't pop the stateStack.
|
|
// Leave the root scope on the tree in case the program is appended to.
|
|
};
|
|
|
|
Interpreter.prototype["stepReturnStatement"] = function (stack, state, node) {
|
|
if (node["argument"] && !state.done_) {
|
|
state.done_ = true;
|
|
return new Interpreter.State(node["argument"], state.scope);
|
|
}
|
|
this.unwind(Interpreter.Completion.RETURN, state.value, undefined);
|
|
};
|
|
|
|
Interpreter.prototype["stepSequenceExpression"] = function (stack, state, node) {
|
|
var n = state.n_ || 0;
|
|
var expression = node["expressions"][n];
|
|
if (expression) {
|
|
state.n_ = n + 1;
|
|
return new Interpreter.State(expression, state.scope);
|
|
}
|
|
stack.pop();
|
|
stack[stack.length - 1].value = state.value;
|
|
};
|
|
|
|
Interpreter.prototype["stepSwitchStatement"] = function (stack, state, node) {
|
|
if (!state.test_) {
|
|
state.test_ = 1;
|
|
return new Interpreter.State(node["discriminant"], state.scope);
|
|
}
|
|
if (state.test_ === 1) {
|
|
state.test_ = 2;
|
|
// Preserve switch value between case tests.
|
|
state.switchValue_ = state.value;
|
|
state.defaultCase_ = -1;
|
|
}
|
|
|
|
while (true) {
|
|
var index = state.index_ || 0;
|
|
var switchCase = node["cases"][index];
|
|
if (!state.matched_ && switchCase && !switchCase["test"]) {
|
|
// Test on the default case is null.
|
|
// Bypass (but store) the default case, and get back to it later.
|
|
state.defaultCase_ = index;
|
|
state.index_ = index + 1;
|
|
continue;
|
|
}
|
|
if (!switchCase && !state.matched_ && state.defaultCase_ !== -1) {
|
|
// Ran through all cases, no match. Jump to the default.
|
|
state.matched_ = true;
|
|
state.index_ = state.defaultCase_;
|
|
continue;
|
|
}
|
|
if (switchCase) {
|
|
if (!state.matched_ && !state.tested_ && switchCase["test"]) {
|
|
state.tested_ = true;
|
|
return new Interpreter.State(switchCase["test"], state.scope);
|
|
}
|
|
if (state.matched_ || state.value === state.switchValue_) {
|
|
state.matched_ = true;
|
|
var n = state.n_ || 0;
|
|
if (switchCase["consequent"][n]) {
|
|
state.isSwitch = true;
|
|
state.n_ = n + 1;
|
|
return new Interpreter.State(switchCase["consequent"][n], state.scope);
|
|
}
|
|
}
|
|
// Move on to next case.
|
|
state.tested_ = false;
|
|
state.n_ = 0;
|
|
state.index_ = index + 1;
|
|
} else {
|
|
stack.pop();
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepThisExpression"] = function (stack, state, node) {
|
|
stack.pop();
|
|
stack[stack.length - 1].value = this.getValueFromScope("this", node);
|
|
};
|
|
|
|
Interpreter.prototype["stepThrowStatement"] = function (stack, state, node) {
|
|
if (!state.done_) {
|
|
state.done_ = true;
|
|
return new Interpreter.State(node["argument"], state.scope);
|
|
} else {
|
|
this.throwException(state.value);
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepTryStatement"] = function (stack, state, node) {
|
|
if (!state.doneBlock_) {
|
|
state.doneBlock_ = true;
|
|
return new Interpreter.State(node["block"], state.scope);
|
|
}
|
|
if (state.cv && state.cv.type === Interpreter.Completion.THROW && !state.doneHandler_ && node["handler"]) {
|
|
state.doneHandler_ = true;
|
|
var nextState = new Interpreter.State(node["handler"], state.scope);
|
|
nextState.throwValue = state.cv.value;
|
|
state.cv = undefined; // This error has been handled, don't rethrow.
|
|
return nextState;
|
|
}
|
|
if (!state.doneFinalizer_ && node["finalizer"]) {
|
|
state.doneFinalizer_ = true;
|
|
return new Interpreter.State(node["finalizer"], state.scope);
|
|
}
|
|
stack.pop();
|
|
if (state.cv) {
|
|
// There was no catch handler, or the catch/finally threw an error.
|
|
// Throw the error up to a higher try.
|
|
this.unwind(state.cv.type, state.cv.value, state.cv.label);
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepUnaryExpression"] = function (stack, state, node) {
|
|
if (!state.done_) {
|
|
state.done_ = true;
|
|
var nextState = new Interpreter.State(node["argument"], state.scope);
|
|
nextState.components = node["operator"] === "delete";
|
|
return nextState;
|
|
}
|
|
stack.pop();
|
|
var value = state.value;
|
|
if (node["operator"] === "-") {
|
|
value = -value;
|
|
} else if (node["operator"] === "+") {
|
|
value = +value;
|
|
} else if (node["operator"] === "!") {
|
|
value = !value;
|
|
} else if (node["operator"] === "~") {
|
|
value = ~value;
|
|
} else if (node["operator"] === "delete") {
|
|
var result = true;
|
|
// If value is not an array, then it is a primitive, or some other value.
|
|
// If so, skip the delete and return true.
|
|
if (Array.isArray(value)) {
|
|
var obj = value[0];
|
|
if (obj === Interpreter.SCOPE_REFERENCE) {
|
|
// 'delete foo;' is the same as 'delete window.foo'.
|
|
obj = state.scope;
|
|
}
|
|
var name = String(value[1]);
|
|
try {
|
|
delete obj.properties[name];
|
|
} catch (e) {
|
|
if (state.scope.strict) {
|
|
this.throwException(this.TYPE_ERROR, "Cannot delete property '" + name + "' of '" + obj + "'");
|
|
} else {
|
|
result = false;
|
|
}
|
|
}
|
|
}
|
|
value = result;
|
|
} else if (node["operator"] === "typeof") {
|
|
value = value && value.class === "Function" ? "function" : typeof value;
|
|
} else if (node["operator"] === "void") {
|
|
value = undefined;
|
|
} else {
|
|
throw SyntaxError("Unknown unary operator: " + node["operator"]);
|
|
}
|
|
stack[stack.length - 1].value = value;
|
|
};
|
|
|
|
Interpreter.prototype["stepUpdateExpression"] = function (stack, state, node) {
|
|
if (!state.doneLeft_) {
|
|
state.doneLeft_ = true;
|
|
var nextState = new Interpreter.State(node["argument"], state.scope);
|
|
nextState.components = true;
|
|
return nextState;
|
|
}
|
|
if (!state.leftSide_) {
|
|
state.leftSide_ = state.value;
|
|
}
|
|
if (state.doneGetter_) {
|
|
state.leftValue_ = state.value;
|
|
}
|
|
if (!state.doneGetter_) {
|
|
var leftValue = this.getValue(state.leftSide_, node);
|
|
state.leftValue_ = leftValue;
|
|
if (leftValue && typeof leftValue === "object" && leftValue.isGetter) {
|
|
// Clear the getter flag and call the getter function.
|
|
leftValue.isGetter = false;
|
|
state.doneGetter_ = true;
|
|
var func = /** @type {!Interpreter.Object} */ (leftValue);
|
|
return this.createGetter_(func, state.leftSide_);
|
|
}
|
|
}
|
|
if (state.doneSetter_) {
|
|
// Return if setter function.
|
|
// Setter method on property has completed.
|
|
// Ignore its return value, and use the original set value instead.
|
|
stack.pop();
|
|
stack[stack.length - 1].value = state.setterValue_;
|
|
return;
|
|
}
|
|
var leftValue = Number(state.leftValue_);
|
|
var changeValue;
|
|
if (node["operator"] === "++") {
|
|
changeValue = leftValue + 1;
|
|
} else if (node["operator"] === "--") {
|
|
changeValue = leftValue - 1;
|
|
} else {
|
|
throw SyntaxError("Unknown update expression: " + node["operator"]);
|
|
}
|
|
var returnValue = node["prefix"] ? changeValue : leftValue;
|
|
var setter = this.setValue(state.leftSide_, changeValue);
|
|
if (setter) {
|
|
state.doneSetter_ = true;
|
|
state.setterValue_ = returnValue;
|
|
return this.createSetter_(setter, state.leftSide_, changeValue);
|
|
}
|
|
// Return if no setter function.
|
|
stack.pop();
|
|
stack[stack.length - 1].value = returnValue;
|
|
};
|
|
|
|
Interpreter.prototype["stepVariableDeclaration"] = function (stack, state, node) {
|
|
var declarations = node["declarations"];
|
|
var n = state.n_ || 0;
|
|
var declarationNode = declarations[n];
|
|
if (state.init_ && declarationNode) {
|
|
// This setValue call never needs to deal with calling a setter function.
|
|
// Note that this is setting the init value, not defining the variable.
|
|
// Variable definition is done when scope is populated.
|
|
this.setValueToScope(declarationNode["id"]["name"], state.value);
|
|
state.init_ = false;
|
|
declarationNode = declarations[++n];
|
|
}
|
|
while (declarationNode) {
|
|
// Skip any declarations that are not initialized. They have already
|
|
// been defined as undefined in populateScope_.
|
|
if (declarationNode["init"]) {
|
|
state.n_ = n;
|
|
state.init_ = true;
|
|
return new Interpreter.State(declarationNode["init"], state.scope);
|
|
}
|
|
declarationNode = declarations[++n];
|
|
}
|
|
stack.pop();
|
|
};
|
|
|
|
Interpreter.prototype["stepWithStatement"] = function (stack, state, node) {
|
|
if (!state.doneObject_) {
|
|
state.doneObject_ = true;
|
|
return new Interpreter.State(node["object"], state.scope);
|
|
} else if (!state.doneBody_) {
|
|
state.doneBody_ = true;
|
|
var scope = this.createSpecialScope(state.scope, state.value);
|
|
return new Interpreter.State(node["body"], scope);
|
|
} else {
|
|
stack.pop();
|
|
}
|
|
};
|
|
|
|
Interpreter.prototype["stepWhileStatement"] = Interpreter.prototype["stepDoWhileStatement"];
|
|
|
|
// Preserve top-level API functions from being pruned/renamed by JS compilers.
|
|
// Add others as needed.
|
|
// The global object ('window' in a browser, 'global' in node.js) is 'this'.
|
|
//this['Interpreter'] = Interpreter;
|
|
Interpreter.prototype["step"] = Interpreter.prototype.step;
|
|
Interpreter.prototype["run"] = Interpreter.prototype.run;
|
|
Interpreter.prototype["appendCode"] = Interpreter.prototype.appendCode;
|
|
Interpreter.prototype["createObject"] = Interpreter.prototype.createObject;
|
|
Interpreter.prototype["createObjectProto"] = Interpreter.prototype.createObjectProto;
|
|
Interpreter.prototype["createAsyncFunction"] = Interpreter.prototype.createAsyncFunction;
|
|
Interpreter.prototype["createNativeFunction"] = Interpreter.prototype.createNativeFunction;
|
|
Interpreter.prototype["getProperty"] = Interpreter.prototype.getProperty;
|
|
Interpreter.prototype["setProperty"] = Interpreter.prototype.setProperty;
|
|
Interpreter.prototype["nativeToPseudo"] = Interpreter.prototype.nativeToPseudo;
|
|
Interpreter.prototype["pseudoToNative"] = Interpreter.prototype.pseudoToNative;
|
|
// Obsolete. Do not use.
|
|
Interpreter.prototype["createPrimitive"] = function (x) {
|
|
return x;
|
|
};
|
|
|
|
export { Interpreter };
|