一、前言
项目来源:
该项目是著名课程Nand2Tetris的课程项目,总共分12部分,从零开始构建属于自己的hack计算机。该文项目属于第六个子项目。
项目路线介绍:
在硬件部分,你将进入 01 的世界,用与非门构造出逻辑电路,并逐步搭建出一个 CPU 来运行一套课程作者定义的简易汇编代码。在软件部分,你将编写一个编译器,将作者开发的一个名为Jack的高级语言编译为可以运行在虚拟机上的字节码,然后进一步翻译为汇编代码。你还将开发一个简易的 OS,让你的计算机支持输入输出图形界面。至此,你可以用 Jack 开发一个俄罗斯方块的小游戏,将它编译为汇编代码,运行在你用与非门搭建出的 CPU 上,通过你开发的 OS 进行交互。
二、项目介绍
本章的目标是用高级语言写出一个汇编器,我使用的是Java(并且实现很幼稚),这个Hack语言的汇编器并不难,因为Hack语言里唯一需要动态构建的只有A语句,而A语句的结构又很简单——@+数字/符号
,所以各位可以放心大胆地去试试,相信你可以做得比我好的
目标:
- HackAssembler
要求:推荐使用一门高级语言,来完成一个可以读取.asm文件,并将其翻译成.hack文件的汇编器程序
细节:
-
推荐使用书里面的分成四个模块来实现
- Parser(语法分析器模块):本模块主要做的就是对命令进行获取,进行类型判断,获取符号(预定义符号,标记命令,变量)
- Code(编码模块):将Hack汇编语言助记符翻译成二进制码。
- 符号表模块:符号表用于建立和维持符号与其地址之间的关联。常用的有哈希表。
- 无符号程序的汇编编译器:这种情况意味着不用处理符号,所以只有两个阶段,第一阶段翻译无符号汇编程序,然后将其扩展成具有符号处理能力的汇编编译器。
- 有符号程序的汇编编译器:使用两次读取代码的方式来实现,第一遍构建符号表,但不翻译程序;第二遍翻译程序,并把符号变成地址。
-
个人对于这个汇编器的理解是:读入语言->解析语言->语句分类->分别处理->封装汇编后语句
-
还有一个值得注意的点就是对于注释语句的处理,可以说每个语句都要进行注释语句的判断和之后的改装,还有空格的处理,因为处理频繁,建议独立出一个处理方法处理
三、项目展示
1.程序结构图
2.实现
2.1.Parser
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
/*
* 这是hack汇编器的解析器,负责对命令进行提取和对命令进行分类和拆解方便逐个处理
须实现功能如下功能如下:
1.通过给定的文件名创建一条输入流
2.能够判断流后面是否还有命令
3.在得知还存在命令后,读入一条命令,存进“当前命令”变量中
4.对当前命令进行分类主要有三大分类:
1.A_COMMAND
2.C_COMMAND
3,L_COMMAND
5.返回当前A命令或者伪命令后的数字或者符号
6.返回当前C命令的dest字符串
7.返回当前C命令的comp字符串
8.返回当前C命令的jump字符串
*/
public class Parser implements CommandInterface{
String nowCommad;
BufferedReader asmReader;
public Parser(File file) {
try {
asmReader = new BufferedReader(new FileReader(file) );
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean HasCommand() {
boolean tag=false;
try {
String line = asmReader.readLine();
if (line != null) {
nowCommad = line;
tag = true;
}
} catch (IOException e) {
e.printStackTrace();
}
return tag;
}
public int typeOfCommand() {
String command = nowCommad;
command=filtration(command);
if (command.contains("(")) {
return L_COMMAND;
}else if (command.contains("@")) {
return A_COMMAND;
}else if(command.equals("")){
return NO_COMMAND;
}
return C_COMMAND;
}
public String getACommand() {
String command = filtration(nowCommad);
return command.substring(1);
}
public String getLabel() {
String label;
String command = filtration(nowCommad);
int start = command.indexOf("(");
int end = command.indexOf(")");
label = command.substring(start+1, end);
return label.trim();
}
public String getDest() {
String dest = "null";
String command = filtration(nowCommad);
if (command.contains("=")) {
int one = command.indexOf('=');
dest = command.substring(0, one);
}
return dest.trim();
}
public String getJump() {
String jump = "null";
String command = filtration(nowCommad);
if (command.contains(";")) {
int one = command.indexOf(';');
jump = command.substring(one+1);
}
return jump.trim();
}
public String getComp() {
int end;
int start;
String comp = "0";
String command = filtration(nowCommad);
if (command.contains("=")&&command.contains(";")) {
start = command.indexOf("=");
end = command.indexOf(";");
comp = command.substring(start+1,end);
}else if(command.contains(";")) {
end = command.indexOf(";");
comp = command.substring(0,end);
}else if (command.contains("=")) {
start = command.indexOf("=");
comp = command.substring(start+1);
}else {
comp = command;
}
return comp.trim();
}
public void close() {
try {
asmReader.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
// public static void main(String[] args) {
//
// Parser parser = new Parser(new File("C:\\Users\\LinKa\\Desktop\\nand2tetris\\projects\\06\\max\\Max.asm"));
// parser.nowCommad = "D=M";
// System.out.println(parser.getComp());
// }
}
2.2.Code
import java.util.HashMap;
import java.util.Map;
/*
这是hack汇编器中真正进行翻译的模块,可以根据各种命令字符串给出其机器命令
1.根据dest字符串参数返回其机器码
2.根据comp字符串参数返回其机器码
4.根据jump字符串参数返回其机器码
5.将十进制字符串转换成2进制字符串并返回
*/
public class Code {
Map<String,String> destTable = new HashMap<>();
Map<String,String> compTable = new HashMap<>();
Map<String, String> jumpTable = new HashMap<>();
//构造函数初始化翻译表
public Code() {
destTable.put("null", "000");
destTable.put("M", "001");
destTable.put("D", "010");
destTable.put("MD", "011");
destTable.put("A", "100");
destTable.put("AM", "101");
destTable.put("AD", "110");
destTable.put("AMD", "111");
jumpTable.put("null", "000");
jumpTable.put("JGT", "001");
jumpTable.put("JEQ", "010");
jumpTable.put("JGE", "011");
jumpTable.put("JLT", "100");
jumpTable.put("JNE", "101");
jumpTable.put("JLE", "110");
jumpTable.put("JMP", "111");
compTable.put("0", "0101010");
compTable.put("1", "0111111");
compTable.put("-1", "0111010");
compTable.put("D", "0001100");
compTable.put("A", "0110000");
compTable.put("!D", "0001101");
compTable.put("!A", "0110001");
compTable.put("-D", "0001111");
compTable.put("-A", "0110011");
compTable.put("D+1", "0011111");
compTable.put("A+1", "0110111");
compTable.put("D-1", "0001110");
compTable.put("A-1", "0110010");
compTable.put("D+A", "0000010");
compTable.put("D-A", "0010011");
compTable.put("A-D", "0000111");
compTable.put("D&A", "0000000");
compTable.put("D|A", "0010101");
compTable.put("M", "1110000");
compTable.put("!M", "1110001");
compTable.put("M+1", "1110111");
compTable.put("M-1", "1110010");
compTable.put("D+M", "1000010");
compTable.put("D-M", "1010011");
compTable.put("M-D", "1000111");
compTable.put("D&M", "1000000");
compTable.put("D|M", "1010101");
}
public String destCode(String dest) {
String code = destTable.get(dest);
return code;
}
public String jumpCode(String jump) {
String code = jumpTable.get(jump);
return code;
}
public String compCode(String comp) {
String code = compTable.get(comp);
return code;
}
public String decimalToBinay(String decimal) {
int decimalNumber = Integer.parseInt(decimal);
String binay = Integer.toString(decimalNumber, 2);
while (binay.length()<16) {
binay = "0"+binay;
}
return binay;
}
}
2.3.Assembler
import java.awt.image.SampleModel;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.util.Scanner;
//这是Hack汇编器的入口,也就是程序的main模块,在此模块要实现的事情如下:
//1.获得汇编程序的名字
//2.通过获得名字创建Parser对象和一个写入文件流对象
//3.创建一个Code对象,方便翻译使用
//4.创建一个SymbolTable,初始化符号表
//5.进行第一遍预汇编,将标签连同其地址添加进符号表
//6.进行第二遍汇编,通过解析器(Paser)获得命令,通过code类获得汇编成的机器指令,并将机器指令写进.hack文件(注意每行后面写换行)
//7.完成编译
public class Assembler implements CommandInterface {
public static void main(String[] args) {
int preCount=0;
int variableAddress = 16;
Code code = new Code();
SymbolTable table = new SymbolTable();
String adderssString = "磁盘根目录\\nand2tetris\\projects\\06";
Scanner scanner = new Scanner(System.in);
File asmFile;
do {
System.out.println("请输入正确文件:");
String fileName = scanner.next();
fileName = adderssString + fileName;
asmFile = new File(fileName);
} while (!asmFile.exists());
System.out.println("请输入目标文件名字及上一级目录:(如:\\add\\add.hack)");
String hackFile = scanner.next();
FileOutputStream fos;
BufferedWriter fileWriter = null;
try {
fos = new FileOutputStream(adderssString+hackFile);
fileWriter = new BufferedWriter(new OutputStreamWriter(fos));
} catch (FileNotFoundException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println("进入预汇编。");
Parser preParser = new Parser(asmFile);
Parser parser = new Parser(asmFile);
while (preParser.HasCommand()) {
if (preParser.typeOfCommand()==L_COMMAND) {
String label = preParser.getLabel();
table.put(label, Integer.toString(preCount));
}
if (preParser.typeOfCommand()==A_COMMAND||preParser.typeOfCommand()==C_COMMAND) {
preCount++;
}
}
System.out.println("预汇编结束,开始汇编。");
preParser.close();
while (parser.HasCommand()) {
if (parser.typeOfCommand()==C_COMMAND) {
String dest = parser.getDest();
String comp = parser.getComp();
String jump = parser.getJump();
String destCode = code.destCode(dest);
String compCode = code.compCode(comp);
String jumpCode = code.jumpCode(jump);
String C_Code = "111"+compCode+destCode+jumpCode;
try {
fileWriter.write(C_Code);
fileWriter.newLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}else if (parser.typeOfCommand()==A_COMMAND) {
String A_Code;
boolean tag = true;
String symbol = parser.getACommand();
for ( byte c : symbol.getBytes()) {
if(c<'0'||c>'9') {
tag = false;
}
}
if (tag) {
A_Code = code.decimalToBinay(symbol);
}else {
String address;
if (table.isHas(symbol)) {
address = table.getAddress(symbol);
}else {
table.put(symbol, Integer.toString(variableAddress));
address = table.getAddress(symbol);
variableAddress++;
}
A_Code = code.decimalToBinay(address);
}
try {
fileWriter.write(A_Code);
fileWriter.newLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
System.out.println("汇编完成");
parser.close();
try {
fileWriter.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
2.4.SymbolTable
/*
这是hack汇编器的符号表,存储<符号,数字>单元,实现用map
功能如下:
1.创建一个空表并进行预定义的初始化
2.将<symbol,address>添加进表
3.查看表中是否存在某个符号
4.返回以目的symbol为键的相关联的地址*/
public class SymbolTable {
Map<String,String> table = new HashMap<>();
public SymbolTable() {
table.put("SP", "0");
table.put("LCL", "1");
table.put("ARG", "2");
table.put("THIS", "3");
table.put("THAT", "4");
table.put("R0", "0");
table.put("R1", "1");
table.put("R2", "2");
table.put("R3", "3");
table.put("R4", "4");
table.put("R5", "5");
table.put("R6", "6");
table.put("R7", "7");
table.put("R8", "8");
table.put("R9", "9");
table.put("R10", "10");
table.put("R11", "11");
table.put("R12", "12");
table.put("R13", "13");
table.put("R14", "14");
table.put("R15", "15");
table.put("SCREEN", "16384");
table.put("KBD", "24576");
}
public boolean isHas(String symbol) {
return table.containsKey(symbol);
}
public void put(String symbol, String address) {
table.put(symbol, address);
}
public String getAddress(String symbol) {
return table.get(symbol);
}
}
2.5.CommandInterface
public interface CommandInterface {
int A_COMMAND = 1;
int C_COMMAND = 2;
int L_COMMAND = 3;
int NO_COMMAND = 4;
default String filtration(String commandLine) {
String command = commandLine;
command=command.trim();
if (command.contains("//")) {
int one = command.indexOf('/');
command = command.substring(0,one);
}
return command;
}
}
参考文章:https://blog.csdn.net/qq_41634283/article/details/104044354