bitburner-src/src/Netscript/Evaluator.js
2017-04-27 15:59:57 -05:00

551 lines
18 KiB
JavaScript

/* 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) {
var env = workerScript.env;
switch (exp.type) {
case "num":
case "str":
case "bool":
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
resolve(exp.value);
});
break;
case "var":
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
try {
resolve(env.get(exp.value));
} catch (e) {
throw new Error("|" + workerScript.serverIp + "|" + workerScript.name + "|" + e.toString());
}
});
break;
//Can currently only assign to "var"s
case "assign":
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
if (exp.left.type != "var")
throw new Error("|" + workerScript.serverIp + "|" + workerScript.name + "| Cannot assign to " + JSON.stringify(exp.left));
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
var expRightPromise = evaluate(exp.right, workerScript);
expRightPromise.then(function(expRight) {
resolve(expRight);
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime)
});
p.then(function(expRight) {
try {
env.set(exp.left.value, expRight);
} catch (e) {
throw new Error("|" + workerScript.serverIp + "|" + workerScript.name + "|" + e.toString());
}
resolve("assignFinished");
}, function(e) {
reject(e);
});
});
case "binary":
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
var pLeft = new Promise(function(resolve, reject) {
setTimeout(function() {
var promise = evaluate(exp.left, workerScript);
promise.then(function(valLeft) {
resolve(valLeft);
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime);
});
pLeft.then(function(valLeft) {
var pRight = new Promise(function(resolve, reject) {
setTimeout(function() {
var promise = evaluate(exp.right, workerScript);
promise.then(function(valRight) {
resolve([valLeft, valRight]);
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime);
});
pRight.then(function(args) {
try {
resolve(apply_op(exp.operator, args[0], args[1]));
} catch (e) {
reject("|" + workerScript.serverIp + "|" + workerScript.name + "|" + e.toString());
}
}, function(e) {
reject(e);
});
}, function(e) {
reject(e);
});
});
break;
//TODO
case "if":
var numConds = exp.cond.length;
var numThens = exp.then.length;
if (numConds == 0 || numThens == 0 || numConds != numThens) {
throw new Error ("|" + workerScript.serverIp + "|" + workerScript.name + "|Number of conds and thens in if structure don't match (or there are none)");
}
for (var i = 0; i < numConds; i++) {
var cond = evaluate(exp.cond[i], workerScript);
if (cond) return evaluate(exp.then[i], workerScript);
}
//Evaluate else if it exists, snce none of the conditionals
//were true
return exp.else ? evaluate(exp.else, workerScript) : false;
case "for":
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
console.log("for loop encountered in evaluator");
workerScript.scriptRef.log("Entering for loop");
var pInit = new Promise(function(resolve, reject) {
setTimeout(function() {
var resInit = evaluate(exp.init, workerScript);
resInit.then(function(foo) {
resolve(resInit);
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime);
});
pInit.then(function(expInit) {
var pForLoop = evaluateFor(exp, workerScript);
pForLoop.then(function(forLoopRes) {
resolve("forLoopDone");
workerScript.scriptRef.log("Exiting for loop");
}, function(e) {
reject(e);
});
}, function(e) {
reject(e);
});
});
break;
case "while":
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
var pEvaluateWhile = evaluateWhile(exp, workerScript);
pEvaluateWhile.then(function(whileLoopRes) {
resolve("whileLoopDone");
}, function(e) {
reject(e);
});
});
break;
case "prog":
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
var evaluateProgPromise = evaluateProg(exp, workerScript, 0);
evaluateProgPromise.then(function(w) {
resolve(workerScript);
}, function(e) {
reject(e);
});
});
break;
/* Currently supported function calls:
* hack()
* sleep(N) - sleep N seconds
* print(x) - Prints a variable or constant
*
*/
case "call":
//Define only valid function calls here, like hack() and stuff
//var func = evaluate(exp.func, env);
//return func.apply(null, exp.args.map(function(arg){
// return evaluate(arg, env);
//}));
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
setTimeout(function() {
if (exp.func.value == "hack") {
if (exp.args.length != 1) {
throw new Error("|" + workerScript.serverIp + "|" + workerScript.name + "|Hack() call has incorrect number of arguments. Takes 1 argument");
}
//IP of server to hack
var ipPromise = evaluate(exp.args[0], workerScript);
ipPromise.then(function(ip) {
//Check if its a valid IP address. If it's not, assume its a hostname and
//try to get the server. If its not a server, there is an error
var server = null;
if (!isValidIPAddress(ip)) {
//It's not an IP address, so see if its a hostanme
server = GetServerByHostname(ip);
} else {
server = AllServers[ip];
}
if (server == null) {
resolve("Invalid IP or server hostname passed in");
workerScript.scriptRef.log("Cannot hack(). Invalid IP or hostname passed in: " + ip);
}
//Calculate the hacking time
var hackingTime = scriptCalculateHackingTime(server); //This is in seconds
if (server.hasAdminRights == false) {
console.log("Cannot hack server " + server.hostname);
resolve("Cannot hack, no admin rights");
workerScript.scriptRef.log("Cannot hack this server because user does not have root access");
}
workerScript.scriptRef.log("Attempting to hack " + ip + " in " + hackingTime + " seconds");
var p = new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
console.log("Hacking " + server.hostname + " after " + hackingTime.toString() + " seconds.");
setTimeout(function() {
var hackChance = scriptCalculateHackingChance(server);
var rand = Math.random();
var expGainedOnSuccess = scriptCalculateExpGain(server);
var expGainedOnFailure = Math.round(expGainedOnSuccess / 4);
if (rand < hackChance) { //Success!
var moneyGained = scriptCalculatePercentMoneyHacked(server);
moneyGained = Math.floor(server.moneyAvailable * moneyGained);
//Safety check
if (moneyGained <= 0) {moneyGained = 0;}
server.moneyAvailable -= moneyGained;
Player.gainMoney(moneyGained);
workerScript.scriptRef.onlineMoneyMade += moneyGained;
Player.gainHackingExp(expGainedOnSuccess);
workerScript.scriptRef.onlineExpGained += expGainedOnSuccess;
console.log("Script successfully hacked " + server.hostname + " for $" + moneyGained + " and " + expGainedOnSuccess + " exp");
workerScript.scriptRef.log("Script SUCCESSFULLY hacked " + server.hostname + " for $" + moneyGained + " and " + expGainedOnSuccess + " exp");
resolve("Hack success");
} else {
//Player only gains 25% exp for failure? TODO Can change this later to balance
Player.gainHackingExp(expGainedOnFailure);
workerScript.scriptRef.onlineExpGained += expGainedOnFailure;
console.log("Script unsuccessful to hack " + server.hostname + ". Gained " + expGainedOnFailure + " exp");
workerScript.scriptRef.log("Script FAILED to hack " + server.hostname + ". Gained " + expGainedOnFailure + " exp");
resolve("Hack failure");
}
}, hackingTime * 1000);
});
p.then(function(res) {
resolve("hackExecuted");
}, function(e) {
reject(e);
});
}, function(e) {
reject(e);
});
} else if (exp.func.value == "sleep") {
if (exp.args.length != 1) {
throw new Error("|" + workerScript.serverIp + "|" + workerScript.name + "|Sleep() call has incorrect number of arguments. Takes 1 argument.");
}
var sleepTimePromise = evaluate(exp.args[0], workerScript);
sleepTimePromise.then(function(sleepTime) {
workerScript.scriptRef.log("Sleeping for " + sleepTime + " milliseconds");
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("foo");
}, sleepTime);
});
p.then(function(res) {
resolve("sleepExecuted");
}, function(e) {
reject(e);
});
}, function(e) {
reject(e)
});
} else if (exp.func.value == "print") {
if (exp.args.length != 1) {
throw new Error("|" + workerScript.serverIp + "|" + workerScript.name + "| Print() call has incorrect number of arguments. Takes 1 argument");
}
var p = new Promise(function(resolve, reject) {
setTimeout(function() {
var evaluatePromise = evaluate(exp.args[0], workerScript);
evaluatePromise.then(function(res) {
resolve(res);
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime);
});
p.then(function(res) {
post(res.toString());
resolve("printExecuted");
}, function(e) {
reject(e);
});
}
}, CONSTANTS.CodeInstructionRunTime);
});
break;
default:
throw new Error("|" + workerScript.serverIp + "|" + workerScript.name + "| Can't evaluate type " + exp.type);
}
}
//Evaluate the looping part of a for loop (Initialization block is NOT done in here)
function evaluateFor(exp, workerScript) {
var env = workerScript.env;
console.log("evaluateFor() called");
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
var pCond = new Promise(function(resolve, reject) {
setTimeout(function() {
var evaluatePromise = evaluate(exp.cond, workerScript);
evaluatePromise.then(function(resCond) {
console.log("Conditional evaluated to: " + resCond);
resolve(resCond);
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime);
});
pCond.then(function(resCond) {
if (resCond) {
console.log("About to evaluate an iteration of for loop code");
//Run the for loop code
var pCode = new Promise(function(resolve, reject) {
setTimeout(function() {
var evaluatePromise = evaluate(exp.code, workerScript);
evaluatePromise.then(function(resCode) {
console.log("Evaluated an iteration of for loop code");
resolve(resCode);
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime);
});
//After the code executes make a recursive call
pCode.then(function(resCode) {
var pPostLoop = new Promise(function(resolve, reject) {
setTimeout(function() {
var evaluatePromise = evaluate(exp.postloop, workerScript);
evaluatePromise.then(function(foo) {
console.log("Evaluated for loop postloop");
resolve("postLoopFinished");
}, function(e) {
reject(e);
});
}, CONSTANTS.CodeInstructionRunTime);
});
pPostLoop.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 {
console.log("Cond is false, stopping for loop");
resolve("endForLoop"); //Doesn't need to resolve to any particular value
}
}, function(e) {
reject(e);
});
});
}
function evaluateWhile(exp, workerScript) {
var env = workerScript.env;
console.log("evaluateWhile() called");
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
var pCond = new Promise(function(resolve, reject) {
setTimeout(function() {
var evaluatePromise = evaluate(exp.cond, workerScript);
evaluatePromise.then(function(resCond) {
console.log("Conditional evaluated to: " + 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.code, workerScript);
evaluatePromise.then(function(resCode) {
console.log("Evaluated an iteration of while loop code");
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 {
console.log("Cond is false, stopping while loop");
resolve("endWhileLoop"); //Doesn't need to resolve to any particular value
}
}, function(e) {
reject(e);
});
});
}
function evaluateProg(exp, workerScript, index) {
var env = workerScript.env;
console.log("evaluateProg() called");
return new Promise(function(resolve, reject) {
if (env.stopFlag) {reject(workerScript);}
if (index >= exp.prog.length) {
console.log("Prog done. Resolving recursively");
resolve("progFinished");
} else {
//Evaluate this line of code in the prog
var code = new Promise(function(resolve, reject) {
setTimeout(function() {
var evaluatePromise = evaluate(exp.prog[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 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 num(a) + num(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);
}
//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 = (Player.hacking_chance_mult * Player.hacking_skill);
var skillChance = (skillMult - server.requiredHackingSkill) / skillMult;
var chance = skillChance * difficultyMult;
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 = (difficultyMult + 1000) / (Player.hacking_skill + 50);
var hackingTime = 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) {
return Math.round(server.hackDifficulty * Player.hacking_exp_mult);
}
//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;
console.log("Percent money hacked calculated to be: " + percentMoneyHacked);
if (percentMoneyHacked < 0) {return 0;}
if (percentMoneyHacked > 1) {return 1;}
return percentMoneyHacked;
}