• 8.JavaCC官方入门指南-例3


    例3:计算器—double类型加法

      下面我们对上个例子的代码进行进一步的修改,使得代码具有简单的四则运算的功能。
      第一步修改,我们将打印出每一行的值,使得计算器更具交互性。一开始,我们只是把数字加起来,然后再关注其他运算,比如减法、乘法和除法。

    1.Options和class声明块

      描述文件calculator0.jj的第一部分如下所示:

    /* calculator0.jj An interactive calculator. */
    options {
        STATIC = false ;
    }
    PARSER_BEGIN(Calculator)
        import java.io.PrintStream ;
        class Calculator {
            public static void main( String[] args )
                throws ParseException, TokenMgrError, NumberFormatException {
                Calculator parser = new Calculator( System.in ) ;
                parser.Start( System.out ) ;
            }
            double previousValue = 0.0 ;
        }
    PARSER_END(Calculator)
    

      Calculator类中的previousValue属性,用于存储上一行的计算结果的,我们将在另一个版本中使用到该值,到时可以使用美元符号来表示它。import导入语句声明说明了在PARSER_BEGIN和PARSER_END之间可能有import导入声明;这些代码都会被原样复制到生成的语法解析类和token管理类中去。同样还可以有包package的声明,package的声明将会被复制到最后生成的所有java类中去。

    2.词法描述文件

      词法分析器的描述文件在这里将会发生一些变化,首先一行的结束符也被声明为了token,并给这些行结束符命名为EOL,这样一来这个token也可以被传递给语法分析器了。

    SKIP : { " " }
    TOKEN : { < EOL : "
    " | "
    " | "
    " > }
    TOKEN : { < PLUS : "+" > }
    

      接下来,我们要定义合适的token使得允许输入中的数值有小数点。为此我们修改NUMBER这个token的定义,使得它可以识别decimal类型的数值。当数值中有小数点,它可以有如下的4中类型,我们分别用竖线分隔开来了,这4中类型分别是:整型,没有小数点、小数点在中间、小数点在末尾和小数点在开头。满足此需求的描述串如下:

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

      有时候,同样的规则表达式可能会出现多次。为了更好的可读性,最好是给这些重复出现的表达式起一个名字。对于那些只在词法描述文件中使用到,但又不是token的规则表达式,我们创建了一个特殊的标识来表示它:#。因此,对于上面的词法描述,可以替换成如下:

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

      可以看到,我们把([”0”-”9”])+这串规则表达式提取了出来,并将其命名为了DIGITS。但是要注意到,DIGITS这个并不是token,这意味着在后面生成的Token类中,将不会有DIGITS对应的属性,而在语法分析器中也无法使用DIGITS。

    3.语法描述文件

      语法分析器的输入由零行或多行组成。每行包含一个表达式。通过使用BNF符号表达式,语法分析器可以写成如下:

    Start -->(Expression EOL) * EOF
    

      由此我们可以得出BNF生产式如下:

    void Start() :
    {}
    {
        (
            Expression()
            <EOL>
        )*
        <EOF>
    }
    

      我们在上面的BNF生产式中填充上java代码,使得它具备接收入参、记录并打印每一行的计算结果:

    void Start(PrintStream printStream) throws NumberFormatException :
    {}
    {
        (
            previousValue = Expression()
            <EOL> { printStream.println( previousValue ) ; }
        )*
        <EOF>
    }
    

      每个表达式由一个或多个数字组成,这些数字目前用加号隔开。用BNF符号表达式如下:

    Expression --> Primary(PLUS Primary)*
    

      在这里的Primary,我们暂时用它来表示数值。
      上面的BNF符号表达式用JavaCC表示出来如下所示:

    double Expression() throws NumberFormatException : {
        double i ;
        double value ;
    }
    {
        value = Primary()
        (
            <PLUS>
            i = Primary()
            { value += i ; }
        )*
        { return value ; }
    }
    

      这个跟我们前面例子中的Start BNF生产式差不多,我们只是将数值的类型由int修改成了double类型而已。至于Primary(),跟上面例子也非常类似,它用BNF符号表达式来表示:

    Primary --> NUMBER
    

      Primary()对应的JavaCC描述文件其实也差不多,只不过在这里它是对double精度的数值进行的转换计算:

    double Primary() throws NumberFormatException :
    {
        Token t ;
    }
    {
        t = <NUMBER>
        { return Double.parseDouble( t.image ) ; }
    }
    

      下面我们用BNF符号表达式将语法分析器的逻辑表示出来:

    Start --> (Expression EOL) * EOF
    Expression --> Primary (PLUS Primary)*
    Primary --> NUMBER
    

      至此,我们就把calculator0.jj描述文件都修改完了,接下来可以跑几个例子进行测试。

    4.测试

    4.1 calculator0.jj

      经过上面的修改,最后得到的.jj描述文件如下:

    /* calculator0.jj An interactive calculator. */
    options {
        STATIC = false ;
    }
    PARSER_BEGIN(Calculator)
        import java.io.PrintStream ;
        class Calculator {
            public static void main( String[] args )
                throws ParseException, TokenMgrError, NumberFormatException {
                Calculator parser = new Calculator( System.in ) ;
                parser.Start( System.out ) ;
            }
            double previousValue = 0.0 ;
        }
    PARSER_END(Calculator)
    
    
    SKIP : { " " }
    TOKEN : { < EOL : "
    " | "
    " | "
    " > }
    TOKEN : { < PLUS : "+" > }
    TOKEN : { < NUMBER : <DIGITS>
                       | <DIGITS> "." <DIGITS>
                       | <DIGITS> "."
                       | "."<DIGITS> >
            }
    TOKEN : { < #DIGITS : (["0"-"9"])+ > }
    
    
    void Start(PrintStream printStream) throws NumberFormatException :
    {}
    {
        (
            previousValue = Expression()
            <EOL> { printStream.println( previousValue ) ; }
        )*
        <EOF>
    }
    
    double Expression() throws NumberFormatException : {
        double i ;
        double value ;
    }
    {
        value = Primary()
        (
            <PLUS>
            i = Primary()
            { value += i ; }
        )*
        { return value ; }
    }
    
    double Primary() throws NumberFormatException :
    {
        Token t ;
    }
    {
        t = <NUMBER>
        { return Double.parseDouble( t.image ) ; }
    }
    

    4.2 运行

      我们首先计算1+2,如下所示:

      接下来计算1+2.,即小数点在末尾的情形:

      接下来计算1 + 2.3,即小数点在中间的情形:

      接下来计算1 + .2,即小数点在开头的情形:

  • 相关阅读:
    安装xcache3.0.3/3.2,为php加速
    编译安装lamp环境
    使用rpm包安装lamp环境
    centos6下通用二进制安装mysql5.5.33
    centos中进程管理工具
    centos6下的lvm逻辑卷的管理
    centos6中创建软raid方法
    解决vmware虚拟机克隆后启动centos报错device eth0 does not seem to be present, delaying initialization
    centos下常用文件管理命令
    centos下httpd-2.4的编译安装
  • 原文地址:https://www.cnblogs.com/suhaha/p/11733680.html
Copyright © 2020-2023  润新知