Java VMTranslator Part I

目录

堆栈运算命令

基本思路

核心代码

Parser

[Code Writer](#Code Writer)

Main

实验结果,使用SimpleAdd、StackTest进行验证

内存访问命令

基本思路

核心代码

Parser

[Code Writer](#Code Writer)

Main

实验结果,使用进行验证。对比生成的二进制代码文件。


用Java写一个翻译器,将Java的字节码翻译成汇编语言

堆栈运算命令

基本思路

主要写两个类,一个解析器类Parser负责处理输入的vm文件,解析vm指令,一个类CodeWriter负责将经过Parser解析过的vm指令翻译成汇编指令,输出asm文件。

首先编写类Parser,有六个成员函数,包含构造函数负责打开文件流并准备读取vm指令,hasMoreCommands函数判断是否还有指令,advance函数负责将vm指令处理干净,去掉空白和注释,commandType函数判断vm指令的类型,arg1和arg2函数负责返回指令的组成部分。

然后编写CodeWriter类,构造函数打开一个文件,准备写入asm指令,writeArithmetic函数写入算术逻辑运算asm指令,writerPushPop函数写入push和pop的asm指令。

然后最关键的地方来了,如何从vm指令到asm指令?

我们首先从算术逻辑运算指令来看,以二元运算为例,计算的两个数是放在栈上的,位于栈指针SP上面两个位置,而我们只有M和D两个寄存器可以用来计算,在A寄存器保存栈指针地址的情况下。

因此,对于所有二元运算,我们首先要把参与计算的两个数放在M和D寄存器上,具体操作是,栈指针SP自减,把M的值赋给D,然后再将栈指针上移。

然后再执行二元运算,这样就比较简单了。

对于一元运算比较简单,直接栈指针自减,对M进行操作即可。

而对于gt、eq和lt这样比较指令,则比较复杂,因为涉及到跳转,同样是二元运算,因此我们需要先按照上述方法先将参与计算的两个数拿出来放在M和D寄存器,然后计算M和D的差,通过比较差和0的大小来跳转。

而对于push constant x指令,将一个常数压入栈就比较简单了。

拿到常数的值后将它写入栈指针执行的内存,然后栈指针自增就行了。

核心代码

Parser

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Objects;
import java.util.Scanner;

public class Parser {
    private String command = null;
    private Scanner scanner = null;
    private String cmd0 = null;
    private String cmd1 = null;
    private int cmd2;

    public Parser(File file) throws FileNotFoundException {
        scanner = new Scanner(new FileReader(file));
    }

    public boolean hasMoreCommands() {
        boolean hasMore = false;
        while (scanner.hasNextLine()) {
            command = scanner.nextLine();
            if (!Objects.equals(command, "") && command.charAt(0) != '/') { //去掉空白行和注释
                String[] pure = command.split("/");
                command = pure[0];
                hasMore = true;
                break;
            }
        }
        return hasMore;
    }

    public void advance() {
        String[] cmd = command.split(" ");
        cmd0 = cmd[0];
        if (cmd.length > 1) {
            cmd1 = cmd[1];
            if (cmd.length > 2) {
                cmd2 = Integer.parseInt(cmd[2]);
            }
        }
    }

    public String commandType() {
        if (Objects.equals(cmd0, "push")) {
            return "C_PUSH";
        } else {
            return "C_ARITHMETIC";
        }
    }

    public String arg1() {
        if (Objects.equals(commandType(), "C_ARITHMETIC"))
            return cmd0;
        return cmd1;
    }

    public int arg2() {
        return cmd2;
    }

    public void close(){scanner.close();}

}

Code Writer

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;

public class CodeWriter {
    private FileWriter asm = null;
    private String asmCommand;
    private final HashMap<String, String> vmToAsm = new HashMap<>();
    private int jump=0;

    public CodeWriter(File file) throws IOException {
        asm = new FileWriter(file);
        String fetch = "@SP\nM=M-1\nA=M\nD=M\nA=A-1\n";
        vmToAsm.put("add", fetch + "M=M+D\n");
        vmToAsm.put("sub", fetch + "M=M-D\n");
        vmToAsm.put("and", fetch + "M=M&D\n");
        vmToAsm.put("or", fetch + "M=M|D\n");
        vmToAsm.put("gt", fetch + "D=M-D\n@TRUE\nD;JGT\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
        vmToAsm.put("eq", fetch + "D=M-D\n@TRUE\nD;JEQ\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
        vmToAsm.put("lt", fetch + "D=M-D\n@TRUE\nD;JLT\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
        vmToAsm.put("neg", "D=0\n@SP\nA=M-1\nM=D-M\n");
        vmToAsm.put("not", "@SP\nA=M-1\nM=!M\n");
    }

    public void writeArithmetic(String vmCommand) throws IOException {
        asmCommand=vmToAsm.get(vmCommand);
        if(Objects.equals(vmCommand, "gt") || Objects.equals(vmCommand, "eq") || Objects.equals(vmCommand, "lt")){
            asmCommand=asmCommand.replaceAll("TRUE","TRUE"+Integer.toString(jump));
            asmCommand=asmCommand.replaceAll("END","END"+Integer.toString(jump));
            jump++;
        }
        asm.write(asmCommand);
    }

    public void writePushPop(String cmd, String segment, int index) throws IOException {
        if (Objects.equals(cmd, "C_PUSH")) {
            if (Objects.equals(segment, "constant")) {
                asmCommand = "@" + Integer.toString(index) + "\nD=A\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            }
        }
        asm.write(asmCommand);
    }

    public void close() throws IOException {
        asm.close();
    }
}

Main

import java.io.File;
import java.io.IOException;
import java.util.Objects;

public class Main {
    public static void main(String[] args) throws IOException {
        Parser parser=new Parser(new File("C:\\Users\\Yezi\\Desktop\\Java程序设计\\nand2tetris\\projects\\07\\StackArithmetic\\StackTest\\StackTest.vm"));
        CodeWriter codeWriter=new CodeWriter(new File("C:\\Users\\Yezi\\Desktop\\Java程序设计\\nand2tetris\\projects\\07\\StackArithmetic\\StackTest\\StackTest.asm"));
        while(parser.hasMoreCommands()){
            parser.advance();
            if(Objects.equals(parser.commandType(), "C_ARITHMETIC")){
                codeWriter.writeArithmetic(parser.arg1());
            }else{
                codeWriter.writePushPop(parser.commandType(), parser.arg1(), parser.arg2());
            }
        }
        codeWriter.close();
        parser.close();
    }
}

实验结果,使用SimpleAdd、StackTest进行验证

我们用CPU Emulator装载.tst文件,用运行程序得到的.out文件和所给的.cmp文件进行比较,其中SimpleAdd比较结果如下图所示,可见成功翻译

StackTest的结果如下图所示,可见第一阶段翻译成功。

内存访问命令

基本思路

首先要搞明白的是,push操作是将内存上的数值压入栈中,而pop操作是将栈中的数值弹出来到内存中。

对于constant段,我们第一阶段已经解决了。

对于local、argument、this和that字段,就是从它们相应的内存地址上读取或写入数据。

Push的话,先拿到segment+i的地址所指向的数值,然后将这个数值压入栈中,栈指针自增。

Pop的话,要复杂一些,因为我们只有A、M和D寄存器可以用,而pop我们首先要拿到segment+i的地址,所以我们要先找一个地方存下来,原本的R系列寄存器在这里已经被字段占用了,所以我们这里取地址255的内存空间暂存一下地址。

而temp字段的push和pop操作相对而言要简单许多。

此时读写的地址为5+i。

对于pointer字段,其实就是把this和that的数值压入栈或者弹栈的数值到this和that中。

当参数为0时,对this进行操作,当参数为1时,对that进行操作,在this和that的地址上进行读写数据。

而对于static字段,与前面的字段相比,不过就是换了运算的地址空间而已。

Asm代码基本和前面的操作一样,就是运算的地址变成了16开始的地址。

核心代码

Parser

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Objects;
import java.util.Scanner;

public class Parser {
    private String command = null;
    private final Scanner scanner;
    private String cmd0 = null;
    private String cmd1 = null;
    private int cmd2;

    public Parser(File file) throws FileNotFoundException {
        scanner = new Scanner(new FileReader(file));
    }

    public boolean hasMoreCommands() {
        boolean hasMore = false;
        while (scanner.hasNextLine()) {
            command = scanner.nextLine();
            if (!Objects.equals(command, "") && command.charAt(0) != '/') { //去掉空白行和注释
                String[] pure = command.split("/");
                command = pure[0];
                hasMore = true;
                break;
            }
        }
        return hasMore;
    }

    public void advance() {
        String[] cmd = command.split(" ");
        cmd0 = cmd[0];
        if (cmd.length > 1) {
            cmd1 = cmd[1];
            if (cmd.length > 2) {
                cmd2 = Integer.parseInt(cmd[2]);
            }
        }
    }

    public String commandType() {
        if (Objects.equals(cmd0, "push")) {
            return "C_PUSH";
        } else if (Objects.equals(cmd0, "pop")) {
            return "C_POP";
        } else {
            return "C_ARITHMETIC";
        }
    }

    public String arg1() {
        if (Objects.equals(commandType(), "C_ARITHMETIC"))
            return cmd0;
        return cmd1;
    }

    public int arg2() {
        return cmd2;
    }

    public void close() {
        scanner.close();
    }

}

Code Writer

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;

public class CodeWriter {
    private final FileWriter asm;
    private String asmCommand;
    private final HashMap<String, String> vmToAsm = new HashMap<>();
    private int jump = 0;

    public CodeWriter(File file) throws IOException {
        asm = new FileWriter(file);
        String fetch = "@SP\nM=M-1\nA=M\nD=M\nA=A-1\n";
        vmToAsm.put("add", fetch + "M=M+D\n");
        vmToAsm.put("sub", fetch + "M=M-D\n");
        vmToAsm.put("and", fetch + "M=M&D\n");
        vmToAsm.put("or", fetch + "M=M|D\n");
        vmToAsm.put("gt", fetch + "D=M-D\n@TRUE\nD;JGT\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
        vmToAsm.put("eq", fetch + "D=M-D\n@TRUE\nD;JEQ\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
        vmToAsm.put("lt", fetch + "D=M-D\n@TRUE\nD;JLT\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
        vmToAsm.put("neg", "D=0\n@SP\nA=M-1\nM=D-M\n");
        vmToAsm.put("not", "@SP\nA=M-1\nM=!M\n");
    }

    public void writeArithmetic(String vmCommand) throws IOException {
        asmCommand = vmToAsm.get(vmCommand);
        if (Objects.equals(vmCommand, "gt") || Objects.equals(vmCommand, "eq") || Objects.equals(vmCommand, "lt")) {
            asmCommand = asmCommand.replaceAll("TRUE", "TRUE" + jump);
            asmCommand = asmCommand.replaceAll("END", "END" + jump);
            jump++;
        }
        asm.write(asmCommand);
    }

    public void writePushPop(String cmd, String segment, int index) throws IOException {
        if (Objects.equals(cmd, "C_PUSH")) {
            if (Objects.equals(segment, "constant")) {
                asmCommand = "@" + index + "\nD=A\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            } else if (Objects.equals(segment, "local")) {
                asmCommand = "@LCL\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            } else if (Objects.equals(segment, "argument")) {
                asmCommand = "@ARG\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            } else if (Objects.equals(segment, "this")) {
                asmCommand = "@THIS\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            } else if (Objects.equals(segment, "that")) {
                asmCommand = "@THAT\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            } else if (Objects.equals(segment, "temp")) {
                asmCommand = "@" + (5 + index) + "\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            } else if (Objects.equals(segment, "pointer")) {
                if (index == 0) {
                    asmCommand = "@THIS\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
                } else {
                    asmCommand = "@THAT\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
                }
            } else if (Objects.equals(segment, "static")) {
                asmCommand = "@" + (16 + index) + "\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
            }
        } else {
            if (Objects.equals(segment, "local")) {
                asmCommand = "@LCL\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
            } else if (Objects.equals(segment, "argument")) {
                asmCommand = "@ARG\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
            } else if (Objects.equals(segment, "this")) {
                asmCommand = "@THIS\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
            } else if (Objects.equals(segment, "that")) {
                asmCommand = "@THAT\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
            } else if (Objects.equals(segment, "temp")) {
                asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@" + (5 + index) + "\nM=D\n";
            } else if (Objects.equals(segment, "pointer")) {
                if (index == 0) {
                    asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@THIS\nM=D\n";
                } else {
                    asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@THAT\nM=D\n";
                }
            } else if (Objects.equals(segment, "static")) {
                asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@" + (16 + index) + "\nM=D\n";
            }
        }
        asm.write(asmCommand);
    }

    public void close() throws IOException {
        asm.close();
    }
}

Main

main函数没变

实验结果,使用进行验证。对比生成的二进制代码文件。

我们用CPU Emulator装载.tst文件,用运行程序得到的.out文件和所给的.cmp文件进行比较,其中BasicTest的比较结果如下图所示,可见成功翻译。

PointerTest的比较结果如下图所示,可见成功翻译

StaticTest的结果如下图所示,可见第二阶段翻译成功。

相关推荐
Mr_Xuhhh4 分钟前
递归搜索与回溯算法--递归(2)
开发语言·数据结构·c++·算法·链表·深度优先
最笨的羊羊37 分钟前
日常分享系列之:学习mysql-binlog-connector-java
java·mysql·binlog·日常分享系列·connector
ClinEvol1 小时前
JAVA后端生成图片滑块验证码 springboot+js完整案例
java·javascript·spring boot·滑块验证码
----云烟----1 小时前
Eclipse下载与安装
java·ide·eclipse
上趣工作室2 小时前
uniapp中使用全局样式文件引入的三种方式
开发语言·rust·uni-app
提笔惊蚂蚁2 小时前
java-web-苍穹外卖-day1:软件开发步骤简化版+后端环境搭建
java·开发语言·前端·程序人生
Ethan Hunt丶2 小时前
C语言实现IIR型零相位带通滤波器
c语言·开发语言·算法
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS美发门店管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·kafka·开源
Slow菜鸟2 小时前
SpringBoot教程(二十五) | SpringBoot配置多个数据源
java·spring boot·后端
孑么2 小时前
力扣 最小路径和
java·算法·leetcode·职场和发展