I have been reading this great book and I decided to do the Project in Chapter 5: Writing an Assembler for the Hack Assembly Language.
You can find the specification for the Hack Machine Language and the Hack Assembly Language here.
Here is my implementation:
package biz.tugay.hack.assembler;
/* User: [email protected] Date: 07/03/15 Time: 10:47 */
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class HackAssembler {
private static File machineLanguageFile;
private static File assembled;
private static FileWriter fileWriter;
private static int variableAddressCounter = 16;
private static Map<String,Integer> variableAddressMap = new HashMap<String,Integer>();
private static Map<String,Integer> loopAddressMap = new HashMap<String, Integer>();
public static void main(String[] args) throws IOException {
doChecksForSourceFile(args);
checkLabelsAndPopulateLoopAddressMap(machineLanguageFile);
Scanner machineLanguageFileScanner = new Scanner(machineLanguageFile);
fileWriter = new FileWriter(assembled);
while(machineLanguageFileScanner.hasNextLine()){
String nextAssemblyLine = machineLanguageFileScanner.nextLine();
assemble(nextAssemblyLine);
}
fileWriter.flush();
fileWriter.close();
}
private static void assemble(String lineToBeAssembled) throws IOException {
if(lineToBeAssembled.startsWith("(")) {
}
else if(lineToBeAssembled.startsWith("@")) {
assemble_A_Instruction(lineToBeAssembled);
fileWriter.write(System.lineSeparator());
}
else {
assemble_C_Instruction(lineToBeAssembled);
fileWriter.write(System.lineSeparator());
}
}
private static void assemble_C_Instruction(String lineToBeAssembled) throws IOException {
String compMnemonnic;
String destinationMnemonnic = "null";
String jumpMnemonnic = "null";
String compBinaryRepresentation;
String jumpBinaryRepresentation;
String destinationBinaryRepresentation;
String[] split = lineToBeAssembled.split("=");
String s = split[split.length - 1];
String[] split1 = s.split(";");
compMnemonnic = split1[0];
compBinaryRepresentation = BInstructionsMap.computationMnemonicMap.get(compMnemonnic);
if(lineToBeAssembled.contains("=")) {
destinationMnemonnic = lineToBeAssembled.split("=")[0];
}
destinationBinaryRepresentation = BInstructionsMap.destinationMnemonnicMap.get(destinationMnemonnic);
if(lineToBeAssembled.contains(";")) {
String[] x = lineToBeAssembled.split(";");
jumpMnemonnic = x[x.length-1];
}
jumpBinaryRepresentation = BInstructionsMap.jumpMnemonnicMap.get(jumpMnemonnic);
fileWriter.write("111" + compBinaryRepresentation + destinationBinaryRepresentation + jumpBinaryRepresentation);
}
private static void assemble_A_Instruction(String lineToBeAssembled) throws IOException {
String subString = lineToBeAssembled.substring(1);
try {
Integer.parseInt(subString);
assemble_A_InstructionWithDirectAddress(subString);
} catch (NumberFormatException e) {
assemble_A_InstructionWithVariableOrLabel(subString);
}
}
private static void assemble_A_InstructionWithVariableOrLabel(String subString) throws IOException {
int assignedAddress = variableAddressCounter;
if(loopAddressMap.containsKey(subString)) {
assignedAddress = loopAddressMap.get(subString);
}
else if(variableAddressMap.containsKey(subString)) {
assignedAddress = variableAddressMap.get(subString);
}
else if(!variableAddressMap.containsKey(subString)) {
variableAddressMap.put(subString,variableAddressCounter);
variableAddressCounter++;
}
fileWriter.write(appendZerosToString(Integer.toBinaryString(assignedAddress)));
}
private static void assemble_A_InstructionWithDirectAddress(String lineToBeAssembled) throws IOException {
fileWriter.write(appendZerosToString(Integer.toBinaryString(Integer.parseInt(lineToBeAssembled))));
}
private static String appendZerosToString(String stringToBeAppended) {
char[] chars = stringToBeAppended.toCharArray();
int zerosToAppend = 16 - chars.length;
StringBuilder stringBuilder = new StringBuilder();
for(int i=0;i<zerosToAppend;i++) {
stringBuilder.append("0");
}
stringBuilder.append(stringToBeAppended);
return stringBuilder.toString();
}
public static void doChecksForSourceFile(String[] args) throws IOException {
// Check if filename has been passed to the program.
if(args.length == 0) {
System.out.println("Please pass the filename as parameter without the file extension.");
System.exit(0);
}
// Check if the passed file exists.
machineLanguageFile = new File(args[0]+".asm");
if(!machineLanguageFile.exists()) {
System.out.println("File does not exist..");
System.exit(0);
}
// Create the new file which will contain the machine instructions in binary format.
assembled = new File(args[0]+".hack");
boolean delete = true;
if(assembled.exists()) {
delete = assembled.delete();
}
boolean newFile = assembled.createNewFile();
if(!delete ||!newFile) {
System.out.println("Can not create new file..");
System.exit(0);
}
}
private static void checkLabelsAndPopulateLoopAddressMap(File machineLanguageFile)
throws FileNotFoundException {
Scanner machineLanguageFileScanner = new Scanner(machineLanguageFile);
int counter = 0;
while(machineLanguageFileScanner.hasNextLine()){
String nextAssemblyLine = machineLanguageFileScanner.nextLine();
if(nextAssemblyLine.startsWith("(")) {
String loopLabel = nextAssemblyLine.substring(1,nextAssemblyLine.length()-1);
loopAddressMap.put(loopLabel,counter);
counter--;
}
counter++;
}
}
}
package biz.tugay.hack.assembler;
/* User: [email protected] Date: 07/03/15 Time: 13:14 */
import java.util.HashMap;
import java.util.Map;
public class BInstructionsMap {
public static Map<String,String> computationMnemonicMap
= new HashMap<String,String>();
public static Map<String,String> jumpMnemonnicMap
= new HashMap<String, String>();
public static Map<String,String> destinationMnemonnicMap
= new HashMap<String, String>();
static {
computationMnemonicMap.put("0" ,"0101010");
computationMnemonicMap.put("1" ,"0111111");
computationMnemonicMap.put("-1" ,"0111010");
computationMnemonicMap.put("D" ,"0001100");
computationMnemonicMap.put("A" ,"0110000");
computationMnemonicMap.put("!D" ,"0001101");
computationMnemonicMap.put("!A" ,"0110001");
computationMnemonicMap.put("-D" ,"0001111");
computationMnemonicMap.put("-A" ,"0110011");
computationMnemonicMap.put("D+1","0011111");
computationMnemonicMap.put("A+1","0110111");
computationMnemonicMap.put("D-1","0001110");
computationMnemonicMap.put("A-1","0110010");
computationMnemonicMap.put("D+A","0000010");
computationMnemonicMap.put("D-A","0010011");
computationMnemonicMap.put("A-D","0000111");
computationMnemonicMap.put("D&A","0000000");
computationMnemonicMap.put("D|A","0010101");
computationMnemonicMap.put("M" ,"1110000");
computationMnemonicMap.put("!M" ,"1110001");
computationMnemonicMap.put("-M" ,"1110011");
computationMnemonicMap.put("M+1","1110111");
computationMnemonicMap.put("M-1","1110010");
computationMnemonicMap.put("D+M","1000010");
computationMnemonicMap.put("D-M","1010011");
computationMnemonicMap.put("M-D","1000111");
computationMnemonicMap.put("D&M","1000000");
computationMnemonicMap.put("D|M","1010101");
}
static {
jumpMnemonnicMap.put("null","000");
jumpMnemonnicMap.put("JGT" ,"001");
jumpMnemonnicMap.put("JEQ" ,"010");
jumpMnemonnicMap.put("JGE" ,"011");
jumpMnemonnicMap.put("JLT" ,"100");
jumpMnemonnicMap.put("JNE" ,"101");
jumpMnemonnicMap.put("JLE" ,"110");
jumpMnemonnicMap.put("JMP" ,"111");
}
static {
destinationMnemonnicMap.put("null","000");
destinationMnemonnicMap.put("M" ,"001");
destinationMnemonnicMap.put("D" ,"010");
destinationMnemonnicMap.put("MD" ,"011");
destinationMnemonnicMap.put("A" ,"100");
destinationMnemonnicMap.put("AM" ,"101");
destinationMnemonnicMap.put("AD" ,"110");
destinationMnemonnicMap.put("AMD" ,"111");
}
}
If you would also like to do this exercise, you can find the software here.