mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2025-03-07 11:04:36 +01:00
JS-Interpreter fix sprintf and vsprintf by converting JS Interpreter objects to Native objects when passing in arguments. JS-Interpreter add error/exception line numbers wherever possible
This commit is contained in:
2
dist/engine.bundle.js
vendored
2
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -30,9 +30,12 @@ import * as acorn from "../utils/acorn";
|
|||||||
* @param {Function=} opt_initFunc Optional initialization function. Used to
|
* @param {Function=} opt_initFunc Optional initialization function. Used to
|
||||||
* define APIs. When called it is passed the interpreter object and the
|
* define APIs. When called it is passed the interpreter object and the
|
||||||
* global scope object.
|
* global scope object.
|
||||||
|
* @param {Number} Bitburner-specific number used for determining exception line numbers
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
var Interpreter = function(code, opt_initFunc) {
|
var Interpreter = function(code, opt_initFunc, lineOffset=0) {
|
||||||
|
this.sourceCode = code;
|
||||||
|
this.sourceCodeLineOffset = lineOffset;
|
||||||
if (typeof code === 'string') {
|
if (typeof code === 'string') {
|
||||||
code = acorn.parse(code, Interpreter.PARSE_OPTIONS);
|
code = acorn.parse(code, Interpreter.PARSE_OPTIONS);
|
||||||
}
|
}
|
||||||
@ -147,6 +150,37 @@ Interpreter.VALUE_IN_DESCRIPTOR = {};
|
|||||||
*/
|
*/
|
||||||
Interpreter.toStringCycles_ = [];
|
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.
|
* Add more code to the interpreter.
|
||||||
* @param {string|!Object} code Raw JavaScript text or AST.
|
* @param {string|!Object} code Raw JavaScript text or AST.
|
||||||
@ -2575,7 +2609,7 @@ Interpreter.prototype.setValue = function(ref, value) {
|
|||||||
* provided) or the value to throw (if no message).
|
* provided) or the value to throw (if no message).
|
||||||
* @param {string=} opt_message Message being thrown.
|
* @param {string=} opt_message Message being thrown.
|
||||||
*/
|
*/
|
||||||
Interpreter.prototype.throwException = function(errorClass, opt_message) {
|
Interpreter.prototype.throwException = function(errorClass, opt_message, lineNumber) {
|
||||||
if (opt_message === undefined) {
|
if (opt_message === undefined) {
|
||||||
var error = errorClass; // This is a value to throw, not an error class.
|
var error = errorClass; // This is a value to throw, not an error class.
|
||||||
} else {
|
} else {
|
||||||
@ -2583,7 +2617,11 @@ Interpreter.prototype.throwException = function(errorClass, opt_message) {
|
|||||||
this.setProperty(error, 'message', opt_message,
|
this.setProperty(error, 'message', opt_message,
|
||||||
Interpreter.NONENUMERABLE_DESCRIPTOR);
|
Interpreter.NONENUMERABLE_DESCRIPTOR);
|
||||||
}
|
}
|
||||||
this.unwind(Interpreter.Completion.THROW, error, undefined);
|
var lineNumErrorMsg;
|
||||||
|
if (lineNumber != null) {
|
||||||
|
lineNumErrorMsg = this.getErrorLineNumberMessage(lineNumber);
|
||||||
|
}
|
||||||
|
this.unwind(Interpreter.Completion.THROW, error, undefined, lineNumErrorMsg);
|
||||||
// Abort anything related to the current step.
|
// Abort anything related to the current step.
|
||||||
throw Interpreter.STEP_ERROR;
|
throw Interpreter.STEP_ERROR;
|
||||||
};
|
};
|
||||||
@ -2597,7 +2635,7 @@ Interpreter.prototype.throwException = function(errorClass, opt_message) {
|
|||||||
* @param {Interpreter.Value=} value Value computed, returned or thrown.
|
* @param {Interpreter.Value=} value Value computed, returned or thrown.
|
||||||
* @param {string=} label Target label for break or return.
|
* @param {string=} label Target label for break or return.
|
||||||
*/
|
*/
|
||||||
Interpreter.prototype.unwind = function(type, value, label) {
|
Interpreter.prototype.unwind = function(type, value, label, lineNumberMsg="") {
|
||||||
if (type === Interpreter.Completion.NORMAL) {
|
if (type === Interpreter.Completion.NORMAL) {
|
||||||
throw TypeError('Should not unwind for NORMAL completions');
|
throw TypeError('Should not unwind for NORMAL completions');
|
||||||
}
|
}
|
||||||
@ -2645,9 +2683,9 @@ Interpreter.prototype.unwind = function(type, value, label) {
|
|||||||
var name = this.getProperty(value, 'name').toString();
|
var name = this.getProperty(value, 'name').toString();
|
||||||
var message = this.getProperty(value, 'message').valueOf();
|
var message = this.getProperty(value, 'message').valueOf();
|
||||||
var type = errorTable[name] || Error;
|
var type = errorTable[name] || Error;
|
||||||
realError = type(message);
|
realError = type(message + lineNumberMsg);
|
||||||
} else {
|
} else {
|
||||||
realError = String(value);
|
realError = String(value + lineNumberMsg);
|
||||||
}
|
}
|
||||||
throw realError;
|
throw realError;
|
||||||
};
|
};
|
||||||
@ -2839,15 +2877,17 @@ Interpreter.prototype['stepBinaryExpression'] = function(stack, state, node) {
|
|||||||
case '>>>': value = leftValue >>> rightValue; break;
|
case '>>>': value = leftValue >>> rightValue; break;
|
||||||
case 'in':
|
case 'in':
|
||||||
if (!rightValue || !rightValue.isObject) {
|
if (!rightValue || !rightValue.isObject) {
|
||||||
|
let lineNum = this.getErrorLineNumber(node);
|
||||||
this.throwException(this.TYPE_ERROR,
|
this.throwException(this.TYPE_ERROR,
|
||||||
"'in' expects an object, not '" + rightValue + "'");
|
"'in' expects an object, not '" + rightValue + "'", lineNum);
|
||||||
}
|
}
|
||||||
value = this.hasProperty(rightValue, leftValue);
|
value = this.hasProperty(rightValue, leftValue);
|
||||||
break;
|
break;
|
||||||
case 'instanceof':
|
case 'instanceof':
|
||||||
if (!this.isa(rightValue, this.FUNCTION)) {
|
if (!this.isa(rightValue, this.FUNCTION)) {
|
||||||
|
let lineNum = this.getErrorLineNumber(node);
|
||||||
this.throwException(this.TYPE_ERROR,
|
this.throwException(this.TYPE_ERROR,
|
||||||
'Right-hand side of instanceof is not an object');
|
'Right-hand side of instanceof is not an object', lineNum);
|
||||||
}
|
}
|
||||||
value = leftValue.isObject ? this.isa(leftValue, rightValue) : false;
|
value = leftValue.isObject ? this.isa(leftValue, rightValue) : false;
|
||||||
break;
|
break;
|
||||||
@ -2920,7 +2960,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
|||||||
if (node['type'] === 'NewExpression') {
|
if (node['type'] === 'NewExpression') {
|
||||||
if (func.illegalConstructor) {
|
if (func.illegalConstructor) {
|
||||||
// Illegal: new escape();
|
// Illegal: new escape();
|
||||||
this.throwException(this.TYPE_ERROR, func + ' is not a constructor');
|
let lineNum = this.getErrorLineNumber(node);
|
||||||
|
this.throwException(this.TYPE_ERROR, func + ' is not a constructor', lineNum);
|
||||||
}
|
}
|
||||||
// Constructor, 'this' is new object.
|
// Constructor, 'this' is new object.
|
||||||
var proto = func.properties['prototype'];
|
var proto = func.properties['prototype'];
|
||||||
@ -2939,7 +2980,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
|||||||
if (!state.doneExec_) {
|
if (!state.doneExec_) {
|
||||||
state.doneExec_ = true;
|
state.doneExec_ = true;
|
||||||
if (!func || !func.isObject) {
|
if (!func || !func.isObject) {
|
||||||
this.throwException(this.TYPE_ERROR, func + ' is not a function');
|
let lineNum = this.getErrorLineNumber(node);
|
||||||
|
this.throwException(this.TYPE_ERROR, func + ' is not a function', lineNum);
|
||||||
}
|
}
|
||||||
var funcNode = func.node;
|
var funcNode = func.node;
|
||||||
if (funcNode) {
|
if (funcNode) {
|
||||||
@ -2977,7 +3019,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
|||||||
var ast = acorn.parse(code.toString(), Interpreter.PARSE_OPTIONS);
|
var ast = acorn.parse(code.toString(), Interpreter.PARSE_OPTIONS);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Acorn threw a SyntaxError. Rethrow as a trappable error.
|
// Acorn threw a SyntaxError. Rethrow as a trappable error.
|
||||||
this.throwException(this.SYNTAX_ERROR, 'Invalid code: ' + e.message);
|
let lineNum = this.getErrorLineNumber(node);
|
||||||
|
this.throwException(this.SYNTAX_ERROR, 'Invalid code: ' + e.message, lineNum);
|
||||||
}
|
}
|
||||||
var evalNode = new this.nodeConstructor();
|
var evalNode = new this.nodeConstructor();
|
||||||
evalNode['type'] = 'EvalProgram_';
|
evalNode['type'] = 'EvalProgram_';
|
||||||
@ -3014,7 +3057,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
|||||||
var f = new F();
|
var f = new F();
|
||||||
f();
|
f();
|
||||||
*/
|
*/
|
||||||
this.throwException(this.TYPE_ERROR, func.class + ' is not a function');
|
let lineNum = this.getErrorLineNumber(node);
|
||||||
|
this.throwException(this.TYPE_ERROR, func.class + ' is not a function', lineNum);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Execution complete. Put the return value on the stack.
|
// Execution complete. Put the return value on the stack.
|
||||||
@ -3129,8 +3173,9 @@ Interpreter.prototype['stepForInStatement'] = function(stack, state, node) {
|
|||||||
if (node['left']['declarations'] &&
|
if (node['left']['declarations'] &&
|
||||||
node['left']['declarations'][0]['init']) {
|
node['left']['declarations'][0]['init']) {
|
||||||
if (state.scope.strict) {
|
if (state.scope.strict) {
|
||||||
|
let lineNum = this.getErrorLineNumber(node);
|
||||||
this.throwException(this.SYNTAX_ERROR,
|
this.throwException(this.SYNTAX_ERROR,
|
||||||
'for-in loop variable declaration may not have an initializer.');
|
'for-in loop variable declaration may not have an initializer.', lineNum);
|
||||||
}
|
}
|
||||||
// Variable initialization: for (var x = 4 in y)
|
// Variable initialization: for (var x = 4 in y)
|
||||||
return new Interpreter.State(node['left'], state.scope);
|
return new Interpreter.State(node['left'], state.scope);
|
||||||
|
@ -166,9 +166,11 @@ function startNetscript1Script(workerScript) {
|
|||||||
workerScript.running = true;
|
workerScript.running = true;
|
||||||
|
|
||||||
//Process imports
|
//Process imports
|
||||||
var ast;
|
var codeWithImports, codeLineOffset;
|
||||||
try {
|
try {
|
||||||
ast = processNetscript1Imports(code, workerScript);
|
let importProcessingRes = processNetscript1Imports(code, workerScript);
|
||||||
|
codeWithImports = importProcessingRes.code;
|
||||||
|
codeLineOffset = importProcessingRes.lineOffset;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e);
|
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e);
|
||||||
workerScript.env.stopFlag = true;
|
workerScript.env.stopFlag = true;
|
||||||
@ -197,6 +199,23 @@ function startNetscript1Script(workerScript) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
|
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
|
||||||
|
} else if (name === "sprintf" || name === "vsprintf") {
|
||||||
|
let tempWrapper = function() {
|
||||||
|
let fnArgs = [];
|
||||||
|
|
||||||
|
//All of the Object/array elements are in JSInterpreter format, so
|
||||||
|
//we have to convert them back to native format to pass them to these fns
|
||||||
|
for (let i = 0; i < arguments.length; ++i) {
|
||||||
|
if (typeof arguments[i] === 'object' || arguments[i].constructor === Array) {
|
||||||
|
fnArgs.push(int.pseudoToNative(arguments[i]));
|
||||||
|
} else {
|
||||||
|
fnArgs.push(arguments[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.apply(null, fnArgs);
|
||||||
|
}
|
||||||
|
int.setProperty(scope, name, int.createNativeFunction(tempWrapper));
|
||||||
} else {
|
} else {
|
||||||
let tempWrapper = function() {
|
let tempWrapper = function() {
|
||||||
let res = entry.apply(null, arguments);
|
let res = entry.apply(null, arguments);
|
||||||
@ -205,7 +224,6 @@ function startNetscript1Script(workerScript) {
|
|||||||
return res;
|
return res;
|
||||||
} else if (res.constructor === Array || (res === Object(res))) {
|
} else if (res.constructor === Array || (res === Object(res))) {
|
||||||
//Objects and Arrays must be converted to the interpreter's format
|
//Objects and Arrays must be converted to the interpreter's format
|
||||||
console.log("Function returning object detected: " + name);
|
|
||||||
return int.nativeToPseudo(res);
|
return int.nativeToPseudo(res);
|
||||||
} else {
|
} else {
|
||||||
return res;
|
return res;
|
||||||
@ -225,7 +243,7 @@ function startNetscript1Script(workerScript) {
|
|||||||
|
|
||||||
var interpreter;
|
var interpreter;
|
||||||
try {
|
try {
|
||||||
interpreter = new Interpreter(ast, interpreterInitialization);
|
interpreter = new Interpreter(codeWithImports, interpreterInitialization, codeLineOffset);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e);
|
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e);
|
||||||
workerScript.env.stopFlag = true;
|
workerScript.env.stopFlag = true;
|
||||||
@ -273,7 +291,11 @@ function startNetscript1Script(workerScript) {
|
|||||||
we'll implement it ourselves by parsing the Nodes in the AST out.
|
we'll implement it ourselves by parsing the Nodes in the AST out.
|
||||||
|
|
||||||
@param code - The script's code
|
@param code - The script's code
|
||||||
@returns - ES5-compliant AST with properly imported functions
|
@returns {Object} {
|
||||||
|
code: Newly-generated code with imported functions
|
||||||
|
lineOffset: Net number of lines of code added/removed due to imported functions
|
||||||
|
Should typically be positive
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
function processNetscript1Imports(code, workerScript) {
|
function processNetscript1Imports(code, workerScript) {
|
||||||
//allowReserved prevents 'import' from throwing error in ES5
|
//allowReserved prevents 'import' from throwing error in ES5
|
||||||
@ -294,10 +316,12 @@ function processNetscript1Imports(code, workerScript) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var generatedCode = ""; //Generated Javascript Code
|
var generatedCode = ""; //Generated Javascript Code
|
||||||
|
var hasImports = false;
|
||||||
|
|
||||||
//Walk over the tree and process ImportDeclaration nodes
|
//Walk over the tree and process ImportDeclaration nodes
|
||||||
walk.simple(ast, {
|
walk.simple(ast, {
|
||||||
ImportDeclaration: (node) => {
|
ImportDeclaration: (node) => {
|
||||||
|
hasImports = true;
|
||||||
let scriptName = node.source.value;
|
let scriptName = node.source.value;
|
||||||
let script = getScript(scriptName);
|
let script = getScript(scriptName);
|
||||||
if (script == null) {
|
if (script == null) {
|
||||||
@ -336,7 +360,7 @@ function processNetscript1Imports(code, workerScript) {
|
|||||||
|
|
||||||
//Finish
|
//Finish
|
||||||
generatedCode += (
|
generatedCode += (
|
||||||
"})(" + namespace + " || " + "(" + namespace + " = {}));"
|
"})(" + namespace + " || " + "(" + namespace + " = {}));\n"
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
//import {...} from script
|
//import {...} from script
|
||||||
@ -366,22 +390,34 @@ function processNetscript1Imports(code, workerScript) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//If there are no imports, just return the original code
|
||||||
|
if (!hasImports) {return {code:code, lineOffset:0};}
|
||||||
|
|
||||||
//Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level
|
//Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level
|
||||||
|
var linesRemoved = 0;
|
||||||
if (ast.type !== "Program" || ast.body == null) {
|
if (ast.type !== "Program" || ast.body == null) {
|
||||||
throw new Error("Code could not be properly parsed");
|
throw new Error("Code could not be properly parsed");
|
||||||
}
|
}
|
||||||
for (let i = ast.body.length-1; i >= 0; --i) {
|
for (let i = ast.body.length-1; i >= 0; --i) {
|
||||||
if (ast.body[i].type === "ImportDeclaration") {
|
if (ast.body[i].type === "ImportDeclaration") {
|
||||||
ast.body.splice(i, 1);
|
ast.body.splice(i, 1);
|
||||||
|
++linesRemoved;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Calculated line offset
|
||||||
|
var lineOffset = (generatedCode.match(/\n/g) || []).length - linesRemoved;
|
||||||
|
|
||||||
//Convert the AST back into code
|
//Convert the AST back into code
|
||||||
code = generate(ast);
|
code = generate(ast);
|
||||||
|
|
||||||
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
|
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
|
||||||
code = generatedCode + code;
|
code = generatedCode + code;
|
||||||
return parse(code, {ecmaVersion:5});
|
var res = {
|
||||||
|
code: code,
|
||||||
|
lineOffset: lineOffset
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Loop through workerScripts and run every script that is not currently running
|
//Loop through workerScripts and run every script that is not currently running
|
||||||
|
Reference in New Issue
Block a user