| return r; | return r; | ||||
| } | } | ||||
| function tokenize(l){ | |||||
| var m = l.match(/([^()]+)|([()])/g); | |||||
| var ip = false; | |||||
| t = []; | |||||
| m.forEach((mi)=>{ | |||||
| if (mi === "("){ | |||||
| ip = true; | |||||
| } else { | |||||
| if (ip){ | |||||
| mi.split(/[\s,]+/).forEach((i)=>{ | |||||
| if (i !== "") | |||||
| t.push("+" + i); | |||||
| }); | |||||
| } else { | |||||
| mi.split(/[\s,]+/).forEach((i)=>{ | |||||
| if (i !== "") | |||||
| t.push(i); | |||||
| }); | |||||
| } | |||||
| } | |||||
| }); | |||||
| } | |||||
| class Assembler{ | class Assembler{ | ||||
| constructor(initpc){ | constructor(initpc){ | ||||
| // Labels that hold variable values. | // Labels that hold variable values. | ||||
| this.__PC = (initpc >= 0) ? initpc : 0; | this.__PC = (initpc >= 0) ? initpc : 0; | ||||
| } | } | ||||
| function __AddrModeVal(tA, tB){ | |||||
| var mode = ""; | |||||
| var v = Number.NaN; | |||||
| if (tA[0] === '#'){ | |||||
| mode = "i"; // Immediate | |||||
| if (tA[1] === '$'){ | |||||
| v = parseInt(tA.substr(2), 16); | |||||
| } else if (tA[1] === 'b'){ | |||||
| v = parseInt(tA.substr(2), 2); | |||||
| } else { | |||||
| v = parseInt(tA.substr(1)); | |||||
| if (isNaN(v)){ | |||||
| let lbl = tA.substr(1); | |||||
| if (lbl in this.__varlabels){ | |||||
| if (this.__varlabels[lbl] < 256) | |||||
| v = this.__varlabels[lbl] | |||||
| } | |||||
| } | |||||
| } | |||||
| } else if (tA[0] === '$'){ | |||||
| v = parseInt(tA.substr(1), 16); | |||||
| if (tA.length === 3){ | |||||
| // Zero Page | |||||
| if (tB !== null){ | |||||
| if (tB === "X") | |||||
| mode = "zX"; | |||||
| } else { | |||||
| mode = "z"; | |||||
| } | |||||
| } else if (tA.length === 5){ | |||||
| // Absolute | |||||
| if (tB !== null){ | |||||
| if (tB === "X"){ | |||||
| mode = "aX"; | |||||
| } else if (tB === "Y"){ | |||||
| mode = "aY"; | |||||
| } | |||||
| } else { | |||||
| mode = "a"; | |||||
| } | |||||
| } | |||||
| } else if (tA.startsWith("+")){ | |||||
| if (tB !== null){ | |||||
| if (tB === "+X"){ // The plus exists due to how tokenize() works. | |||||
| mode = "nX"; | |||||
| } else if (tB === "Y"){ | |||||
| mode = "nY"; | |||||
| } | |||||
| if (mode !== ""){ | |||||
| if (tA.startsWith("+$")){ | |||||
| v = parseInt(tA.substr(2)); | |||||
| } else { | |||||
| let lbl = tA.substr(1); | |||||
| if (lbl in this.__varlabels){ | |||||
| if (this.__varlabels[lbl] < 256) | |||||
| v = vlb[lbl]; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } else { | |||||
| // We've been given a variable name. Need to look it up. | |||||
| // if the var is 1 byte, assume "Zero Page". If var 2 bytes, assume "Absolute" | |||||
| if (tA in this.__varlabel){ | |||||
| v = this.__varlabel[tA]; | |||||
| if (v < 256){ | |||||
| if (tB !== null){ | |||||
| if (tB === "X") | |||||
| mode = "zX"; | |||||
| } else { | |||||
| mode = "z"; | |||||
| } | |||||
| } else { | |||||
| if (tB !== null){ | |||||
| if (tB === "X"){ | |||||
| mode = "aX"; | |||||
| } else if (tB === "Y"){ | |||||
| mode = "aY"; | |||||
| } | |||||
| } else { | |||||
| mode = "a"; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if (mode !== "" && ! isNaN(v)) | |||||
| return [mode, v]; | |||||
| throw new Error("Malformed op-code or value on program address " + toHexString(this.__PC)); | |||||
| } | |||||
| compile(src){ | compile(src){ | ||||
| var op = []; | var op = []; | ||||
| src.split("\n").forEach((line)=>{ | src.split("\n").forEach((line)=>{ | ||||
| line = line.trim(); | line = line.trim(); | ||||
| if (line[0] !== ";"){ // Skip comment lines. | if (line[0] !== ";"){ // Skip comment lines. | ||||
| line = line.split(";")[0].trim(); // Take out any trailing comments. | line = line.split(";")[0].trim(); // Take out any trailing comments. | ||||
| var tokens = line.split(/\s+/); | |||||
| var tokens = tokenize(line); | |||||
| if (tokens[0] === 'define'){ | if (tokens[0] === 'define'){ | ||||
| // Variable label!! | // Variable label!! | ||||
| } else if (tokens[0][tokens[0].length - 1] === ':'){ | } else if (tokens[0][tokens[0].length - 1] === ':'){ | ||||
| // Jump label!! | // Jump label!! | ||||
| } else if (tokens[0].length === 3){ | |||||
| } else if (tokens[0].length === 3){ | |||||
| let procFailed = false; | |||||
| let mv = null; | |||||
| // Possible op code. | // Possible op code. | ||||
| switch(tokens[0].toLowerCase()){ | switch(tokens[0].toLowerCase()){ | ||||
| // --- ADC | |||||
| case 'adc': | case 'adc': | ||||
| break; | |||||
| if (tokens.length >= 2 && tokens.length <= 3){ | |||||
| mv = addrModeVal(tokens[1]); | |||||
| this.__PC += 2; | |||||
| switch(mv[0]){ | |||||
| case "i": | |||||
| op.push(0x69); | |||||
| op.push(mv[1]); | |||||
| break; | |||||
| case "z": | |||||
| case "zX": | |||||
| op.push((mv[0] === "z") ? 0x65 : 0x75); | |||||
| op.push(mv[1]); | |||||
| break; | |||||
| case "a": | |||||
| case "aX": | |||||
| case "aY": | |||||
| op.push((mv[0] === "a") ? 0x6D : ((mv[0] === "aX") ? 0x7D : 0x79)); | |||||
| op.push(mv[1] & 0x000000FF); | |||||
| op.push((mv[1] & 0x0000FF00) >> 8); | |||||
| this.__PC += 1; | |||||
| break; | |||||
| case "nX": | |||||
| case "nY": | |||||
| op.push((mv[0] === "nX") ? 0x61 : 0x71); | |||||
| op.push(mv[1]); | |||||
| break; | |||||
| } | |||||
| } else { procFailed = true; } | |||||
| break; | |||||
| // --- AND | |||||
| case 'and': | case 'and': | ||||
| break; | break; | ||||
| // --- ASL | |||||
| case 'asl': | case 'asl': | ||||
| break; | break; | ||||
| // --- BCC | |||||
| case 'bcc': | case 'bcc': | ||||
| break; | break; | ||||
| // --- BCS | |||||
| case 'bcs': | case 'bcs': | ||||
| break; | break; | ||||
| // --- BEQ | |||||
| case 'beq': | case 'beq': | ||||
| break; | break; | ||||
| case 'bit': | case 'bit': | ||||
| break; | break; | ||||
| case 'bpl': | case 'bpl': | ||||
| break; | break; | ||||
| // --- BRK | |||||
| case 'brk': | case 'brk': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0x00); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| case 'bvc': | case 'bvc': | ||||
| break; | break; | ||||
| case 'bvs': | case 'bvs': | ||||
| break; | break; | ||||
| // --- CLC | |||||
| case 'clc': | case 'clc': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0x18); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- CLD | |||||
| case 'cld': | case 'cld': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xD8); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- CLI | |||||
| case 'cli': | case 'cli': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0x58); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- CLV | |||||
| case 'clv': | case 'clv': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xB8); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| case 'cmp': | case 'cmp': | ||||
| break; | break; | ||||
| break; | break; | ||||
| case 'dec': | case 'dec': | ||||
| break; | break; | ||||
| // --- DEX | |||||
| case 'dex': | case 'dex': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xCA); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- DEY | |||||
| case 'dey': | case 'dey': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0x88); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| case 'eor': | case 'eor': | ||||
| break; | break; | ||||
| case 'inc': | case 'inc': | ||||
| break; | break; | ||||
| // --- INX | |||||
| case 'inx': | case 'inx': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xE8); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- INY | |||||
| case 'iny': | case 'iny': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xC8); | |||||
| this.__PC += 1 | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| case 'jmp': | case 'jmp': | ||||
| break; | break; | ||||
| break; | break; | ||||
| case 'lsr': | case 'lsr': | ||||
| break; | break; | ||||
| // --- NOP | |||||
| case 'nop': | case 'nop': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xEA); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| case 'ora': | case 'ora': | ||||
| break; | break; | ||||
| break; | break; | ||||
| case 'sbc': | case 'sbc': | ||||
| break; | break; | ||||
| // --- SEC | |||||
| case 'sec': | case 'sec': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0x38); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- SED | |||||
| case 'sed': | case 'sed': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xF8); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- SEI | |||||
| case 'sei': | case 'sei': | ||||
| op.push(0x78); | |||||
| this.__PC += 1; | |||||
| break; | break; | ||||
| case 'sta': | case 'sta': | ||||
| break; | break; | ||||
| break; | break; | ||||
| case 'sty': | case 'sty': | ||||
| break; | break; | ||||
| // --- TAX | |||||
| case 'tax': | case 'tax': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xAA); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- TAY | |||||
| case 'tay': | case 'tay': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0xA8); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- TSX | |||||
| case 'tsx': | case 'tsx': | ||||
| break; | break; | ||||
| // --- TXA | |||||
| case 'txa': | case 'txa': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0x8A); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- TXS | |||||
| case 'txs': | case 'txs': | ||||
| break; | break; | ||||
| // --- TYA | |||||
| case 'tya': | case 'tya': | ||||
| if (tokens.length === 1){ | |||||
| op.push(0x98); | |||||
| this.__PC += 1; | |||||
| } else { procFailed = true; } | |||||
| break; | break; | ||||
| // --- --- | |||||
| default: | default: | ||||
| throw new Error("Unknown op-code '" + tokens[0].toUpperCase() + "' at program address " + toHexString(this.__PC)); | throw new Error("Unknown op-code '" + tokens[0].toUpperCase() + "' at program address " + toHexString(this.__PC)); | ||||
| } | } | ||||
| if (procFailed) | |||||
| throw new Error("Malformed op-code on program address " + toHexString(this.__PC)); | |||||
| } else { | } else { | ||||
| throw new Error("Failed to compile line '" + line + "' at program address " + toHexString(this.__PC)); | throw new Error("Failed to compile line '" + line + "' at program address " + toHexString(this.__PC)); | ||||
| } | } |