Finished implementing Netscript. Not completely tested yet. Find out how to make it multithreaded (Web Workers is the best way according to internet

This commit is contained in:
Daniel Xie
2016-11-17 16:25:40 -06:00
parent a4f92f7520
commit 8d87b74eaf
10 changed files with 544 additions and 109 deletions
+48
View File
@@ -0,0 +1,48 @@
/* Environment
* NetScript program environment
*/
function Environment(parent) {
this.vars = Object.create(parent ? parent.vars : null);
this.parent = parent;
}
Environment.prototype = {
//Create a "subscope", which is a new new "sub-environment"
//The subscope is linked to this through its parent variable
extend: function() {
return new Environment(this);
},
//Finds the scope where the variable with the given name is defined
lookup: function(name) {
var scope = this;
while (scope) {
if (Object.prototype.hasOwnProperty.call(scope.vars, name))
return scope;
scope = scope.parent;
}
},
//Get the current value of a variable
get: function(name) {
if (name in this.vars)
return this.vars[name];
throw new Error("Undefined variable " + name);
},
//Sets the value of a variable in any scope
set: function(name, value) {
var scope = this.lookup(name);
// let's not allow defining globals from a nested environment
//
// If scope is null (aka existing variable with name could not be found)
// and this is NOT the global scope, throw error
if (!scope && this.parent)
throw new Error("Undefined variable " + name);
return (scope || this).vars[name] = value;
},
//Creates (or overwrites) a variable in the current scope
def: function(name, value) {
return this.vars[name] = value;
}
};
+124
View File
@@ -0,0 +1,124 @@
/* Evaluator
* Evaluates the Abstract Syntax Tree for Netscript
* generated by the Parser class
*/
function evaluate(exp, env) {
switch (exp.type) {
case "num":
case "str":
case "bool":
return exp.value;
case "var":
return env.get(exp.value);
//Can currently only assign to "var"s
case "assign":
if (exp.left.type != "var")
throw new Error("Cannot assign to " + JSON.stringify(exp.left));
return env.set(exp.left.value, evaluate(exp.right, env));
case "binary":
return apply_op(exp.operator,
evaluate(exp.left, env),
evaluate(exp.right, env));
case "if":
var numConds = exp.cond.length;
var numThens = exp.then.length;
if (numConds == 0 || numThens == 0 || numConds != numThens) {
throw new Error ("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], env);
if (cond) return evaluate(exp.then, env);
}
//Evaluate else if it exists, snce none of the conditionals
//were true
return exp.else ? evaluate(exp.else, env) : false;
case "for":
evaluate(exp.init, env);
cond = evaluate(exp.cond, env);
console.log("Evaluated the conditional");
while (cond) {
evaluate(exp.code, env);
evaluate(exp.postloop, env);
cond = evaluate(exp.cond, env);
}
//TODO Return somethin?
break;
case "while":
cond = evaluate(exp.cond, env);
while (cond) {
evaluate(exp.code, env);
cond = evaluate(exp.cond, env);
}
//TODO DO i need to return anything?
break;
case "prog":
var val = false;
exp.prog.forEach(function(exp){ val = evaluate(exp, env) });
return val;
/* 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);
//}));
if (exp.func.value == "hack") {
console.log("Execute hack()");
} else if (exp.func.value == "sleep") {
console.log("Execute sleep()");
} else if (exp.func.value == "print") {
console.log(evaluate(exp.args[0], env));
}
break;
default:
throw new Error("I don't know how to evaluate " + exp.type);
}
}
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);
}
+244 -1
View File
@@ -6,5 +6,248 @@
var FALSE = {type: "bool", value: false};
function Parser(input) {
var PRECEDENCE = {
"=": 1,
"||": 2,
"&&": 3,
"<": 7, ">": 7, "<=": 7, ">=": 7, "==": 7, "!=": 7,
"+": 10, "-": 10,
"*": 20, "/": 20, "%": 20,
};
return parse_toplevel();
//Returns true if the next token is a punc type with value ch
function is_punc(ch) {
var tok = input.peek();
return tok && tok.type == "punc" && (!ch || tok.value == ch) && tok;
}
//Returns true if the next token is the kw keyword
function is_kw(kw) {
var tok = input.peek();
return tok && tok.type == "kw" && (!kw || tok.value == kw) && tok;
}
//Returns true if the next token is an op type with the given op value
function is_op(op) {
var tok = input.peek();
return tok && tok.type == "op" && (!op || tok.value == op) && tok;
}
//Checks that the next character is the given punctuation character and throws
//an error if it's not. If it is, skips over it in the input
function checkPuncAndSkip(ch) {
if (is_punc(ch)) input.next();
else input.croak("Expecting punctuation: \"" + ch + "\"");
}
//Checks that the next character is the given keyword and throws an error
//if its not. If it is, skips over it in the input
function checkKeywordAndSkip(kw) {
if (is_kw(kw)) input.next();
else input.croak("Expecting keyword: \"" + kw + "\"");
}
//Checks that the next character is the given operator and throws an error
//if its not. If it is, skips over it in the input
function checkOpAndSkip(op) {
if (is_op(op)) input.next();
else input.croak("Expecting operator: \"" + op + "\"");
}
function unexpected() {
input.croak("Unexpected token: " + JSON.stringify(input.peek()));
}
function maybe_binary(left, my_prec) {
var tok = is_op();
if (tok) {
var his_prec = PRECEDENCE[tok.value];
if (his_prec > my_prec) {
input.next();
return maybe_binary({
type : tok.value == "=" ? "assign" : "binary",
operator : tok.value,
left : left,
right : maybe_binary(parse_atom(), his_prec)
}, my_prec);
}
}
return left;
}
function delimited(start, stop, separator, parser) {
var a = [], first = true;
checkPuncAndSkip(start);
while (!input.eof()) {
if (is_punc(stop)) break;
if (first) first = false; else checkPuncAndSkip(separator);
if (is_punc(stop)) break;
a.push(parser());
}
checkPuncAndSkip(stop);
return a;
}
function parse_call(func) {
return {
type: "call",
func: func,
args: delimited("(", ")", ",", parse_expression),
};
}
function parse_varname() {
var name = input.next();
if (name.type != "var") input.croak("Expecting variable name");
return name.value;
}
/* type: "if",
* cond: [ {"type": "var", "value": "cond1"}, {"type": "var", "value": "cond2"}...]
* then: [ {"type": "var", "value": "then1"}, {"type": "var", "value": "then2"}...]
* else: {"type": "var", "value": "foo"}
*/
function parse_if() {
console.log("Parsing if token");
checkKeywordAndSkip("if");
//Conditional
var cond = parse_expression();
//Body
var then = parse_expression();
var ret = {
type: "if",
cond: [],
then: [],
};
ret.cond.push(cond);
ret.then.push(then);
// Parse all elif branches
while (is_kw("elif")) {
input.next();
var cond = parse_expression();
var then = parse_expression();
ret.cond.push(cond);
ret.then.push(then);
}
// Parse else branch, if it exists
if (is_kw("else")) {
input.next();
ret.else = parse_expression();
}
return ret;
}
/* for (init, cond, postloop) {code;}
*
* type: "for",
* init: assign node,
* cond: var node,
* postloop: assign node
* code: prog node
*/
function parse_for() {
console.log("Parsing for token");
checkKeywordAndSkip("for");
splitExpressions = delimited("(", ")", ";", parse_expression);
console.log("Parsing code in for loop");
code = parse_expression();
if (splitExpressions.length != 3) {
throw new Error("for statement has incorrect number of arugments");
}
//TODO Check type of the init, cond, and postloop nodes
return {
type: "for",
init: splitExpressions[0],
cond: splitExpressions[1],
postloop: splitExpressions[2],
code: code
}
}
/* while (cond) {}
*
* type: "while",
* cond: var node
* code: prog node
*/
function parse_while() {
console.log("Parsing while token");
checkKeywordAndSkip("while");
var cond = parse_expression();
var code = parse_expression();
return {
type: "while",
cond: cond,
code: code
}
}
function parse_bool() {
return {
type : "bool",
value : input.next().value == "true"
};
}
function maybe_call(expr) {
expr = expr();
return is_punc("(") ? parse_call(expr) : expr;
}
function parse_atom() {
return maybe_call(function(){
if (is_punc("(")) {
input.next();
var exp = parse_expression();
checkPuncAndSkip(")");
return exp;
}
if (is_punc("{")) return parse_prog();
if (is_kw("if")) return parse_if();
if (is_kw("for")) return parse_for();
if (is_kw("while")) return parse_while();
//Note, let for loops be function calls (call node types)
if (is_kw("true") || is_kw("false")) return parse_bool();
var tok = input.next();
if (tok.type == "var" || tok.type == "num" || tok.type == "str")
return tok;
unexpected();
});
}
function parse_toplevel() {
var prog = [];
while (!input.eof()) {
prog.push(parse_expression());
if (!input.eof()) checkPuncAndSkip(";");
}
//Return the top level Abstract Syntax Tree, where the top node is a "prog" node
return { type: "prog", prog: prog };
}
function parse_prog() {
console.log("Parsing prog token");
var prog = delimited("{", "}", ";", parse_expression);
if (prog.length == 0) return FALSE;
if (prog.length == 1) return prog[0];
return { type: "prog", prog: prog };
}
function parse_expression() {
return maybe_call(function(){
return maybe_binary(parse_atom(), 0);
});
}
}
+8 -16
View File
@@ -3,27 +3,19 @@
* Tokens can be accessed with peek() and next().
*
* Token types:
* {type: "punc", value: "(" } // punctuation: parens, comma, semicolon etc.
* {type: "num", value: 5 } // numbers
* {type: "str", value: "Hello World!" } // strings
* {type: "kw", value: "lambda" } // keywords
* {type: "var", value: "a" } // identifiers
* {type: "op", value: "!=" } // operators
*
*
*
*
*
*
*
*
*
* {type: "punc", value: "(" } // punctuation: parens, comma, semicolon etc.
* {type: "num", value: 5 } // numbers (including floats)
* {type: "str", value: "Hello World!" } // strings
* {type: "kw", value: "for/if/" } // keywords, see defs below
* {type: "var", value: "a" } // identifiers/variables
* {type: "op", value: "!=" } // operator characters
* {type: "bool", value: "true" } // Booleans
*
*/
function Tokenizer(input) {
var current = null;
var keywords = " if then else true false while for ";
var keywords = " if elif else true false while for ";
return {
next : next,