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