| @@ -0,0 +1,96 @@ | |||
| // Each array orders op codes as... | |||
| // [<immediate>, <zero page>, <zero page X/Y>, <absolute>, <absolute X>, <absolute Y>, <Indirect X>, <Indirect Y>, <Indirect>, <accumulator>] | |||
| // If an opcode does not support a particular format, null will be in that space. | |||
| // NOTE: Opcode that do not have arguments will store their opcode value directly (no array). | |||
| const CODES = { | |||
| "ADC":[0x69, 0x65, 0x75, 0x6D, 0x7D, 0x79, 0x61, 0x71, null, null], | |||
| "AND":[0x29, 0x25, 0x35, 0x2D, 0x3D, 0x39, 0x21, 0x31, null, null], | |||
| "ASL":[null, 0x06, 0x16, 0x0E, 0x1E, null, null, null, null, 0x0A], | |||
| "BCC":0x90, | |||
| "BCS":0xB0, | |||
| "BEQ":0xF0, | |||
| "BIT":[null, 0x24, null, 0x2C, null, null, null, null, null, null], | |||
| "BMI":0x30, | |||
| "BNE":0xD0, | |||
| "BPL":0x10, | |||
| "BRK":0x00, | |||
| "BVC":0x50, | |||
| "BVS":0x70, | |||
| "CLC":0x18, | |||
| "CLD":0xD8, | |||
| "CLI":0x58, | |||
| "CLV":0xB8, | |||
| "CMP":[0xC9, 0xC5, 0xD5, 0xCD, 0xDD, 0xD9, 0xC1, 0xD1, null, null], | |||
| "CPX":[0xE0, 0xE4, null, 0xEC, null, null, null, null, null, null], | |||
| "CPY":[0xC0, 0xC4, null, 0xCC, null, null, null, null, null, null], | |||
| "DEC":[null, 0xC6, 0xD6, 0xCE, 0xDE, null, null, null, null, null], | |||
| "DEX":0xCA, | |||
| "DEY":0x88, | |||
| "EOR":[0x49, 0x45, 0x55, 0x4D, 0x5D, 0x59, 0x41, 0x51, null, null], | |||
| "INC":[null, 0xE6, 0xF6, 0xEE, 0xFE, null, null, null, null, null], | |||
| "INX":0xE8, | |||
| "INY":0xC8, | |||
| "JMP":[null, null, null, 0x4C, null, null, null, null, 0x6C, null], | |||
| "JSR":[null, null, null, 0x20, null, null, null, null, null, null], | |||
| "LDA":[0xA9, 0xA5, 0xB5, 0xAD, 0xBD, 0xB9, 0xA1, 0xB1, null, null], | |||
| "LDX":[0xA2, 0xA6, 0xB6, 0xAE, 0xBE, null, null, null, null, null], | |||
| "LDY":[0xA0, 0xA4, 0xB4, 0xAC, 0xBC, null, null, null, null, null], | |||
| "LSR":[null, 0x46, 0x56, 0x4E, 0x5E, null, null, null, null, 0x4A], | |||
| "NOP":0xEA, | |||
| "ORA":[0x09, 0x05, 0x15, 0x0D, 0x1D, 0x19, 0x01, 0x11, null, null], | |||
| "PHA":0x48, | |||
| "PHP":0x08, | |||
| "PLA":0x68, | |||
| "PLP":0x28, | |||
| "ROL":[null, 0x26, 0x36, 0x2E, 0x3E, null, null, null, null, 0x2A], | |||
| "ROR":[null, 0x66, 0x76, 0x6E, 0x7E, null, null, null, null, 0x6A], | |||
| "RTI":0x40, | |||
| "RTS":0x60, | |||
| "SBC":[0xE9, 0xE5, 0xF5, 0xED, 0xFD, 0xF9, 0xE1, 0xF1, null, null], | |||
| "SEC":0x38, | |||
| "SED":0xF8, | |||
| "SEI":0x78, | |||
| "STA":[null, 0x85, 0x95, 0x8D, 0x9D, 0x99, 0x81, 0x91, null, null], | |||
| "STX":[null, 0x86, 0x96, 0x8E, null, null, null, null, null, null], | |||
| "STY":[null, 0x84, 0x94, 0x8C, null, null, null, null, null, null], | |||
| "TAX":0xAA, | |||
| "TAY":0xA8, | |||
| "TSX":0xBA, | |||
| "TXA":0x8A, | |||
| "TXS":0x9A, | |||
| "TYA":0x98 | |||
| }; | |||
| var NAMES = Object.keys(CODES); | |||
| module.exports = Object.freeze({ | |||
| MODES:{ | |||
| IMMEDIATE: 0, | |||
| ZEROPAGE: 1, | |||
| ZEROPAGEXY: 2, | |||
| ABSOLUTE: 3, | |||
| ABSOLUTEX: 4, | |||
| ABSOLUTEY: 5, | |||
| INDIRECTX: 6, | |||
| INDIRECTY: 7, | |||
| INDIRECT: 8, | |||
| ACCUMULATOR: 9 | |||
| }, | |||
| CODES: CODES, | |||
| NAMES: NAMES, | |||
| isCode:function(op){return (NAMES.indexOf(op) >= 0);}, | |||
| getCode:function(op, mode){ | |||
| if (NAMES.indexOf(op) >= 0){ | |||
| if (typeof(CODES[op]) === 'number') | |||
| return CODES[op]; | |||
| return (!(mode >= 0 && mode <= 9)) ? CODES[op][mode] : null; | |||
| } | |||
| return null; | |||
| } | |||
| }); | |||
| @@ -0,0 +1,192 @@ | |||
| function GetTextStream(input){ | |||
| var pos = 0; | |||
| var line = 0; | |||
| var col = 0; | |||
| function peek(){ | |||
| return input.getChar(pos); | |||
| } | |||
| function next(){ | |||
| let c = input.charAt(pos); | |||
| pos += 1; | |||
| if (c === "\n"){ | |||
| line += 1; | |||
| col = 0; | |||
| } else {col += 1;} | |||
| return c; | |||
| } | |||
| function eof(){ | |||
| return (input.charAt(pos) === ""); | |||
| } | |||
| function die(msg){ | |||
| throw new Error(msg + " Line: " + line + ", Col: " + col); | |||
| } | |||
| return { | |||
| peek: peek, | |||
| get: next, | |||
| eof: eof | |||
| }; | |||
| } | |||
| // ---------------------------------------------- | |||
| // VALIDATORS | |||
| // ---------------------------------------------- | |||
| function isWhiteSpace(c){ | |||
| return ("\t\n ".indexOf(c) >= 0); | |||
| } | |||
| function isStringStart(c){ | |||
| return (c === "\"" || c === "'"); | |||
| } | |||
| function isNumType(c){ | |||
| return ("$%".indexOf(c) >= 0 || isDigit(c)); | |||
| } | |||
| function isDigit(c){ | |||
| return /[0-9]/i.test(c); | |||
| } | |||
| function isHex(c){ | |||
| return /[0-9a-fA-F]/i.test(c); | |||
| } | |||
| function isBinary(c){ | |||
| return ("01".indexOf(c) >= 0); | |||
| } | |||
| function isLabelStart(c){ | |||
| return /[a-fA-F_]/i.test(c); | |||
| } | |||
| function isLabel(c){ | |||
| return (isLabelStart(c) || isDigit(c)); | |||
| } | |||
| function isPunctuation(c){ | |||
| return (",()".indexOf(c) >= 0); | |||
| } | |||
| function isOp(c){ | |||
| return ("=+-/*".indexOf(c) >= 0); | |||
| } | |||
| // ---------------------------------------------- | |||
| // Read Operations | |||
| // ---------------------------------------------- | |||
| function skipComment(stream){ | |||
| readWhile(stream, (c)=>{return c != "\n";}); | |||
| stream.next(); | |||
| } | |||
| function readHex(stream){ | |||
| stream.next(); | |||
| var str = readWhile(stream, isHex); | |||
| return {type:'number', val: parseInt(str, 16)}; | |||
| } | |||
| function readBinary(stream){ | |||
| stream.next(); | |||
| var str = readWhile(stream, isBinary); | |||
| return {type:'number', val: parseInt(str, 2)}; | |||
| } | |||
| function readNumber(stream){ | |||
| let c = stream.peek(); | |||
| if (c === "$") | |||
| return readHex(stream); | |||
| if (c === "%") | |||
| return readBinary(stream); | |||
| var dot = false; | |||
| var str = readWhile(stream, (c)=>{ | |||
| if (c === "."){ | |||
| if (dot){return false;} | |||
| dot = true; | |||
| return true; | |||
| } | |||
| return isDigit(c); | |||
| }); | |||
| return {type:'number', val:parseFloat(str)}; | |||
| } | |||
| function readString(stream, end){ | |||
| var str = ""; | |||
| var escaped = false; | |||
| stream.next(); | |||
| while (!stream.eof()){ | |||
| let c = stream.next(); | |||
| if (escaped){ | |||
| str += c; | |||
| escaped = false; | |||
| } else if (c === "\\"){ | |||
| escaped = true; | |||
| } else if (c === end){ | |||
| break; | |||
| } else { | |||
| str += c; | |||
| } | |||
| } | |||
| return {type: "string", val: str}; | |||
| } | |||
| function readLabel(stream){ | |||
| str = readWhile(stream, isLabel); | |||
| return {type:"label", val: str}; | |||
| } | |||
| function readWhile(stream, validator){ | |||
| var str = ""; | |||
| while (!stream.eof() && validator(stream.peek())) | |||
| str += stream.next(); | |||
| return str; | |||
| } | |||
| function nextToken(stream){ | |||
| readWhile(stream, isWhiteSpace); | |||
| if (stream.eof()){return null;} | |||
| let c = stream.peek(); | |||
| if (c === ";"){ | |||
| skipComment(stream); | |||
| return nextToken(stream); | |||
| } else if (isStringStart(c)){ | |||
| return readString(stream, c); | |||
| } else if (isNumType(c)){ | |||
| return readNumber(stream); | |||
| } else if (isLabelStart(c)){ | |||
| return readLabel(stream); | |||
| } else if (isPunctuation(c)){ | |||
| return {type:"punctuation", val:stream.next()}; | |||
| } else if (isMathOp(c)){ | |||
| return {type:"op", val:stream.next()}; | |||
| } | |||
| stream.die("Unable to process character '" + c + "'."); | |||
| } | |||
| function tokenize(input){ | |||
| var stream = GetTextStream(input); | |||
| var tokens = []; | |||
| var t = nextToken(stream); | |||
| while (t !== null){ | |||
| tokens.push(t); | |||
| t = nextToken(stream); | |||
| } | |||
| return tokens; | |||
| } | |||
| module.exports = tokenize; | |||