“antlr是什么” 不是本文的重点,请自行百度。
为了方便编写文法,我们使用antlrworks来一边写,一边检查,这还有一个好处:可以显示各个文法的DFA图,方便检查,方便排错。
同时使用antlrworks可以严格检查是否有语法错误,也可以方便的生成java代码(不用命令行了哦)
下面开始了。。。。
第一步,打开antlrworks,出现窗口提示选择文法格式,这里我们选择*.g格式
接下来,会要求我们进行一些简单的设置,来自动生成一些简单的内容,对于设计一个计算器,我建议的设置如下图:
这样就会产生如下的内容:
grammar Calc;
INT :'0'..'9'+
;
FLOAT
: ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
| '.' ('0'..'9')+ EXPONENT?
| ('0'..'9')+ EXPONENT
;
WS : ( ' '
| '\t'
| '\r'
| '\n'
) {$channel=HIDDEN;}
;
fragment
EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
/////////////////////////////////////此时建议保存一下,但是注意:保存文件名必须为Calc.g(与grammar Calc保持一致,这点和java很像哦)我们可以把鼠标点到FLOAT所在的区域,下面就会显示DFA了,如果你学过编译原理,那应该很容易看明白是什么意思: 第二步,我们要来修改一下,加入一些新的东西expr returns[double value]:
e=highLevelExpr {$value=$e.value;}
(
'+' e=highLevelExpr {$value+=$e.value;}
| '-' e=highLevelExpr {$value-=$e.value;}
)*
;
highLevelExpr returns[double value]:
e=atom{$value=$e.value;}
(
'^' e=atom {$value=Math.pow($value,$e.value);}
|'*' e=atom {$value*=$e.value;}
|'/' e=atom {$value/=$e.value;}
)*
;
atom returns [double value]:
INT{$value = Integer.parseInt($INT.text);}
|FLOAT{$value = Double.parseDouble($FLOAT.text);}
|'('expr')'{$value = $expr.value;}
;
说明:expr,表达式段,这是指级别较低的表达式,它的操作仅限+-(就一个元素时直接求值),它操作的元素是高级别的表达式,如果我们要计算一个式子的值,这个式子最终可以归结为expr
highLevelExpr ,高级别表达式,所谓高级别就是值运算级别高,它进行的操作可以为*/^,运算级别很明显级别比+-高,它操作的元素的原子元素(粒度最小的单位,直接的数字或括号表达式)
atom,原子表达式,具有直接值的元素,或带括号的表达式expr,因为括号的级别很高,他也看做原子元素,但是他的值是递归求出来的
每一个段以一个单词(段名,会对应产生java代码的一个方法)开始,首字母小写,returns [double value] 表示有返回值且类型为double,返回的引用是value(在下面会给value赋值),段体以冒号:开始,以分号;结束,中间描述文法定义,同时给value赋值。
第三步,开始产生代码
在生成代码之前先让我们检查一下语法,菜单Grammar - Check Grammar,或直接使用快捷键Ctrl + R
如果提示错误请检查一下是否有写错的东西,接下来生成java代码,菜单Generate- Generate Code,或直接使用快捷键Ctrl + Shift + G
之后就会在桌面上生成一个文件夹件output里面有3个文件
Calc.tokens
CalcLexer.java
CalcParser.java
我们再在手动编写一个测试类(请确保antlr-runtime能够被编译器找到)import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
public class Test {
public static void main(String[] args) throws Exception {
CalcLexer lexer = new CalcLexer(new ANTLRStringStream("4^2*2/4-(6+2)+4"));
CommonTokenStream tokens = new CommonTokenStream(lexer);
CalcParser parser = new CalcParser(tokens);
System.out.println(parser.expr());
}
}
然后在当前目录下执行命令javac *.javajava Test如果没有错误的话,就会看到输出4.0atom returns [double value]:
INT{$value = Integer.parseInt($INT.text);}
| FLOAT{$value = Double.parseDouble($FLOAT.text);}
| '('expr')'{$value = $expr.value;}
| 'sin' e=atom {$value = Math.sin($e.value);}
| 'cos' e=atom {$value = Math.cos($e.value);}
| 'tan' e=atom {$value = Math.tan($e.value);}
| 'ln' e=atom {$value = Math.log($e.value);}
| 'lg' e=atom {$value = Math.log10($e.value);}
| 'log(' e1=expr ','e2=expr')'{$value = Math.log($e1.value)/Math.log($e2.value);}
;
甚至可以加入阶乘,排列,组合等,但是java的Math类没直接提供方法,我们可以写到一个类里面,像调用Math.sin(..)那样调用
接下来再生成java代码,修改一下Test.java,试一个复杂一点的表达式吧
注:自动生成的识别 INT 和 FLOAT 文法可能不是很完美,如果你是个完美控可以再写一个复杂的文法来识别