算术表达式语言

了解ANTLR最好的方法就是实例。构建一个简单的计算器是个不错的主意。为了使它容易理解且保持简单,我们将只允许基本的算术运算符(加、减、乘、除)、括号表达式、整数和变量。

  1. grammar Calc;
  2. prog
  3. : stat+
  4. ;
  5. stat
  6. : expr
  7. | ID '=' expr
  8. ;
  9. expr
  10. : expr ('*'|'/') expr
  11. | expr ('+'|'-') expr
  12. | INT
  13. | ID
  14. | '(' expr ')'
  15. ;
  16. ID : [a-zA-Z]+ ;
  17. INT : [0-9]+ ;
  18. WS : [ \t\r\n]+ -> skip ; // toss out whitespace

在上述的语法中,程序是由空格(换行符也被当作空格)终止的语句序列,语句可以是表达式或者赋值。那些以小写字母开头的像stat和expr是语法规则;由大写字母开头的诸如ID和INT为词法规则,用于识别标志符和整数这样的记号。我们用“|”分隔规则的选项,我们也可以用“()”把符号分组成子规则。例如,子规则('*'|'/')匹配乘法符号或者除法符号。

ANTLR v4最重要的新特性是它有能力处理(大多数类型的)左递归规则。例如,规则expr前两个选项就在左边缘递归地调用了expr自身。这种指定算术表达式表示法的方法比那些典型的自顶向下语法分析器策略更容易。当然,在这种策略下,我们需要定义多个规则,每个运算符优先级一个规则。

记号定义的表示法对那些有正则表达式经验的应该很熟悉。唯一不寻常的是在WS规则上的-> skip指令,它告诉词法分析器去匹配但丢弃空格,不要把它们放到记号流中,这样在语法分析树上空格就不会有对应的记号。(每个可能的输入字符都必须被至少一个词法规则匹配。)我们通过使用形式化的ANTLR表示法避免捆绑语法到某个特定的目标语言,而不是在语法中插入任意代码片段来告诉词法分析器去忽略。

这里是一些用来评估所有语法特性的测试序列:

  1. 193
  2. a=5
  3. b=6
  4. a+b*2
  5. (1+2)*3

把它们放入文件calc.txt中,然后执行以下命令:

  1. antlr Calc.g
  2. compile *.java
  3. grun Calc prog -gui calc.txt

TestRig会弹出一个显示语法分析树的窗口:

11. 算术表达式语言 - 图1