| const OP = require('./op.js'); | const OP = require('./op.js'); | ||||
| const PRECEDENCE = { | |||||
| "#": 0, // Precedence 0 should be ignored! | |||||
| "=": 1, | |||||
| "<":7, "<=":7, ">":7, ">=":7, "==":7, "!=":7, | |||||
| "+": 10, "-": 10, | |||||
| "*": 20, "/": 20 | |||||
| }; | |||||
| function TokenStream(input){ | function TokenStream(input){ | ||||
| var pos = 0; | var pos = 0; | ||||
| function isTokenType(type, val){ | function isTokenType(type, val){ | ||||
| let t = stream.peek(); | let t = stream.peek(); | ||||
| if (t !== null && t.type === type){ | |||||
| if (!val || t.val === val) | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| return (t && t.type === type && (!val || t.val === val) && t); | |||||
| } | } | ||||
| function isPunc(ch){ | function isPunc(ch){ | ||||
| function skipPunc(ch){ | function skipPunc(ch){ | ||||
| if (isPunc(ch)){ | |||||
| stream.next(); | |||||
| } else { | |||||
| if (!isPunc(ch)) | |||||
| stream.die("Unexpected punctuation '" + ch + "'."); | stream.die("Unexpected punctuation '" + ch + "'."); | ||||
| } | |||||
| stream.next(); | |||||
| } | } | ||||
| parseDelimited(s,e,d,parser){ | |||||
| function parseDelimited(s,e,d,parser){ | |||||
| let toEOL = (s === null || e === null); | let toEOL = (s === null || e === null); | ||||
| let a = []; | let a = []; | ||||
| let first = true; | let first = true; | ||||
| return a; | return a; | ||||
| } | } | ||||
| function parseByteDirective(){ | |||||
| stream.next(); | |||||
| return { | |||||
| type: "directive", | |||||
| op: ".byte", | |||||
| args: parseDelimited(null, null, ",", parseExpression); | |||||
| } | |||||
| } | |||||
| parseOpCode(){ | |||||
| function parseOpCode(){ | |||||
| let val = stream.next(); | let val = stream.next(); | ||||
| let mode = 0; // Guess between absolute and zero page. | let mode = 0; // Guess between absolute and zero page. | ||||
| let args = []; // TODO: Finish figuring out how to get the argument list! | |||||
| if (isOp("#")){ | if (isOp("#")){ | ||||
| stream.next(); | stream.next(); | ||||
| mode = 1; // Immediate | mode = 1; // Immediate | ||||
| } else if (isPunc("(")){ | } else if (isPunc("(")){ | ||||
| mode = 2; // Indirect | mode = 2; // Indirect | ||||
| // TODO: Use parseDelimited() | |||||
| } | |||||
| } | |||||
| return { | return { | ||||
| type: "opcode", | type: "opcode", | ||||
| val: val, | val: val, | ||||
| args: args, | |||||
| args: parseDelimited(null, null, ",", parseExpression), | |||||
| mode: mode | mode: mode | ||||
| }; | }; | ||||
| } | } | ||||
| parseAtom(){ | |||||
| function parseAtom(){ | |||||
| if (isPunc("(")){ | if (isPunc("(")){ | ||||
| stream.next(); | stream.next(); | ||||
| let exp = parseExpression(); | let exp = parseExpression(); | ||||
| } | } | ||||
| } else if (isOpCode()){ | } else if (isOpCode()){ | ||||
| return parseOpCode(); | return parseOpCode(); | ||||
| } else if (isDirective(".byte")){ | |||||
| return parseByteDirective(); | |||||
| } | } | ||||
| let tok = stream.next(); | |||||
| if (tok.type === "number" || tok.type === "string" || tok.type === "label") | |||||
| return tok; | |||||
| stream.die("Unexpected token " + tok); | |||||
| } | } | ||||
| parse(tokens){ | |||||
| function parseExpression(){ | |||||
| maybeCall(function(){ | |||||
| return maybeBinary(parseAtom, 0); | |||||
| }); | |||||
| } | |||||
| function parseCall(func){ | |||||
| return { | |||||
| type: "call", | |||||
| func: func, | |||||
| args: parseDelimited("(", ")", ",", parseExpression); | |||||
| }; | |||||
| } | |||||
| function maybeCall(expr){ | |||||
| expr = expr(); | |||||
| return (isOp("(")) ? parseCall(expr) : expr; | |||||
| } | |||||
| function maybeBinary(left, pres){ | |||||
| let tok = isOp(); | |||||
| if (tok){ | |||||
| let cpres = PRECEDENCE[tok.val]; | |||||
| if (cpres > pres){ | |||||
| stream.next(); | |||||
| return maybeBinary({ | |||||
| type: (tok.val === "=") ? "assign" : "binary", | |||||
| op: tok.val, | |||||
| left: left, | |||||
| right: maybeBinary(parseAtom(), cpres) | |||||
| }, pres); | |||||
| } | |||||
| } | |||||
| return left; | |||||
| } | |||||
| function parse(tokens){ | |||||
| let p = { | let p = { | ||||
| type: "prog", | type: "prog", | ||||
| expressions: []; | expressions: []; | ||||
| }; | }; | ||||
| stream = TokenStream(tokens); | stream = TokenStream(tokens); | ||||
| while (!stream.eof()){ | while (!stream.eof()){ | ||||
| p.expressions.push(parseExpression()); | |||||
| let e = parseExpression(); | |||||
| if (e.type === "label"){ | |||||
| e = { | |||||
| type: "assign", | |||||
| op: "=", | |||||
| left: e, | |||||
| right: "*" // To designate "current program counter". | |||||
| } | |||||
| } | |||||
| p.expressions.push(e); | |||||
| } | } | ||||
| stream = null; | stream = null; | ||||
| return p; | return p; |