Nand2Tetris——project08

一、前言

项目来源:

该项目是著名课程Nand2Tetris的课程项目,总共分12部分,从零开始构建属于自己的hack计算机。该文项目属于第八个子项目。

项目路线介绍:

在硬件部分,你将进入 01 的世界,用与非门构造出逻辑电路,并逐步搭建出一个 CPU 来运行一套课程作者定义的简易汇编代码。在软件部分,你将编写一个编译器,将作者开发的一个名为Jack的高级语言编译为可以运行在虚拟机上的字节码,然后进一步翻译为汇编代码。你还将开发一个简易的 OS,让你的计算机支持输入输出图形界面。至此,你可以用 Jack 开发一个俄罗斯方块的小游戏,将它编译为汇编代码,运行在你用与非门搭建出的 CPU 上,通过你开发的 OS 进行交互。

二、项目介绍

1.涉及知识

疑问:

在学习完第八章后我们知道,我们的VM机要有可以进行跨存储域进行函数调用的能力,即这个文件内的函数有权调用存储在别的文件里面的函数(所有函数的访问权限都是支持不同模块调用的,类似Java里的public),这其实是仅仅我们在VM汇编器上无法解决的,单单不说个返回值问题,仅仅在调用上就无法实现,以为汇编器是针对单个.asm文件,如果这个.asm文件里面只有一个模块的细节,那我们也无法将之与其他模块进行联结,所以本章中一个对于翻译的处理细节是把多个vm文件翻译成一整个的.asm文件,所有的函数细节都在这里面,这样的效果就是对函数调用进行行为拆解了——函数调用=修改存储结构+命令跳转+恢复存储结构+命令跳转。

项目理解:

第八章是在第七章的基础上完成的,所以我们大可以以7章的项目框架来进行增补操作,来完成第八章要求的程序控制流命令和子函数调用命令翻译工作,像前面说的那样,第八章完成后要进行多个vm文件翻译成一个asm文件,所以翻译器的输入输出结构不再是一个vm文件对应一个asm文件,而是一至多个vm文件对应一个asm文件,这就要求我们对主类(VmTranslator类进行大修改),以及CodeWriter类的输出文件进行协调成一个通用的文件。

2.实践要求

目标:

  • VMTranslator的后半段
    要求:推荐使用一门高级语言,来完成一个可以读取一至多个.vm文件,并将其翻译成一个.asm文件的VM翻译程序
    细节:
  1. 推荐使用书里面的分成三个模块来实现(可以把CommandType模块独立出去)
    1. Parser:分析.vm文件,封装输入代码的访问,其功能有二:1.解析命令的类型;2.对命令进行洁净化处理
    2. CodeWriter:翻译.vm文件,封装输出代码的通道
    3. VMTranslator:主程序,程序的入口
  2. 个人对于这个翻译器的理解是:加强版HackAssembler,它的工作模式依旧是:获取命令和命令类型->根据类型对命令进行不一样的翻译工作,不一样的点是汇编器的翻译工作相近性高,而这个翻译器低一点,比如:一个Hack命令对应一条01命令,而一个VM命令可能需要几条hack命令来达成。
  3. 流程控制命令翻译:流程控制语句说到底是高级一点的跳转语句,在hack汇编语言中有很多的基于D寄存器可用的跳转语句,像0;JMP就是无条件跳转,但是我们需要考虑的是怎么跳转?依据标签跳转。标签规范如何?文件名$函数名。依次我们把流程控制语句分为三部分实现(vm语言的流程控制命令也有三种类型)
    1. writeLabel:翻译标签语句
    2. writeGoto:翻译无条件跳转语句
    3. writeIf:翻译条件跳转语句
  4. 子函数调用命令翻译:这部分的翻译是该项目最难的部分,因为我们在这一部分中不仅要兼顾于函数调用和返回,还要把函数这一概念引入我们的翻译机中,否则按照我们实现至此的功能,我们的翻译机是无法识别函数的,所以我们还要处理FUNCTION命令,其中我们不仅仅是直接翻译函数名,我们还要给函数名一个通用的标签,以便实现基于标签跳转的函数调用,所以在此我们也把子函数调用功能分四部分实现
    1. writeFunction:翻译函数声明命令
    2. writeCall:翻译函数调用命令
    3. writeReturn:翻译函数返回命令
    4. writeSave

三、项目展示

1.程序结构图

在这里插入图片描述

2.2.CodeWriter

/**   
* 此堆栈用于存放调用的函数. <br/>  
 * 因为标签在不同的函数中需要不同的函数名来区分。  
 */  
private Stack<String> callStack;


/**   
* 构造函数,初始化编写器。<br>  
 * 由于一个代码编写器面向一个文件目录,我们需要判断文件参数 <br>  
 * 是否为 file。但在以后的应用程序中,这通常是目录。  
 */
public CodeWriter(File file) throws FileNotFoundException {  
   this.callStack = new Stack<>();  
   if(file.isFile()) {  
      this.setFile(file);  
      // 首先将文件名推入堆栈,以避免空堆栈  
      this.callStack.push(file.getName());  
      setFileName(file.getPath());  
      this.writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile)));  
   }else {  
      // 首先将文件名推入堆栈,以避免空堆栈  
      this.callStack.push(file.getName());  
      String outputFilePath = file.getPath()+"\\"+file.getName()+".asm";  
      File oldFile = new File(outputFilePath);  
      if(oldFile.exists()) {  
         oldFile.delete();  
      }  
      this.outputFile = new File(outputFilePath);  
      this.writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile)));  
   }  
}

/**  
    * 编写初始化代码。  
    * 事实上,只有在解析 Sys.vm 文件时才调用这个函数。  
    * 并且这个方法应该由 writeFunctionmethod 调用。  
    * 如果函数名是 Sys.init,writeFunctionmethod 就调用这个方法。  
    * @throws IOException   
*/  
   private void writeInit() throws IOException {  
//    this.writer.write("// initialize sp=256\r\n"+  
//                   "@256\r\n" + //                   "D=A\r\n" + //                   "@SP\r\n" + //                   "M=D\r\n");  
      this.writer.write("// Sys.init function start\r\n" +  
                     "(Sys.init)\r\n");  
      this.callStack.push("Sys.init");  
      this.writer.write("\r\n");  
   }  
     
   /**  
    * 翻译标签命令  
    * @throws IOException   
*/  
   public void writeLabel(Parser parser) throws IOException {  
      String arg = parser.args1();  
      this.writer.write("// vm command: "+parser.getCurrentCommand()+"\r\n");  
      this.writer.write("("+this.callStack.peek()+"$"+arg+")\r\n");  
      this.writer.write("\r\n");  
   }  
     
   public void writeGoto(Parser parser) throws IOException {  
      String arg = parser.args1();  
      this.writer.write("// vm command: "+parser.getCurrentCommand()+"\r\n");  
      this.writer.write("@"+this.callStack.peek()+"$"+arg+"\r\n"  
                  + "0;JMP\r\n");  
      this.writer.write("\r\n");  
   }  
     
   public void writeIf(Parser parser) throws IOException {  
      String arg = parser.args1();  
      this.writer.write("// vm command: "+parser.getCurrentCommand()+"\r\n");  
      getTopSP();  
      this.writer.write("@"+this.callStack.peek()+"$"+arg+"\r\n" +   
                    "D;JNE\r\n");  
      this.writer.write("\r\n");  
   }  
     
   private void saveScene() throws IOException {  
      this.writer.write("// save work\r\n" +   
                    "@"+this.callStack.peek()+"$retAddr"+i+"\r\n" +     //这是函数返回地址  
                    "D=A\r\n");  
      pushValueIntoStack("D");  
      this.writer.write("@LCL\r\n" +   
                    "D=M\r\n");  
      pushValueIntoStack("D");  
      this.writer.write("@ARG\r\n" +   
                    "D=M\r\n");   
      pushValueIntoStack("D");  
      this.writer.write("@THIS\r\n" +   
                    "D=M\r\n");   
      pushValueIntoStack("D");   
      this.writer.write("@THAT\r\n" +   
                    "D=M\r\n");   
      pushValueIntoStack("D");  
   }  
     
   public void writeCall(Parser parser) throws IOException {  
      String arg1 = parser.args1();  
      String arg2 = parser.args2();  
      /*  
      * 当程序遇到调用函数时,我们将函数名推入调用堆栈,  
      * 调用堆栈可以通过写标签方法使用。  
      * 当程序返回方法时,我们弹出函数名。*/  
      this.callStack.push(arg1+(++this.i));  
  
        
      this.writer.write("// vm command: "+parser.getCurrentCommand()+"\r\n");  
      saveScene();  
      this.writer.write("// argument process\r\n" +   
                    "@SP\r\n" +   
                    "D=M\r\n" +   
                    "@5\r\n" +   
                    "D=D-A\r\n" +   
                    "@"+arg2+"\r\n" +   
                    "D=D-A\r\n" +  
                    "@ARG\r\n" +   
                    "M=D\r\n" +  
                    "// LCL=SP\r\n" +   
                    "@SP\r\n" +   
                    "D=M\r\n" +   
                    "@LCL\r\n" +   
                    "M=D\r\n" +  
                    "// go to called function\r\n" +  
                    "@"+arg1+"\r\n" +   
                    "0;JMP\r\n" +   
                    "("+this.callStack.peek()+"$retAddr"+i+")\r\n");  
      this.writer.write("\r\n");  
   }  
     
   public void writeReturn(Parser parser) throws IOException {  
      this.writer.write("// vm command: "+parser.getCurrentCommand()+"\r\n");  
      //获取返回地址并存入R14寄存器  
      this.writer.write("@LCL\r\n" +   
                    "D=M\r\n" +   
                    "@R13\r\n" +   
                    "M=D        // temporarily store the endFrame\r\n" +  
                    "@R13\r\n" +   
                    "D=M\r\n" +   
                    "@5\r\n" +   
                    "A=D-A      // get the return address\r\n" +   
                    "D=M\r\n" +  
                    "@R14\r\n" +   
                    "M=D        // temporarily store the return address\r\n");  
      //将栈顶的值(返回值)存入以被调用函数为视角的ARG 0 内存段,作为返回值  
      //先获取ARG内存段的首地址,并存入R15寄存器  
      this.writer.write("@ARG\r\n"  
                  + "D=M\r\n"  
                  + "@0\r\n"  
                  + "D=D+A\r\n");  
      storeValueInGR("R15");  
      //获取栈顶元素并存入R15寄存器所保存的地址的块  
      getTopSP();  
      this.writer.write("// store the top value\r\n"  
                  + "@R15\r\n"  
                  + "A=M\r\n"  
                  + "M=D\r\n");  
      //把ARG段的下一个地址作为SP地址  
      this.writer.write("// set the SP\r\n" +  
                    "@ARG\r\n" +   
                    "D=M\r\n" +   
                    "@SP\r\n" +   
                    "M=D+1\r\n" +  
            //以LCL地址为基准,逐步做减一操作,获得调用者的切片并映射回去  
                    "// restore scene\r\n" +   
                    "@R13\r\n" +   
                    "D=M\r\n" +   
                    "@R15\r\n" +   
                    "M=D\r\n" +   
                    "\r\n" +   
                    "@R15\r\n" +   
                    "M=M-1\r\n" +   
                    "A=M\r\n" +   
                    "D=M\r\n" +   
                    "@THAT\r\n" +   
                    "M=D\r\n" +   
                    "\r\n" +   
                    "@R15\r\n" +   
                    "M=M-1\r\n" +   
                    "A=M\r\n" +   
                    "D=M\r\n" +   
                    "@THIS\r\n" +   
                    "M=D\r\n" +   
                    "\r\n" +   
                    "@R15\r\n" +   
                    "M=M-1\r\n" +   
                    "A=M\r\n" +   
                    "D=M\r\n" +   
                    "@ARG\r\n" +   
                    "M=D\r\n" +   
                    "\r\n" +   
                    "@R15\r\n" +   
                    "M=M-1\r\n" +   
                    "A=M\r\n" +   
                    "D=M\r\n" +   
                    "@LCL\r\n" +   
                    "M=D\r\n" +   
                    "\r\n" +   
                    "// goto return address\r\n" +  
                    "@R14\r\n" +   
                    "A=M\r\n" +   
                     "0;JMP\r\n");  
      this.writer.write("\r\n");  
   }  
     
   public void writeFunction(Parser parser) throws IOException {  
      // 获取两个参数  
      String arg1 = parser.args1();  
      String arg2 = parser.args2();  
  
      if(arg1.equals("Sys.init")) {  
         this.writer.write("// vm command: "+parser.getCurrentCommand()+"\r\n");  
         writeInit();  
      }else {  
         this.writer.write("// vm command: "+parser.getCurrentCommand()+"\r\n");  
         this.writer.write("("+arg1+")\r\n" +  
                       "// initialize local segment\r\n" +  
                       "@"+arg2+"\r\n" +   
                       "D=A\r\n" +   
                       "("+arg1+"$LOOP)\r\n" +   
                       "D=D-1\r\n" +   
                       "@"+arg1+"$END\r\n" +   
                       "D;JLT\r\n");  
         pushValueIntoStack("0");   
         this.writer.write("@"+arg1+"$LOOP\r\n" +   
                       "0;JMP\r\n" +   
                       "("+arg1+"$END)\r\n");  
         this.writer.write("\r\n");  
      }  
   }

2.3.VMTranslator


/**  
 *实际上,我们只需要构造一个代码编写器,因为输出文件是一个代码编写器。  
 * 但是解析器定位文件,换句话说,每个文件都需要一个解析器。  
 * 因此,在这个类中,每个目录都需要构造一个 VM 转换器。  
 * 所以这门课是根据这个原则设计的。  
 * @param file  
 * @throws IOException  
 */

/**  
 * 使用此函数开始编译文件。  
 * @param file  
 * @throws IOException   
*/  
public void doCompile(File file) throws IOException {  
   if(file.isFile()) {  
      this.parser = new Parser(file, new FileInputStream(file));  
   }else {  
      this.parseDirectory(file);  
   }  
}  
  
/**  
 * 阅读给定的目录。  
 * 这个方法将区分 Sys.vm 并做相关的工作。  
 * @param fileDirectory  
 *                    parsing file path  
 * @throws IOException   
*/  
private void parseDirectory(File fileDirectory) throws IOException {  
   File[] files = fileDirectory.listFiles();  
   ArrayList<File> VMFileList = new ArrayList<>();  
     
   // select the vm file from given directory  
   for(File i : files) {  
      String fileName = i.getName();  
      String laterName = fileName.substring(fileName.lastIndexOf(".")+1, fileName.length());  
      if(laterName.equals("vm")) {  
         VMFileList.add(i);  
      }else {  
         continue;  
      }  
   }  
     
   // firstly choose Sys.vm file to parse  
   for(File i : VMFileList) {  
      if(i.getName().equals("Sys.vm")) {  
         this.parseFile(i);  
         VMFileList.remove(i);  
         break;  
      }  
   }  
     
   // parse other vm file  
   for(File i : VMFileList) {  
      this.parseFile(i);  
   }  
}  
  
/**  
 * @param file the file needed to parse  
 * @throws IOException   
*/  
private void parseFile(File file) throws IOException {  
   this.parser = new Parser(file, new FileInputStream(file));  
   do {  
      this.doWrite();  
   }while(parser.hasMoreCommands());  
}  
  
/**  
 * Write assembly code. * @throws IOException  
 */  
private void doWrite() throws IOException {  
   // get a command  
   this.parser.advance();  
   CommandType commandType = this.parser.commandType();  
   if(commandType.equals(CommandType.C_ARITHMETIC)) {  
      codeWriter.writeArithmetic(this.parser);  
   }else if(commandType.equals(CommandType.C_PUSH) || commandType.equals(CommandType.C_POP)) {  
      codeWriter.writePushPop(this.parser);  
   }else if(commandType.equals(CommandType.C_CALL)) {  
      codeWriter.writeCall(this.parser);  
   }else if(commandType.equals(CommandType.C_FUNCTION)) {  
      codeWriter.writeFunction(this.parser);  
   }else if(commandType.equals(CommandType.C_GOTO)) {  
      codeWriter.writeGoto(this.parser);  
   }else if(commandType.equals(CommandType.C_IF)) {  
      codeWriter.writeIf(this.parser);  
   }else if(commandType.equals(CommandType.C_LABEL)) {  
      codeWriter.writeLabel(this.parser);  
   }else if(commandType.equals(CommandType.C_RETURN)) {  
      codeWriter.writeReturn(this.parser);  
   }else {  
      System.out.println("unknown command type!");  
   }  
   this.codeWriter.getWriter().flush();  
}
  • 26
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值