《搜索引擎零距离》第三章 IRS虚拟机及编译器实现原理(4)

阅读更多
3.3 词法分析和语法分析[size=large][/size]
为了理解IRS语言编译器的实现原理,我们首先要了解关于词法分析和语法分析的知识。
3.3.1 定义与简介
所谓词法分析,就是把文本中的内容按照一定规则识别为一系列的词语单元。例如,假设我们规定“连续的一串字符叫做一个WORD,句号叫做DOT”,那么,序列I love this game就能够被识别为WORD  WORD  WORD  WORD DOT。
所谓语法分析,就是按照一定的规则把多个连读的个词语单元识别为一系列语义单元。例如,假设我们规定“连续的WORD 跟上一个DOT 叫做一个SENTENSE(句子)”,那么上例中的WORD  WORD  WORD  WORD DOT就被识别为SENTENSE。以一段C语言的源码为例:
int main() {
return 0 ;
}
C语言的词法分析器会把这段代码分解为如下的一些标识符:
"int ", " ", "main ", " (",")","","{","\n ", "\t ",
"return " " ", "0 ", " ", "; ", "\n ", "} ", "\n ", " "
还会把这些标识符归类,在上面的例子中,这些标识符的类别为:
KWINT, SPACE, ID, OPAR, CPAR,
SPACE, OBRACE, SPACE, SPACE, KWRETURN,
SPACE, OCTALCONST, SPACE, SEMICOLON, SPACE,
CBRACE, SPACE, EOF
其中EOF类别的标识符代表源文件的结束。这些标识符被传送给语法分析器。在C语言中,语法分析不需要所有的这些类别,在本例中,SPACE类别的标识符不传送给语法分析器。语法分析器会分析这些标识符,判断程序的结构。在许多编译程序中,语法分析器用语法树来表示程序的结构,而编译器通过这棵树来产生代码。
“词法分析”和“语法分析”是计算机本科专业课程《编译原理》中的两个重要概念,具体可以参考以下两本经典教材:
 Compilers Principles :Techniques and Tools(英文版)      中文名:编译原理 技术与工具,就是“龙书”,最经典的编译原理教材。
 Compiler Construction - Principles and Practice(英文版)   中文名:编译原理及实践,附tiny编译器的全套源码。
由于手工编写词法分析器和语法分析器通常非常枯燥烦琐而且容易出错,因此在实际项目中“词法分析器”和“语法分析器”都是用一定的工具来完成的。在Java领域中常用的工具有三种。
1.JavaCC 
网址: https://javacc.dev.java.net/ Java技术合作网上的JavaCC项目的主页

英文简介:
Java Compiler Compiler [tm] (JavaCC [tm]) is the most popular parser generator for use with Java [tm] applications. A parser generator is a tool that reads a grammar specification and converts it to a Java program that can recognize matches to the grammar. In addition to the parser generator itself, JavaCC provides other standard capabilities related to parser generation such as tree building (via a tool called JJTree included with JavaCC), actions, debugging, etc.

翻译:
JavaCC [tm]是Java领域中最流行的语法分析器生成器。语法分析器生成器指是这样一个工具:它读入语法定义文件并将其转化为能够识别这种语法的Java程序。JavaCC除了是一个“语法分析器生成器”之外,它还提供了“语法树生成”,“语义动作”,“调试”等功能。

3. ANTLR
网址:http://www.antlr.org/   ANTLR项目官网
英文简介:
ANTLR, ANother Tool for Language Recognition, is a language tool that provides a framework for constructing recognizers, interpreters, compilers, and translators from grammatical descriptions containing actions in a variety of target languages. ANTLR provides excellent support for tree construction, tree walking, translation, error recovery, and error reporting.
ANTLR currently generates Java, C#, C++, and Python.
翻译:
ANTLR(ANother Tool for Language Recognition)是这样一个工具:它是从包含语法描述以及语义动作描述的文本中构建多种目标语言上的识别器、解释器、编译器及翻译器的工具。ANTLR对语法树构建、语法树遍历、转换、错误恢复及错误报告等等这些功能提供了出色的支持。
ANTLR当前支持Java、C#、 C++、和 Python等多种语言的代码生成。

3.SableCC
网址:http://sablecc.org/ SableCC项目官网
英文简介:
SableCC is a parser generator which generates fully featured object-oriented frameworks for building compilers, interpreters and other text parsers. In particular, generated frameworks include intuitive strictly-typed abstract syntax trees and tree walkers. SableCC also keeps a clean separation between machine-generated code and user-written code which leads to a shorter development cycle.
翻译:
SableCC是一个分析器生成器,它可以通过生成全特性的、面对对象的框架代码来构建编译器、解释器和其他文本分析器。特别是它可以生成容易理解的、类型严格的抽象语法树和语法树遍历器。SableCC同时也把机器生成的代码和用户手写的代码清楚地分隔开来,以便获得较短的开发周期。
你可以在以下网址看到更多的词法/语法解析器生成工具:
http://www.bearcave.com/software/antlr/antlr_expr.html
由于SableCC的面向对象特征以及易用性,笔者在IRS语言的解释器开发过程中使用了SableCC作为词法、语法分析器生成工具。因此,下文将着重讲述SableCC的方方面面。
3.3.2 SableCC
根据笔者的计算机本科课程的学习经历,众多的专业课程中,最让学生们觉得迷惘的应该就是《编译原理》这门课,迷迷糊糊学了半年,90%的学生都不知道这门课上学的东西怎么与实际应用结合起来。
SableCC作为一个优秀的编译器生成工具,它的使用有比较高的复杂性,出于普及这一优秀工具的目的,笔者选择了一些优秀的英语文章进行翻译,向普通的Java开发者打开一扇通向编译器开发的大门。

3.3.2.1 使用SableCC编写一个编译器
原文网址: http://www.brainycreatures.org/compiler/sablecc.asp
作者联系方式: [email protected]
我们要按照以下步骤使用SableCC来编写一个编译器:
(1) 生成一个包含我们设计的目标语言的词法和语法定义的SableCC定义文件。
(2) .生成好这个SableCC定义文件之后,通过在这个定义文件上启动SableCC来生成框架代码。
(3) 本步骤生成工作类。这是我们用JavaTM编写的唯一代码。
(4) 本步骤编写语义分析器、代码生成器,或者可能还有代码优化器。 在这个解释器的例子里,编写一个简单的类。这些工作类可能是由SableCC在上一个步骤中生成的analysis子文件夹中的某个类的子类。在本步骤中,生成编译器的驱动类,它被用来激活词法分析器、语法分析器和工作类。
(5) 最后,使用JavaTM编译器来编译编译器。
为了清楚地展现这个过程,下面按照以上的步骤实现一个Pascal语言的子集
下面开始具体的开始实现工作:SableCC规格文件。
在开始编写Pascal语言子集的SableCC规格定义文件的时候,我们先用BNF来描述一下SableCC的语法

 → [] [] 
 [] [] 
 [] [] 

正如我们从语法中看到的,SableCC规格定义文件可以是一个空文件。你是否记得在[和]之间的内容的含义?这代表[和]之间的内容可以为空或者出现一次。
从SableCC规格定义文件所允许的段落来看,我们将不考虑,因为我们不准备使用这个功能。但是如果读者对它的工作原理感兴趣,那么可以看参考文献[1]。现在我们将要描述其他产生式的语法。用来命名目标根目录,也就是所有生成的文件放置的目录。下面按照如下格式声明包名:
Package uk.co.brainycreatures.smallpascal;
在这个例子中,根目录是uk\co\brainycreatures\smallpascal,所有生成的子文件夹将被放在这个目录之下。一个包名可以是一个以字母开头、后面跟上0个或多个字母或数字的、由”.”分隔的标识符。
id = [‘a’ .. ‘z’] ([‘a’ .. ‘z’] | [‘0’ .. ‘9’])*
在这个例子中,我们通过定义digit = [‘0’ .. ‘9’] 和letter = [‘a’ .. ‘z’]来简化这个正则表达式,于是,我们的正则表达式ID变成
id = letter (letter | digit)*
letter和digit是我们的辅助元素,下面是我们如何在SableCC中定义辅助元素:
Helpers   
 letter = [‘a’ .. ‘z’];  
 digit = [‘0’ .. ‘9’];

后面跟着的是,这段内容里定义终结符。下面用例子来展示如何在SableCC中定义终结符。
Tokens  
 id = letter (letter | digit)*; 

这种格式与在正则表达式教程中的正则表达式非常相似。单引号之间的一串字符词语可以定义一个词语单元(Token),每个声明用一个分号结尾。在段后面是段, 也就是说,声明的Token这部分会被分析器忽略。例如:注释、空格、和回车等等。
以下是一个例子:
Helpers  
 any_charater = [0x0 .. 0xfffff]; 
 nl = '\n '; 
Tokens  
 comment = '⁄⁄ ' any_character nl 
 blank = 10 | 10 13 | 9; 
Ignored Tokens  
 comment, 
 blank; 

本例中,注释和空格将被分析器忽略。
最后,是段,我们将为这个语言定义产生式语法。产生式是由BNF或者EBNF定义的。
Tokens  
 identifier = letter (letter | digit)*; 
 number = digit+; 
 plus = ‘+’; 
Productions  

expression = identifier plus identifier minus number ;
正如我们看到的,它的形式和在语法教程中表述的“上下文无关语法”相似。唯一的不同点是在SableCC中我们不使用”<”和”>”来包围一个非终结符,而却我们用”=”代替了“→”。在产生式段里,有时需要在词语单元(Token)前放一个”T.”,在产生式(production)前放一个”P.”
Tokens  
 program = ‘program’; 
 semicolon = ‘;’ ; 
Productions  
 program = T.program identifier semicolon ; 

既然我们已经熟悉SableCC语法了,让我们开始实现我们的Pascal语言子集
我们的包的根是uk.co.brainycreatures.smallpascal,于是在“包声明段”我们将得到:

Package uk.co.brainycreatures.smallpascal;
定义好包之后,我们将如下定义我们的辅助元素:
Helpers
          a = 'a '| 'A ';
          b = 'b ' | 'B ';
          e = 'e '| 'E ';
          g = 'g '| 'G ';
          i = 'i ' | 'I ';
          m = 'm ' | 'M ';
          n = 'n ' | 'N ';
          o = 'o ' | 'O ';
          p = 'p '| ' P ';
          r = 'r ' | 'R ';
          t = 't '| 'T ';
          v = 'v ' | 'V ';
          w = 'w' | 'W ';
          cr = 13 ; ⁄⁄ carriage return
          lf = 10 ; ⁄⁄ line feed
          tab = 9 ; ⁄⁄ tab char
          ascii_char = [32 .. 127] ;
          blank = ‘’ ;
          digit = ['0 ' .. '9 '] ;
          letter = [['a '.. 'z '] + ['A ' .. 'Z ']] ;
          l_brace = '{ ' ;
          r_brace = '} ' ;
          l_paren_star = ' (*';
          r_paren_star = '*) ';

注意Pascal子集语言关键字定义中的字母之间的空格,在这个例子中我们需要他们,因为每个字符都是一个辅助元素(Helpers)单元,因此,在定义Token的时候,我们有一个由空格分隔的Helpers序列。
最后,描述这个语言的语法,如下:
Productions
          program =
                    program_heading
                    global_declaration_part
                    begin
                    main_statement_part
                    end dot;
          program_heading =
                    {non_empty} program identifier semicolon |
                    {empty} ;
          global_declaration_part =
                    var_declaration;
          var_declaration =
                    var var_decl+;
          var_decl =
                    identifier_list colon type;
          identifier_list =
                    {single} identifier |
                    {sequence} identifier_list comma identifier;
          type =
                    integer;
          main_statement_part =
                    statement_sequence;
          statement_sequence =
                    {single} statement |
                    {sequence} statement_sequence semicolon statement ;
          statement =
                    {assign} identifier assignop expression |
                    {writeln} writeln_stmt ;
          writeln_stmt =
                    {simple}writeln |
                    {arguments} writeln l_paren expression_list r_paren ;
          expression_list =
                    {single} expression |
                    {sequence} expression_list comma expression ;
          expression =
                    {term} term |
                    {plus} expression plus term |
                    {minus} expression minus term ;
          term =
                    {factor} factor |
                    {mult} term mult factor |
                    {div} term div factor;
          factor =
                    {identifier} identifier |
                    {integer} integer; 

生成工作类:
现在为这个语言实现语义分析和代码生成,先按如下方式实现语义分析。
1. 语义分析器
语义分析器处理语法分析器传递给她的语句,通过确保语句是否有有意义,完成源码的静态分析阶段。例如,Pascal赋值语句的语法没有要求标识符是已声明过的、是变量、属于类似的类型、是否可以在这个类型上操作。所有这些约束是由Pascal的静态语义定义的,由语义分析器来执行相关的检查。为了执行这些检查,语义分析器完整的处理所有声明,并产生对应的信息(属性信息)。这些信息被存储在称为“符号表”的数据结构里。符号表被用来提供与标识符相关的信息。
对于这个小的Pascal语言子集,它所需要进行的唯一的标识符检查是标识符在使用之前必须声明过,而且只能声明一次。不需要检查类型,因为所有标识符是相同类型的。语义分析过程是通过深度优先遍历抽象语法树来完成的,当访问过包含变量声明的产生式子树之后,这个过程把标识符的信息存储在符号表里。当我们遇到因子和变量等等标识符的产生式的时候,将用到符号表里的信息。
既然已经有了实现这个语义分析器所需的信息,现在讲述如何实现它。回到之前提到的语法,现在要解释每条产生式的替换式中{和}之间的名字的作用。语法的产生式中每条替换式的名字有一前缀A并且跟着一个产生式的名字,这样就为这条替换式构造了一个类型名。。例如,对于下面的产生式
Factor = {identifier} identifier | {integer} integer_literal | … ;
一个命名为AIdentifierFactor和一个命名为AIntegerFactor的类型将被生成。
在获得以上信息的前提下,通过定义替换式和标识符上的方法来设计语义分析器。也就是,替换式AsSingleIdentifierList、ASequenceIdentifierList、AAssignStatement 和 AIdentifierFactor。并且,我们也需要处理替换式 AVarDecl。需要注意, var_decl的替换式没有名字,在SableCC中,在以下条件下,这是允许的:如果如果产生式只由一个替换式定义,并且结果类型是以A为前缀的产生式的名字。下面是语义分析器的实现:
package uk.co.brainycreatures.smallpascal;

import uk.co.brainycreatures.smallpascal.node.*;
import uk.co.brainycreatures.smallpascal.analysis.*;
import java.util.*; ⁄⁄ for the Hashtable

public class SemanticAnalyzer extends DepthFirstAdapter {
     ⁄⁄ stores the identifiers being defined
     Hashtable symbol_table = new Hashtable(); ⁄⁄ create a new table
	
	⁄*检查标识符是否已经在符号表里了
     *check if the identifier is already in the table and report an errorif it is*/
     public void outASingleIdentifierList(AidentifierList node) {
          ⁄⁄ identifier to be stored in the symbol table
          TIdentifier ident = node.getIdentifier();
          ⁄⁄ name of the identifier to be stored in the table
          String key = ident.getText().toUpperCase();

          ⁄⁄ is the identifier in the table?
          if (symbol_table.containsKey(key)) { ⁄⁄ report an error
               System.out.println(“Identifier already defined.”);
               System.exit(0);
          }
          else {
               symbol_table.put(key, key);
          }
     }

     public void outASingleIdentifierList(AidentifierList node) {
          ⁄⁄ identifier to be stored in the symbol table
          TIdentifier ident = node.getIdentifier();
          ⁄⁄ name of the identifier to be stored in the table
          String key = ident.getText().toUpperCase();

          ⁄⁄ is the identifier in the table?
          if (symbol_table.containsKey(key)) { ⁄ report an error
               System.out.println("Error: ["ident.getLine() + "," + ident.getPos() + "] Identifier already defined. ");
               System.exit(0);
          }
          else {
               symbol_table.put(key, key);
          }
     }

	⁄*检查赋值语句中的标识符是否已经声明过,如果没有的话就报错
     checks if the identifier in the assignment statement was previously
     declared, and report an error if it wasn’t*/
     public void outAAssignStatement(AAssignStatement node) {
          Tidentifier ident = node.getIdentifier();
          String key = ident.getText().toUpperCase();

          ⁄⁄ Is the identifier in the table?
          ⁄⁄ if not report error
          if (!symbol_table.containsKey(key)) {
               System.out.println("Error: [" + ident.getLine() + ","
               ident.getPos() + "] Unknown identifier. ");
               System.exit(0);
          }
     }

     public void outAIdentifierFactor(AIdentifierFactor node) {
          Tidentifier ident = node.getIdentifier();
          String key = ident.getText().toUpperCase();
          ⁄⁄ Is the identifier in the table?
          ⁄⁄ if not report error
          if (!symbol_table.containsKey(key)) {
               System.out.println("Error: [" + ident.getLine() + "," + ident.getPos() + "] Unknown identifier. ");
               System.exit(0);
          }
     }
}  


通过扩展DepthFirstAdapter(深度优先访问类)来实现SemanticAnalyser类。
DepthFirstAdapter类自动提供了对AST(抽象语法树)的深度优先访问。为了检查标识符的声明,我们只需要outASingleIdentifierList、outASequenceIdentifierList方法。为了检查标识符在使用前是否已经被声明过,我们只需要outAAssignStatement和outAIdentifierFactor方法。想更详细的了解SableCC如何生成方法,请参考文献[1]的第5、6章。现在我们已经完成了语义分析器。除了代码生成之外,我们还需要处理符号表里的标识符。
2. 类生成器
类生成器也是类DepthFirstAdapter的子类。为了实现代码生成器,我们需要对语法树做深度优先遍历,正如我们实现语义分析器时所做的一样。但是对于代码生成来说,我们也会使用其他产生式中的替换式。例如,如果程序中出现program heading的话,就使用跟在program关键字后面的标识符的名字作为类名。也就是说,如果程序中有program heading的话,就使用跟在program关键字后的标识符的名字作为类名。如果程序中没有出现program heading,输出的类名是a.out, 这个基于Unix的系统上的C编译器的执行结果相同。下面开始描述我们的代码生成器的设计。比如,从产生式开始:
program_heading =
          {non_empty} T.program identifier semicolon |
          {empty} ;
我们将要定义方法outANonEmptyProgramHeading 和outAEmptyProgramHeading。为了使用Jas API来生成class文件,我们需要先生成一个类ClassEnv的实例,然后我们设置类属性:类名、父类、源代码和访问控制标识。这些都在上面说明过的方法里做好了。我们还需要生成一个默认构造函数。尽管我们不实现,Java编译器为我们生成了一个默认构造函数。以下是Java汇编代码看上去的样子:

.method public ()V
     aload_0
     invokevirtual java⁄lang⁄Object⁄()V
     return
.end method 

我们可以在program_heading的产生式或者在program产生式遍历子树之前进行方法的定义――这取决于程序员的偏好。在我们的例子中,将要在方法inAProgram定义这个构造函数:
public void inAProgram(AProgram node) {
     ⁄⁄ create an new instance of CodeAttr
     init = new CodeAttr();
     main_code = new CodeAttr(); ⁄⁄ create a new object for main program body
     try {
          ⁄⁄ define the method
          init.addInsn(new Insn(opc_aload_0));
          init.addInsn(new Insn(opc_invokevirtual, new MethodCP(" java/lang/Object ", "  ", " ()V ")));
          init.addInsn(new Insn(opc_return));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
 
	定义好这个方法之后,我们需要把它加到类上去,这个工作可以在outAEmptyProgramHeading方法或者outANonEmptyProgramHeading方法里进行。以下是实现:
public void outANonEmptyProgramHeading(ANonEmptyProgramHeading node) {
     ⁄⁄ name of the class to be generated
     class_name = node.getIdentifier().getText();

     try {
          ⁄⁄ set class attributes
          main_class.setClass((new ClassCP(class_name));
          main_class.setSuper(new ClassCP(“java/lang/Object”));
          ⁄⁄ source from which it is compiled
          main_class.setSource(new SourceAttr(source_file_name));
          ⁄⁄ make class public
          main_class.setClassAccess((short) ACC_PUBLIC);
          ⁄⁄ add the constructor method to the class
          main_class.addMethod((short) ACC_PUBLIC, , “”, “()V”, init, null);
     }
     catch (Exception e) {
          System.out.println(e);
     }
}

这些代码是很浅显的,所以我们不做过多解释。
方法outAEmptyProgramHeading的代码和上面的代码相似,除了类名之外。它的类名是a.out。继续处理语法,现在要定义处理标识符的代码,也就是定义标识符的代码。Pascal变量将被定义为Java字段。为了使用Jas API定义字段,我们使用类ClassEnv上的addField方法。生成后的变量名应该是小写。我们使用它所在类的类名而不是使用一个符号表来获取字段的签名。以下是实现:
public void outASingleIdentifierList(AsingleIdentifierList node) {
     ⁄⁄ get the name of the name of the variable in lower case
     String var_name = node.getIdentifier().getText().toLowerCase();

     try {
⁄⁄ add the field to the class
          main_class.addField(new Var((short) (ACC_STATIC | ACC_PUBLIC)), new AsciiCP(var_name), new AsciiCP(“I”), new ConstAttr(new IntegerCP(0)))));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}

以上的代码等于以下的Java声明:
public static int = 0;
其中的与所声明的变量名相同。方法outASequenceIdentifierList的代码和上面outASingleIdentifierList的代码相同。因此不需要再次描述它。
现在来描述如何实现“语句”(statements)。在进一步表述之前,先分析一下如何把Pascal语句转化为Java汇编码。因为Java虚拟机是基于堆栈的,我们需要把所有的有名的因子压栈,并且弹出结果,放入左面的变量。例如,赋值语句:
a := b + c
将被翻译成如下的java汇编代码:
getstatic ⁄b I
getstatic ⁄c I
iadd
putstatic ⁄a I
是字段a、b和c所在的类的类名。在Java里,使用.,但是在Java汇编代码里,使用/来访问方法或者字段。变量名后面的I代表这个变量是int类型的。指令getstatic被用来把值存储进类的静态字段。另外将被用在这个语言子集代码生成过程的算术指令是:
isub –整数相减。这条执行期望在栈顶找到两个整数,如果找到的话,弹出他们,把他们相减,然后把结果压到栈顶。imul –与isub相同,但是不是相减操作而是相乘操作。idiv –用来整数相除
为了生成合适的代码,需要遍历相关操作符的语法子树,访问他们之后再针对操作符来生成相关的操作码。在乘积因子中遇到数字和标识符的时候,就需要把它们压栈,然后生成乘积表达式和项的算术指令的操作符。下面是实现:
public void caseAIdentifierFactor(AidentifierFactor node) {
     String var_name = node.getIdentifier().getText().toLowerCase();
     try {
          ⁄⁄ getstatic  I
          code.addInsn(new Insn(opc_getstatic, new FieldCP(class_name, var_name, " I ")));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
 
(1)	整数因子的代码
public void caseAIntegerFactor(AIntegerFactor node) {
     ⁄⁄ get the string value
     String num_image = node.getIdentifier().getText();
     int value = Integer.parseInt(num_image);

     try {
          switch(value) {
               case –1 :
                    code.addInsn(new Insn(opc_iconst_m1)); break;
               case 0: 
                    code.addInsn(new Insn(opc_iconst_0)); break;
               case 1 : 
                    code.addInsn(new Insn(opc_iconst_1)); break;
               case 2: 
                    code.addInsn(new Insn(opc_iconst_2)); break;
               case 3: 
                    code.addInsn(new Insn(opc_iconst_3)); break;
               case 4: 
                    code.addInsn(new Insn(opc_iconst_4)); break;
               case 5:
                    code.addInsn(new Insn(opc_iconst_5)); break;
               default:
                    if (value => –128 && value <= 127) {
                         code.addInsn(new Insn(opc_bipush, value)); 
                    }
                    else if (value >= –65536 && value <= 65535) {
                    code.addInsn(new Insn(opc_sipush, value));
                    }
                    else {
                         code.addInsn(new Insn(opc_ldc, value));
                    }
                    break;
          }
     }
}
(2)	 加操作符的代码
public void outAPlusExpression(APlusExpression node) {
     try {
          code.addInsn(new Insn(opc_iadd));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
(3)	 减操作符的代码
public void outAMinusExpression(AMinusExpression node) {
     try {
          code.addInsn(new Insn(opc_isub));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
(4)	乘操作符的代码
public void outAMultTerm(AMultTerm node) {
     try {
          code.addInsn(new Insn(opc_imul));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
(5)	 除操作符的代码

public void outADivTerm(ADivTerm node) {
     try {
          code.addInsn(new Insn(opc_idiv));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
 
(6)	 赋值语句的代码

public void outAAssignStatement(AassignStatement node) {
     String var_name = node.getIdentifier().getText().toLowerCase();

     try {
          code.addInsn(new Insn(opc_putstatic, new FieldCP(class_name, var_name, “I”)));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
(7)	 writeln语句的代码
public void inAWritelnStatement(AwritelnStatement node) {
     try {
          code.addInsn(new Insn(opc_getstatic, new FieldCP(“java/lang/Object”, “out”, “Ljava/io/PrintStream;”)));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}


public void outAWritelnStatement(AwritelnStatement node) {
     try {
          code.addInsn(new Insn(opc_invokevirtual, new MethodCP(“java/io/PrintStream”, “println”, “(I)V”)));
     }
     catch (Exception e) {
          System.out.println(e);
     }
}
	生成所有代码之后,加上额外的返回指令。把这个方法加到类上,然后输出这个类。这些工作在方法outAProgram里进行,这里就是退出程序的地方。以下是代码:
public void outAProgram(AProgram node) {
     try {
          ⁄⁄ add return instruction to the end of the method
          code.addInsn(new Insn(opc_return);
          ⁄⁄ add main method to main_class
          main_class.addMethod((short)( ACC_PUBLIC | ACC_STATIC), “main”, “([Ljava/lang/String;)V”, code, null));

          ⁄⁄ generate class file
          main_class.write(new DataOutputStream(new FileOutputStream(class_name + “.class”)));

          ⁄⁄ output status
          System.out.println(“Wrote “ + class_name + “.class”]”);
     }
     catch (Exception e) {
          System.out.println(e);
     }
}


3. 主类
既然已经实现了工作类,现在要来实现主类。这个类简单地激活词法分析器、语法分析器、语义分析器和类生成器 :
package uk.co.brainycreatures.smallpascal;

import uk.co.brainycreatures.smallpascal.semantic.SemanticAnalyser;
import uk.co.brainycreatures.smallpascal.code.ClassGenerator;
import uk.co.brainycreatures.smallpascal.parser.*;
import uk.co.brainycreatures.smallpascal.lexer.*;
import uk.co.brainycreatures.smallpascal.node.*;

import java.io.*;

public class Main {
     public static void main(String[] args) {
          long start_time, stop_time; ⁄⁄ times compilation

          if (args.length < 1) {
               System.out.println(“Usage:”);
               System.out.println(“ java uk.co.brainycreatures.smallpascal.Main ”);
          }

          try {
               start_time = System.currentTimeMillis();

               ⁄⁄ create lexer
               Lexer lexer = new Lexer (new PushbackReader(new BufferedReader(new FileReader(args[0])), 1024));

               ⁄⁄ parser program
               Parser parser = new Parser(lexer);

               Start ast = parser.parse();

               ⁄⁄ check program semantics
               ast.apply(new SemanticAnalyser());

               ⁄⁄ generate class file
               ast.apply(new ClassGenerator());
          }
          catch (Exception e) {
               System.out.println(e);
          }
     }
}  

既然已经实现了每个功能,现在该编译所有代码了。假设当前工作目录是我们的CLASSPATH,我们可以按如下编译这个编译器:
C:\mycompilers\javac   uk\co\brainycreatures\Main.java

你可能感兴趣的:(虚拟机,搜索引擎,正则表达式,Pascal,C#)