JavaCC学习笔记

JavaCC学习笔记

 

编译原理和解释器构造是很重要的课程,它几乎是计算机行业的一项最为关键的技术,所以这学期下决心要把这门课程学好,学得更加熟练一些。大二的时候我们的编译原理课程是由李莉老师教授的,而在上课期间,我也自己用c++实现了一个编译器,这个编译器是基于Kenneth C. Louden编写的编译器,我在上面添加了数组,指针,函数的定义,以及浮点数的使用和声明语句。并且自主实现了词法分析,语法分析以及语义分析和代码生成。这次需要使用javaCC来实现词法和语法的分析,由于有大二的基础,所以在完成作业的时候感觉轻松不少,而且由于对语义分析比较感兴趣,所以在做语法分析的同时也做了一些语义分析。所以我将我的笔记主要分为五部分,第一部分为JavaCC工具的介绍以及总结,第二部分为CMM语言的词法语法分析,第三部分为CMM语言的语义分析,第四部分为JJTree的简要介绍,第五部分是自己的一些感悟。

 

第一部分:什么是JavaCC?(参考于网站与书籍)

 

JavaCC是一个词法分析生成器和语法分析生成器。词法分析和语法分析是处理输入字符序列的软件构件,编译器和解释器协同词法分析和语法分析来“解密”程序文件,不仅如此,词法分析和语法分析有更广泛的用途。JavaCC并不是一个词法分析器或者语法分析器,它只是一个生成器。就是说,它读取文本后,基于一定的规则产生词法分析器和语法分析器的Java代码。词法分析和语法分析变的越来越复杂,软件工程师直接用Java进行词法分析和语法分析时必须要充分考虑各规则间的相互作用。例如,在对 C语言的词法分析中,处理整型常量和浮点常量的代码是不能分开的,因为浮点数和整数的前面部分是一样的。使用诸如JavaCC语法分析产生器,对整型常量 和浮点常量是可以区分的,它们的共同点可在代码生成过程中提取出来。这种模块性意味着JavaCC文件比直接的Java程序更容易写,更容易读,也更容易 修改。通过JavaCC语法分析生成器的使用,软件工程师可以节省大量的时间,并且软件的质量也更高。

    JavaCC具有如下特点:

JavaCC是一个用Java语言写的一个Java语法分析生成器,它所产生的文件都是纯Java代码文件,JavaCC和它所自动生成的语法分析器可以在多个平台上运行。

下面是JavaCC的一些具体特点:

1.       TOP-DOWN:JavaCC产生自顶向下的语法分析器,而YACC等工具则产生的是自底向上的语法分析器。采用自顶向下的分析方法允许更通用的语法(但是包含左递归的语法除外)。自顶向下的语法分析器还有其他的一些优点,比如:易于调试,可以分析语法中的任何非终结符,可以在语法分析的过程中在语法分析树中上下传值等。

2.       LARGEUSER COMMUNTIY:是一个用JAVA开发的最受欢迎的语法分析生成器。拥有成百上千的下载量和不计其数是使用者。我们的邮件列表(https://JavaCC.dev.java.net/doc/mailinglist.html )和新闻组(comp.compilers.tools.JavaCC)里的参与者有1000多人。

3.       LEXICALAND GRAMMAR SPECIFICATIONS IN ONE FILE:词法规范(如正则表达式、字符串等)和语法规范(BNF范式)书写在同一个文件里。这使得语法易读和易维护。

4.       TREEBUILDING PREPROCESSOR: JavaCC提供的JJTree工具,是一个强有力的语法树构造的预处理程序。

5.       EXTREMELYCUSTOMIZABLE:JavaCC提供了多种不同的选项供用户自定义JavaCC的行为和它所产生的语法分析器的行为。

6.       CERTIFIEDTO BE 100% PURE JAVA:JavaCC可以在任何java平台V1.1以后的版本上运行。它可以不需要特别的移植工作便可在多种机器上运行。是Java语言”Write Once, Run Everywhere”特性的证明。

7.       DOCUMENTGENERATION:JavaCC包括一个叫JJDoc的工具,它可以把文法文件转换成文本本件(Html).

8.       MANYMANY EXAMPLES:JavaCC的发行版包括一系列的包括Java和HTML文法的例子。这些例子和相应的文档是学习JavaCC的捷径。

9.       INTERNATIONALIZED:JavaCC的词法分析器可以处理全部的Unicode输入,并且词法规范何以包括任意的Unicode字符。这使得语言元素的描述,例如Java标识符变得容易。

10.   SYNTACTIC AND SEMANTICLOOKAHEAD SPECIFICATIONS:默认的,JavaCC产生的是LL(1)的语法分析器,然而有许多语法不是LL(1)的。JavaCC提供了根据语法和语义向前看的能力来解决在一些局部的移进-归约的二义性。例如,一个LL(k)的语法分析器只在这些有移进-归约冲突的地方保持LL(k),而在其他地方为了更好的效率而保持LL(1)。移进-归约和归约-归约冲突不是自顶向下语法分析器的问题。

11.   PERMITS EXTENDED BNFSPECIFICATIONS:JavaCC允许拓展的BNF范式——例如(A)*,(A)+等。拓展的BNF范式在某种程度上解决了左递归。事实上,拓展的BNF范式写成A ::= y(x)* 或 A ::= Ax|y更容易阅读。

12.   LEXICAL STATES ANDLEXICAL ACTIONS:JavaCC提供了像lex的词法状态和词法动作的能力。

13.   CASE-INSENSITIVELEXICAL ANALYSIS:词法描述可以在整个词法描述的全局域或者独立的词法描述中定义大小写不敏感的Tokens。

14.   EXTENSIVE DEBUGGINGCAPABILITIES:使用选项DEBUG_PARSER, DEBUG_LOOKAHEAD, 和 DEBUG_TOKEN_MANAGER,使用者可以在语法分析和Token处理中使用深层次的分析。

15.   SPECIAL TOKENS:Tokens可以在词法说明中被定义成特殊的Tokens从而在语法分析的过程中被忽略,但这些Tokens可以通过工具进行处理。

16.   VERY GOOD ERRORREPORTING:JavaCC的错误提示在众多语法分析生成器中是最好的。JavaCC产生的语法分析器可以清楚的指出语法分析的错误并提供完整的诊断信息。

javaCC有三个工具:

javaCC 来处法文件(jj)生成解析代

jjTree 来处jjt文件,生成树节点代jj文件,然后再通javaCC生成解析代

jjDoc 根据jj文件生成bnf范式文html)。工具都十分重要,分别贯穿着编写时程。

JavaCC在使用的时候会首先创建一个JJ文件。这个语法文件以一些JavaCC所提供的Options的参数设置开始。在这个例子中,Options的参数都是它们的默认值。因此,这些参数实际上是不需要的。程序员甚至可以完全忽略Options这一部分,或者省略其中的一个或多个Options的参数,详细的关于Options的参数设置的问题请参考JavaCC的文档。

接下来的是一个处在”PARSER_BEGIN(name)”和”PARSER_END(name)”中间的编译单元。这个编译单元可以是任意的复杂。在这个编译单元中唯一的限制就是它必须定一个一个叫”name”的类——与PARSER_BEGIN和PARSER_END的参数的相同。这个”name”被用作语法分析产生器生成的java文件的前缀。

 

JavaCC的安装:

很简单,直接下载插件,然后直接安装在eclipse中就可以使用,这里不再赘述。

参考资料:http://www.cnblogs.com/Gavin_Liu/archive/2009/03/07/1405029.html

百度百科:http://baike.baidu.com/view/1896120.htm

 

第二部分:CMM词法分析和语法分析

 

首先说一下自己的感受。以前自己写词法分析器的源代码的时候,真的感觉快崩溃了,因为那些有穷自动机画起来感觉好复杂啊,一不小心就会出错,而且如果结构设计不好的话,很可能随时会推倒重来。词法分析器就是在不断地识别token,所以JavaCC好就好在这里了,它给你留出的接口和编程模式很符合人的思维,你只需要声明一下token的类型,然后使用正则表达式来吧词法分析和语法分析一块给做了。而且JavaCC的那种特有的编程模式我使用起来感觉很开心,敲代码写语义分析的时候,感觉头也不花了,耳朵也不鸣了,一连写了一下午加一个晚上,总之一个字:爽。所以我觉得这正是JavaCC的强大之处吧,就像很多现在的产品框架和游戏引擎,使用起来真的很顺手,生产效率也高了很多。

好了废话不多说,我们来看看我声明的token的类型。

SKIP :

{

  " "

| "\r"

| "\t"

| "\n"

| < "//"(~["\n","\r"])*("\n"|"\r"|"\r\n") >

| < "/*"(~["*"])*"*"(~["/"](~["*"])*"*")*"/">

}

 

TOKEN : /* OPERATORS */

{

  < PLUS : "+" >

| < MINUS : "-" >

| < MULTIPLY : "*" >

| < DIVIDE : "/" >

| < ASSIGN : "=" >

}

 

TOKEN :/* RELATIONSHIPOPERATORS*/

{

  < EQ : "==">

| < LT : "< ">

| < NEQ : "<>">

}

TOKEN : /*RESERVEDWORDS*/

{

  < IF : "if">

| < ELSE : "else">

| < WHILE : "while">

| < READ : "read">

| < WRITE : "write">

| < INT : "int">

| < REAL : "real">

| < RETURN : "return">

| < CLASS : "class">

}

 

TOKEN : /*SYMBOLS*/

{

  < UNDERLINE : "_">

| < COMMA:",">

| < SEMICOLON:";">

| < COLON:":">

| < LPARENT: "(" >

| < RPARENT: ")" >

| < LBRACE : "{" >

| < RBRACE : "}" >

| < LARRAY : "[" >

| < RARRAY : "]" >

}

TOKEN :

{

  < IDENTIFIER: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","0"-"9"] )* >

| < INT_LITERAL:["1"-"9"](<DIGIT>)*>/*integer*/

| <REAL_LITERAL:(<DIGIT>)+/*real*/

 

 | (<DIGIT>)+"."

 

 | (<DIGIT>)+"."(<DIGIT>)+

 

 | "."(<DIGIT>)+>

| < #DIGIT : [ "0"-"9" ] >

}

这些token各个代表了什么,可以看它们的名字,名如其意。我按照他们的类型分为了很多类,有保留字,有操作符,有符号,还有需要忽略的token。单行注释和多行注释对应的那个正则表达式好难写啊,搜索了好久才有点头绪。

还有一点我需要说明的是,我写的jj文件并不是以标准的控制台输入作为输入流的,我是通过文件流来初始化EG1类的。所以我在相应的目录文件夹下建立了program.cmm文件,通过从这个文件中读取源代码来进行词法分析和语法分析。

其实词法分析和语法分析是一块做的,在做的过程中我还参考了如下的自动机模型:

我在写相应的节点函数的时候,是按照大二时候写的C++代码中的函数的名称来写的,这样会让我感到更加的熟悉。我主要增加了以下几种:

指针:在identifier之后需要一个*的正则。

函数的定义:被dec_stmt所调用。

/*函数定义*/

void function_stmt():{}

{

  (

      < LPARENT >

      (

        [((< INT >|< REAL >)(< MULTIPLY >)*< IDENTIFIER >[< ASSIGN > expression()])(< COMMA >((< INT >|< REAL >)(< MULTIPLY >)*< IDENTIFIER > [< ASSIGN > expression()]))*]

      )

      < RPARENT >< LBRACE >

      (

       statement()

      )*

      < RETURN >expression()< SEMICOLON >

      < RBRACE >

  )

}

还有就是类定义

/*类定义*/

void class_stmt():{}

{

  < CLASS >< IDENTIFIER >statement_sequence()

}

 

这就是我所进行的词法分析和语法分析。具体的代码可以参考我的写的JJ文件,这里不再赘述啦。

 

第三部分:CMM的语义分析

 

在做完了词法和语法分析之后,看到算不出来结果,感觉很不爽,所以我又简单的做了一下语义分析。刚开始的时候感觉自己能做到底,但是我忽略了一点,那就是我不能通过这个工具来一遍完成cmm的编译和解释执行,反正我是想不出来,或许这个工具真的做不到这一点,因为它是通过正则表达式来进行代码生成的。我当时做的时候却是想着在词法和语法分析的同时来进行语义分析从而直接把结果计算出来。但是事实证明这么做是行不通的,尽管我构造了符号表,构造了简单的语法数节点,但是或许还真的必须先构造语法树,然后再遍历语法树解释执行才可以。

我做了四则运算,还做了输入输出,但是当做到if语句和while语句的时候,自己实在不知道该怎么做了,才意识到上面那个问题。但是已经做了的部分,语义分析都是没有问题的,都能够产生有效的输出。

这是输入程序:

这是输出结果:

从上图可以看出,我还增加了异常处理哦。

刚才说了,我增加了两个类,一个是SymTab,用来存储全局符号表,另一个是TreeNode,用来描述节点信息。

 

第四部分:JJTree的简介

 

和YACC等一样,JavaCC不直接支持分析树或抽象语法树(AST)的生成,如果要完成这些功能,用户需要自己编写相应的代码。幸运的是,JavaCC有一个扩充支持分析树或抽象语法树的生成,这就是JJTree。

实际上,JJTree可以看成是JavaCC的预处理程序。它的输入文件的后缀是jjt。经它处理之后的jj文件就包含了生成分析树的能力。JJTree采用压栈出栈的方法生成分析树。当它碰到一个非终结符要展开时,它会做一个标记,然后开始分析展开后的各个非终结符(此时,分析子树作为节点压入栈中),之后从栈中弹出合适个数的节点(分析子树),并以被展开的非终结符生成一个新节点,以这个新节点为根节点,以刚弹出的节点为子节点生成新的分析子树(分析树)。

 

因为前面我们已经完成了我们的jj文件,在jj文件中已经完成了对于文法CMM的语言描述,所以这里在写jjt文件的时候,基本上可以把前面的代码copy过来就可以了。这样对于每一个函数,都会成为一棵树的一个结点,并以函数的名字作为结点的名字,如果不想让某个函数生成结点打印出来,只需要将该函数的名字后面加上#void即可。如果希望生成的结点的名字不是函数的名字,同样也可以通过#name(写在函数名之后)来改变。

 

 

第五部分:感悟

 

自己通过同学的指点,再加上一晚上以及一天的练习和琢磨,终于能很熟练的使用这个工具了。总体来说,这个工具真的很好用,简洁易懂,开发一个分析器只需要十几分钟。但是刚开始也是一头雾水。所以没什么是自己学不会的,就像当时自己开发游戏一样,刚开始觉得什么都做不了,最后不是开发出一款不错的嘛。所以要相信自己,并且坚持下去。这样你一定就会成功。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值