JAVACC教程

本文介绍了JavaCC,一个用于生成解析器和词法分析器的工具。JavaCC通过输入的语言定义,自动生成Java代码,简化了解析器和词法分析器的编写。文章通过实例展示了如何使用JavaCC生成词法分析器和解析器,处理简单的加法运算,并逐步扩展到支持减法、乘法和除法,以及括号、负数和历史记录的处理,构建了一个简易的四则运算计算器。
摘要由CSDN通过智能技术生成

JavaCC 简介

JavaCC 与解析器生成器

JavaCC是一个解析器生成器和词法分析生成器。解析器和词法分析器用于处理输入的字符串。编译器和解释器被用来和解析器/词法分析器一起处理文件中的程序。但是解析器/词法分析器在实际中有更加广泛的应用,正如我在本文中希望介绍的一样。 那么,什么是解析器/词法分析器?词法分析器可以吧一个字符串分离成若干叫做“Token”的子字串,并同时对这些Token进行分类。考虑下面的程序:

int main(){
   
            return 0;
    }

一个C语言的词法分析器将会把这段代码分离成下列子串:

int    \s    main    (    )    \s    {    \n    \t
return    \s    0    \s    ;    \n    }    \n    \s

同时,它会对这些子串分类,在本例中分类结果是:
KWINT SPACE ID OPAR CPAR SPACE OBRACE SPACE SPACE
KWRETURN SPACE OCTALCONST SPACE SEMCOLON SPACE CBRACE SPACE EOF

EOF表示文件(输入)结束,这些Token串将会被送到解析器,在C语言中,解析器并不需要所有这些Token,本例中的分类为SPACE的token就被忽略了。解析器将会分析这些Token串来决定程序的结构。通常在编译器中,解析器输出的是程序的树状结构。这个树会被编译器中的代码生成模块处理。

如果输入不符合目标词法和词法时,词法分析器和解析器同时也负责生产错误信息。 JavaCC本身并不是一个词法分析器或者解析器而是一个代码生成器,这意味着它可以根据输入的语言定义输出一个词法分析器和解析器。JavaCC输出的代码是合法的可编译Java代码。 解析器和词法分析器本身就是一个冗长而复杂的组件,手工编写一个这样的程序需要仔细考虑各条件的相互作用,例如分析C语言时,处理整数的代码不可能和处理浮点的代码相互独立,因为整数和浮点数的开头都是数字。而使用像JavaCC这样的分析器生成器时处理整数的规则和处理浮点数的规则是分开书写的,而两者之间的共享代码在代码生成是被自动添加了。这就增强了程序的模块性,同时也意味着定义文件较手工编写的Java代码来说更加易于编写,阅读和更改。通过 JavaCC这样的解析器生成器,程序员们能节省更多时间,同时也能增加编写的软件的质量。

为了更加明确以上内容,先做一个简单地加法吧!

99+42+0+15

我们忽略所有数字和符号间的空格和换行符,除此之外,我们不接受除了10个数字和加号之外的其他字符。 这一节的剩下的部分中的代码都是文件adder.jj的一部分。这个文件包含了符合JavaCC词法的解析器/词法分析器的定义,并且将作为JavaCC 程序的输入文件。

选项和类定义
这个文件的第一部分是:

/*adder.jj 吧一堆数字相加*/
    options{
   
            STATIC = false;
    }
    PARSER_BEGIN(Adder)
    public class Adder{
   
            public static void main(String[] args) throws ParseException, TokenMgrError{
   
                    Adder parser = new Adder(System.in);
                    parser.Start();//方法名竟然是大写开头的
            }
    }
    PARSER_END(Adder)

开头部分的options节说明了除了我们明确指定的STATIC选项,所有其他的JavaCC选项为都默认值。关于 JavaCC选项的详细信息,请参考JavaCC文档。接下来我们定义了一个名为Adder的Java类,但是我们并没有写出这个类的全部代码,JavaCC会在处理时自动生成其他的代码。main方法声明抛出的ParserException和TokenMgrError有可能在执行这些代码时被抛出。

指定一个词法解析器吧!

我们待会儿在看那个main函数,现在我们首先来定义一个词法分析器。在这个简单的例子中,词法分析器的定义只有下面4行:

    SKIP:{
   “ “}
    SKIP:{
   “\n”|”\r”|”\r\n”}
    TOKEN:{
   < PLUS :+>}
    TOKEN:{
   < NUMBER : ([0-9])+ >}
* 第一行说明了空格是一个token,但是会被忽略。于是乎解析器并不会收到任何单独的空格。
* 第二行也说了差不多的事情,只不过被忽略的是换行符,我们用一个小竖线分割了不同的匹配模式。
* 第三行告诉JavaCC一个单独的加号是一个token,并且给这个Token一个名字:PLUS。
* 最后一行叫JavaCC吧连续的数字当作一个token,命名为NUMBER,如果你使用过Perl或者 Java的正则表达式库,就应该能明白定义的含义。

让我们仔细看一下这个表达式([“0”-“9”])+。圆括号中间的部分[“0”-“9”]是一个匹配所有数字字符的正则表达式(不过正则表达式好像不用引号 ——译者吐槽),这表明所有unicode中的0-9之间的支付都能被匹配。其他的部分:(x)+可以匹配一连串符合模式x的字符。所以表达式 ([“0”-“9”])+就可以匹配一个或者多个连续的数字。这四行中的每一行都被称作一个“正则表达式结果(regular expression production)” 。

还有另一种可以被词法分析器生成的token,它的名字是EOF,正如其名,代表了输入的终止。不能,也不需要任何对EOF的匹配,JavaCC会自动生成他们。 考虑下面的输入:

“123 + 456\n”

我们定义的词法分析器将会找到7个token: NUMBER, 空格, PLUS, 又一个空格, 另一个数字,一个换行, 然后是EOF,当然,标记了SKIP的token不会被传到解析器。于是乎,我们还没出生的解析器会看到这些东西:

NUMBER, PLUS, NUMBER, EOF

现在试想一个我们没有想到的问题:如果有其他字符呢?例如:

“123 – 456\n”

在处理完第一个空格之后,我们的可爱的词法分析器将遇到一个不认识的字符:减号,由于没有任何token的定义可以容纳一个减号,词法分析器将会扔一个TokenMgrError出来以示抗议。 现在我们看看另一种情况:

“123++456\n”

我们的词法分析器会得出如下结论:

NUMBER,PLUS,PLUS,NUMBER,EOF

当然,词法分析器并不能知道这个token序列是否有意义,这通常是解析器的工作。我们接下来要定义的解析器会找到这个有两个加号的错误,然后完美的罢工。所以解析器实际上处理的只有:

NUMBER,PLUS,PLUS

同时,跳过(skip)一个token并不代表忽略(ignore)。考虑下列输入:

“123 456\n”

词法分析器会发现3个token:两个NUMBER和一个空格。然后解析器又会优美的罢工了……

出现吧,我的解析器!

解析器的定义使用了一种叫BNF范式的东西,这看起来有点像Java的方法定义,但是这为编程语言提供了一个很好的思路,就是结合,我感觉这个是java和正规式的杂交吧,而定义和方法体又分别在两个{}内,由这两个{}共同组成了一个方法,所以学习不能死板:

void Start(): 
{
   }
{
   
        <NUMBER>
        (
                <PLUS>
                <NUMBER>
        )*
        <EOF>
}

这个BNF范式声明了一个有效的token序列的模式,从而避免了错误的语法。我们研究一下它的意思:一个以 NUMBER开头的序列,以EOF结束,中间存在若干以一个PLUS和一个NUMBER组成的子序列。 正如所见,一个解析器仅仅决定了一个输入序列是否合法,而没有吧数字们实际上加起来。待会儿我们会来调教这个解析器好让他能够好好的干活,但是首先我们先让我们目前的成果跑一下吧!

开始炼成解析器和词法分析器咯!

我们已经有一个叫adder.jj的文件了,接下来我们用JavaCC进行提炼。原作者罗嗦了一堆OS 相关的玩意儿我们掠过不表,直接看就行了:

E:\javacc-book>javacc adder.jj Java Compiler Compiler Version 4.2
(Parser Generator) (type “javacc” with no arguments for help) Reading
from file adder.jj . . . File “TokenMgrError.java” does not exist.
Will create one. File “ParseException.java” does not exist. Will
create one. File “Token.java” does not exist. Will create one. File
“SimpleCharStream.java” does not exist. Will create one. Parser
generated successfully.

这个操作生成了7个Java类,每个都在独立的java文件中:

* TokenMgrError是一个简单的错误类;用于表示词法分析器参数的错误,父类是java.lang.Throwable
* ParserException 是另一个代表错误的异常类;表示解析器罢工的情况,父类是java.lang.Excpetion
* Token是表示token的类,每个Token对象都有一个int类型的字段:kind,表示它的类型(PLUS,NUMBER或者EOF)和一个String类型的字段:image,存储了token所代表的内容。
* SimpleCharStream 是一个辅助类,用于吧输入的字符串传给词法分析器
* AdderConstants 是一个包含了常量的辅助性接口
* AdderTokenManager 就是传说中的词法分析器咯
* Adder就是可爱的解析器

现在我们可以把它们编译一下咯~

E:\javacc-book>javac *.java
注意:Adder.java 使用了未经检查或不安全的操作。
注意:要了解详细信息,请使用 -Xlint:unchecked 重新编译。

终于要运行咯!

现在我们来回头看看 Adder这个类吧。

static void main(String[
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JYL_CG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值