| @@ -33,7 +33,7 @@ function TokenStream(input){ | |||
| } | |||
| function eol(){ | |||
| return (pos < input.length - 1) ? (input[pos].line !== input[pos+1].line) : true; | |||
| return (pos > 0) ? (input[pos-1].line !== input[pos].line) : true; | |||
| } | |||
| function eof(){ | |||
| @@ -49,6 +49,7 @@ function TokenStream(input){ | |||
| next: next, | |||
| line: line, | |||
| col: col, | |||
| eol: eol, | |||
| eof, eof, | |||
| die: die | |||
| }; | |||
| @@ -88,6 +89,13 @@ function skipPunc(ch){ | |||
| } | |||
| function skipDirective(ch){ | |||
| if (!isDirective(ch)) | |||
| stream.die("Unexpected directive '" + ch + "'."); | |||
| stream.next(); | |||
| } | |||
| function parseDelimited(s,e,d,parser){ | |||
| let toEOL = (s === null || e === null); | |||
| let a = []; | |||
| @@ -96,7 +104,7 @@ function parseDelimited(s,e,d,parser){ | |||
| while (!stream.eof() && ((!toEOL && isPunc(e)) || (toEOL && !stream.eol()))){ | |||
| if (first){ | |||
| first = false; | |||
| } else {skipPunk(d);} | |||
| } else {skipPunc(d);} | |||
| a.push(parser()); | |||
| } | |||
| if (!toEOL){skipPunc(e);} | |||
| @@ -104,16 +112,65 @@ function parseDelimited(s,e,d,parser){ | |||
| } | |||
| function parseByteDirective(){ | |||
| let line = stream.line(); | |||
| let col = stream.col(); | |||
| stream.next(); | |||
| return { | |||
| type: "directive", | |||
| op: ".byte", | |||
| args: parseDelimited(null, null, ",", parseExpression); | |||
| args: parseDelimited(null, null, ",", parseExpression), | |||
| line: line, | |||
| col: col | |||
| } | |||
| } | |||
| function parseElseIfDirective(){ | |||
| skipDirective(".elseif"); | |||
| let cond = parseExpression(); | |||
| skipDirective(".then"); | |||
| let then = parseBlock([".else", ".elif", ".endif"]); | |||
| let f = { | |||
| type: "directive", | |||
| op: "if", | |||
| cond: cond, | |||
| then: then | |||
| } | |||
| if (isDirective(".elif")){ | |||
| f["else"] = parseElseIfDirective(); | |||
| } else if (isDirective(".else")){ | |||
| f["else"] = parseBlock([".endif"]); | |||
| } else if(!isDirective(".endif")){ | |||
| stream.die("Expected '.endif' Directive."); | |||
| } | |||
| return f; | |||
| } | |||
| function parseIfDirective(){ | |||
| skipDirective(".if"); | |||
| let cond = parseExpression(); | |||
| skipDirective(".then"); | |||
| let then = parseExpression(); | |||
| let f = { | |||
| type: "directive", | |||
| op: "if", | |||
| cond: cond, | |||
| then: then | |||
| }; | |||
| if (isDirective(".elif")){ | |||
| f["else"] = parseElseIfDirective(); | |||
| } else if (isDirective(".else")){ | |||
| f["else"] = parseBlock([".endif"]); | |||
| } | |||
| skipDirective(".endif"); | |||
| return f; | |||
| } | |||
| function parseOpCode(){ | |||
| let line = stream.line(); | |||
| let col = stream.col(); | |||
| let val = stream.next(); | |||
| let mode = 0; // Guess between absolute and zero page. | |||
| if (isOp("#")){ | |||
| @@ -124,9 +181,11 @@ function parseOpCode(){ | |||
| } | |||
| return { | |||
| type: "opcode", | |||
| val: val, | |||
| op: val, | |||
| args: parseDelimited(null, null, ",", parseExpression), | |||
| mode: mode | |||
| mode: mode, | |||
| line: line, | |||
| col: col | |||
| }; | |||
| } | |||
| @@ -140,20 +199,22 @@ function parseAtom(){ | |||
| } | |||
| } else if (isOpCode()){ | |||
| return parseOpCode(); | |||
| } else if (isDirective(".byte")){ | |||
| } else if (isDirective(".if")){ | |||
| return parseIfDirective(); | |||
| } else if (isDirective(".bytes")){ | |||
| return parseByteDirective(); | |||
| } | |||
| let tok = stream.next(); | |||
| if (tok.type === "number" || tok.type === "string" || tok.type === "label") | |||
| return tok; | |||
| stream.die("Unexpected token " + tok); | |||
| stream.die("Unexpected token {type:" + tok.type + ", val:'" + tok.val + "'}."); | |||
| } | |||
| function parseExpression(){ | |||
| maybeCall(function(){ | |||
| return maybeBinary(parseAtom, 0); | |||
| return maybeCall(function(){ | |||
| return maybeBinary(parseAtom(), 0); | |||
| }); | |||
| } | |||
| @@ -161,7 +222,7 @@ function parseCall(func){ | |||
| return { | |||
| type: "call", | |||
| func: func, | |||
| args: parseDelimited("(", ")", ",", parseExpression); | |||
| args: parseDelimited("(", ")", ",", parseExpression) | |||
| }; | |||
| } | |||
| @@ -180,31 +241,56 @@ function maybeBinary(left, pres){ | |||
| type: (tok.val === "=") ? "assign" : "binary", | |||
| op: tok.val, | |||
| left: left, | |||
| right: maybeBinary(parseAtom(), cpres) | |||
| right: maybeBinary(parseAtom(), cpres), | |||
| line: tok.line, | |||
| col: tok.col | |||
| }, pres); | |||
| } | |||
| } | |||
| return left; | |||
| } | |||
| function parse(tokens){ | |||
| let p = { | |||
| type: "prog", | |||
| expressions: []; | |||
| }; | |||
| stream = TokenStream(tokens); | |||
| function parseBlock(bed){ | |||
| let exp = []; | |||
| let isBlockEnd = (t) => { | |||
| return (bed && t.type === 'directive' && bed.indexOf(t.val) >= 0); | |||
| } | |||
| while (!stream.eof()){ | |||
| if (isBlockEnd(stream.peek())) | |||
| break; | |||
| let e = parseExpression(); | |||
| if (e.type === "label"){ | |||
| e = { | |||
| type: "assign", | |||
| op: "=", | |||
| left: e, | |||
| right: "*" // To designate "current program counter". | |||
| right: "*", // To designate "current program counter". | |||
| line: e.line, | |||
| col: e.col | |||
| } | |||
| } | |||
| p.expressions.push(e); | |||
| exp.push(e); | |||
| if (!stream.eol()) | |||
| stream.die("Expected End of Line."); | |||
| } | |||
| return exp; | |||
| } | |||
| function parseProg(ed){ | |||
| let line = stream.line(); | |||
| let col = stream.col(); | |||
| return { | |||
| type: "prog", | |||
| expressions: parseBlock(), | |||
| line: line, | |||
| col: col | |||
| }; | |||
| } | |||
| function parse(tokens){ | |||
| stream = TokenStream(tokens); | |||
| let p = parseProg(); | |||
| stream = null; | |||
| return p; | |||
| } | |||