const PRECEDENCE = { "#": 0, // Precedence 0 should be ignored! "=": 1, "<":7, "<=":7, ">":7, ">=":7, "==":7, "!=":7, "+": 10, "-": 10, "*": 20, "/": 20 }; function TokenStream(input){ var pos = 0; function peek(off){ off = off || 0; return (pos+off < input.length) ? input[pos+off] : null; } function next(){ if (pos < input.length){ let v = input[pos]; pos += 1; return v; } return null; } function line(){ return (pos < input.length) ? input[pos].line : -1; } function col(){ return (pos < input.length) ? input[pos].col : -1; } function eol(){ return (pos > 0 && pos < input.length) ? (input[pos-1].line !== input[pos].line) : true; } function eof(){ return (pos >= input.length); } function die(msg){ throw new Error(msg + " Line: " + input[pos].line + ", Col: " + input[pos].col); } function getPos(){return pos;} return { peek: peek, next: next, line: line, col: col, pos: getPos, eol: eol, eof, eof, die: die }; } // var stream = null; // var SKIPEOL = false; function isTokenType(stream, type, val){ let t = stream.peek(); return (t && t.type === type && (!val || t.val === val) && t); } class Parser{ constructor(pof){ if (typeof(pof) !== 'function') throw new TypeError("Expected op code parsing function."); this.__stream = null; this.__SKIPEOL = false; this.__output = null; this.__PCLabel = "__PC__"; this.parseOpCode = pof(this); } set tokens(t){ this.__stream = new TokenStream(t); this.__output = null; this.__SKIPEOL = false; } get stream(){return this.__stream;} get PCLabel(){return this.__PCLabel;} set PCLabel(pcl){ if (typeof(pcl) !== 'string' || pcl === "") throw new TypeError("PC Label must be a non-zero-length string."); this.__PCLabel = pcl; } isPunc(ch){ return isTokenType(this.stream, "punc", ch); } isOpCode(ch){ return isTokenType(this.stream, "opcode", ch); } isLabel(ch){ return isTokenType(this.stream, "label", ch); } isDirective(ch){ return isTokenType(this.stream, "directive", ch); } isOp(ch){ return isTokenType(this.stream, "op", ch); } skipPunc(ch){ if (!this.isPunc(ch)) this.stream.die("Unexpected punctuation '" + ch + "'."); this.stream.next(); } skipDirective(ch){ if (!this.isDirective(ch)) this.stream.die("Unexpected directive '" + ch + "'."); this.stream.next(); } parseDelimited(s,e,d,parser){ let toEOL = (s === null || e === null); let a = []; let first = true; if (!toEOL){this.skipPunc(s);} while (!this.stream.eof() && ((!toEOL && !this.isPunc(e)) || (toEOL && !this.stream.eol()))){ if (first){ first = false; } else {this.skipPunc(d);} a.push(parser()); } if (!toEOL){this.skipPunc(e);} return a; } parseByteDirective(){ let line = this.stream.line(); let col = this.stream.col(); this.stream.next(); return { type: "directive", op: ".byte", args: this.parseDelimited(null, null, ",", this.parseExpression.bind(this) ), line: line, col: col } } parseElseIfDirective(){ this.skipDirective(".elif"); let cond = this.parseExpression(); this.skipDirective(".then"); let then = this.parseBlock([".else", ".elif", ".endif"]); let f = { type: "directive", op: "if", cond: cond, then: then } if (this.isDirective(".elif")){ f["else"] = this.parseElseIfDirective(); } else if (this.isDirective(".else")){ this.skipDirective(".else"); f["else"] = this.parseBlock([".endif"]); } else if(!this.isDirective(".endif")){ this.stream.die("Expected '.endif' Directive."); } return f; } parseIfDirective(){ this.skipDirective(".if"); let cond = this.parseExpression(); this.skipDirective(".then"); let then = this.parseBlock([".elif", ".else", ".endif"]); let f = { type: "directive", op: "if", cond: cond, then: then }; if (this.isDirective(".elif")){ f["else"] = this.parseElseIfDirective(); } else if (this.isDirective(".else")){ this.skipDirective(".else"); f["else"] = this.parseBlock([".endif"]); } this.skipDirective(".endif"); return f; } parseLabel(t){ if (!this.stream.eof()){ let ct = this.stream.peek(); if (ct.type === "opcode" && t.col === t.val.length){ this.__SKIPEOL = true; return { type: "assign", op: "=", left: t, right: this.__PCLabel, // To designate "current program counter". line: t.line, col: t.col } } } return t; } parseTempLabel(){ let isEOL = this.stream.eol(); this.stream.next(); if (isEOL){ this.__SKIPEOL = true; return { type:"temp-label", val:-1, // Value of -1 means "set new temp label" line:this.stream.line(), col:this.stream.col() }; } let t = this.stream.next(); if (t.type === "number"){ t.type = "temp-label"; return t; } this.stream.die("Temp label missing index. New temp labels must be defined at the start of the line."); } parseAtom(){ if (this.isPunc("(")){ this.stream.next(); let exp = this.parseExpression(); if (this.isPunc(")")){ this.stream.next(); return exp; } } else if (this.isPunc(":")){ return this.parseTempLabel(); } else if (this.isOpCode()){ return this.parseOpCode(); } else if (this.isDirective(".if")){ return this.parseIfDirective(); } else if (this.isDirective(".bytes")){ return this.parseByteDirective(); } let tok = this.stream.next(); if (tok.type === "number" || tok.type === "string"){ return tok; } else if (tok.type === "label"){ return this.parseLabel(tok); } this.stream.die("Unexpected token {type:" + tok.type + ", val:'" + tok.val + "'}."); } parseExpression(){ return this.maybeCall((function(){ return this.maybeBinary(this.parseAtom(), 0); }).bind(this)); } parseCall(func){ return { type: "call", func: func, args: this.parseDelimited( "(", ")", ",", this.parseExpression.bind(this) ) }; } maybeCall(expr){ expr = expr(); return (this.isOp("(")) ? this.parseCall(expr) : expr; } maybeBinary(left, pres){ let tok = this.isOp(); if (tok){ let cpres = PRECEDENCE[tok.val]; if (cpres > pres){ this.stream.next(); return this.maybeBinary({ type: (tok.val === "=") ? "assign" : "binary", op: tok.val, left: left, right: this.maybeBinary(this.parseAtom(), cpres), line: tok.line, col: tok.col }, pres); } } return left; } parseBlock(bed){ let line = this.stream.line(); let col = this.stream.col(); let exp = []; let isBlockEnd = (t) => { return (bed && t.type === 'directive' && bed.indexOf(t.val) >= 0); } while (!this.stream.eof()){ if (isBlockEnd(this.stream.peek())) break; exp.push(this.parseExpression()); if (!this.stream.eol()){ if (!this.__SKIPEOL) this.stream.die("Expected End of Line."); this.__SKIPEOL = false; } } return { type: "block", expressions: exp, line: line, col: col }; } /* parseProg(){ let line = this.stream.line(); let col = this.stream.col(); return { type: "prog", block: this.parseBlock(), line: line, col: col }; } */ parse(tokens){ if (tokens) this.tokens = tokens; if (this.__stream !== null){ if (this.__output === null && this.__stream.pos() === 0) this.__output = this.parseBlock(); return this.__output; } return null; } } module.exports = Parser;