bitburner-src/src/NetscriptEvaluator.js

916 lines
40 KiB
JavaScript
Raw Normal View History

import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Player } from "./Player";
import { Environment } from "./NetscriptEnvironment";
import { WorkerScript, addWorkerScript} from "./NetscriptWorker";
import { Server, getServer} from "./Server";
import { Settings } from "./Settings";
import { Script, findRunningScript,
RunningScript } from "./Script";
import {parse, Node} from "../utils/acorn";
import {arrayToString} from "../utils/helpers/arrayToString";
import {isValidIPAddress} from "../utils/helpers/isValidIPAddress";
import {isString} from "../utils/helpers/isString";
var Promise = require("bluebird");
Promise.config({
warnings: false,
longStackTraces: false,
cancellation: true,
monitoring: false
});
/* Evaluator
* Evaluates/Interprets the Abstract Syntax Tree generated by Acorns parser
*
* Returns a promise
*/
function evaluate(exp, workerScript) {
return Promise.delay(Settings.CodeInstructionRunTime).then(function() {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
if (exp == null) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error: NULL expression", exp));
}
if (env.stopFlag) {return Promise.reject(workerScript);}
2017-06-05 06:48:37 +02:00
switch (exp.type) {
2017-06-28 11:47:42 +02:00
case "BlockStatement":
case "Program":
var evaluateProgPromise = evaluateProg(exp, workerScript, 0); //TODO: make every block/program use individual enviroment
return evaluateProgPromise.then(function(w) {
return Promise.resolve(workerScript);
}).catch(function(e) {
if (e.constructor === Array && e.length === 2 && e[0] === "RETURNSTATEMENT") {
return Promise.reject(e);
} else if (isString(e)) {
2017-06-28 11:47:42 +02:00
workerScript.errorMessage = e;
return Promise.reject(workerScript);
2017-06-28 11:47:42 +02:00
} else if (e instanceof WorkerScript) {
return Promise.reject(e);
} else {
return Promise.reject(workerScript);
}
2017-06-28 11:47:42 +02:00
});
break;
case "Literal":
return Promise.resolve(exp.value);
2017-06-28 11:47:42 +02:00
break;
case "Identifier":
2018-01-27 07:52:39 +01:00
//Javascript constructor() method can be used as an exploit to run arbitrary code
if (exp.name == "constructor") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Illegal usage of constructor() method. If you have your own function named 'constructor', you must re-name it.", exp));
2018-01-27 07:52:39 +01:00
}
2017-06-28 11:47:42 +02:00
if (!(exp.name in env.vars)){
return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.name + " not defined", exp));
2017-06-05 06:48:37 +02:00
}
return Promise.resolve(env.get(exp.name))
2017-06-05 06:48:37 +02:00
break;
2017-06-28 11:47:42 +02:00
case "ExpressionStatement":
return evaluate(exp.expression, workerScript);
2017-06-05 06:48:37 +02:00
break;
2017-06-28 11:47:42 +02:00
case "ArrayExpression":
var argPromises = exp.elements.map(function(arg) {
return evaluate(arg, workerScript);
});
return Promise.all(argPromises).then(function(array) {
return Promise.resolve(array)
2017-06-05 06:48:37 +02:00
});
break;
2017-06-28 11:47:42 +02:00
case "CallExpression":
return evaluate(exp.callee, workerScript).then(function(func) {
return Promise.map(exp.arguments, function(arg) {
return evaluate(arg, workerScript);
}).then(function(args) {
if (func instanceof Node) { //Player-defined function
//Create new Environment for the function
//Should be automatically garbage collected...
var funcEnv = env.extend();
//Define function arguments in this new environment
for (var i = 0; i < func.params.length; ++i) {
var arg;
if (i >= args.length) {
arg = null;
} else {
arg = args[i];
}
funcEnv.def(func.params[i].name, arg);
}
//Create a new WorkerScript for this function evaluation
var funcWorkerScript = new WorkerScript(workerScript.scriptRef);
funcWorkerScript.serverIp = workerScript.serverIp;
funcWorkerScript.env = funcEnv;
workerScript.fnWorker = funcWorkerScript;
return evaluate(func.body, funcWorkerScript).then(function(res) {
2017-10-10 06:56:48 +02:00
//If the function finished successfuly, that means there
//was no return statement since a return statement rejects. So resolve to null
workerScript.fnWorker = null;
return Promise.resolve(null);
}).catch(function(e) {
2017-10-10 06:56:48 +02:00
if (e.constructor === Array && e.length === 2 && e[0] === "RETURNSTATEMENT") {
//Return statement from function
return Promise.resolve(e[1]);
workerScript.fnWorker = null;
2017-10-10 06:56:48 +02:00
} else if (isString(e)) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e));
} else if (e instanceof WorkerScript) {
//Parse out the err message from the WorkerScript and re-reject
var errorMsg = e.errorMessage;
var errorTextArray = errorMsg.split("|");
if (errorTextArray.length === 4) {
errorMsg = errorTextArray[3];
return Promise.reject(makeRuntimeRejectMsg(workerScript, errorMsg));
} else {
if (env.stopFlag) {
return Promise.reject(workerScript);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error in one of your functions. Could not identify which function"));
}
}
} else if (e instanceof Error) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e.toString()));
}
});
} else if (exp.callee.type === "MemberExpression"){
return evaluate(exp.callee.object, workerScript).then(function(object) {
2017-06-28 11:47:42 +02:00
try {
if (func === "NETSCRIPTFOREACH") {
return evaluateForeach(object, args, workerScript).then(function(res) {
return Promise.resolve(res);
}).catch(function(e) {
return Promise.reject(e);
});
}
var res = func.apply(object,args);
return Promise.resolve(res);
2017-06-28 11:47:42 +02:00
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e, exp));
2017-06-28 11:47:42 +02:00
}
});
} else {
2017-06-28 11:47:42 +02:00
try {
var out = func.apply(null,args);
if (out instanceof Promise){
return out.then(function(res) {
return Promise.resolve(res)
2017-06-28 11:47:42 +02:00
}).catch(function(e) {
if (isScriptErrorMessage(e)) {
//Functions don't have line number appended in error message, so add it
var num = getErrorLineNumber(exp, workerScript);
e += " (Line " + num + ")";
}
return Promise.reject(e);
2017-06-28 11:47:42 +02:00
});
} else {
return Promise.resolve(out);
2017-06-28 11:47:42 +02:00
}
} catch (e) {
if (isScriptErrorMessage(e)) {
if (isScriptErrorMessage(e)) {
//Functions don't have line number appended in error message, so add it
var num = getErrorLineNumber(exp, workerScript);
e += " (Line " + num + ")";
}
return Promise.reject(e);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e, exp));
}
2017-06-28 11:47:42 +02:00
}
}
2017-06-28 11:47:42 +02:00
});
});
2017-06-05 06:48:37 +02:00
break;
2017-06-28 11:47:42 +02:00
case "MemberExpression":
return evaluate(exp.object, workerScript).then(function(object) {
2017-06-28 11:47:42 +02:00
if (exp.computed){
return evaluate(exp.property, workerScript).then(function(index) {
if (index >= object.length) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid index for arrays", exp));
}
return Promise.resolve(object[index]);
2017-06-28 11:47:42 +02:00
}).catch(function(e) {
if (e instanceof WorkerScript || isScriptErrorMessage(e)) {
return Promise.reject(e);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid MemberExpression", exp));
}
2017-06-28 11:47:42 +02:00
});
} else {
2018-01-27 07:52:39 +01:00
if (exp.property.name === "constructor") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Illegal usage of constructor() method. If you have your own function named 'constructor', you must re-name it.", exp));
2018-01-27 07:52:39 +01:00
}
if (object != null && object instanceof Array && exp.property.name === "forEach") {
return "NETSCRIPTFOREACH";
}
2017-06-28 11:47:42 +02:00
try {
return Promise.resolve(object[exp.property.name])
2017-06-28 11:47:42 +02:00
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to get property: " + e.toString(), exp));
2017-06-28 11:47:42 +02:00
}
}
2017-06-05 06:48:37 +02:00
});
break;
2017-06-28 11:47:42 +02:00
case "LogicalExpression":
case "BinaryExpression":
return evalBinary(exp, workerScript);
2017-06-05 06:48:37 +02:00
break;
case "UnaryExpression":
return evalUnary(exp, workerScript);
break;
2017-06-28 11:47:42 +02:00
case "AssignmentExpression":
return evalAssignment(exp, workerScript);
2017-06-05 06:48:37 +02:00
break;
case "VariableDeclaration":
return evalVariableDeclaration(exp, workerScript);
break;
2017-06-28 11:47:42 +02:00
case "UpdateExpression":
if (exp.argument.type==="Identifier"){
if (exp.argument.name in env.vars){
if (exp.operator === "++" || exp.operator === "--") {
switch (exp.operator) {
case "++":
env.set(exp.argument.name,env.get(exp.argument.name)+1);
break;
case "--":
env.set(exp.argument.name,env.get(exp.argument.name)-1);
break;
default: break;
}
return Promise.resolve(env.get(exp.argument.name));
}
//Not sure what prefix UpdateExpressions there would be besides ++/--
2017-06-28 11:47:42 +02:00
if (exp.prefix){
return Promise.resolve(env.get(exp.argument.name))
2017-06-28 11:47:42 +02:00
}
switch (exp.operator){
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". You are trying to use code that is currently unsupported", exp));
}
return Promise.resolve(env.get(exp.argument.name))
2017-06-28 11:47:42 +02:00
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.argument.name + " not defined", exp));
}
2017-06-05 06:48:37 +02:00
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "argument must be an identifier", exp));
2017-06-28 11:47:42 +02:00
}
break;
case "EmptyStatement":
return Promise.resolve(false);
2017-06-28 11:47:42 +02:00
break;
case "ReturnStatement":
return evaluate(exp.argument, workerScript).then(function(res) {
return Promise.reject(["RETURNSTATEMENT", res]);
});
2017-06-28 11:47:42 +02:00
break;
case "BreakStatement":
return Promise.reject("BREAKSTATEMENT");
2017-06-28 11:47:42 +02:00
break;
case "ContinueStatement":
return Promise.reject("CONTINUESTATEMENT");
break;
2017-06-28 11:47:42 +02:00
case "IfStatement":
return evaluateIf(exp, workerScript);
2017-06-28 11:47:42 +02:00
break;
case "SwitchStatement":
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Switch statements are not yet implemented in Netscript", exp));
break;
2017-06-28 11:47:42 +02:00
case "WhileStatement":
return evaluateWhile(exp, workerScript).then(function(res) {
return Promise.resolve(res);
2017-06-28 11:47:42 +02:00
}).catch(function(e) {
if (e == "BREAKSTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "BREAKSTATEMENT")) {
return Promise.resolve("whileLoopBroken");
} else {
return Promise.reject(e);
}
2017-06-28 11:47:42 +02:00
});
break;
case "ForStatement":
return evaluate(exp.init, workerScript).then(function(expInit) {
2017-06-28 11:47:42 +02:00
return evaluateFor(exp, workerScript);
}).then(function(forLoopRes) {
return Promise.resolve("forLoopDone");
2017-06-28 11:47:42 +02:00
}).catch(function(e) {
if (e == "BREAKSTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "BREAKSTATEMENT")) {
return Promise.resolve("forLoopBroken");
} else {
return Promise.reject(e);
}
2017-06-28 11:47:42 +02:00
});
2017-06-05 06:48:37 +02:00
break;
case "FunctionDeclaration":
if (exp.id && exp.id.name) {
env.set(exp.id.name, exp);
return Promise.resolve(true);
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid function declaration", exp));
}
break;
case "ImportDeclaration":
return evaluateImport(exp, workerScript).then(function(res) {
return Promise.resolve(res);
}).catch(function(e) {
return Promise.reject(e);
});
break;
case "ThrowStatement":
return evaluate(exp.argument, workerScript).then(function(res) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, res));
});
break;
2017-06-05 06:48:37 +02:00
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". This is currently unsupported in Netscript", exp));
2017-06-05 06:48:37 +02:00
break;
} //End switch
}).catch(function(e) {
return Promise.reject(e);
2017-06-05 06:48:37 +02:00
}); // End Promise
}
2017-06-28 11:47:42 +02:00
function evalBinary(exp, workerScript){
return evaluate(exp.left, workerScript).then(function(expLeft) {
//Short circuiting
if (expLeft && exp.operator === "||") {
return Promise.resolve(expLeft);
}
if (!expLeft && exp.operator === "&&") {
return Promise.resolve(expLeft);
}
return evaluate(exp.right, workerScript).then(function(expRight) {
switch (exp.operator){
case "===":
case "==":
return Promise.resolve(expLeft===expRight);
break;
case "!==":
case "!=":
return Promise.resolve(expLeft!==expRight);
break;
case "<":
return Promise.resolve(expLeft<expRight);
break;
case "<=":
return Promise.resolve(expLeft<=expRight);
break;
case ">":
return Promise.resolve(expLeft>expRight);
break;
case ">=":
return Promise.resolve(expLeft>=expRight);
break;
case "+":
return Promise.resolve(expLeft+expRight);
break;
case "-":
return Promise.resolve(expLeft-expRight);
break;
case "*":
return Promise.resolve(expLeft*expRight);
break;
case "/":
if (expRight === 0) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "ERROR: Divide by zero"));
} else {
return Promise.resolve(expLeft/expRight);
}
break;
case "%":
return Promise.resolve(expLeft%expRight);
break;
case "in":
return Promise.resolve(expLeft in expRight);
break;
case "instanceof":
return Promise.resolve(expLeft instanceof expRight);
break;
case "||":
return Promise.resolve(expLeft || expRight);
break;
case "&&":
return Promise.resolve(expLeft && expRight);
break;
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unsupported operator: " + exp.operator));
2017-09-15 16:06:59 +02:00
}
2017-06-28 11:47:42 +02:00
});
});
}
function evalUnary(exp, workerScript){
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
return evaluate(exp.argument, workerScript).then(function(res) {
if (exp.operator == "!") {
return Promise.resolve(!res);
} else if (exp.operator == "-") {
if (isNaN(res)) {
return Promise.resolve(res);
} else {
return Promise.resolve(-1 * res);
2017-06-28 11:47:42 +02:00
}
} else {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Unimplemented unary operator: " + exp.operator));
}
2017-06-28 11:47:42 +02:00
});
}
2017-09-15 16:06:59 +02:00
//Takes in a MemberExpression that should represent a Netscript array (possible multidimensional)
//The return value is an array of the form:
// [0th index (leftmost), array name, 1st index, 2nd index, ...]
function getArrayElement(exp, workerScript) {
var indices = [];
return evaluate(exp.property, workerScript).then(function(idx) {
if (isNaN(idx)) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid access to array. Index is not a number: " + idx));
} else {
if (exp.object.name === undefined && exp.object.object) {
return getArrayElement(exp.object, workerScript).then(function(res) {
res.push(idx);
indices = res;
return Promise.resolve(indices);
}).catch(function(e) {
return Promise.reject(e);
});
2017-09-15 16:06:59 +02:00
} else {
indices.push(idx);
indices.push(exp.object.name);
return Promise.resolve(indices);
2017-09-15 16:06:59 +02:00
}
}
2017-09-15 16:06:59 +02:00
});
}
2017-06-28 11:47:42 +02:00
function evalAssignment(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
2017-06-28 11:47:42 +02:00
if (exp.left.type != "Identifier" && exp.left.type != "MemberExpression") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to " + JSON.stringify(exp.left)));
}
2017-06-28 11:47:42 +02:00
if (exp.operator !== "=" && !(exp.left.name in env.vars)){
return Promise.reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.left.name + " not defined"));
}
return evaluate(exp.right, workerScript).then(function(expRight) {
if (exp.left.type == "MemberExpression") {
if (!exp.left.computed) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to an object's property. This is currently unsupported in Netscript", exp));
}
//Assign to array element
//Array object designed by exp.left.object.name
//Index designated by exp.left.property
return getArrayElement(exp.left, workerScript).then(function(res) {
if (!(res instanceof Array) || res.length < 2) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Error evaluating array assignment. This is (probably) a bug please report to game dev"));
}
2017-09-15 16:06:59 +02:00
//The array name is the second value
var arrName = res.splice(1, 1);
arrName = arrName[0];
2017-09-15 16:06:59 +02:00
var res;
try {
res = env.setArrayElement(arrName, res, expRight);
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, e));
}
return Promise.resolve(res);
}).catch(function(e) {
return Promise.reject(e);
});
} else {
//Other assignments
try {
var assign;
switch (exp.operator) {
case "=":
assign = expRight; break;
case "+=":
assign = env.get(exp.left.name) + expRight; break;
case "-=":
assign = env.get(exp.left.name) - expRight; break;
case "*=":
assign = env.get(exp.left.name) * expRight; break;
case "/=":
assign = env.get(exp.left.name) / expRight; break;
case "%=":
assign = env.get(exp.left.name) % expRight; break;
default:
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Bitwise assignment is not implemented"));
2017-06-28 11:47:42 +02:00
}
env.set(exp.left.name, assign);
return Promise.resolve(assign);
} catch (e) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to set environment variable: " + e.toString()));
2017-06-28 11:47:42 +02:00
}
}
2017-06-28 11:47:42 +02:00
});
}
function evalVariableDeclaration(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
if (!(exp.declarations instanceof Array)) {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Variable declarations parsed incorrectly. This may be a syntax error"));
}
if (exp.kind !== "var") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Only 'var' declarations are currently allowed (let, const, etc. are not allowed)"));
}
return Promise.all(exp.declarations.map((decl)=>{
evalVariableDeclarator(decl, workerScript);
})).then(function(res) {
return Promise.resolve(res);
});
}
//A Variable Declaration contains an array of Variable Declarators
function evalVariableDeclarator(exp, workerScript) {
var env = workerScript.env;
if (exp.type !== "VariableDeclarator") {
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid AST Node passed into evalVariableDeclarator: " + exp.type));
}
if (exp.init == null) {
env.set(exp.id.name, null);
return Promise.resolve(null);
} else {
return evaluate(exp.init, workerScript).then(function(initValue) {
env.set(exp.id.name, initValue);
});
}
}
2017-06-28 11:47:42 +02:00
function evaluateIf(exp, workerScript, i) {
var env = workerScript.env;
return evaluate(exp.test, workerScript).then(function(condRes) {
if (condRes) {
return evaluate(exp.consequent, workerScript).then(function(res) {
return Promise.resolve(true);
}, function(e) {
return Promise.reject(e);
});
} else if (exp.alternate) {
return evaluate(exp.alternate, workerScript).then(function(res) {
return Promise.resolve(true);
}, function(e) {
return Promise.reject(e);
});
} else {
return Promise.resolve("endIf");
}
});
}
//Evaluate the looping part of a for loop (Initialization block is NOT done in here)
function evaluateFor(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
return new Promise(function(resolve, reject) {
function recurse() {
//Don't return a promise so the promise chain is broken on each recursion (saving memory)
evaluate(exp.test, workerScript).then(function(resCond) {
if (resCond) {
return evaluate(exp.body, workerScript).then(function(res) {
return evaluate(exp.update, workerScript);
}).catch(function(e) {
if (e == "CONTINUESTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "CONTINUESTATEMENT")) {
//Continue statement, recurse to next iteration
return evaluate(exp.update, workerScript).then(function(resPostloop) {
return evaluateFor(exp, workerScript);
}).then(function(foo) {
return Promise.resolve("endForLoop");
}).catch(function(e) {
return Promise.reject(e);
});
} else {
return Promise.reject(e);
}
}).then(recurse, reject).catch(function(e) {
return Promise.reject(e);
});
} else {
resolve();
}
}).catch(function(e) {
reject(e);
});
}
recurse();
});
}
function evaluateForeach(arr, args, workerScript) {
console.log("evaluateForeach called");
if (!(arr instanceof Array)) {
return Promise.reject("Invalid array passed into forEach");
}
if (!(args instanceof Array) && args.length != 1) {
return Promise.reject("Invalid argument passed into forEach");
}
var func = args[0];
if (typeof func !== "function") {
return Promise.reject("Invalid function passed into forEach");
}
console.log(func);
return new Promise(function(resolve, reject) {
//Don't return a promise so the promise chain is broken on each recursion
function recurse(i) {
console.log("recurse() called with i: " + i);
if (i >= arr.length) {
resolve();
} else {
return Promise.delay(Settings.CodeInstructionRunTime).then(function() {
console.log("About to apply function");
var res = func.apply(null, [arr[i]]);
console.log("Applied function");
++i;
Promise.resolve(res).then(function(val) {
recurse(i);
}, reject).catch(function(e) {
return Promise.reject(e);
});
});
}
}
recurse(0);
});
}
function evaluateWhile(exp, workerScript) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
return new Promise(function (resolve, reject) {
function recurse() {
//Don't return a promise so the promise chain is broken on each recursion (saving memory)
evaluate(exp.test, workerScript).then(function(resCond) {
if (resCond) {
return evaluate(exp.body, workerScript).catch(function(e) {
if (e == "CONTINUESTATEMENT" ||
(e instanceof WorkerScript && e.errorMessage == "CONTINUESTATEMENT")) {
//Continue statement, recurse
return evaluateWhile(exp, workerScript).then(function(foo) {
return Promise.resolve("endWhileLoop");
}, function(e) {
return Promise.reject(e);
});
} else {
return Promise.reject(e);
}
}).then(recurse, reject).catch(function(e) {
return Promise.reject(e);
});
} else {
resolve();
}
}).catch(function(e) {
reject(e);
});
}
recurse();
});
}
function evaluateProg(exp, workerScript, index) {
var env = workerScript.env;
if (env.stopFlag) {return Promise.reject(workerScript);}
if (index >= exp.body.length) {
return Promise.resolve("progFinished");
} else {
//Evaluate this line of code in the prog
//After the code finishes evaluating, evaluate the next line recursively
return evaluate(exp.body[index], workerScript).then(function(res) {
return evaluateProg(exp, workerScript, index + 1);
}).then(function(res) {
return Promise.resolve(workerScript);
}).catch(function(e) {
return Promise.reject(e);
});
}
}
function evaluateImport(exp, workerScript, checkingRam=false) {
//When its checking RAM, it exports an array of nodes for each imported function
var ramCheckRes = [];
var env = workerScript.env;
if (env.stopFlag) {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(workerScript);
}
//Get source script and name of all functions to import
var scriptName = exp.source.value;
var namespace, namespaceObj, allFns = false, fnNames = [];
if (exp.specifiers.length === 1 && exp.specifiers[0].type === "ImportNamespaceSpecifier") {
allFns = true;
namespace = exp.specifiers[0].local.name;
} else {
for (var i = 0; i < exp.specifiers.length; ++i) {
fnNames.push(exp.specifiers[i].local.name);
}
}
//Get the code
var server = getServer(workerScript.serverIp), code = "";
if (server == null) {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to identify server. This is a bug please report to dev", exp));
}
for (var i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === scriptName) {
code = server.scripts[i].code;
break;
}
}
if (code === "") {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Could not find script " + scriptName + " to import", exp));
}
//Create the AST
try {
var ast = parse(code, {sourceType:"module"});
} catch(e) {
console.log("Failed to parse import script");
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Failed to import functions from " + scriptName +
" This is most likely due to a syntax error in the imported script", exp));
}
if (allFns) {
//A namespace is implemented as a JS obj
env.set(namespace, {});
namespaceObj = env.get(namespace);
}
//Search through the AST for all imported functions
var queue = [ast];
while (queue.length != 0) {
var node = queue.shift();
switch (node.type) {
case "BlockStatement":
case "Program":
for (var i = 0; i < node.body.length; ++i) {
if (node.body[i] instanceof Node) {
queue.push(node.body[i]);
}
}
break;
case "FunctionDeclaration":
if (node.id && node.id.name) {
if (allFns) {
//Import all functions under this namespace
if (checkingRam) {
ramCheckRes.push(node);
} else {
namespaceObj[node.id.name] = node;
}
} else {
//Only import specified functions
if (fnNames.includes(node.id.name)) {
if (checkingRam) {
ramCheckRes.push(node);
} else {
env.set(node.id.name, node);
}
}
}
} else {
if (checkingRam) {return ramCheckRes;}
return Promise.reject(makeRuntimeRejectMsg(workerScript, "Invalid function declaration in imported script " + scriptName, exp));
}
break;
default:
break;
}
for (var prop in node) {
if (node.hasOwnProperty(prop)) {
if (node[prop] instanceof Node) {
queue.push(node[prop]);
}
}
}
}
if (!checkingRam) {workerScript.scriptRef.log("Imported functions from " + scriptName);}
if (checkingRam) {return ramCheckRes;}
return Promise.resolve(true);
}
function killNetscriptDelay(workerScript) {
if (workerScript instanceof WorkerScript) {
if (workerScript.delay) {
clearTimeout(workerScript.delay);
workerScript.delayResolve();
}
}
}
2017-09-15 16:06:59 +02:00
function netscriptDelay(time, workerScript) {
return new Promise(function(resolve, reject) {
workerScript.delay = setTimeout(()=>{
workerScript.delay = null;
resolve();
}, time);
workerScript.delayResolve = resolve;
});
2017-06-05 06:48:37 +02:00
}
function makeRuntimeRejectMsg(workerScript, msg, exp=null) {
var lineNum = "";
if (exp != null) {
var num = getErrorLineNumber(exp, workerScript);
lineNum = " (Line " + num + ")"
}
return "|"+workerScript.serverIp+"|"+workerScript.name+"|" + msg + lineNum;
}
//Run a script from inside a script using run() command
function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
//Check if the script is already running
var runningScriptObj = findRunningScript(scriptname, args, server);
if (runningScriptObj != null) {
workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname);
return Promise.resolve(false);
}
2017-06-28 11:47:42 +02:00
//'null/undefined' arguments are not allowed
for (var i = 0; i < args.length; ++i) {
if (args[i] == null) {
workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument");
return Promise.resolve(false);
}
}
//Check if the script exists and if it does run it
for (var i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename == scriptname) {
//Check for admin rights and that there is enough RAM availble to run
var script = server.scripts[i];
var ramUsage = script.ramUsage;
2017-09-13 16:22:22 +02:00
threads = Math.round(Number(threads)); //Convert to number and round
ramUsage = ramUsage * threads;
var ramAvailable = server.maxRam - server.ramUsed;
2017-06-28 11:47:42 +02:00
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot run script " + scriptname + " on " + server.hostname + " because you do not have root access!");
return Promise.resolve(false);
} else if (ramUsage > ramAvailable){
workerScript.scriptRef.log("Cannot run script " + scriptname + "(t=" + threads + ") on " + server.hostname + " because there is not enough available RAM!");
return Promise.resolve(false);
} else {
//Able to run script
if(workerScript.disableLogs.ALL == null && workerScript.disableLogs.exec == null && workerScript.disableLogs.run == null && workerScript.disableLogs.spawn == null) {
workerScript.scriptRef.log("Running script: " + scriptname + " on " + server.hostname + " with " + threads + " threads and args: " + arrayToString(args) + ". May take a few seconds to start up...");
}
var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
server.runningScripts.push(runningScriptObj); //Push onto runningScripts
addWorkerScript(runningScriptObj, server);
return Promise.resolve(true);
}
2017-06-28 11:47:42 +02:00
}
}
workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname);
return Promise.resolve(false);
}
function getErrorLineNumber(exp, workerScript) {
var code = workerScript.scriptRef.scriptRef.code;
//Split code up to the start of the node
try {
code = code.substring(0, exp.start);
return (code.match(/\n/g) || []).length + 1;
} catch(e) {
return -1;
}
}
function isScriptErrorMessage(msg) {
if (!isString(msg)) {return false;}
let splitMsg = msg.split("|");
if (splitMsg.length != 4){
return false;
}
var ip = splitMsg[1];
if (!isValidIPAddress(ip)) {
return false;
}
return true;
}
export {makeRuntimeRejectMsg, netscriptDelay, runScriptFromScript, evaluate,
isScriptErrorMessage, killNetscriptDelay, evaluateImport};