/* Parser * Creates Abstract Syntax Tree Nodes * Operates on a stream of tokens from the Tokenizer */ 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() { 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() { checkKeywordAndSkip("for"); temp = delimited("(", ")", ";", parse_expression); var splitExpressions = temp.slice(); code = parse_expression(); if (splitExpressions.length != 3) { throw new Error("for statement has incorrect number of arguments"); } //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() { checkKeywordAndSkip("while"); var cond = parse_expression(); var code = parse_expression(); return { type: "while", cond: cond, code: code } } /* hacknetnodes[i].operator */ function parse_hacknetnodes() { var index = null; if (is_punc("[")) { index = parse_expression(); if (index.type != "index") { unexpected(); } } if (is_punc(".")) { checkPuncAndSkip("."); var op = maybe_call(function() { var tok = input.next(); return tok; }); return { type: "var", value: "hacknetnodes", index: index, op: op, }; } unexpected(); } //Declaring a new array with Array[1,2,3] function parse_arraydecl() { var array = delimited("[", "]", ",", parse_expression); return {type: "var", value: "array", array: array }; } //Parsing an operation on an array, such as accessing, push(), etc. //tok is a reference to a token of type var function parse_arrayop(tok) { //Returns a variable node except with an extra "index" field so //we can identify it as an index if (is_punc("[")) { var index = parse_arrayindex(); if (index.type != "index") { unexpected(); } } var op = null; if (is_punc(".")) { checkPuncAndSkip("."); op = maybe_call(function() { var callTok = input.next(); return callTok; }); } tok.index = index; tok.op = op; //Will be null if no operation return tok; } function parse_arrayindex() { var index = delimited("[", "]", ";", parse_expression); var val = 0; if (index.length == 1) { val = index[0]; } else { unexpected(); } return { type: "index", value: val }; } 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_punc("[")) return parse_arrayindex(); if (is_kw("if")) return parse_if(); if (is_kw("for")) return parse_for(); if (is_kw("while")) return parse_while(); if (is_kw("true") || is_kw("false")) return parse_bool(); var tok = input.next(); if (tok.type == "var" && tok.value == "hacknetnodes") return parse_hacknetnodes(); if (tok.type == "var" && tok.value == "Array") return parse_arraydecl(); if (tok.type == "var" && (is_punc("[") || is_punc("."))) { return parse_arrayop(tok); } 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() { 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); }); } }