on 2012-07-14 21:24 Bang 阅读(102) 评论(0) 编辑 收藏 

续 第二部分

初始后端实现

框架后端支持编译器和解释器。现在框架抽象类Backend有两个极简版实现,一个为编译器另一个为解释器。图2-7 展示了它们的UML类图。

图2-7 子类CodeGenerator和Executor分别是后端的编译器和解析器实现。

编译器

编译器后端做代码生成。backend.compiler包中的类CodeGenerator实现框架抽象类Backend。现在它被最大简化了。清单2-20 展示了它的父类方法process的实现。方法参数引用自中间码和符号表,它产生一条消息指出生成器生成了多少条机器语言指令(目前0条,因为没有实际生成过程)以及代码生成耗用时间。它调用sendMessage()方法发送编译摘要消息。

编译器约定摘要格式为:

  • instructionCount:指令生成条数。
  • elapsedTime:代码生成耗用时间。

所有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()发送消息。

解释器约定摘要消息格式为:

  • executionCount:执行语句数
  • runtimeErrors:运行期错误数
  • elapsedTime:耗费时间

所有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
i : integer;

BEGIN {hello}
FOR i := TO DO BEGIN
writeln('Hello, world.');
END;
END {hello}.

则初级编译器将输出如清单2-25所示的内容。

001 PROGRAM hello (output);
002
003 {Write 'Hello, world.' ten times.}
004
005 VAR
006 i : integer;
007
008 BEGIN {hello}
009 FOR i := 1 TO 10 DO BEGIN
010 writeln('Hello, world.');
011 END;
012 END {hello}. ----------代码解析统计信--------------
源文件共有 12行。
有 0个语法错误.
解析共耗费 0.03秒. ----------编译统计信--------------
共生成 0 条指令
代码生成共耗费 0.00秒

设计笔记

本章有很多烦人事是确保框架是源语言无关且能支持任何编译器或解释器。即使Pascal类相当不可知你也将Pascal相关组件加到前端以及编译或解释相关组件加到后端。

软件工程的一个主要挑战是管理变更。你能更改源语言,解析类型,或要一个编译器还是一个解释器。因为框架被设计成可伸缩,它将是可靠的,并不需要什么改动。而你仅需要实现子类时做些改动。

如果说你想要一个bottom-up而不是top-down解析器(parser,也称语法分析器,以后都称为解析器),那么使用同样的前端ParserScanner类,增加一个新的Parser实现如PascalParserBU。或者更改源语言为Java,实现一个Parser子类JavaParserTD,不过这个子类需要一个新的Scanner比如JavaScanner。

因此为管理变更,应用此条软件工程法则,那就是封装多变的代码使其与不变的代码隔离开来。在前端,Parser和Scanner可能有变化,则把它们封装在子类实现中。后端的编译器和解释器同样是子类。变化的子类与不变的框架类隔离开来。

因为我们所有的解析器,其中PascalParserTD是一个例子,它继承自Parser基类,所有解析器组成一个可互换的类族。其它类族有Scanner的子类(比如PascalScanner,Token子类(比如EofToken)以及Backend子类(比如CodeGenerator和Executor)。在每个类族中,你能增加和交换族成员而不需修改框架代码。

策略模式(Strategy Design Pattern)详细说明了此等类族的用法。早点实现这种设计模式去管理变更会让你在接下来章节的进一步开发中获益更多。

初级解释器将生成如清单2-26的输出。

清单2-26: Pascal 解释器输出

001 PROGRAM hello (output);
002
003 {Write 'Hello, world.' ten times.}
004
005 VAR
006 i : integer;
007
008 BEGIN {hello}
009 FOR i := 1 TO 10 DO BEGIN
010 writeln('Hello, world.');
011 END;
012 END {hello}. ----------代码解析统计信--------------
源文件共有 12行。
有 0个语法错误.
解析共耗费 0.03秒. ----------解释统计信息------------
共执行 0 条语句。
运行中发生了 0 个错误。
执行共耗费 0.00 秒。

这些端对端测试运行满足本章的第四和最后一个目标。

 

java编写编译器和解释器的更多相关文章

  1. 学了编译原理能否用 Java 写一个编译器或解释器?

    16 个回答 默认排序​ RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...

  2. 一个Java编写的小玩意儿--脚本语言解释器(一)

    今天开始想写一个脚本语言编译器.在这个领域,我还是知道的太少了,写的这个过程肯定是艰辛的,因为之前从来没有接触过这类的东西.写在自己的博客里,算是记录自己的学习历程吧.相信将来自己有幸再回过头来看到自 ...

  3. [转帖]java的编译器,解释器和即时编译器概念

    java的编译器,解释器和即时编译器概念 置顶 2019-04-20 13:18:55 菠萝科技 阅读数 268更多 分类专栏: java jvm虚拟机 操作系统/linux   版权声明:本文为博主 ...

  4. atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结

    atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结 1. 建立AST 抽象语法树 Abstract Syntax Tree,AST) 1 ...

  5. Python 编译器与解释器

    Python 编译器与解释器 Python的环境我们已经搭建好了,可以开始学习基础知识了.但是,在此之前,还要先说说编译器与解释器相关的内容. 如果这部分内容,让你觉得难以理解或不能完全明白,可以暂时 ...

  6. 11 个最佳的 Python 编译器和解释器

    原作:Archie Mistry 翻译:豌豆花下猫@Python猫 原文:https://morioh.com/p/765b19f066a4 Python 是一门对初学者友好的编程语言,是一种多用途的 ...

  7. Python环境搭建-2 编译器和解释器

    编译器与解释器 编译器/解释器:高级语言与机器之间的翻译官 都是将代码翻译成机器可以执行的二进制机器码,只不过在运行原理和翻译过程有不同而已. 那么两者有什么区别呢? 编译器:先整体编译再执行 解释器 ...

  8. Linux 小知识翻译 - 「编译器和解释器」

    这次聊聊「编译器和解释器」. 编程语言中,有以C为代表的编译型语言和以Perl为代表的解释型语言.不管是哪种,程序都是以人类能够理解的形式记录的,这种形式计算机是无法理解的. 因此,才会有编译器和解释 ...

  9. java编写service详细笔记 - centos7.2实战笔记(windows类似就不在重复了)

    java编写service详细笔记 - centos7.2实战笔记(windows类似就不在重复了)  目标效果(命令行启动服务): service xxxxd start #启动服务  servic ...

随机推荐

  1. 识别真假搜索引擎(搜索蜘蛛)方法(baidu,google,Msn,sogou,soso等)

    http://www.useragentstring.com/pages/useragentstring.php 今天分析研究了两个网站的 Apache 日志,分析日志虽然很无聊,但却是很有意义的事情 ...

  2. 实体框架 Code First

    原文:https://msdn.microsoft.com/zh-cn/en-zn/data/jj591621

  3. php PDO遇到的坑

    <?php $dbConn = new PDO( "mysql:host=localhost;dbname=adtuu",'root','root', array( // 强 ...

  4. zw版【转发·台湾nvp系列Delphi例程】HALCON SigmaImage2

    zw版[转发·台湾nvp系列Delphi例程]HALCON SigmaImage2 procedure TForm1.Button1Click(Sender: TObject);var op: HOp ...

  5. 小试---EF5.0入门实例1

    现在做个小练习吧~~~ 第一步:首先新建一个数据库名字为Test;数据库里面只有一个表UserTable 脚本为: USE [master] GO /****** 对象: Database [Test ...

  6. JDBC报错记录

    用JDBC连接oracle时 有如下问题: 问题一.java.lang.ClassNotFoundException: oracle.jdbc.driver.oracledriver 解决: 可以在环 ...

  7. web前端----jQuery扩展(很重要!!)

    1.jQuery扩展语法 把扩展的内容就可以写到xxxx.js文件了,在主文件中直接导入就行了. 用法1.$.xxx() $.extend({ "GDP": function () ...

  8. where T : class含义

    .NET支持的类型参数约束有以下五种: where T : struct                               | T必须是一个结构类型where T : class       ...

  9. 安装使用composer基本流程

    composer工作原理: 这里经过几个步骤:1.composer读取composer.json(这个文件手动建立,官网有格式),这个json是在当前执行composer目录的,如果目录下没有这个js ...

  10. 20145321《网络对抗技术》逆向与Bof基础

    20145321<网络对抗技术>逆向与Bof基础 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何 ...