转自:http://blog.youkuaiyun.com/kmlzkma
今天刚学,啥也不明白,网罗了一堆文档教程什么的,英文的中文的啥都有了,看了会最简单的,还凑合写出来,看看能说
明白不. 看教程上的例子,学着写了一个,还真好使了,就是一个十进制的加法表达式,比如
1+1
呵呵,我知道等于2,可这个小程序不是算结果的,是检验表达式合法不,基本就这样了,得先写个什么.jj文件规范JavaCC的解析器和词法分析器,将会作为JavaCC处理文件的输入.就叫"Adder.jj"吧,跟教程上一样.
- PARSER_BEGIN(Adder)
- public class Adder{
- public static void main(String[] args)
- throws ParseException,TokenMgrError{
- Adder adder = new Adder(System.in);
- adder.start();
- }
- }
- PARSER_END(Adder)
- SKIP:{" "|"/t"|"/n"|"/r"|"/r/n"}
- TOKEN:{
- <PLUS:"+">
- |<NUMBER:(["0"-"9"])+>
- }
- void start():
- {}
- {
- <NUMBER>(<PLUS><NUMBER>)*<EOF>
- }
1.PARSER_BEGIN(Adder)与PARSER_END(Adder)之间的代码是Java语法,这里生成Adder.java的主程序入口,基本就是这种格式,其中要抛出两个异常: 1.1 ParseException : 解析错误,解析器负责检查,Exception的子类.比如输入"1++1",这种就是解析错误,因为它里面的分割符都是我们在TOKEN:中定义的,没有其它字符. 1.2 TokenMgrError : 分割符错误,词法分析器负责检查,Error的子类.比如输入"1-1"就是这种错误,因为TOKEN:中没有定义"-". 这两个类都会自动生成,现在刚研究还不知道为啥一个来之Exception,一个来自Error,以后会明白的.
2.new Adder(System.in),这个构造子这还不是很明白,不知道为啥默认成这型的,就知道是自动产生的,以后再说.
3.start() 方法是解析的主体,是下面需要实现的.
4.
SKIP:{" "|"/t"|"/n"|"/r"|"/r/n"}
这个定义的是忽略的字符,它们虽然构成分割符但不被送给解析器,就是你在输入的时候有这些字符不会发生错误,但它们不会被解析出来.这里定义了5个,基本上就是空格,跳格,回车换行什么的,以为各个操作系统间的回车换行什么的据说不一样,我也不知道都咋回事,都写上吧.各个元素之间用"|"分开,格式就是这样了.
5.
- TOKEN:{
- <PLUS:"+">
- |<NUMBER:(["0"-"9"])+>
- }
这个是说分割符了,就是把一个句子里的什么字符取出来,也就是你这句子里只能有这些规定好的字符,别的不行,这里就是"+"号和0-9十个数字. 两着用"|"分开. 每个分割符是这样规定的,用<>括上,代号和符号用":"隔开,就是用前面的代号代替后面在句子中会出现的元素或元素的集合.这些代号在下面会用到.其中数字用了正则式,就是1个或1个以上的由0-9之间的数字组成的串.
4和5两个部分如果用不到可以没有.
6.
- void start():
- {}
- {
- <NUMBER>
- (<PLUS><NUMBER>)*
- <EOF>
- }
这个就是传说中的解析器了,说是符合什么BNF产生式,基本就这形状了,以后漫漫弄明白吧.里面看起来还像是正则式,就是用符号替换了,是以数字开头,中间跟0个或0个以上的由+和数字组成的序列,这个顺序不能变,最后是结束符,自带定义的.
程序基本上就这么回事了,因为简单,也形式上也没什么.
然后编译吧,设好环境变量什么的,在控制台切换到在Adder.jj所在文件夹:
javacc Adder.jj
成功就会出现提示:
- Java Compiler Compiler Version 4.0beta1 (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文件:
- Adder.java : 生成的主程序,里面是程序入口,也是解析器.可以看到片段如下:
- /* Generated By:JavaCC: Do not edit this line. Adder.java */
- public class Adder implements AdderConstants {
- public static void main(String[] args) throws ParseException, TokenMgrError{
- Adder adder = new Adder(System.in);
- adder.start();
- }
- static final public void start() throws ParseException {
- jj_consume_token(NUMBER);
- label_1:
- while (true) {
- switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
- case PLUS:
- ;
- break;
- default:
- jj_la1[0] = jj_gen;
- break label_1;
- }
- jj_consume_token(PLUS);
- jj_consume_token(NUMBER);
- }
- jj_consume_token(0); }
上面我们定义到:
void start():{}{ <NUMBER>(<PLUS><NUMBER>)*<EOF>}
这里start()方法先消费一个number:jj_consume_token(NUMBER);然后是一个(<PLUS><NUMBER>)*的过程,循环中(jj_ntk==-1)?jj_ntk():jj_ntk来判断下一个未读取的token类型的,感觉从循环体上来看,这个合法的token类型,只能是<PLUS>或<EOF>,因为每次成对的消费PLUS和NUMBER,所以是PLUS就跳出switch,执行20,21行进行消费<PLUS><NUMBER>,如果是其它的,感觉这里只能是0,也就是<EOF>.这个类里还有其它的一些数据,比如几个重载的构造子,一些变量和方法.
-
Token.java : 分割符的信息.里面有一些数据成员和方法,包含了我们定义的那3个token.其中image是我们常在程序中调用的成员,它是一个 public String 类型,记录了 从输入中得到的token,比如我们的"1","+","1",都以字符串的方式存储在image里供人们直接使用.还有一些上面程序片段看到的已经调用过的方法.
-
SimpleCharStream.java : 实现CharStream接口的适配器类,传递字符到词法分析器.貌似只用ASCII,非unicode.
-
AdderConstants.java : 接口.定义了词法分析器和解析器用到的一些常量.这里可以清楚的看到:
- int EOF = 0;
- int PLUS = 6;
- int NUMBER = 7;
- int DEFAULT = 0;
- String[] tokenImage = {
- "<EOF>",
- "/" /"",
- "/"//t/"",
- "/"//n/"",
- "/"//r/"",
- "/"//r//n/"",
- "/"+/"",
- "<NUMBER>",
- };
- AdderTokenManager.java : 词法分析器.
- TokenMgrError.java 和 ParseException.java : 上面说到的异常类.
编译了这些文件:
javac *.java
运行 Adder :
java Adder
这时候控制台等待输入,可以试试拉:
1+1
没提示就是合法,ctrl + c 结束输入
可以试着弄点错误什么的,看看异常类型跟错误的情况是怎么对应的.
都完事了,还挺简单的,呵呵.啊 累死了,下回再说.
上次的表达式没有计算功能,这次继续,让它先算加法.
先看BNF产生式代码: Adder.jj
- int start() throws NumberFormatException :
- {
- Token t;
- int i;
- int value;
- }{
- t = <NUMBER>
- { i = Integer.parseInt( t.image ); }
- { value = i; }
- (
- <PLUS>
- t = <NUMBER>
- { i = Integer.parseInt( t.image ); }
- { value += i; }
- )*
- <EOF>
- { return value; }
- }
先看这个方法,首先行1语句的返回值由void变成int了,因为我们要返回和.行2和行6的大括号之间原来是声明变量用的,是java语法,因为java语句中不能直接调用<NUMBER>,所以得用个变量t.i ; 中间临时变量 i,得到t的具体数可以被java识别的据. value是累加后的和.在"{...}"书写java代码,并且顺序相关.8行中的t.image得到token的字符串表现形式.然后解析成int,最后通过 value += i 进行累加.最后返回累加的和,这段代码比较容易理解,可看起来还是杂乱,我们可以用"函数"的概念进行重构.
- int start() throws NumberFormatException :
- {
- int i ;
- int value ;
- }{
- value = primary()
- (
- <PLUS>
- i = primary()
- { value += i ; }
- )*
- <EOF>
- { return value ; }
- }
- int primary() throws NumberFormatException :
- {
- Token t ;
- }{
- t = <NUMBER>
- { return Integer.parseInt( t.image ) ; }
- }
这里用了两个BNF产生式,可读性加强了.
主程序的要对int类型的返回值进行相应改变.
- public static void main( String[] args )throws ParseException, TokenMgrError, NumberFormatException {
- Adder adder = new Adder( System.in ) ;
- int value = adder.start() ;
- System.out.println(value);
- }
然后进行编译,运行测试.注意,先把原来生成的文件全删除.程序还是比较好理解的,下面加入减法.
- PARSER_BEGIN(Adder)
- public class Adder{
- public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
- Adder adder = new Adder(System.in);
- int value = adder.start();
- System.out.println(value);
- }
- }
- PARSER_END(Adder)
- SKIP:{" "|"/t"|"/n"|"/r"|"/r/n"}
- TOKEN:{
- <PLUS:"+">
- |<MINUS:"-">
- |<NUMBER:(["0"-"9"])+>
- }
- int start() throws NumberFormatException :
- {
- int value;
- }
- {
- {value = primary();}
- (
- <PLUS>
- {value += primary();}
- |<MINUS>
- {value -= primary();}
- )*
- <EOF>
- {return value;}
- }
- int primary() throws NumberFormatException :
- {
- Token t;
- }
- {
- t = <NUMBER>
- {return Integer.parseInt(t.image);}
- }
加法和减法都有了,可现在有个问题,每次只有按ctrl + c退出程序才可以得到结果,能不能每次回车换行时候即时输出呢?
首先需要把输出流定义到System.out上:
- PARSER_BEGIN(Adder)
- import java.io.PrintStream ;
- public class Adder{
- public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
- Adder adder = new Adder(System.in);
- adder.start(System.out);
- }
- }
- PARSER_END(Adder)
因为要识别回车换行,所以Token需要识别回车换行:
- SKIP:{" "|"/t"}
- TOKEN:{
- <PLUS:"+">
- |<MINUS:"-">
- |<NUMBER:(["0"-"9"])+>
- |<EOL:"/n"|"/r"|"/r/n">
- }
start BNF产生式要这样改变:
- void start(PrintStream printStream) throws NumberFormatException :
- {
- int value;
- }
- {
- (
- value = expression()
- <EOL>
- { printStream.println( value ) ; }
- )*
- <EOF>
- }
- int expression() throws NumberFormatException :
- {
- int value ;
- }
- {
- value = primary()
- (
- <PLUS>
- { value += primary() ; }
- |<MINUS>
- { value -= primary() ; }
- )*
- { return value ; }
- }
用BNF标志表示:
start → (expression EOL) * EOF expression → primary (PLUS primary)* primary → NUMBER
接下来加入乘除法,由于除法会产生小数,所以先把int换成double,而浮点数的Token 可以这样定义:
TOKEN : { < NUMBER : <DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "."<DIGITS> > } TOKEN : { < #DIGITS : (["0"-"9"])+ > }
其中"#"符号,代表这样一种情况:它后面所跟的名字并不代表一种Token类型,它对于词法分析器只代表一个位置,仅此而已.
其它各返回值都需要把int换成double.
Token中加入乘除符号:
TOKEN : { < TIMES : "*" > } TOKEN : { < DIVIDE : "/" > }
由于乘除法的优先级高于加减法,所以需要这样处理:
expression → term (PLUS term | MINUS term)* term → primary (TIMES primary | DIVIDE primary)*
完整代码:Adder.jj
- PARSER_BEGIN(Adder)
- import java.io.PrintStream ;
- public class Adder{
- public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
- Adder adder = new Adder(System.in);
- adder.start(System.out);
- }
- }
- PARSER_END(Adder)
- SKIP:{" "|"/t"}
- TOKEN:{
- <PLUS:"+">
- |<MINUS:"-">
- | <TIMES:"*">
- |<DIVIDE:"/">
- |<NUMBER:<DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "."<DIGITS> >
- |<#DIGITS : (["0"-"9"])+ >
- |<EOL:"/n"|"/r"|"/r/n">
- }
- void start(PrintStream printStream) throws NumberFormatException :
- {
- double value;
- }
- {
- (
- value = expression()
- <EOL>
- { printStream.println( value ) ; }
- )*
- <EOF>
- }
- double expression() throws NumberFormatException :
- {
- double value ;
- }
- {
- value =term()
- (
- <PLUS>
- { value += term() ; }
- |<MINUS>
- { value -= term() ; }
- )*
- { return value ; }
- }
- double term() throws NumberFormatException :
- {
- double value ;
- }
- {
- value = primary()
- (
- <TIMES>
- { value *= primary() ; }
- |<DIVIDE>
- { value /= primary() ; }
- )*
- { return value ; }
- }
- double primary() throws NumberFormatException :
- {
- Token t;
- }
- {
- t = <NUMBER>
- {return Double.parseDouble(t.image);}
- }
编译运行一下,这回象样多了,呵呵.再加点猛料,各种括号:
增加Token符号:
|<OPEN_PAR: "(" > |<CLOSE_PAR: ")" > |<OPEN_BET : "["> |<CLOSE_BET : "]"> |<OPEN_BCE : "{"> |<CLOSE_BCE : "}">
BNF标志:
primary → NUMBER | OPEN_PAR expression CLOSE_PAR | OPEN_BET expression CLOSE_BET | OPEN_BCE expression CLOSE_BCE
有点像递归函数,不难理解.
- PARSER_BEGIN(Adder)
- import java.io.PrintStream ;
- public class Adder{
- public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
- Adder adder = new Adder(System.in);
- adder.start(System.out);
- }
- }
- PARSER_END(Adder)
- SKIP:{" "|"/t"}
- TOKEN:{
- <PLUS:"+">
- |<MINUS:"-">
- |<TIMES:"*">
- |<DIVIDE:"/">
- |<NUMBER:<DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "."<DIGITS> >
- |<#DIGITS: (["0"-"9"])+ >
- |<OPEN_PAR: "(" >
- |<CLOSE_PAR: ")" >
- |<OPEN_BET : "[">
- |<CLOSE_BET : "]">
- |<OPEN_BCE : "{">
- |<CLOSE_BCE : "}">
- |<EOL:"/n"|"/r"|"/r/n">
- }
- void start(PrintStream printStream) throws NumberFormatException :
- {
- double value;
- }
- {
- (
- value = expression()
- <EOL>
- { printStream.println( value ) ; }
- )*
- <EOF>
- }
- double expression() throws NumberFormatException :
- {
- double value ;
- }
- {
- value =term()
- (
- <PLUS>
- { value += term() ; }
- |<MINUS>
- { value -= term() ; }
- )*
- { return value ; }
- }
- double term() throws NumberFormatException :
- {
- double value ;
- }
- {
- value = primary()
- (
- <TIMES>
- { value *= primary() ; }
- |<DIVIDE>
- { value /= primary() ; }
- )*
- { return value ; }
- }
- double primary() throws NumberFormatException :
- {
- Token t;
- double value;
- }
- {
- t = <NUMBER>
- {return Double.parseDouble(t.image);}
- |<OPEN_PAR> value = expression() <CLOSE_PAR>
- { return value ; }
- |<OPEN_BET>value = expression() <CLOSE_BET>
- { return value ; }
- |<OPEN_BCE>value = expression() <CLOSE_BCE>
- { return value ; }
- }
4则混合运算基本OK了,来吧,测试一下成果.忙了半天了.
javacc Adder.jj javac *.java java Adder
控制台输入:
2*{1*[3*(1+1)-2]+1}
看看得到了多少?