mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 12:15:44 +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:
parent
49d081f42e
commit
5161ca7739
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
|
||||
* 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) {
|
||||
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);
|
||||
}
|
||||
@ -147,6 +150,37 @@ Interpreter.VALUE_IN_DESCRIPTOR = {};
|
||||
*/
|
||||
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.
|
||||
@ -2575,7 +2609,7 @@ Interpreter.prototype.setValue = function(ref, value) {
|
||||
* provided) or the value to throw (if no message).
|
||||
* @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) {
|
||||
var error = errorClass; // This is a value to throw, not an error class.
|
||||
} else {
|
||||
@ -2583,7 +2617,11 @@ Interpreter.prototype.throwException = function(errorClass, opt_message) {
|
||||
this.setProperty(error, 'message', opt_message,
|
||||
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.
|
||||
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 {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) {
|
||||
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 message = this.getProperty(value, 'message').valueOf();
|
||||
var type = errorTable[name] || Error;
|
||||
realError = type(message);
|
||||
realError = type(message + lineNumberMsg);
|
||||
} else {
|
||||
realError = String(value);
|
||||
realError = String(value + lineNumberMsg);
|
||||
}
|
||||
throw realError;
|
||||
};
|
||||
@ -2839,15 +2877,17 @@ Interpreter.prototype['stepBinaryExpression'] = function(stack, state, node) {
|
||||
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 + "'");
|
||||
"'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');
|
||||
'Right-hand side of instanceof is not an object', lineNum);
|
||||
}
|
||||
value = leftValue.isObject ? this.isa(leftValue, rightValue) : false;
|
||||
break;
|
||||
@ -2920,7 +2960,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
||||
if (node['type'] === 'NewExpression') {
|
||||
if (func.illegalConstructor) {
|
||||
// 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.
|
||||
var proto = func.properties['prototype'];
|
||||
@ -2939,7 +2980,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
||||
if (!state.doneExec_) {
|
||||
state.doneExec_ = true;
|
||||
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;
|
||||
if (funcNode) {
|
||||
@ -2977,7 +3019,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
||||
var ast = acorn.parse(code.toString(), Interpreter.PARSE_OPTIONS);
|
||||
} catch (e) {
|
||||
// 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();
|
||||
evalNode['type'] = 'EvalProgram_';
|
||||
@ -3014,7 +3057,8 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) {
|
||||
var f = new 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 {
|
||||
// Execution complete. Put the return value on the stack.
|
||||
@ -3129,8 +3173,9 @@ Interpreter.prototype['stepForInStatement'] = function(stack, state, node) {
|
||||
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.');
|
||||
'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);
|
||||
|
@ -166,9 +166,11 @@ function startNetscript1Script(workerScript) {
|
||||
workerScript.running = true;
|
||||
|
||||
//Process imports
|
||||
var ast;
|
||||
var codeWithImports, codeLineOffset;
|
||||
try {
|
||||
ast = processNetscript1Imports(code, workerScript);
|
||||
let importProcessingRes = processNetscript1Imports(code, workerScript);
|
||||
codeWithImports = importProcessingRes.code;
|
||||
codeLineOffset = importProcessingRes.lineOffset;
|
||||
} catch(e) {
|
||||
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e);
|
||||
workerScript.env.stopFlag = true;
|
||||
@ -197,6 +199,23 @@ function startNetscript1Script(workerScript) {
|
||||
});
|
||||
}
|
||||
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 {
|
||||
let tempWrapper = function() {
|
||||
let res = entry.apply(null, arguments);
|
||||
@ -205,7 +224,6 @@ function startNetscript1Script(workerScript) {
|
||||
return res;
|
||||
} else if (res.constructor === Array || (res === Object(res))) {
|
||||
//Objects and Arrays must be converted to the interpreter's format
|
||||
console.log("Function returning object detected: " + name);
|
||||
return int.nativeToPseudo(res);
|
||||
} else {
|
||||
return res;
|
||||
@ -225,7 +243,7 @@ function startNetscript1Script(workerScript) {
|
||||
|
||||
var interpreter;
|
||||
try {
|
||||
interpreter = new Interpreter(ast, interpreterInitialization);
|
||||
interpreter = new Interpreter(codeWithImports, interpreterInitialization, codeLineOffset);
|
||||
} catch(e) {
|
||||
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e);
|
||||
workerScript.env.stopFlag = true;
|
||||
@ -273,7 +291,11 @@ function startNetscript1Script(workerScript) {
|
||||
we'll implement it ourselves by parsing the Nodes in the AST out.
|
||||
|
||||
@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) {
|
||||
//allowReserved prevents 'import' from throwing error in ES5
|
||||
@ -294,10 +316,12 @@ function processNetscript1Imports(code, workerScript) {
|
||||
}
|
||||
|
||||
var generatedCode = ""; //Generated Javascript Code
|
||||
var hasImports = false;
|
||||
|
||||
//Walk over the tree and process ImportDeclaration nodes
|
||||
walk.simple(ast, {
|
||||
ImportDeclaration: (node) => {
|
||||
hasImports = true;
|
||||
let scriptName = node.source.value;
|
||||
let script = getScript(scriptName);
|
||||
if (script == null) {
|
||||
@ -336,7 +360,7 @@ function processNetscript1Imports(code, workerScript) {
|
||||
|
||||
//Finish
|
||||
generatedCode += (
|
||||
"})(" + namespace + " || " + "(" + namespace + " = {}));"
|
||||
"})(" + namespace + " || " + "(" + namespace + " = {}));\n"
|
||||
)
|
||||
} else {
|
||||
//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
|
||||
var linesRemoved = 0;
|
||||
if (ast.type !== "Program" || ast.body == null) {
|
||||
throw new Error("Code could not be properly parsed");
|
||||
}
|
||||
for (let i = ast.body.length-1; i >= 0; --i) {
|
||||
if (ast.body[i].type === "ImportDeclaration") {
|
||||
ast.body.splice(i, 1);
|
||||
++linesRemoved;
|
||||
}
|
||||
}
|
||||
|
||||
//Calculated line offset
|
||||
var lineOffset = (generatedCode.match(/\n/g) || []).length - linesRemoved;
|
||||
|
||||
//Convert the AST back into code
|
||||
code = generate(ast);
|
||||
|
||||
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user