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; |