javacc学习笔记

转自:http://blog.csdn.net/kmlzkma

今天刚学,啥也不明白,网罗了一堆文档教程什么的,英文的中文的啥都有了,看了会最简单的,还凑合写出来,看看能说

明白不. 看教程上的例子,学着写了一个,还真好使了,就是一个十进制的加法表达式,比如

1+1

呵呵,我知道等于2,可这个小程序不是算结果的,是检验表达式合法不,基本就这样了,得先写个什么.jj文件规范JavaCC的解析器和词法分析器,将会作为JavaCC处理文件的输入.就叫"Adder.jj"吧,跟教程上一样.

  1. PARSER_BEGIN(Adder)
  2.  public class Adder{
  3.   public static void main(String[] args)
  4. throws ParseException,TokenMgrError{
  5.    Adder adder = new Adder(System.in);
  6.    adder.start();
  7.   }
  8.  }
  9. PARSER_END(Adder)
  10. SKIP:{" "|"/t"|"/n"|"/r"|"/r/n"}
  11. TOKEN:{
  12.  <PLUS:"+">
  13.  |<NUMBER:(["0"-"9"])+>
  14. }
  15. void start():
  16. {}
  17. {
  18.  <NUMBER>(<PLUS><NUMBER>)*<EOF>
  19. }

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.

  1. TOKEN:{
  2. <PLUS:"+">
  3.  |<NUMBER:(["0"-"9"])+>
  4. }

这个是说分割符了,就是把一个句子里的什么字符取出来,也就是你这句子里只能有这些规定好的字符,别的不行,这里就是"+"号和0-9十个数字. 两着用"|"分开. 每个分割符是这样规定的,用<>括上,代号和符号用":"隔开,就是用前面的代号代替后面在句子中会出现的元素或元素的集合.这些代号在下面会用到.其中数字用了正则式,就是1个或1个以上的由0-9之间的数字组成的串.

4和5两个部分如果用不到可以没有.

6.

  1. void start():
  2. {}
  3. {
  4.  <NUMBER>
  5.         (<PLUS><NUMBER>)*
  6.  <EOF>
  7. }

这个就是传说中的解析器了,说是符合什么BNF产生式,基本就这形状了,以后漫漫弄明白吧.里面看起来还像是正则式,就是用符号替换了,是以数字开头,中间跟0个或0个以上的由+和数字组成的序列,这个顺序不能变,最后是结束符,自带定义的.

程序基本上就这么回事了,因为简单,也形式上也没什么.

然后编译吧,设好环境变量什么的,在控制台切换到在Adder.jj所在文件夹:

javacc Adder.jj

成功就会出现提示:

  1. Java Compiler Compiler Version 4.0beta1 (Parser Generator)
  2. (type "javacc" with no arguments for help)
  3. Reading from file Adder.jj . . .
  4. File "TokenMgrError.java" does not exist.  Will create one.
  5. File "ParseException.java" does not exist.  Will create one.
  6. File "Token.java" does not exist.  Will create one.
  7. File "SimpleCharStream.java" does not exist.  Will create one.
  8. Parser generated successfully.

一共生成了7个.java文件:

  1. Adder.java : 生成的主程序,里面是程序入口,也是解析器.可以看到片段如下:

     

    1. /* Generated By:JavaCC: Do not edit this line. Adder.java */
    2. public class Adder implements AdderConstants {
    3.   public static void main(String[] args) throws ParseException, TokenMgrError{
    4.      Adder adder = new Adder(System.in);
    5.      adder.start();
    6.   }
    7.  
    8.   static final public void start() throws ParseException {
    9.     jj_consume_token(NUMBER);
    10.     label_1:
    11.     while (true) {
    12.       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
    13.       case PLUS:
    14.         ;
    15.         break;
    16.       default:
    17.         jj_la1[0] = jj_gen;
    18.         break label_1;
    19.       }
    20.       jj_consume_token(PLUS);
    21.       jj_consume_token(NUMBER);
    22.     }
    23.     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>.这个类里还有其它的一些数据,比如几个重载的构造子,一些变量和方法.

  2. Token.java : 分割符的信息.里面有一些数据成员和方法,包含了我们定义的那3个token.其中image是我们常在程序中调用的成员,它是一个 public String 类型,记录了 从输入中得到的token,比如我们的"1","+","1",都以字符串的方式存储在image里供人们直接使用.还有一些上面程序片段看到的已经调用过的方法.

  3. SimpleCharStream.java : 实现CharStream接口的适配器类,传递字符到词法分析器.貌似只用ASCII,非unicode.

  4. AdderConstants.java : 接口.定义了词法分析器和解析器用到的一些常量.这里可以清楚的看到:

    1. int EOF = 0;
    2. int PLUS = 6;
    3. int NUMBER = 7;
    4. int DEFAULT = 0;
    5.  
    6. String[] tokenImage = {
    7.     "<EOF>",
    8.     "/" /"",
    9.     "/"//t/"",
    10.     "/"//n/"",
    11.     "/"//r/"",
    12.     "/"//r//n/"",
    13.     "/"+/"",
    14.     "<NUMBER>",
    15.   };

     

  5. AdderTokenManager.java : 词法分析器.

     

  6. TokenMgrError.java 和 ParseException.java : 上面说到的异常类.

编译了这些文件:

javac *.java

运行 Adder :

java Adder

这时候控制台等待输入,可以试试拉:

1+1

没提示就是合法,ctrl + c 结束输入

可以试着弄点错误什么的,看看异常类型跟错误的情况是怎么对应的.

都完事了,还挺简单的,呵呵.啊 累死了,下回再说.

上次的表达式没有计算功能,这次继续,让它先算加法.

先看BNF产生式代码: Adder.jj

  1. int start() throws NumberFormatException :
  2. {
  3.     Token t;
  4.     int i;
  5.     int value;
  6. }{
  7.     t = <NUMBER>
  8.     { i = Integer.parseInt( t.image ); }
  9.     { value = i; }
  10.     (
  11.         <PLUS>
  12.         t = <NUMBER>
  13.         { i = Integer.parseInt( t.image ); }
  14.         { value += i; }
  15.     )*
  16.     <EOF>
  17.     { return value; }
  18. }

 

先看这个方法,首先行1语句的返回值由void变成int了,因为我们要返回和.行2和行6的大括号之间原来是声明变量用的,是java语法,因为java语句中不能直接调用<NUMBER>,所以得用个变量t.i ; 中间临时变量 i,得到t的具体数可以被java识别的据. value是累加后的和.在"{...}"书写java代码,并且顺序相关.8行中的t.image得到token的字符串表现形式.然后解析成int,最后通过 value += i 进行累加.最后返回累加的和,这段代码比较容易理解,可看起来还是杂乱,我们可以用"函数"的概念进行重构.

 

  1. int start() throws  NumberFormatException :
  2. {
  3.     int  i ;
  4.     int  value ;
  5. }{
  6.     value = primary()
  7.     (
  8.         <PLUS>
  9.         i = primary()
  10.         { value += i ; }
  11.     )*
  12.     <EOF>
  13.     { return value ; }
  14. }
  15.  
  16. int  primary()  throws  NumberFormatException :
  17. {
  18.     Token  t ;
  19. }{
  20.     t = <NUMBER>
  21.     { return Integer.parseInt( t.image ) ; }
  22. }

 

这里用了两个BNF产生式,可读性加强了.

主程序的要对int类型的返回值进行相应改变.

  1. public static void main( String[] args )throws ParseException, TokenMgrError, NumberFormatException {
  2.     Adder adder = new Adder( System.in ) ;
  3.     int value = adder.start() ;
  4.     System.out.println(value);
  5. }

 

 

然后进行编译,运行测试.注意,先把原来生成的文件全删除.程序还是比较好理解的,下面加入减法.

  1. PARSER_BEGIN(Adder)
  2.   public class Adder{
  3.     public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
  4.       Adder adder = new Adder(System.in);
  5.       int value = adder.start();
  6.       System.out.println(value);
  7.     }
  8.   }
  9. PARSER_END(Adder)
  10.  
  11. SKIP:{" "|"/t"|"/n"|"/r"|"/r/n"}
  12.  
  13. TOKEN:{
  14.        <PLUS:"+">
  15.       |<MINUS:"-">
  16.       |<NUMBER:(["0"-"9"])+>
  17. }
  18.  
  19. int start() throws NumberFormatException :
  20. {
  21.  int value;
  22. }
  23. {
  24.    {value = primary();}
  25.    (
  26.        <PLUS>
  27.        {value += primary();}
  28.       |<MINUS>
  29.        {value -= primary();}
  30.    )*
  31.    <EOF>
  32.    {return value;}
  33. }
  34.  
  35. int primary() throws NumberFormatException :
  36. {
  37.   Token t;
  38. }
  39. {
  40.   t = <NUMBER>
  41.   {return Integer.parseInt(t.image);}
  42. }

 

加法和减法都有了,可现在有个问题,每次只有按ctrl + c退出程序才可以得到结果,能不能每次回车换行时候即时输出呢?

首先需要把输出流定义到System.out上:

  1. PARSER_BEGIN(Adder)
  2.  import java.io.PrintStream ;
  3.  public class Adder{
  4.   public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
  5.    Adder adder = new Adder(System.in);
  6.    adder.start(System.out);
  7.   }
  8.  }
  9. PARSER_END(Adder)

 

因为要识别回车换行,所以Token需要识别回车换行:

  1. SKIP:{" "|"/t"}
  2.  
  3. TOKEN:{
  4.        <PLUS:"+">
  5.       |<MINUS:"-">
  6.       |<NUMBER:(["0"-"9"])+>
  7.       |<EOL:"/n"|"/r"|"/r/n">
  8. }

 

start BNF产生式要这样改变:

  1. void start(PrintStream printStream) throws NumberFormatException :
  2.   int value;
  3. }
  4. {
  5.   (
  6.     value = expression()
  7.     <EOL>
  8.     { printStream.println( value ) ; }
  9.   )*
  10.   <EOF>
  11. }
  12.  
  13. int expression() throws NumberFormatException :
  14. {
  15.   int value ;
  16. }
  17. {
  18.   value = primary()  
  19.   (
  20.     <PLUS>
  21.     { value += primary() ; }
  22.    |<MINUS>
  23.     { value -= primary() ; }
  24.   )*
  25.   { return value ; }
  26. }

 

用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

  1. PARSER_BEGIN(Adder)
  2.  import java.io.PrintStream ;
  3.  public class Adder{
  4.   public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
  5.    Adder adder = new Adder(System.in);
  6.    adder.start(System.out);
  7.   }
  8.  }
  9. PARSER_END(Adder)
  10.  
  11. SKIP:{" "|"/t"}
  12.  
  13. TOKEN:{
  14.        <PLUS:"+">
  15.       |<MINUS:"-">
  16.       | <TIMES:"*">
  17.       |<DIVIDE:"/">
  18.       |<NUMBER:<DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "."<DIGITS> >
  19.       |<#DIGITS : (["0"-"9"])+ >
  20.       |<EOL:"/n"|"/r"|"/r/n">
  21. }
  22.  
  23. void start(PrintStream printStream) throws NumberFormatException :
  24. {
  25.   double value;
  26. }
  27. {
  28.   (
  29.     value = expression()
  30.     <EOL>
  31.     { printStream.println( value ) ; }
  32.   )*
  33.   <EOF>
  34. }
  35.  
  36. double expression() throws NumberFormatException :
  37. {
  38.   double value ;
  39. }
  40. {
  41.   value =term()
  42.   (
  43.     <PLUS>
  44.     { value += term() ; }
  45.    |<MINUS>
  46.     { value -= term() ; }
  47.   )*
  48.   { return value ; }
  49. }
  50.  
  51. double term() throws NumberFormatException :
  52. {
  53.   double value ;
  54. }
  55. {
  56.   value = primary()
  57.   (
  58.     <TIMES>
  59.     { value *= primary() ; }
  60.    |<DIVIDE>
  61.     { value /= primary() ; }
  62.   )*
  63.   { return value ; }
  64. }
  65.  
  66. double primary() throws NumberFormatException :
  67. {
  68.   Token t;
  69. }
  70. {
  71.   t = <NUMBER>
  72.   {return Double.parseDouble(t.image);}
  73. }

 

编译运行一下,这回象样多了,呵呵.再加点猛料,各种括号:

增加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

有点像递归函数,不难理解.

  1. PARSER_BEGIN(Adder)
  2.  import java.io.PrintStream ;
  3.  public class Adder{
  4.   public static void main(String[] args) throws ParseException,TokenMgrError,NumberFormatException {
  5.    Adder adder = new Adder(System.in);
  6.    adder.start(System.out);
  7.   }
  8.  }
  9. PARSER_END(Adder)
  10. SKIP:{" "|"/t"}
  11. TOKEN:{
  12.        <PLUS:"+">
  13.       |<MINUS:"-">
  14.       |<TIMES:"*">
  15.       |<DIVIDE:"/">
  16.       |<NUMBER:<DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "."<DIGITS> >
  17.       |<#DIGITS: (["0"-"9"])+ >
  18.       |<OPEN_PAR: "(" >
  19.       |<CLOSE_PAR: ")" >
  20.       |<OPEN_BET : "[">
  21.       |<CLOSE_BET : "]">
  22.       |<OPEN_BCE : "{">
  23.       |<CLOSE_BCE : "}">
  24.       |<EOL:"/n"|"/r"|"/r/n">
  25. }
  26.  
  27. void start(PrintStream printStream) throws NumberFormatException :
  28. {
  29.  double value;
  30. }
  31. {
  32.  (
  33.   value = expression()
  34.   <EOL>
  35.   { printStream.println( value ) ; }
  36.  )*
  37.  <EOF>
  38. }
  39.  
  40. double expression() throws NumberFormatException :
  41.   double value ;
  42. }
  43. {
  44.  value =term()
  45.  (
  46.   <PLUS>
  47.   { value += term() ; }
  48.        |<MINUS>
  49.   { value -= term() ; }
  50.  )*
  51.  { return value ; }
  52. }
  53.  
  54. double term() throws NumberFormatException :
  55. double value ;
  56. }
  57. {
  58.  value = primary()
  59.  (
  60.   <TIMES>
  61.   { value *= primary() ; }
  62.  |<DIVIDE>
  63.   { value /= primary() ; }
  64.  )*
  65.  { return value ; }
  66. }
  67.  
  68. double primary() throws NumberFormatException :
  69. {
  70.  Token t;
  71.  double value;
  72. }
  73. {
  74.  t = <NUMBER>
  75.  {return Double.parseDouble(t.image);}
  76.  |<OPEN_PAR> value = expression() <CLOSE_PAR>
  77.  { return value ; }
  78.  |<OPEN_BET>value = expression() <CLOSE_BET>
  79.  { return value ; }
  80.  |<OPEN_BCE>value = expression() <CLOSE_BCE>
  81.  { return value ; }
  82. }
  83.  

 

4则混合运算基本OK了,来吧,测试一下成果.忙了半天了.

javacc Adder.jj javac *.java java Adder

控制台输入:

2*{1*[3*(1+1)-2]+1}

看看得到了多少?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值