/* Evaluator * Evaluates the Abstract Syntax Tree for Netscript * generated by the Parser class */ // Evaluator should return a Promise, so that any call to evaluate() can just //wait for that promise to finish before continuing function evaluate(exp, workerScript) { return new Promise(function(resolve, reject) { var env = workerScript.env; if (env.stopFlag) {return reject(workerScript);} if (exp == null) { return reject(makeRuntimeRejectMsg(workerScript, "Error: NULL expression")); } setTimeout(function() { if (env.stopFlag) {return reject(workerScript);} switch (exp.type) { case "BlockStatement": case "Program": var evaluateProgPromise = evaluateProg(exp, workerScript, 0); //TODO: make every block/program use individual enviroment evaluateProgPromise.then(function(w) { resolve(workerScript); }, function(e) { if (typeof e === 'string' || e instanceof String) { workerScript.errorMessage = e; reject(workerScript); } else if (e instanceof WorkerScript) { reject(e); } else { reject(workerScript); } }); break; case "Literal": resolve(exp.value); break; case "Identifier": if (!(exp.name in env.vars)){ reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.name + " not definied")); } resolve(env.get(exp.name)) break; case "ExpressionStatement": var e = evaluate(exp.expression, workerScript); e.then(function(res) { resolve("expression done"); }, function(e) { reject(e); }); break; case "ArrayExpression": var argPromises = exp.elements.map(function(arg) { return evaluate(arg, workerScript); }); Promise.all(argPromises).then(function(array) { resolve(array) }).catch(function(e) { reject(e); }); break; case "CallExpression": evaluate(exp.callee, workerScript).then(function(func) { var argPromises = exp.arguments.map(function(arg) { return evaluate(arg, workerScript); }); Promise.all(argPromises).then(function(args) { if (exp.callee.type == "MemberExpression"){ evaluate(exp.callee.object, workerScript).then(function(object) { try { var res = func.apply(object,args); resolve(res); } catch (e) { reject(makeRuntimeRejectMsg(workerScript, e)); } }).catch(function(e) { reject(e); }); } else { try { var out = func.apply(null,args); if (out instanceof Promise){ out.then(function(res) { resolve(res) }).catch(function(e) { reject(e); }); } else { resolve(out); } } catch (e) { if (isScriptErrorMessage(e)) { reject(e); } else { reject(makeRuntimeRejectMsg(workerScript, e)); } } } }).catch(function(e) { reject(e); }); }).catch(function(e) { reject(e); }); break; case "MemberExpression": var pObject = evaluate(exp.object, workerScript); pObject.then(function(object) { if (exp.computed){ var p = evaluate(exp.property, workerScript); p.then(function(index) { resolve(object[index]); }).catch(function(e) { reject(makeRuntimeRejectMsg(workerScript, "Invalid MemberExpression")); }); } else { try { resolve(object[exp.property.name]) } catch (e) { return reject(makeRuntimeRejectMsg(workerScript, "Failed to get property: " + e.toString())); } } }).catch(function(e) { reject(e); }); break; case "LogicalExpression": case "BinaryExpression": var p = evalBinary(exp, workerScript, resolve, reject); p.then(function(res) { resolve(res); }).catch(function(e) { reject(e); }); break; case "AssignmentExpression": var p = evalAssignment(exp, workerScript); p.then(function(res) { resolve(res); }).catch(function(e) { reject(e); }); break; case "UpdateExpression": if (exp.argument.type==="Identifier"){ if (exp.argument.name in env.vars){ if (exp.prefix){ resolve(env.get(exp.argument.name)) } 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: reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". This is a bug please report to game developer")); } if (env.prefix){ return; } resolve(env.get(exp.argument.name)) } else { reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.argument.name + " not definied")); } } else { reject(makeRuntimeRejectMsg(workerScript, "argument must be an identifier")); } break; case "EmptyStatement": resolve(false); break; case "ReturnStatement": reject(makeRuntimeRejectMsg(workerScript, "Not implemented ReturnStatement")); break; case "BreakStatement": reject(makeRuntimeRejectMsg(workerScript, "Not implemented BreakStatement")); break; case "IfStatement": evaluateIf(exp, workerScript).then(function(forLoopRes) { resolve("forLoopDone"); }).catch(function(e) { reject(e); }); break; case "SwitchStatement": reject(makeRuntimeRejectMsg(workerScript, "Not implemented SwitchStatement")); break;e case "WhileStatement": evaluateWhile(exp, workerScript).then(function(forLoopRes) { resolve("forLoopDone"); }).catch(function(e) { reject(e); }); break; case "ForStatement": evaluate(exp.init, workerScript).then(function(expInit) { return evaluateFor(exp, workerScript); }).then(function(forLoopRes) { resolve("forLoopDone"); }).catch(function(e) { reject(e); }); break; default: reject(makeRuntimeRejectMsg(workerScript, "Unrecognized token: " + exp.type + ". This is a bug please report to game developer")); break; } //End switch }, Settings.CodeInstructionRunTime); //End setTimeout, the Netscript operation run time }); // End Promise } /* function evalFunction(exp, workerScript){ return new Promise(function(resolve, reject) { if (exp.callee.type!="Identifier"){ reject(makeRuntimeRejectMsg(workerScript, "callee must be an Identifier")); return; } switch(exp.callee.name){ case "print": if (exp.arguments.length != 1) { return reject(makeRuntimeRejectMsg(workerScript, "print() call has incorrect number of arguments. Takes 1 argument")); } var evaluatePromise = evaluate(exp.arguments[0], workerScript); evaluatePromise.then(function(res) { workerScript.scriptRef.log(res.toString()); resolve(true); }).catch(function(e) { reject(e); }); break; case "scan": if (exp.arguments.length != 1) { exp.arguments = [{value:Player.getCurrentServer().hostname,type:"Literal"}]; } var ipPromise = evaluate(exp.arguments[0], workerScript); ipPromise.then(function (ip) { var server = getServer(ip); if (server == null) { workerScript.scriptRef.log('getServerOpenPortsCount() failed. Invalid IP or hostname passed in: ' + ip); return reject(makeRuntimeRejectMsg(workerScript, 'Invalid IP or hostname passed into getServerOpenPortsCount() command')); } var out = []; for (var i = 0; i < server.serversOnNetwork.length; i++) { var entry = server.getServerOnNetwork(i).hostname; if (entry == null) { continue; } out.push(entry); } workerScript.scriptRef.log('scan() returned ' + server.serversOnNetwork.length + ' connections for ' + server.hostname); resolve(out); }).catch(function(e) { reject(e); }); break; default: reject(makeRuntimeRejectMsg(workerScript, "Invalid function: " + exp.callee)); } }); } */ function evalBinary(exp, workerScript){ return new Promise(function(resolve, reject) { var expLeftPromise = evaluate(exp.left, workerScript); expLeftPromise.then(function(expLeft) { var expRightPromise = evaluate(exp.right, workerScript); expRightPromise.then(function(expRight) { switch (exp.operator){ case "===": case "==": resolve(expLeft===expRight); break; case "!==": case "!=": resolve(expLeft!==expRight); break; case "<": resolve(expLeft": resolve(expLeft>expRight); break; case ">=": resolve(expLeft>=expRight); break; case "+": resolve(expLeft+expRight); break; case "-": resolve(expLeft-expRight); break; case "*": resolve(expLeft*expRight); break; case "/": resolve(expLeft/expRight); break; case "%": resolve(expLeft%expRight); break; case "in": resolve(expLeft in expRight); break; case "instanceof": resolve(expLeft instanceof expRight); break; case "||": resolve(expLeft || expRight); break; case "&&": resolve(expLeft && expRight); break; default: reject(makeRuntimeRejectMsg(workerScript, "Bitwise operators are not implemented")); } }, function(e) { reject(e); }); }, function(e) { reject(e); }); }); } function evalUnary(exp, workerScript){ return new Promise(function(resolve, reject) { var expLeftPromise = evaluate(exp.left, workerScript); expLeftPromise.then(function(expLeft) { switch(exp.operator){ case "++": break case "--": break; } }, function(e) { reject(e); }); }); } function evalAssignment(exp, workerScript) { var env = workerScript.env; return new Promise(function(resolve, reject) { if (env.stopFlag) {return reject(workerScript);} if (exp.left.type != "Identifier" && exp.left.type != "MemberExpression") { return reject(makeRuntimeRejectMsg(workerScript, "Cannot assign to " + JSON.stringify(exp.left))); } if (exp.operator !== "=" && !(exp.left.name in env.vars)){ return reject(makeRuntimeRejectMsg(workerScript, "variable " + exp.left.name + " not definied")); } var expRightPromise = evaluate(exp.right, workerScript); expRightPromise.then(function(expRight) { if (exp.left.type == "MemberExpression") { //Assign to array element //Array object designed by exp.left.object.name //Index designated by exp.left.property var name = exp.left.object.name; if (!(name in env.vars)){ reject(makeRuntimeRejectMsg(workerScript, "variable " + name + " not definied")); } var arr = env.get(name); if (arr.constructor === Array || arr instanceof Array) { var iPromise = evaluate(exp.left.property, workerScript); iPromise.then(function(idx) { if (isNaN(idx)) { return reject(makeRuntimeRejectMsg(workerScript, "Invalid access to array. Index is not a number: " + idx)); } else if (idx >= arr.length || idx < 0) { return reject(makeRuntimeRejectMsg(workerScript, "Out of bounds: Invalid index in [] operator")); } else { env.setArrayElement(name, idx, expRight); } }).catch(function(e) { return reject(e); }); } else { return reject(makeRuntimeRejectMsg(workerScript, "Trying to access a non-array variable using the [] operator")); } } else { //Other assignments try { switch (exp.operator) { case "=": env.set(exp.left.name,expRight); break; case "+=": env.set(exp.left.name,env.get(exp.left.name) + expRight); break; case "-=": env.set(exp.left.name,env.get(exp.left.name) - expRight); break; case "*=": env.set(exp.left.name,env.get(exp.left.name) * expRight); break; case "/=": env.set(exp.left.name,env.get(exp.left.name) / expRight); break; case "%=": env.set(exp.left.name,env.get(exp.left.name) % expRight); break; default: reject(makeRuntimeRejectMsg(workerScript, "Bitwise assignment is not implemented")); } } catch (e) { return reject(makeRuntimeRejectMsg(workerScript, "Failed to set environment variable: " + e.toString())); } } resolve(false); //Return false so this doesnt cause conditionals to evaluate }, function(e) { reject(e); }); }); } //Returns true if any of the if statements evaluated, false otherwise. Therefore, the else statement //should evaluate if this returns false function evaluateIf(exp, workerScript, i) { var env = workerScript.env; return new Promise(function(resolve, reject) { evaluate(exp.test, workerScript).then(function(condRes) { if (condRes) { evaluate(exp.consequent, workerScript).then(function(res) { resolve(true); }, function(e) { reject(e); }); } else if (exp.alternate) { evaluate(exp.alternate, workerScript).then(function(res) { resolve(true); }, function(e) { reject(e); }); } else { resolve("endIf") } }, function(e) { reject(e); }); }); } //Evaluate the looping part of a for loop (Initialization block is NOT done in here) function evaluateFor(exp, workerScript) { var env = workerScript.env; return new Promise(function(resolve, reject) { if (env.stopFlag) {reject(workerScript); return;} var pCond = evaluate(exp.test, workerScript); pCond.then(function(resCond) { if (resCond) { //Run the for loop code var pBody = evaluate(exp.body, workerScript); //After the code executes make a recursive call pBody.then(function(resCode) { var pUpdate = evaluate(exp.update, workerScript); pUpdate.then(function(resPostloop) { var recursiveCall = evaluateFor(exp, workerScript); recursiveCall.then(function(foo) { resolve("endForLoop"); }, function(e) { reject(e); }); }, function(e) { reject(e); }); }, function(e) { reject(e); }); } else { resolve("endForLoop"); //Doesn't need to resolve to any particular value } }, function(e) { reject(e); }); }); } function evaluateWhile(exp, workerScript) { var env = workerScript.env; return new Promise(function(resolve, reject) { if (env.stopFlag) {reject(workerScript); return;} var pCond = new Promise(function(resolve, reject) { setTimeout(function() { var evaluatePromise = evaluate(exp.test, workerScript); evaluatePromise.then(function(resCond) { resolve(resCond); }, function(e) { reject(e); }); }, CONSTANTS.CodeInstructionRunTime); }); pCond.then(function(resCond) { if (resCond) { //Run the while loop code var pCode = new Promise(function(resolve, reject) { setTimeout(function() { var evaluatePromise = evaluate(exp.body, workerScript); evaluatePromise.then(function(resCode) { resolve(resCode); }, function(e) { reject(e); }); }, CONSTANTS.CodeInstructionRunTime); }); //After the code executes make a recursive call pCode.then(function(resCode) { var recursiveCall = evaluateWhile(exp, workerScript); recursiveCall.then(function(foo) { resolve("endWhileLoop"); }, function(e) { reject(e); }); }, function(e) { reject(e); }); } else { resolve("endWhileLoop"); //Doesn't need to resolve to any particular value } }, function(e) { reject(e); }); }); } /* function evaluateHacknetNode(exp, workerScript) { console.log("here"); var env = workerScript.env; return new Promise(function(resolve, reject) { setTimeout(function() { if (exp.index == null) { if ((exp.op.type == "call" && exp.op.func.value == "length") || (exp.op.type == "var" && exp.op.value == "length")) { resolve(Player.hacknetNodes.length); workerScript.scriptRef.log("hacknetnodes.length returned " + Player.hacknetNodes.length); return; } else { workerScript.scriptRef.log("Invalid/null index for hacknetnodes"); reject(makeRuntimeRejectMsg(workerScript, "Invalid/null index. hacknetnodes array must be accessed with an index")); return; } } var indexPromise = evaluate(exp.index.value, workerScript); indexPromise.then(function(index) { if (isNaN(index) || index >= Player.hacknetNodes.length || index < 0) { workerScript.scriptRef.log("Invalid index value for hacknetnodes[]"); reject(makeRuntimeRejectMsg(workerScript, "Invalid index value for hacknetnodes[].")); return; } var nodeObj = Player.hacknetNodes[index]; if (exp.op == null) { reject(makeRuntimeRejectMsg(workerScript, "No operator or property called for hacknetnodes. Usage: hacknetnodes[i].property/operator")); return; } else if (exp.op.type == "var") { //Get properties: level, ram, cores switch(exp.op.value) { case "level": resolve(nodeObj.level); break; case "ram": resolve(nodeObj.ram); break; case "cores": resolve(nodeObj.numCores); break; default: reject(makeRuntimeRejectMsg(workerScript, "Unrecognized property for Hacknet Node. Valid properties: ram, cores, level")); break; } } else if (exp.op.type == "call") { switch(exp.op.func.value) { case "upgradeLevel": if (exp.op.args.length == 1) { var argPromise = evaluate(exp.op.args[0], workerScript); argPromise.then(function(arg) { if (isNaN(arg) || arg < 0) { reject(makeRuntimeRejectMsg(workerScript, "Invalid argument passed into upgradeLevel()")); return; } arg = Math.round(arg); var res = nodeObj.purchaseLevelUpgrade(arg); if (res) { workerScript.scriptRef.log("Upgraded " + nodeObj.name + " " + arg + " times to level " + nodeObj.level); } resolve(res); }, function(e) { reject(e); }); } else { var res = nodeObj.purchaseLevelUpgrade(1); if (res) { workerScript.scriptRef.log("Upgraded " + nodeObj.name + " once to level " + nodeObj.level); } resolve(res); } break; case "upgradeRam": var res = nodeObj.purchaseRamUpgrade(); if (res) { workerScript.scriptRef.log("Upgraded " + nodeObj.name + "'s RAM to " + nodeObj.ram + "GB"); } resolve(res); break; case "upgradeCore": var res = nodeObj.purchaseCoreUpgrade(); if (res) { workerScript.scriptRef.log("Upgraded " + nodeObj.name + "'s number of cores to " + nodeObj.numCores); } resolve(res); break; default: reject(makeRuntimeRejectMsg(workerScript, "Unrecognized function/operator for hacknet node. Valid functions: upgradeLevel(n), upgradeRam(), upgradeCore()")); break; } } else { reject(makeRuntimeRejectMsg(workerScript, "Unrecognized operation for hacknet node")); return; } }, function(e) { reject(e); }); }, CONSTANTS.CodeInstructionRunTime); }, function(e) { reject(e); }); } */ function evaluateProg(exp, workerScript, index) { var env = workerScript.env; return new Promise(function(resolve, reject) { if (env.stopFlag) {reject(workerScript); return;} if (index >= exp.body.length) { resolve("progFinished"); } else { //Evaluate this line of code in the prog var code = new Promise(function(resolve, reject) { setTimeout(function() { var evaluatePromise = evaluate(exp.body[index], workerScript); evaluatePromise.then(function(evalRes) { resolve(evalRes); }, function(e) { reject(e); }); }, CONSTANTS.CodeInstructionRunTime); }); //After the code finishes evaluating, evaluate the next line recursively code.then(function(codeRes) { var nextLine = evaluateProg(exp, workerScript, index + 1); nextLine.then(function(nextLineRes) { resolve(workerScript); }, function(e) { reject(e); }); }, function(e) { reject(e); }); } }); } function netscriptDelay(time) { return new Promise(function(resolve) { setTimeout(resolve, time); }); } function makeRuntimeRejectMsg(workerScript, msg) { return "|"+workerScript.serverIp+"|"+workerScript.name+"|" + msg; } function apply_op(op, a, b) { function num(x) { if (typeof x != "number") throw new Error("Expected number but got " + x); return x; } function div(x) { if (num(x) == 0) throw new Error("Divide by zero"); return x; } switch (op) { case "+": return a + b; case "-": return num(a) - num(b); case "*": return num(a) * num(b); case "/": return num(a) / div(b); case "%": return num(a) % div(b); case "&&": return a !== false && b; case "||": return a !== false ? a : b; case "<": return num(a) < num(b); case ">": return num(a) > num(b); case "<=": return num(a) <= num(b); case ">=": return num(a) >= num(b); case "==": return a === b; case "!=": return a !== b; } throw new Error("Can't apply operator " + op); } //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); } //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; ramUsage = ramUsage * threads * Math.pow(CONSTANTS.MultithreadingRAMCost, threads-1); var ramAvailable = server.maxRam - server.ramUsed; 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 workerScript.scriptRef.log("Running script: " + scriptname + " on " + server.hostname + " with " + threads + " threads and args: " + printArray(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); } } } workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname); return Promise.resolve(false); } function isScriptErrorMessage(msg) { splitMsg = msg.split("|"); if (splitMsg.length != 4){ return false; } var ip = splitMsg[1]; if (!isValidIPAddress(ip)) { return false; } return true; } //The same as Player's calculateHackingChance() function but takes in the server as an argument function scriptCalculateHackingChance(server) { var difficultyMult = (100 - server.hackDifficulty) / 100; var skillMult = (1.75 * Player.hacking_skill); var skillChance = (skillMult - server.requiredHackingSkill) / skillMult; var chance = skillChance * difficultyMult * Player.hacking_chance_mult; if (chance > 1) {return 1;} if (chance < 0) {return 0;} else {return chance;} } //The same as Player's calculateHackingTime() function but takes in the server as an argument function scriptCalculateHackingTime(server) { var difficultyMult = server.requiredHackingSkill * server.hackDifficulty; var skillFactor = (2.5 * difficultyMult + 500) / (Player.hacking_skill + 50); var hackingTime = 5 * skillFactor / Player.hacking_speed_mult; //This is in seconds return hackingTime; } //The same as Player's calculateExpGain() function but takes in the server as an argument function scriptCalculateExpGain(server) { if (server.baseDifficulty == null) { server.baseDifficulty = server.hackDifficulty; } return (server.baseDifficulty * Player.hacking_exp_mult * 0.4 + 2); } //The same as Player's calculatePercentMoneyHacked() function but takes in the server as an argument function scriptCalculatePercentMoneyHacked(server) { var difficultyMult = (100 - server.hackDifficulty) / 100; var skillMult = (Player.hacking_skill - (server.requiredHackingSkill - 1)) / Player.hacking_skill; var percentMoneyHacked = difficultyMult * skillMult * Player.hacking_money_mult / 240; if (percentMoneyHacked < 0) {return 0;} if (percentMoneyHacked > 1) {return 1;} return percentMoneyHacked; } //Amount of time to execute grow() in milliseconds function scriptCalculateGrowTime(server) { var difficultyMult = server.requiredHackingSkill * server.hackDifficulty; var skillFactor = (2.5 * difficultyMult + 500) / (Player.hacking_skill + 50); var growTime = 16 * skillFactor / Player.hacking_speed_mult; //This is in seconds return growTime * 1000; } //Amount of time to execute weaken() in milliseconds function scriptCalculateWeakenTime(server) { var difficultyMult = server.requiredHackingSkill * server.hackDifficulty; var skillFactor = (2.5 * difficultyMult + 500) / (Player.hacking_skill + 50); var weakenTime = 20 * skillFactor / Player.hacking_speed_mult; //This is in seconds return weakenTime * 1000; }