例1:整数加法运算

在这个例子中,我们将判断如下输入的式子是否是一个合法的加法运算:

99 + 42 + 0 + 15

并且在输入上面式子的时候,数字与加号之间的任何位置,都是可以有空格或者换行符的,也就说,即使我们输入的式子是下面这种形式,我们所编写的词法和语法分析器也应该要能判断出来它是一个合法的加法运算表示形式:

99 + 42 + 0

+ 15

(注:上面输入的式子中既有空格,也有制表符,还有换行符)

1.Option块和class声明块

语法描述文件的第一部分是:

/* adder.jj Adding up numbers */

options {

STATIC = false ;

}

PARSER_BEGIN(Adder)

class Adder {

public static void main( String[] args ) throws ParseException, TokenMgrError {

Adder parser = new Adder( System.in );

parser.Start();

}

}

PARSER_END(Adder)

上面的代码可以分为两个部分,一个是options块,另一个是PARSER_BEGIN(XXX)…… PARSER_END(XXX)块。

在options中,几乎所有配置项的默认值都适用于本例子,除了 STATIC选项,STATIC默认是true,这里要将其修改为false,使得生成的函数不是static 的。

接下来是ARSER_BEGIN(XXX)……PARSER_END(XXX)块,这里定义了一个名为 Adder的类,当然在这个块中定义的并非是Adder类的全部,JavaCC会根据.jj描述文件的其他部分的描述,来生成Adder的其他声明信息。另外注意到,在该类的main方法声明中, 抛出了两个异常类,分别为ParseException和TokenMgrError,这两个异常类会在使用javacc 命令编译当前.jj描述文件的时候生成。)

2.词法描述器

我们在后面还会再讲到main方法。这里让我们先看看词法描述器。当前例子中所需的词法描述器通过如下的四行来进行描述:

SKIP : { " "}

SKIP : { "\n" | "\r" | "\r\n" }

TOKEN : { < PLUS : "+" > }

TOKEN : { < NUMBER : (["0"-"9"])+ > }

第一行是SKIP,表示将会被词法分析器忽略的部分:空格。被忽略掉意味着,它们将不会被传给语法分析器。

第二行也是SKIP,定义了将会被忽略的部分:换行符。之所以会有几个,是因为在不同的系统中,换行符有不同的表示方式——在Unix/Linux系统中,换行符是"\n";在Windows系统中,换行符是"\r";在mac系统中,换行符则是"\r\n"。这几个换行符用一个竖杠分隔,表示“或”的意思。

第三行定义了一个名为PLUS的token,用它来表示加号"+"。

第四行定义了一个名为NUMBET的token,用它来表示([”0”-”9”])+,即所有的正整数序列。可以注意到([”0”-”9”])+是一个正则表达式。

这四行描述都可以被称为表达生产式。

事实上,词法分析器中还可以生成一种token,这种token用EOF表示,用来代表输入序列的末尾。但是没有必要在词法分析器部分显式的定义EOF这个token,因为JavaCC会自动处理文件的结束符了。

假设有如下的输入:

“123 + 456\n”

词法分析器将解析的7个token,依次是NUMBER、空格、PLUS、空格、NUMBER、换行符、EOF。在解析出来的这些token中,被标记为SKIP的token将不会被往下传递给语法分析器。词法分析器在分析完成之后,只会将以下token传递给语法分析器:NUMBER, PLUS, NUMBER, EOF。

再假设一种不合法的输入,如下:

“123 - 456\n”

词法分析器在对上面的输入进行解析时,解析到的第一个token是NUMBER,第二个token是空格,接下来,它就遇到了一个减号字符——因为在我们上面的词法描述器中没有定义减号这个token,因此就无法对其进行解析,此时词法分析器就抛出一个异常:TokenMgrError。

再看另外一种输入情况:

“123 ++ 456\n”

这时词法分析器可以对其进行解析,并给语法分析器传递如下的token序列:NUMBER, PLUS, PLUS, NUMBER, EOF。

很明显,这不是一个合法的“加运算”的输入它连续出现了两个PLUS token。但是,词法分析器的任务是将输入解析成一个个的token,而不是判断token的顺序是否正确。判断tokens序列的顺序是否正确是语法分析器的任务。接下来将会介绍到的语法分析器,它在分析到第二个PLUS token的时候,将会检测到错误,一旦检测到错误,它就停止从词法分析器请求tokens了,所以,实际上真正传递给语法分析器的tokens序列只有NUMBER, PLUS, PLUS。

3.语法分析器

语法分析器的描述由BNF生产式构成。可以看到,语法分析器的描述看起来跟java的方法定义形式有点相似。

void Start() :

{}

{

(

)*

}

上面的BNF生产式指定了合法的token序列的规则:必须以NUMBER token开头,以EOF token结尾,而在NUMBER和EOF中间,可以是0至多个PLUS和NUMBER的token,而且必须是PLUS后跟着NUMBER。

根据上面的语法描述器语法,解析器将只检测输入序列是否无错误,它不会把数字加起来。我们接下来将很快修改解析器描述文件以更正此问题,但是首先,让我们生成Java组件并运行它们。

4.生成词法描述器和语法描述器

将前面部分提到的几个部分都合并起来,保存成adder.jj文件:

/* adder.jj Adding up numbers */

options {

STATIC = false ;

}

PARSER_BEGIN(Adder)

class Adder {

public static void main( String[] args ) throws ParseException, TokenMgrError {

Adder parser = new Adder( System.in );

parser.Start();

}

}

PARSER_END(Adder)

SKIP : { " "}

SKIP : { "\n" | "\r" | "\r\n" }

TOKEN : { < PLUS : "+" > }

TOKEN : { < NUMBER : (["0"-"9"])+ > }

void Start() :

{}

{

(

)*

}

然后在上面调用javacc命令。下面是在windows系统上的演示。

执行完之后,会生成7个java文件。如下所示:

其中:

TokenMgrError 是一个简单的定义错误的类,它是Throwable类的子类,用于定义在词法分析阶段检测到的错误。

ParseException是另一个定义错误的类。它是Exception 和Throwable的子类,用于定义在语法分析阶段检测到的错误。

Token类是一个用于表示token的类。我们在.jj文件中定义的每一个token(PLUS, NUMBER, or EOF),在Token类中都有对应的一个整数属性来表示,此外每一个token都有名为image的string类型的属性,用来表示token所代表的从输入中获取到的真实值。

SimpleCharStream是一个转接器类,用于把字符传递给语法分析器。

AdderConstants是一个接口,里面定义了一些词法分析器和语法分析器中都会用到的常量。

AdderTokenManager 是词法分析器。

Adder 是语法分析器。

接下来我们对这些java文件进行编译:

编译完成之后,可得到对应的class文件:

5.执行程序

现在,让我们来看看Adder类中的main方法:

Pubic static void main( String[] args ) throws ParseException, TokenMgrError {

Adder parser = new Adder( System.in );

parser.Start();

}

首先注意到main方法有可能抛出两个异常错误类:ParseException和TokenMgrError,它们都是Throwable类的子类。这并不是一种好的编码习惯,理论上我们应该对着两异常进行try-catch捕获,但是在本例中我们暂且将其抛出,以使得代码简洁易懂。

main方法的第一行代码new了一个parser对象,使用的是Adder类的默认构造器,它接收一个InputStream类型对象作为输入。此外Adder类还有一个构造器,这个构造器接收的入参是一个Reader对象。构造函数依次创建一个SimpleCharacterStream类实例和一个AdderTokenManager类的实例(即词法分析器对象)。因此,最后的效果是,词法分析器通过SimpleCharacterStream实例对象从System.in中读取字符,而语法分析器则是从语法分析器中读取tokens。

第二行代码则是调用了语法分析器的一个名为Start()的方法。对于在.jj文件中的每一个BNF生产式,javacc在parser类中都会生成相应的方法。此方法负责尝试在其输入流中找到与输入描述匹配的项。比如,在本例中,调用Start()方法将会使得语法分析器尝试从输入中符合下面描述的tokens序列:

()*

我们可以事先准备一个input.txt文件,里面的内容下面会说到。在准备了输入文件之后,接下来就可以用下面的命令来执行程序了。

执行的结果有可能为以下3中情况之一:

程序报一个词法错误。比如,词法错误只有在词法分析器无法解析出输入的字符时才会抛出。假设input.txt文件中的内容是123 – 456,在这种情况下,程序就会抛出一个TokenMgrError的错误,报错的信息是:Exception in thread ”main” TokenMgrError: Lexical error at line 1,column 5. Encountered: ”-” (45), after : ””。即:词法分析器不认识“-”减号,因为在.jj文件中,我们并没有对“-”定义相应的token。

程序报一个语法错误。当语法分析器接收到的tokens序列不匹配Start()方法中的规范时,就会抛出该错误。比如,若input.txt文件中的内容是123 ++ 456或123 456又或者什么都不写,此时程序就会抛出一个ParseException.异常,对于123 ++ 456来说,抛出的异常就是:Exception in thread ”main” ParseException: Encountered ”+” at line 1, column 6. Was expecting: ...。

若输入中的tokens序列跟Start()方法中的规范匹配时,将不抛出任何错误异常,程序正常结束。

但是从我们的代码可以看出,当我们的输入是合法的时候,语法分析器什么也不干,它仅仅用于检查我们的输入是否符合相应的表达式规范而已。在下一小节中,我们将对.jj文件进行一些修改,使得生成的语法分析器有更多的用处。

6.生成的代码解析

想要知道JavaCC生成的语法分析器是如何工作的,我们需要看一些它生成的代码。

final public void Start() throws ParseException {

jj_consume_token(NUMBER);

label_1:

while (true) {

jj_consume_token(PLUS);

jj_consume_token(NUMBER);

switch ((jj_ntk == -1) ? jj_ntk() : jj_ntk) {

case PLUS:

;

break;

default:

jj_la1[0] = jj_gen;

break label_1;

}

}

jj_consume_token(0);

}

jj_consume_token方法以token类型作为入参,并试图从语法分析器中获取指定类型的token,如果下一个获取到的token跟语法分析器中定义的不同,此时就会抛出一个异常。看下面的表达式:

(jj_ntk == -1) ? jj_ntk() : jj_ntk

该表达式计算下一个未读的token。

程序的最后一行是要获取一个为0的token。JavaCC总是使用0来表示EOF的token。

java cc_6.JavaCC官方入门指南-例1相关推荐

  1. 热门Java开发工具IDEA入门指南——了解并学习IDE

    IntelliJ IDEA,是java编程语言开发的集成环境.IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手.代码自动提示.重构.JavaEE支持.各类版本工具(git.sv ...

  2. 热门Java开发工具IDEA入门指南——IntelliJ IDEA概述(下)

    IntelliJ IDEA,是java编程语言开发的集成环境.IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手.代码自动提示.重构.JavaEE支持.各类版本工具(git.sv ...

  3. 热门Java开发工具IDEA入门指南——如何安装IntelliJ IDEA(上)

    IntelliJ IDEA,是java编程语言开发的集成环境.IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手.代码自动提示.重构.JavaEE支持.各类版本工具(git.sv ...

  4. java车机_入门指南-高德地图车机版 | 高德地图API

    Android端 通讯方式 通过系统广播调用的方式实现功能调用或信息透出 ACTION: 高德发送的广播ACTION: AUTONAVI_STANDARD_BROADCAST_SEND 高德接收的广播 ...

  5. Consul 入门指南

    一.安装 Consul Consul 下载地址:https://www.consul.io/downloads.html,下载后解压就是一个可执行的二进制文件consul,配置好环境变量,检查 con ...

  6. 编程入门指南 v1.4

    著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:Badger 链接:http://zhuanlan.zhihu.com/xiao-jing-mo/19959253 来源: ...

  7. 《转载》编程入门指南 v1.4

    编程入门指南 v1.4 Badger · 8 个月前 作者:@萧井陌, @Badger 自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0 CoCode ...

  8. JNI/NDK入门指南之jobject和jclass

          JNI/NDK入门指南之jobject和jclass Android JNI/NDK入门指南目录 JNI/NDK入门指南之正确姿势了解JNI和NDK JNI/NDK入门指南之JavaVM和 ...

  9. java 多模块项目 包路径冲突_多智能体仿真建模在交通中的应用|MATSim入门指南...

    点击蓝字|关注我们 无论是你是更侧重于工程实践还是更侧重于理论研究,交通仿真总是一个绕不开的话题.常用的交通仿真软件包括Vissim.Paramics.Anylogic.Transcad等等.近年来, ...

  10. Java Gradle入门指南之依赖管理(添加依赖、仓库、版本冲突)

    Java Gradle入门指南之依赖管理(添加依赖.仓库.版本冲突) 作者:@gzdaijie 本文为作者原创,转载请注明出处:http://www.cnblogs.com/gzdaijie/p/52 ...

最新文章

  1. C 关于页面刷新和combobox的使用
  2. R语言数据热力图绘制实战(基于原生R函数、ggplot2包、plotly包)
  3. c语言五子棋linux,在linux下ubuntu的五子棋游戏c语言代码.doc
  4. Java并发编程之:Vector和ArrayList的区别
  5. 论坛 php 最好的,新手问下,这是人气最好的php论坛吗?
  6. 矩阵乘法 算法训练 试题_蓝桥杯习题集_ 算法训练 矩阵乘法
  7. 查询数据库中字段内容相同的记录
  8. python中装饰器的作用_Python中装饰器的用法
  9. 怎么下载python的各种库_各种Python库安装包下载地址与安装过程详细介绍(Windows版)...
  10. 〖Python APP 自动化测试实战篇①〗 - 大话闲扯 APP 自动化
  11. My Sunshine
  12. Android 自启项管理器工具
  13. Polkadot + DeFi | 透明公平、高效交易的去中心化金融未来可期
  14. 职场小白新建SSM项目
  15. 村庄规划中的产业发展规划
  16. 如何组建局域网,用组和域管理分别是怎样设置的?
  17. 学Linux网络管理有它就够了
  18. FFT之频率与幅值的确定
  19. Kriging代理模型类毕业论文文献包含哪些?
  20. git submodule 添加子仓库

热门文章

  1. SQL导入/导出Excel
  2. Windows下安装最新的Apache+PHP+MySQL方法--记录方便自己参考
  3. Linux(CentOS)下安装tesseract-ocr以及配置依赖leptonica
  4. 配置ArcGIS Server使用Windows AD Windows集成身份认证
  5. CS20Chapter2
  6. SQL ISNULL 函数
  7. iOS 四种延时的方法
  8. !学习笔记:前端测试 、前端调试、console 等
  9. ZOJ1457 || HDU1016 素数环
  10. 使用 pandas读取 excel 文件的数据