| @@ -71,6 +71,29 @@ function toHexString(v, l){ | |||
| 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{ | |||
| constructor(initpc){ | |||
| // Labels that hold variable values. | |||
| @@ -82,31 +105,159 @@ class Assembler{ | |||
| 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){ | |||
| var op = []; | |||
| src.split("\n").forEach((line)=>{ | |||
| line = line.trim(); | |||
| if (line[0] !== ";"){ // Skip comment lines. | |||
| line = line.split(";")[0].trim(); // Take out any trailing comments. | |||
| var tokens = line.split(/\s+/); | |||
| var tokens = tokenize(line); | |||
| if (tokens[0] === 'define'){ | |||
| // Variable label!! | |||
| } else if (tokens[0][tokens[0].length - 1] === ':'){ | |||
| // Jump label!! | |||
| } else if (tokens[0].length === 3){ | |||
| } else if (tokens[0].length === 3){ | |||
| let procFailed = false; | |||
| let mv = null; | |||
| // Possible op code. | |||
| switch(tokens[0].toLowerCase()){ | |||
| // --- 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': | |||
| break; | |||
| // --- ASL | |||
| case 'asl': | |||
| break; | |||
| // --- BCC | |||
| case 'bcc': | |||
| break; | |||
| // --- BCS | |||
| case 'bcs': | |||
| break; | |||
| // --- BEQ | |||
| case 'beq': | |||
| break; | |||
| case 'bit': | |||
| @@ -117,19 +268,44 @@ class Assembler{ | |||
| break; | |||
| case 'bpl': | |||
| break; | |||
| // --- BRK | |||
| case 'brk': | |||
| if (tokens.length === 1){ | |||
| op.push(0x00); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| case 'bvc': | |||
| break; | |||
| case 'bvs': | |||
| break; | |||
| // --- CLC | |||
| case 'clc': | |||
| if (tokens.length === 1){ | |||
| op.push(0x18); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- CLD | |||
| case 'cld': | |||
| if (tokens.length === 1){ | |||
| op.push(0xD8); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- CLI | |||
| case 'cli': | |||
| if (tokens.length === 1){ | |||
| op.push(0x58); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- CLV | |||
| case 'clv': | |||
| if (tokens.length === 1){ | |||
| op.push(0xB8); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| case 'cmp': | |||
| break; | |||
| @@ -139,17 +315,37 @@ class Assembler{ | |||
| break; | |||
| case 'dec': | |||
| break; | |||
| // --- DEX | |||
| case 'dex': | |||
| if (tokens.length === 1){ | |||
| op.push(0xCA); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- DEY | |||
| case 'dey': | |||
| if (tokens.length === 1){ | |||
| op.push(0x88); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| case 'eor': | |||
| break; | |||
| case 'inc': | |||
| break; | |||
| // --- INX | |||
| case 'inx': | |||
| if (tokens.length === 1){ | |||
| op.push(0xE8); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- INY | |||
| case 'iny': | |||
| if (tokens.length === 1){ | |||
| op.push(0xC8); | |||
| this.__PC += 1 | |||
| } else { procFailed = true; } | |||
| break; | |||
| case 'jmp': | |||
| break; | |||
| @@ -163,7 +359,12 @@ class Assembler{ | |||
| break; | |||
| case 'lsr': | |||
| break; | |||
| // --- NOP | |||
| case 'nop': | |||
| if (tokens.length === 1){ | |||
| op.push(0xEA); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| case 'ora': | |||
| break; | |||
| @@ -185,11 +386,24 @@ class Assembler{ | |||
| break; | |||
| case 'sbc': | |||
| break; | |||
| // --- SEC | |||
| case 'sec': | |||
| if (tokens.length === 1){ | |||
| op.push(0x38); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- SED | |||
| case 'sed': | |||
| if (tokens.length === 1){ | |||
| op.push(0xF8); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- SEI | |||
| case 'sei': | |||
| op.push(0x78); | |||
| this.__PC += 1; | |||
| break; | |||
| case 'sta': | |||
| break; | |||
| @@ -197,21 +411,47 @@ class Assembler{ | |||
| break; | |||
| case 'sty': | |||
| break; | |||
| // --- TAX | |||
| case 'tax': | |||
| if (tokens.length === 1){ | |||
| op.push(0xAA); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- TAY | |||
| case 'tay': | |||
| if (tokens.length === 1){ | |||
| op.push(0xA8); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- TSX | |||
| case 'tsx': | |||
| break; | |||
| // --- TXA | |||
| case 'txa': | |||
| if (tokens.length === 1){ | |||
| op.push(0x8A); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- TXS | |||
| case 'txs': | |||
| break; | |||
| // --- TYA | |||
| case 'tya': | |||
| if (tokens.length === 1){ | |||
| op.push(0x98); | |||
| this.__PC += 1; | |||
| } else { procFailed = true; } | |||
| break; | |||
| // --- --- | |||
| default: | |||
| 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 { | |||
| throw new Error("Failed to compile line '" + line + "' at program address " + toHexString(this.__PC)); | |||
| } | |||