0298-Nand-汇编器

环境

  • Time 2023-07-08

前言

说明

参考:https://www.nand2tetris.org/
参考:https://github.com/AllenWrong/nand2tetris

目标

接上一节,使用 Java 语言实现汇编编译器。

SymbolTable

package com.example.demo;

import java.util.HashMap;
import java.util.stream.IntStream;

public class SymbolTable {
    private final HashMap<String, Integer> map;

    public SymbolTable() {
        this.map = new HashMap<>();
        this.addEntry("SP", 0);
        this.addEntry("LCL", 1);
        this.addEntry("ARG", 2);
        this.addEntry("THIS", 3);
        this.addEntry("THAT", 4);
        IntStream.range(0, 16).forEach(i -> this.addEntry("R" + i, i));
        this.addEntry("SCREEN", 16384);
        this.addEntry("KBD", 24576);
    }

    public void addEntry(String symbol, Integer address) {
        this.map.put(symbol, address);
    }

    public boolean contains(String symbol) {
        return this.map.containsKey(symbol);
    }

    public int getAddress(String symbol) {
        return this.map.getOrDefault(symbol, -1);
    }
}

Code

package com.example.demo;

public class Code {
    public String dest(String symbol) {
        switch (symbol) {
            case "":
                return "000";
            case "M":
                return "001";
            case "D":
                return "010";
            case "MD":
                return "011";
            case "A":
                return "100";
            case "AM":
                return "101";
            case "AD":
                return "110";
            case "AMD":
                return "111";
            default:
                break;
        }
        return null;
    }

    public String comp(String symbol) {
        switch (symbol) {
            case "0":
                return "0101010";
            case "1":
                return "0111111";
            case "-1":
                return "0111010";
            case "D":
                return "0001100";
            case "A":
                return "0110000";
            case "M":
                return "1110000";
            case "!D":
                return "0001101";
            case "!A":
                return "0110001";
            case "!M":
                return "1110001";
            case "-D":
                return "0001111";
            case "-A":
                return "0110011";
            case "-M":
                return "1110011";
            case "D+1":
                return "0011111";
            case "A+1":
                return "0110111";
            case "M+1":
                return "1110111";
            case "D-1":
                return "0001110";
            case "A-1":
                return "0110010";
            case "M-1":
                return "1110010";
            case "D+A":
                return "0000010";
            case "D+M":
                return "1000010";
            case "D-A":
                return "0010011";
            case "D-M":
                return "1010011";
            case "A-D":
                return "0000111";
            case "M-D":
                return "1000111";
            case "D&A":
                return "0000000";
            case "D&M":
                return "1000000";
            case "D|A":
                return "0010101";
            case "D|M":
                return "1010101";
            default:
                break;
        }
        return null;
    }

    public String jump(String symbol) {
        switch (symbol) {
            case "":
                return "000";
            case "JGT":
                return "001";
            case "JEQ":
                return "010";
            case "JGE":
                return "011";
            case "JLT":
                return "100";
            case "JNE":
                return "101";
            case "JLE":
                return "110";
            case "JMP":
                return "111";
            default:
                break;
        }
        return null;
    }
}

CommandType

package com.example.demo;

public enum CommandType {
    A_COMMAND, C_COMMAND, L_COMMAND, UNKNOWN;

    public static CommandType type(String cmd) {
        if (cmd.startsWith("@")) {
            return CommandType.A_COMMAND;
        } else if (cmd.startsWith("(")) {
            return CommandType.L_COMMAND;
        } else if (cmd.contains(";") || cmd.contains("=")) {
            return CommandType.C_COMMAND;
        } else {
            return CommandType.UNKNOWN;
        }
    }

    public boolean isAorC() {
        return this == A_COMMAND || this == C_COMMAND;
    }
}

Parser

package com.example.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Parser {
    Scanner scanner;
    String currentCommand;
    int address = 0;
    List<String> validInstructionSet;

    public Parser(File file) throws FileNotFoundException {
        this.scanner = new Scanner(new FileInputStream(file));
        validInstructionSet = new ArrayList<>();
    }

    public boolean hasMoreCommands() {
        return this.scanner.hasNextLine();
    }

    public void advance() {
        if (hasMoreCommands()) {
            /* when we don't get the command, loop*/
            do {
                this.currentCommand = this.scanner.nextLine();
            } while (!getCommand());
            if (this.commandType().isAorC()) {
                this.address++;
            }
            validInstructionSet.add(this.currentCommand);
        } else {
            this.scanner.close();
        }
    }

    private boolean getCommand() {
        String stringLine = this.currentCommand.trim();
        /* remove the space line*/
        if (stringLine.equals("")) {
            return false;
        }
        /* Remove the comment line*/
        if (stringLine.startsWith("//")) {
            return false;
        }
        /* Throw the comment away*/
        int index = stringLine.indexOf("//");
        if (index != -1) {
            this.currentCommand = stringLine.substring(0, index).trim();
            return true;
        }
        this.currentCommand = stringLine.trim();
        return true;
    }

    public CommandType commandType() {
        return CommandType.type(this.currentCommand);
    }

    public boolean isDigit(String symbol) {
        return symbol.chars().allMatch(Character::isDigit);
    }

    public String paddingZero(String str) {
        StringBuilder builder = new StringBuilder(str);
        while (builder.length() <= 15) {
            builder.insert(0, "0");
        }
        return builder.toString();
    }

    public String symbol() {
        String subStr = null;
        if (commandType().equals(CommandType.A_COMMAND)) {
            subStr = this.currentCommand.substring(1);
        }
        if (commandType().equals(CommandType.L_COMMAND)) {
            subStr = this.currentCommand.substring(1, this.currentCommand.length() - 1);
        }
        if (subStr == null) {
            return null;
        }
        if (isDigit(subStr)) {
            long addr = Long.parseLong(subStr);
            if (addr < 0 || addr > 32767) {// 2^15 - 1

            }
            String addressStr = Long.toBinaryString(address);
            return paddingZero(addressStr);
        } else {
            return subStr;
        }
    }

    public String dest() {
        if (commandType().equals(CommandType.C_COMMAND)) {
            String command = this.currentCommand;
            if (command.contains("=")) {
                return command.substring(0, command.indexOf("="));
            } else {
                return "";
            }
        }
        return null;
    }

    public String comp() {
        if (commandType().equals(CommandType.C_COMMAND)) {
            String command = this.currentCommand;
            if (command.contains("=") && command.contains(";")) {
                return command.substring(command.indexOf("=") + 1, command.indexOf(";"));
            } else if (command.contains(";")) {
                return command.substring(0, command.indexOf(";"));
            } else if (command.contains("=")) {
                return command.substring(command.indexOf("=") + 1);
            } else {
                return "";
            }
        }
        return null;
    }

    public String jump() {
        if (commandType().equals(CommandType.C_COMMAND)) {
            String command = this.currentCommand;
            if (command.contains(";")) {
                return command.substring(command.indexOf(";") + 1);
            } else {
                return "";
            }
        }
        return null;
    }
}

Assembler

package com.example.demo;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

import static com.example.demo.CommandType.L_COMMAND;

public class Assembler {
    /**
     * store compiling file
     */
    File file;
    /**
     * store the compiled file
     */
    File binFile;
    Parser parser;
    Code code;
    SymbolTable symbolTable;
    int freeVarAddress = 16;

    /**
     * the stream to read/write the program file
     */
    BufferedWriter writer;

    /**
     * we think that one file has one symbol table, so we initialize the symbol table in the constructor
     */
    public Assembler(File file) throws FileNotFoundException {
        this.file = file;
        String fileName = file.getName();
        String binFilePath = file.getParent() + "\\" + fileName.substring(0, fileName.indexOf(".")) + ".hack";
        this.writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(binFilePath)));
        this.code = new Code();
        this.symbolTable = new SymbolTable();
    }

    /**
     * the first scan of the file
     * in this procedure, we collect the symbol of l-command and add them to symbol table.
     *
     * @throws FileNotFoundException
     */
    public void firstScanFile() throws FileNotFoundException {
        this.parser = new Parser(this.file);
        do {
            parser.advance();
            CommandType commandType = parser.commandType();
            if (commandType == L_COMMAND) {
                String subL = parser.currentCommand.substring(1, parser.currentCommand.length() - 1);
                if (!parser.isDigit(subL)) {
                    this.symbolTable.addEntry(subL, parser.address);
                }
            }
        } while (parser.hasMoreCommands());
    }

    public void secondScanFile() throws IOException {
        for (String validInstruction : parser.validInstructionSet) {
            parser.currentCommand = validInstruction;
            CommandType commandType = parser.commandType();
            switch (commandType) {
                case A_COMMAND:
                    String subStr = parser.currentCommand.substring(1);
                    if (parser.isDigit(subStr)) {
                        System.out.println(parser.currentCommand + "\t\t" + parser.symbol());
                        writer.write(parser.symbol() + "\n");
                    } else {
                        int address = 0;
                        if (this.symbolTable.contains(subStr)) {
                            address = this.symbolTable.getAddress(subStr);
                        } else {
                            this.symbolTable.addEntry(subStr, this.freeVarAddress);
                            address = this.freeVarAddress++;
                        }

                        String addressStr = parser.paddingZero(Integer.toBinaryString(address));
                        System.out.println(parser.currentCommand + "\t\t" + addressStr);
                        writer.write(addressStr + "\n");
                    }
                    break;
                case C_COMMAND:
                    System.out.print(parser.currentCommand + "\t\t");
                    String dest = code.dest(parser.dest());
                    String comp = code.comp(parser.comp());
                    String jump = code.jump(parser.jump());
                    String binCmd = "111" + comp + dest + jump;
                    System.out.println(binCmd);
                    writer.write(binCmd + "\n");
                    break;
                case L_COMMAND:
                    System.out.println(parser.currentCommand);
                    break;
                default:
                    break;
            }
        }

        this.writer.flush();
    }
}

总结

编写了一个汇编器。

附录

posted @ 2025-10-30 17:48  jiangbo4444  阅读(1)  评论(0)    收藏  举报