下面咱们对上个例子的代码进行进一步的修改,使得代码具备简单的四则运算的功能。
第一步修改,咱们将打印出每一行的值,使得计算器更具交互性。一开始,咱们只是把数字加起来,而后再关注其余运算,好比减法、乘法和除法。java
描述文件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类中去。code
词法分析器的描述文件在这里将会发生一些变化,首先一行的结束符也被声明为了token,并给这些行结束符命名为EOL,这样一来这个token也能够被传递给语法分析器了。orm
SKIP : { " " } TOKEN : { < EOL : "\n" | "\r" | "\r\n" > } TOKEN : { < PLUS : "+" > }
接下来,咱们要定义合适的token使得容许输入中的数值有小数点。为此咱们修改NUMBER这个token的定义,使得它能够识别decimal类型的数值。当数值中有小数点,它能够有以下的4中类型,咱们分别用竖线分隔开来了,这4中类型分别是:整型,没有小数点、小数点在中间、小数点在末尾和小数点在开头。知足此需求的描述串以下:blog
TOKEN { < NUMBER : (["0"-"9"])+ | (["0"-"9"])+ "." (["0"-"9"])+ | (["0"-"9"])+ "." | "." (["0"-"9"])+ > }
有时候,一样的规则表达式可能会出现屡次。为了更好的可读性,最好是给这些重复出现的表达式起一个名字。对于那些只在词法描述文件中使用到,但又不是token的规则表达式,咱们建立了一个特殊的标识来表示它:#。所以,对于上面的词法描述,能够替换成以下:token
TOKEN : { < NUMBER : <DIGITS> | <DIGITS> "." <DIGITS> | <DIGITS> "." | "."<DIGITS> > } TOKEN : { < #DIGITS : (["0"-"9"])+ > }
能够看到,咱们把([”0”-”9”])+这串规则表达式提取了出来,并将其命名为了DIGITS。可是要注意到,DIGITS这个并非token,这意味着在后面生成的Token类中,将不会有DIGITS对应的属性,而在语法分析器中也没法使用DIGITS。ci
语法分析器的输入由零行或多行组成。每行包含一个表达式。经过使用BNF符号表达式,语法分析器能够写成以下:io
Start -->(Expression EOL) * EOF
由此咱们能够得出BNF生产式以下:class
void Start() : {} { ( Expression() <EOL> )* <EOF> }
咱们在上面的BNF生产式中填充上java代码,使得它具有接收入参、记录并打印每一行的计算结果:import
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描述文件都修改完了,接下来能够跑几个例子进行测试。
通过上面的修改,最后获得的.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 : "\n" | "\r" | "\r\n" > } 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 ) ; } }
咱们首先计算1+2,以下所示:
接下来计算1+2.,即小数点在末尾的情形:
接下来计算1 + 2.3,即小数点在中间的情形:
接下来计算1 + .2,即小数点在开头的情形: