6
\$\begingroup\$

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: koray.asm@tugay.biz 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: koray@tugay.biz 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.

\$\endgroup\$
  • \$\begingroup\$ Two questions: package biz.tugay.hack.assembler; what does this line do? Also where would I put the file that I want to convert? I currently placed it in project folder and it is not working. Thanks! \$\endgroup\$ – Dude McDude Dec 20 '17 at 13:49
6
\$\begingroup\$

My two cents:

  • Your lines are long:

    "code gets harder to read the wider it becomes"

  • You pass around ambiguously named variables. At first glance, it's hard to know what args[0] or subString is.
  • Some names are very long.
  • Spaces.

    for(int i=0;i<zerosToAppend;i++);
    

    can easily be

    for(int i = 0; i < zerosToAppend; i++);
    
  • There are no comments describing what the code does.

    "Comments can never be replaced by code alone."

  • Strings are hard-coded all over the place. It's much easier to understand

    assembled = new File(fileName + FILE_SUFFIX);
    

    than

    assembled = new File(args[0]+".hack");
    

    It helps to have these type of things all in one place in anticipation of one of them needing change.

Over all, it looks pretty decent. Just remember: K.I.S.S.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.