// 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; | |||||
} | |||||
}); | |||||
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; | |||||