| @@ -1,5 +1,12 @@ | |||
| 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){ | |||
| var pos = 0; | |||
| @@ -50,11 +57,7 @@ var stream = null; | |||
| function isTokenType(type, val){ | |||
| 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){ | |||
| @@ -79,15 +82,13 @@ function isOp(ch){ | |||
| function skipPunc(ch){ | |||
| if (isPunc(ch)){ | |||
| stream.next(); | |||
| } else { | |||
| if (!isPunc(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 a = []; | |||
| let first = true; | |||
| @@ -102,27 +103,34 @@ parseDelimited(s,e,d,parser){ | |||
| return a; | |||
| } | |||
| function parseByteDirective(){ | |||
| stream.next(); | |||
| return { | |||
| type: "directive", | |||
| op: ".byte", | |||
| args: parseDelimited(null, null, ",", parseExpression); | |||
| } | |||
| } | |||
| parseOpCode(){ | |||
| function parseOpCode(){ | |||
| let val = stream.next(); | |||
| let mode = 0; // Guess between absolute and zero page. | |||
| let args = []; // TODO: Finish figuring out how to get the argument list! | |||
| if (isOp("#")){ | |||
| stream.next(); | |||
| mode = 1; // Immediate | |||
| } else if (isPunc("(")){ | |||
| mode = 2; // Indirect | |||
| // TODO: Use parseDelimited() | |||
| } | |||
| } | |||
| return { | |||
| type: "opcode", | |||
| val: val, | |||
| args: args, | |||
| args: parseDelimited(null, null, ",", parseExpression), | |||
| mode: mode | |||
| }; | |||
| } | |||
| parseAtom(){ | |||
| function parseAtom(){ | |||
| if (isPunc("(")){ | |||
| stream.next(); | |||
| let exp = parseExpression(); | |||
| @@ -132,18 +140,70 @@ parseAtom(){ | |||
| } | |||
| } else if (isOpCode()){ | |||
| 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 = { | |||
| type: "prog", | |||
| expressions: []; | |||
| }; | |||
| stream = TokenStream(tokens); | |||
| 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; | |||
| return p; | |||