Created
March 25, 2016 18:14
-
-
Save frozenfung/525dae17afd555d6b011 to your computer and use it in GitHub Desktop.
Assemble compiler for nandToTetris project 06
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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