Skip to content

Instantly share code, notes, and snippets.

@danshearmur
Last active April 25, 2016 07:08
Show Gist options
  • Save danshearmur/f71619188ac45e7732c87cd7f96341aa to your computer and use it in GitHub Desktop.
Save danshearmur/f71619188ac45e7732c87cd7f96341aa to your computer and use it in GitHub Desktop.
Hack Assembler

Hack Assembler

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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment