java编写编译器和解释器
|
续 第二部分 初始后端实现框架后端支持编译器和解释器。现在框架抽象类Backend有两个极简版实现,一个为编译器另一个为解释器。图2-7 展示了它们的UML类图。 图2-7 子类CodeGenerator和Executor分别是后端的编译器和解析器实现。
编译器编译器后端做代码生成。backend.compiler包中的类CodeGenerator实现框架抽象类Backend。现在它被最大简化了。清单2-20 展示了它的父类方法process的实现。方法参数引用自中间码和符号表,它产生一条消息指出生成器生成了多少条机器语言指令(目前0条,因为没有实际生成过程)以及代码生成耗用时间。它调用sendMessage()方法发送编译摘要消息。 编译器约定摘要格式为:
所有COMILER_SUMMARY消息必须遵循这种约定。 清单2-20:类CodeGenerator的初级版实现 1: /** 2: * <p>编译器的后端代码生成器</p> 3: */ 4: public class CodeGenerator extends Backend 5: {
6: public void process(ICode iCode, SymTab symTab) 7: throws Exception 8: {
9: long startTime = System.currentTimeMillis(); 10: float elapsedTime = (System.currentTimeMillis() - startTime)/1000f; 11: int instructionCount = 0; 12: 13: // 发送编译摘要消息 14: sendMessage(new Message(COMPILER_SUMMARY, 15: new Number[] {instructionCount,
16: elapsedTime})); 17: } 18: } 解释器解释器后端做程序执行。包backend.interpreter中的类Executor实现框架抽象类Backend。现在它也被最大简化了。清单2-21 展示了它的父类方法process的实现。它产生一条解释状态消息表明它执行了多少条语句,运行错误数(目前都是0)以及执行程序所耗费的时间。它调用sendMessage()发送消息。 解释器约定摘要消息格式为:
所有INTERPRETER_SUMMARY消息的监听器必须遵循此种约定。 清单2-21:Executor类process()方法的初级实现 1: /** 2: * <p>解释器后端执行器</p> 3: */ 4: public class Executor extends Backend 5: {
6: public void process(ICode iCode, SymTab symTab) 7: throws Exception 8: {
9: long startTime = System.currentTimeMillis(); 10: float elapsedTime = (System.currentTimeMillis() - startTime)/1000f; 11: int executionCount = 0; 12: int runtimeErrors = 0; 13: 14: // 发送解释摘要消息 15: sendMessage(new Message(INTERPRETER_SUMMARY, 16: new Number[] {executionCount,
17: runtimeErrors, 18: elapsedTime})); 19: } 20: } 后端工厂如前端一样,后端工厂类创建合适的后端组件。 清单2-22 工厂类BackEndFactory 1: /** 2: * <p>产生编译器或解释器后端的工厂</p> 3: */ 4: public class BackendFactory 5: {
6: /** 7: * 视参数产生编译器或解释器后端 8: * @param operation "compile"或者"execute" 9: * @return 后端组件 10: * @throws Exception if an error occurred. 11: */ 12: public static Backend createBackend(String operation) 13: throws Exception 14: {
15: if (operation.equalsIgnoreCase("compile")) {
16: return new CodeGenerator(); 17: } 18: else if (operation.equalsIgnoreCase("execute")) {
19: return new Executor(); 20: } 21: else {
22: throw new Exception("后端工厂: 不支持的操作 '" +
23: operation + "'"); 24: } 25: } 26: } 静态方法createBackend()验证标识是创建一个编译器后端("compiler")还是一个解释器后端("execute")的字符串参数。如果参数正确,方法创建且返回对应的后端组件。
目前你已搞定本章的第二和第三目标,即将初级Pascal相关组件集成进框架前端以及将编译器和解释器组件集成进后端。
程序 2:程序清单框架组件以及初级具体实现组件已就绪并集成完毕。一些简单的端对端(前端对后端)测试将验证你是否正确的设计和开发了这些组件。编译器测试将调用前端解析Pascal源程序,产生一个代码清单以及打印编译后端产生的消息。类似地,解析器测试调用前端解析Pascal源程序,产生一个代码清单以及打印解释器后端产生的消息。
清单2-23 展示了Pascal主类。这个测试背后的意思是你可以编译或执行Pascal源程序。
1: /** 2: * 3: * Pascal外壳程序,根据参数选择性的调用编译器或者解释器 4: * 5: * <p>Copyright (c) 2009 by Ronald Mak</p> 6: * <p>For instructional purposes only. No warranties.</p> 7: */ 8: public class Pascal 9: {
10: private Parser parser; // 语言无关的 parser 11: private Source source; // 语言无关的 scanner 12: private ICode iCode; // 抽象语法树 13: private SymTab symTab; // 符号表 14: private Backend backend; // 后端 15: 16: /** 17: * 编译或者解释源程序 18: * @param operation compile 或者 execute 19: * @param filePath 源文件路径 20: * @param flags 命令行参数标记 21: */ 22: public Pascal(String operation, String filePath, String flags) 23: {
24: try {
25: //显示中间码结构 26: boolean intermediate = flags.indexOf('i') > -1;
27: //显示符号引用 28: boolean xref = flags.indexOf('x') > -1;
29: 30: source = new Source(new BufferedReader(new FileReader(filePath))); 31: source.addMessageListener(new SourceMessageListener()); 32: //top-down是Parser的一种,还有一种本书没有实现的bottom-up。 33: parser = FrontendFactory.createParser("Pascal", "top-down", source);
34: parser.addMessageListener(new ParserMessageListener()); 35: 36: backend = BackendFactory.createBackend(operation); 37: backend.addMessageListener(new BackendMessageListener()); 38: 39: parser.parse(); 40: source.close(); 41: //生成中间码和符号表 42: iCode = parser.getICode(); 43: symTab = parser.getSymTab(); 44: //交由后端处理 45: backend.process(iCode, symTab); 46: } 47: catch (Exception ex) {
48: System.out.println("***** 翻译器出现错误 *****");
49: ex.printStackTrace(); 50: } 51: } 52: 53: private static final String FLAGS = "[-ix]"; 54: private static final String USAGE = 55: "使用方式: Pascal execute|compile " + FLAGS + " <源文件路径>"; 56: 57: /** 58: * 入口程序,参考Pascal构造函数的参数接受过程。<br> 59: * 例如:compile -i hello.pas 60: */ 61: public static void main(String args[]) 62: {
63: try {
64: String operation = args[0]; 65: 66: // 翻译操作类型,compile或execute 67: if (!( operation.equalsIgnoreCase("compile")
68: || operation.equalsIgnoreCase("execute"))) {
69: throw new Exception(); 70: } 71: 72: int i = 0; 73: String flags = ""; 74: 75: // 参数标识 76: while ((++i < args.length) && (args[i].charAt(0) == '-')) {
77: flags += args[i].substring(1); 78: } 79: 80: // 源文件 81: if (i < args.length) {
82: String path = args[i]; 83: new Pascal(operation, path, flags); 84: } 85: else {
86: throw new Exception(); 87: } 88: } 89: catch (Exception ex) {
90: System.out.println(USAGE); 91: } 92: } 93: 94: private static final String SOURCE_LINE_FORMAT = "%03d %s"; 95: 96: /** 97: * 源(也就是源文件)的监听器,用于监听源文件的读取情况,如果注册了监听器,源每产生一条消息比如<br> 98: * 读取了一行等,将会调用相应的监听器处理。典型的Observe模式 99: */ 100: private class SourceMessageListener implements MessageListener 101: {
102: public void messageReceived(Message message) 103: {
104: MessageType type = message.getType(); 105: Object body[] = (Object []) message.getBody(); 106: 107: switch (type) {
108: //源读取了一行 109: case SOURCE_LINE: {
110: int lineNumber = (Integer) body[0]; 111: String lineText = (String) body[1]; 112: 113: System.out.println(String.format(SOURCE_LINE_FORMAT, 114: lineNumber, lineText)); 115: break; 116: } 117: } 118: } 119: } 120: 121: private static final String PARSER_SUMMARY_FORMAT = 122: "源文件共有\t%d行。" + 123: "\n有\t%d个语法错误." + 124: "\n解析共耗费\t%.2f秒.\n"; 125: 126: /** 127: * Parser的监听器,监听来自Parser解析过程中产生的消息,还是Observe模式 128: */ 129: private class ParserMessageListener implements MessageListener 130: {
131: public void messageReceived(Message message) 132: {
133: MessageType type = message.getType(); 134: 135: switch (type) {
136: 137: case PARSER_SUMMARY: {
138: Number body[] = (Number[]) message.getBody(); 139: int statementCount = (Integer) body[0]; 140: int syntaxErrors = (Integer) body[1]; 141: float elapsedTime = (Float) body[2]; 142: System.out.println("\n----------代码解析统计信--------------");
143: System.out.printf(PARSER_SUMMARY_FORMAT, 144: statementCount, syntaxErrors, 145: elapsedTime); 146: break; 147: } 148: } 149: } 150: } 151: 152: private static final String INTERPRETER_SUMMARY_FORMAT = 153: "共执行\t%d 条语句。" + 154: "\n运行中发生了\t%d 个错误。" + 155: "\n执行共耗费\t%.2f 秒。\n"; 156: 157: private static final String COMPILER_SUMMARY_FORMAT = 158: "共生成\t\t%d 条指令" + 159: "\n代码生成共耗费\t%.2f秒\n"; 160: 161: /** 162: * Listener for back end messages. 163: */ 164: private class BackendMessageListener implements MessageListener 165: {
166: /** 167: * Called by the back end whenever it produces a message. 168: * @param message the message. 169: */ 170: public void messageReceived(Message message) 171: {
172: MessageType type = message.getType(); 173: 174: switch (type) {
175: 176: case INTERPRETER_SUMMARY: {
177: Number body[] = (Number[]) message.getBody(); 178: int executionCount = (Integer) body[0]; 179: int runtimeErrors = (Integer) body[1]; 180: float elapsedTime = (Float) body[2]; 181: System.out.println("\n----------解释统计信息------------");
182: System.out.printf(INTERPRETER_SUMMARY_FORMAT, 183: executionCount, runtimeErrors, 184: elapsedTime); 185: break; 186: } 187: 188: case COMPILER_SUMMARY: {
189: Number body[] = (Number[]) message.getBody(); 190: int instructionCount = (Integer) body[0]; 191: float elapsedTime = (Float) body[1]; 192: System.out.println("\n----------编译统计信--------------");
193: System.out.printf(COMPILER_SUMMARY_FORMAT, 194: instructionCount, elapsedTime); 195: break; 196: } 197: } 198: } 199: } 200: } 如果classes目录包含相应的class文件且当前目录包含Pascal源文件hello.pas,使用类似如下的命令行去编译文件。 java –classpath classes Pascal compile hello.pas 解释源文件类似如下命令行 java –classpath classes Pascal execute hello.pas 在Eclipse更简单,因为hello.pas在根目录下,直接创建两个如下的Java Application,一个编译,一个解释,非常happy。
构造函数根据源文件路径创建一个Source对象,接着根据命令行参数调用前端和后端工厂创建相应组件。构造函数调用parser的parse()方法解析源文件,得到生成的中间码和符号表并将它们传给后端的process方法。 注意构造函数调用前端工厂为Pascal源语言创建top-down解析器但不依赖具体源语言或使用的解析器类型。源语言和解析器(parser)类型已传给命令行。同样地,Pascal类也不需要知道到底后端工厂创建的是编译器还是解释器(因为是公共的Backend抽象类)。 三个内部类分别是parser,Source和Backend的监听器。类SourceMessageListener,ParserMessageListener和BackendMessageListener分别遵循source,Parser以及Backend的消息格式约定。它们使用格式串SOURCE_LINE_FORMAT,PARSER_SUMMARY_FORMAT,INTERPRETER_SUMMARY_FORMAT以及COMPILER_SUMMARY_FORMAT,正确地输入格式化后的文本给System.out。 假设文件hello.pas包含清单2-24里的Pascal程序。 清单2-24 Pascal程序hello.pas PROGRAM hello (output);
{Write 'Hello, world.' ten times.} VAR BEGIN {hello} 则初级编译器将输出如清单2-25所示的内容。 001 PROGRAM hello (output);
初级解释器将生成如清单2-26的输出。 清单2-26: Pascal 解释器输出 001 PROGRAM hello (output); 这些端对端测试运行满足本章的第四和最后一个目标。 |
java编写编译器和解释器的更多相关文章
- 学了编译原理能否用 Java 写一个编译器或解释器?
16 个回答 默认排序 RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...
- 一个Java编写的小玩意儿--脚本语言解释器(一)
今天开始想写一个脚本语言编译器.在这个领域,我还是知道的太少了,写的这个过程肯定是艰辛的,因为之前从来没有接触过这类的东西.写在自己的博客里,算是记录自己的学习历程吧.相信将来自己有幸再回过头来看到自 ...
- [转帖]java的编译器,解释器和即时编译器概念
java的编译器,解释器和即时编译器概念 置顶 2019-04-20 13:18:55 菠萝科技 阅读数 268更多 分类专栏: java jvm虚拟机 操作系统/linux 版权声明:本文为博主 ...
- atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结
atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结 1. 建立AST 抽象语法树 Abstract Syntax Tree,AST) 1 ...
- Python 编译器与解释器
Python 编译器与解释器 Python的环境我们已经搭建好了,可以开始学习基础知识了.但是,在此之前,还要先说说编译器与解释器相关的内容. 如果这部分内容,让你觉得难以理解或不能完全明白,可以暂时 ...
- 11 个最佳的 Python 编译器和解释器
原作:Archie Mistry 翻译:豌豆花下猫@Python猫 原文:https://morioh.com/p/765b19f066a4 Python 是一门对初学者友好的编程语言,是一种多用途的 ...
- Python环境搭建-2 编译器和解释器
编译器与解释器 编译器/解释器:高级语言与机器之间的翻译官 都是将代码翻译成机器可以执行的二进制机器码,只不过在运行原理和翻译过程有不同而已. 那么两者有什么区别呢? 编译器:先整体编译再执行 解释器 ...
- Linux 小知识翻译 - 「编译器和解释器」
这次聊聊「编译器和解释器」. 编程语言中,有以C为代表的编译型语言和以Perl为代表的解释型语言.不管是哪种,程序都是以人类能够理解的形式记录的,这种形式计算机是无法理解的. 因此,才会有编译器和解释 ...
- java编写service详细笔记 - centos7.2实战笔记(windows类似就不在重复了)
java编写service详细笔记 - centos7.2实战笔记(windows类似就不在重复了) 目标效果(命令行启动服务): service xxxxd start #启动服务 servic ...
随机推荐
- [LeetCode] 438. Find All Anagrams in a String_Easy
438. Find All Anagrams in a String DescriptionHintsSubmissionsDiscussSolution Pick One Given a str ...
- mysql的innodb存储引擎和myisam存储引擎的区别
主要区别如下: 1.事务支持.innodb支持事务,事务(commit).回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transacti ...
- mysql事务详解
事务的四大特性ACID如下: 原子性:事务中的所有操作,要么全部完成,要么不做任何操作,不能只做部分操作.如果在执行的过程中发了错误,要回滚(Rollback)到事务开始前的状态,就像这个 ...
- ide vscode安装
在linux系统中安装VSCode(Visual Studio Code) 在linux系统中安装VSCode(Visual Studio Code) 1.从官网下载压缩包(话说下载下来解压就直接 ...
- Atcoder Tenka1 Programmer Contest 2019 E - Polynomial Divisors
题意: 给出一个多项式,问有多少个质数\(p\)使得\(p\;|\;f(x)\),不管\(x\)取何值 思路: 首先所有系数的\(gcd\)的质因子都是可以的. 再考虑一个结论,如果在\(\bmod ...
- Python: 类中为什么要定义__init__()方法
学习并转自:https://blog.csdn.net/geerniya/article/details/77487941 1. 不用init()方法定义类 定义一个矩形的类,目的是求周长和面积. c ...
- Java设计模式应用——模板方法模式
所谓模板方法模式,就是在一组方法结构一致,只有部分逻辑不一样时,使用抽象类制作一个逻辑模板,具体是实现类仅仅实现特殊逻辑就行了.类似科举制度八股文,文章结构相同,仅仅具体语句有差异,我们只需要按照八股 ...
- js dom 操作技巧
1.创建元素 创建元素:document.createElement() 使用document.createElement()可以创建新元素.这个方法只接受一个参数,即要创建元素的标签名.这个标签名在 ...
- php ci 报错 Object not found! The requested URL was not found on this server. If you entered the URL manually please check
Object not found! The requested URL was not found on this server. The link on the referring page see ...
- JavaScript中几种 获取元素的方式
1.根据id获取元素 document.getElementById("id属性的值"); 2.根据标签名字获取元素 document.getElementsByTagName(& ...

