To run:
npm install
node index.js ./path/to/File.asm ./path/to/Output.hack
'use strict'; | |
const leftPad = require('left-pad'); | |
const to16bit = (num) => leftPad(Number(num).toString(2), 16, '0'); | |
const destLookup = { | |
M: [0, 0, 1], | |
D: [0, 1, 0], | |
MD: [0, 1, 1], | |
A: [1, 0, 0], | |
AM: [1, 0, 1], | |
AD: [1, 1, 0], | |
AMD: [1, 1, 1], | |
}; | |
const compLookup = { | |
'0': {a: 0, c: [1, 0, 1, 0, 1, 0]}, | |
'1': {a: 0, c: [1, 1, 1, 1, 1, 1]}, | |
'-1': {a: 0, c: [1, 1, 1, 0, 1, 0]}, | |
'D': {a: 0, c: [0, 0, 1, 1, 0, 0]}, | |
'A': {a: 0, c: [1, 1, 0, 0, 0, 0]}, | |
'!D': {a: 0, c: [0, 0, 1, 1, 0, 1]}, | |
'!A': {a: 0, c: [1, 1, 0, 0, 0, 1]}, | |
'-D': {a: 0, c: [0, 0, 1, 1, 1, 1]}, | |
'-A': {a: 0, c: [1, 1, 0, 0, 1, 1]}, | |
'D+1': {a: 0, c: [0, 1, 1, 1, 1, 1]}, | |
'A+1': {a: 0, c: [1, 1, 0, 1, 1, 1]}, | |
'D-1': {a: 0, c: [0, 0, 1, 1, 1, 0]}, | |
'A-1': {a: 0, c: [1, 1, 0, 0, 1, 0]}, | |
'D+A': {a: 0, c: [0, 0, 0, 0, 1, 0]}, | |
'D-A': {a: 0, c: [0, 1, 0, 0, 1, 1]}, | |
'A-D': {a: 0, c: [0, 0, 0, 1, 1, 1]}, | |
'D&A': {a: 0, c: [0, 0, 0, 0, 0, 0]}, | |
'D|A': {a: 0, c: [0, 1, 0, 1, 0, 1]}, | |
'M': {a: 1, c: [1, 1, 0, 0, 0, 0]}, | |
'!M': {a: 1, c: [1, 1, 0, 0, 0, 1]}, | |
'-M': {a: 1, c: [1, 1, 0, 0, 1, 1]}, | |
'M+1': {a: 1, c: [1, 1, 0, 1, 1, 1]}, | |
'M-1': {a: 1, c: [1, 1, 0, 0, 1, 0]}, | |
'D+M': {a: 1, c: [0, 0, 0, 0, 1, 0]}, | |
'D-M': {a: 1, c: [0, 1, 0, 0, 1, 1]}, | |
'M-D': {a: 1, c: [0, 0, 0, 1, 1, 1]}, | |
'D&M': {a: 1, c: [0, 0, 0, 0, 0, 0]}, | |
'D|M': {a: 1, c: [0, 1, 0, 1, 0, 1]}, | |
}; | |
const jumpLookup = { | |
JGT: [0, 0, 1], | |
JEQ: [0, 1, 0], | |
JGE: [0, 1, 1], | |
JLT: [1, 0, 0], | |
JNE: [1, 0, 1], | |
JLE: [1, 1, 0], | |
JMP: [1, 1, 1], | |
}; | |
function cInstruction (dest, comp, jump) { | |
const out = new Array(16); | |
out.fill(0); | |
// it's a c instruction so first 3 bits set to 1 | |
out[0] = out[1] = out[2] = 1; | |
if (!(comp in compLookup)) { | |
throw new Error('No comp'); | |
} | |
const compO = compLookup[comp]; | |
const compBits = [compO.a].concat(compO.c); | |
compBits.forEach((bit, i) => { | |
out[3 + i] = bit; | |
}); | |
if (dest) { | |
const destBits = destLookup[dest]; | |
destBits.forEach((bit, i) => { | |
out[10 + i] = bit; | |
}); | |
} | |
if (jump) { | |
const jumpBits = jumpLookup[jump]; | |
jumpBits.forEach((bit, i) => { | |
out[13 + i] = bit; | |
}); | |
} | |
return out.join(''); | |
} | |
function converter(parsed, symbols) { | |
const output = []; | |
for (let a of parsed) { | |
let type = a[0].type; | |
let val = a[1]; | |
if (type === 'label') continue; | |
if (val.charAt(0) === '@') { | |
let symbol = val.slice(1); | |
if (symbols.has(symbol)) { | |
output.push(to16bit(symbols.get(symbol))); | |
} else { | |
output.push(to16bit(symbol)); | |
} | |
} else { | |
let split = val.split(';'); | |
const jump = split[1]; | |
split = split[0].split('='); | |
let dest; | |
let comp; | |
if (split.length === 1) { | |
comp = split[0] | |
} else { | |
dest = split[0]; | |
comp = split[1]; | |
} | |
output.push(cInstruction(dest, comp, jump)); | |
} | |
} | |
return output.join('\n'); | |
} | |
module.exports = converter; |
'use strict'; | |
if (!process.argv[2] || !process.argv[3]) { | |
console.error('need to supply input and output files `node index.js input.asm out.bin'); | |
exit(); | |
} | |
const fs = require('fs'); | |
const parser = require('./parser'); | |
const symbols = require('./symbols'); | |
const converter = require('./converter'); | |
const input = process.argv[2]; | |
const output = process.argv[3]; | |
const inContents = fs.readFileSync(input, {encoding: 'utf-8'}); | |
const parsed = parser(inContents); | |
const computedSymbols = symbols(parsed); | |
const outContents = converter(parsed, computedSymbols); | |
fs.writeFileSync(output, outContents); |
{ | |
"name": "dans-assembler", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "jest" | |
}, | |
"author": "dans", | |
"license": "MIT", | |
"dependencies": { | |
"left-pad": "^1.0.2" | |
} | |
} |
'use strict'; | |
const commentRe = /\/\/.*$/; | |
const labelRe = /[\(\)]/g; | |
function whiteSpace(str) { | |
if (typeof str !== 'string') return false; | |
str = str.trim().replace(commentRe, '').trim(); | |
return str; | |
} | |
function label(str) { | |
return str.replace(labelRe, ''); | |
} | |
function parser(content) { | |
content = content || ''; | |
const lines = content.split(/\n/); | |
let i = 0; | |
const map = lines.reduce((map, line) => { | |
if (!line) return map; | |
const formatted = whiteSpace(line); | |
let type; | |
let val; | |
if (!formatted) return map; | |
if (formatted.charAt(0) !== '(') { | |
type = 'instruction'; | |
val = formatted; | |
} else { | |
type = 'label'; | |
val = label(formatted); | |
} | |
map.set({ | |
type: type, | |
num: i | |
}, val); | |
if (type === 'instruction') i++; | |
return map; | |
}, new Map()); | |
return map; | |
} | |
module.exports = parser; |
'use strict'; | |
function baseMap() { | |
const out = new Map; | |
out.set('R0', 0); | |
out.set('R1', 1); | |
out.set('R2', 2); | |
out.set('R3', 3); | |
out.set('R4', 4); | |
out.set('R5', 5); | |
out.set('R6', 6); | |
out.set('R7', 7); | |
out.set('R8', 8); | |
out.set('R9', 9); | |
out.set('R10', 10); | |
out.set('R11', 11); | |
out.set('R12', 12); | |
out.set('R13', 13); | |
out.set('R14', 14); | |
out.set('R15', 15); | |
out.set('SCREEN', 16384); | |
out.set('KBO', 24576); | |
out.set('SP', 0); | |
out.set('LCL', 1); | |
out.set('ARG', 2); | |
out.set('THIS', 3); | |
out.set('THAT', 4); | |
return out; | |
} | |
function symbols(parsed) { | |
const map = baseMap(); | |
let count = 16; | |
// first pass for labels | |
for (let arr of parsed) { | |
let o = arr[0]; | |
let val = arr[1]; | |
if (o.type === 'label') { | |
if (!map.has(val)) { | |
map.set(val, o.num); | |
} | |
} | |
} | |
// second pass for variables | |
for (let arr of parsed) { | |
let o = arr[0]; | |
let val = arr[1]; | |
if (o.type === 'instruction') { | |
if (val.charAt(0) === '@') { | |
val = val.slice(1); | |
if (isNaN(Number(val))) { | |
if (!map.has(val)) { | |
map.set(val, count++); | |
} | |
} | |
} | |
} | |
} | |
return map; | |
} | |
module.exports = symbols; |