Skip to content

Instantly share code, notes, and snippets.

@frozenfung
Created March 25, 2016 18:14
Show Gist options
  • Save frozenfung/525dae17afd555d6b011 to your computer and use it in GitHub Desktop.
Save frozenfung/525dae17afd555d6b011 to your computer and use it in GitHub Desktop.
Assemble compiler for nandToTetris project 06
// NandToTetris Part One Final Project
// https://www.coursera.org/learn/build-a-computer/home/welcome
#!/usr/bin/js
// Handling basic I/O
const XMLHttpRequest = require("./node_modules/xmlhttprequest").XMLHttpRequest;
const fs = require("fs");
// Global variable
var database = null;
const Database = function (rawAssembleCode){
this.rawAssembleCode = rawAssembleCode;
this.pureAssembleCode = [];
this.machineCode = [];
this.symbolTable = {
'R0': 0,
'R1': 1,
'R2': 2,
'R3': 3,
'R4': 4,
'R5': 5,
'R6': 6,
'R7': 7,
'R8': 8,
'R9': 9,
'R10': 10,
'R11': 11,
'R12': 12,
'R13': 13,
'R14': 14,
'R15': 15,
'SCREEN': 16384,
'KBD': 24576,
'SP': 0,
'LCL': 1,
'ARG': 2,
'THIS': 3,
'THAT': 4,
};
}
const Parser = function (){
this.removeWhiteSpace = (rawAssembleCode) => {
var assembleCode = rawAssembleCode;
var parsedAssembleCode = assembleCode.replace(/ /g, '');
return parsedAssembleCode;
};
this.removeEmptyLine = (rawAssembleCode) => {
var assembleCode = rawAssembleCode;
var parsedAssembleCode = assembleCode.replace(/^\n$/g, '');
parsedAssembleCode = parsedAssembleCode.replace(/\r/g, '');
return parsedAssembleCode;
};
this.removeInstruction = (rawAssembleCode) => {
var assembleCode = rawAssembleCode;
var parsedAssembleCode = assembleCode.replace(/\/\/\S+/g, '');
return parsedAssembleCode;
};
this.splitAssembleCodeWithLineBreak = (rawAssembleCode) => {
var assembleCode = rawAssembleCode;
var instruction = assembleCode.split('\n').filter(val => val != '');
database.pureAssembleCode = instruction;
return instruction;
};
this.removeLabels = (instructions) => {
var regexp = /\(/;
var labelCount = 0;
var newInstructions = instructions.map((instruction, idx) => {
var isLabel = regexp.test(instruction);
if (isLabel) {
var label = instruction.replace('(', '').replace(')', '');
database.symbolTable[label] = idx - labelCount;
labelCount++;
}
return isLabel ? '' : instruction;
}).filter(val => val != '');
return newInstructions;
};
this.transferLabels = (instructions) => {
var regexp = /@[A-Za-z]/;
var symbolPointer = 16;
var newInstructions = instructions.map((instruction, idx) => {
var isLabel = regexp.test(instruction);
var labelName = instruction.replace('@', '');
var isLabelExist = database.symbolTable[labelName] !== undefined ? true : false;
if (isLabel && !isLabelExist) {
database.symbolTable[labelName] = symbolPointer;
symbolPointer++;
}
var newInstruction = '@' + database.symbolTable[labelName];
return isLabel ? newInstruction : instruction;
});
return newInstructions;
};
}
const Compiler = function(){
const desTable = {
'null': '000',
'M': '001',
'D': '010',
'MD': '011',
'A': '100',
'AM': '101',
'AD': '110',
'AMD': '111',
};
const compTable = {
// a = 0
'0': '0101010',
'1': '0111111',
'-1': '0111010',
'D': '0001100',
'A': '0110000',
'!D': '0001101',
'!A': '0110011',
'-D': '0001111',
'-A': '0110011',
'D+1': '0011111',
'A+1': '0110111',
'D-1': '0001110',
'A-1': '0110010',
'D+A': '0000010',
'D-A': '0010011',
'A-D': '0000111',
'D&A': '0000000',
'D|A': '0010101',
// a = 1
'D|M': '1010101',
'D&M': '1000000',
'M-D': '1000111',
'D-M': '1010011',
'D+M': '1000010',
'M-1': '1110010',
'M+1': '1110111',
'-M': '1110011',
'!M': '1110001',
'M': '1110000',
};
const jumpTable = {
'null': '000',
'JGT': '001',
'JEQ': '010',
'JGE': '011',
'JLT': '100',
'JNE': '101',
'JLE': '110',
'JMP': '111',
};
this.detectInstructionType = (instruction) => {
var instructionType = '';
var regexp = new RegExp("@");
instructionType = regexp.test(instruction) ? 'A' : 'C';
return instructionType;
};
this.compileAInstruction = (rawInstruction) => {
var instruction = rawInstruction;
var binary = '';
instruction = instruction.replace(/@/, '');
binary = this.leftpad(parseInt(instruction).toString(2), 15, 0);
binary = '0' + binary;
return binary;
};
this.compileCInstruction = (rawInstruction) => {
var instruction = rawInstruction;
var binary = '';
var regexp = /(\w+)?=?([^;]+)(;\S+)?/;
var match = regexp.exec(instruction);
var des = match[1] ? match[1].replace('=', '') : 'null';
var comp = match[2];
var jump = match[3] ? match[3].replace(';', '') : 'null';
binary = '111' + compTable[comp] + desTable[des] + jumpTable[jump];
return binary;
};
this.leftpad = (str, len, ch) => {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
};
}
function main() {
initialize();
var parser = new Parser();
var compiler = new Compiler();
var parsedAssembleCode = '';
var pureAssembleCode = [];
var binaryCode = [];
var compiledResult = '';
var rawAssembleCode = database.rawAssembleCode;
parsedAssembleCode = parser.removeWhiteSpace(rawAssembleCode);
parsedAssembleCode = parser.removeEmptyLine(parsedAssembleCode);
parsedAssembleCode = parser.removeInstruction(parsedAssembleCode);
pureAssembleCode = parser.splitAssembleCodeWithLineBreak(parsedAssembleCode);
pureAssembleCode = parser.removeLabels(pureAssembleCode);
pureAssembleCode = parser.transferLabels(pureAssembleCode);
binaryCode = compileAssembleCodeToBinaryCode(pureAssembleCode);
compiledResult = binaryCode.join('\n');
// console.log(compiledResult);
writeFile(process.argv[3], compiledResult);
}
function initialize() {
var path = process.argv[1].replace('compiler.js', '');
var target = process.argv[2];
const rawAssembleCode = readFile("file://" + path + target);
database = new Database(rawAssembleCode);
}
function compileAssembleCodeToBinaryCode(pureAssembleCode) {
const compiler = new Compiler();
var binarys = [];
binarys = pureAssembleCode.map((assemble) => {
var binary = '';
var instructionType = compiler.detectInstructionType(assemble);
switch (instructionType) {
case 'A':
binary = compiler.compileAInstruction(assemble);
break;
case 'C':
binary = compiler.compileCInstruction(assemble);
break;
default:
}
return binary;
});
return binarys;
}
function readFile(file) {
var data = null;
const rawFile = new XMLHttpRequest();
rawFile.open("GET", file, false);
rawFile.onreadystatechange = () => {
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) data = rawFile.responseText;
}
}
rawFile.send(null);
return data;
}
function writeFile(path, data) {
fs.writeFile(path, data);
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment