2017年9月20日
本节探讨主要是在JetBrain公司家的IntelliJ IDEA下使用ANTLR,并实现了课程要求的计算器实例。如有任何问题欢迎斧正。
2017年9月22日
对本节页面进行了调整。
2017年9月23日
添加了错误监听,有待进一步完善。
一、 在IntelliJ IDEA中配置ANTLR
在实际的开发应用当中,IDE能够给予我们一定的便捷。笔者习惯使用JetBrain家的IDE,故而使用IDEA来完成本次实验。
1.1 安装ANTLR插件
-
打开
Plugins
-
搜索
ANTLR
-
点击
Search in repository
-
安装
ANTLR v4 grammar plugin
-
如图
1.2 新建maven项目
-
新建
maven
项目 -
添加
pom.xml
依赖
注意目前最新的antlr版本为4.7,该版本需要与之前插件中的ANTLR Tool版本相匹配。
<dependencies>
<!-- https://mvnrepository.com/artifact/org.antlr/antlr4-runtime -->
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.7</version>
</dependency>
</dependencies>
二、 计算器解析实现
2.1 制定语法规则
新建Labeled.g4:
grammar Labeled;
prog: stat+;
/* 使用#来对事件进行标记 */
stat: ID '=' expr ';' # assign
| 'println(' expr ');' # printExpr
;
expr: expr op=('*'|'/') expr # MulDiv
| expr op=('+'|'-') expr # AddSub
| DOUBLE # double
| INT # int
| ID # id
| '(' expr ')' # parens
;
ID : [a-zA-Z]+ ;
DOUBLE : [0-9]+ '.' [0-9]+;
INT : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
2.2 生成解析文件
-
配置
右键Configure ANTLR
-
生成
右键Generate ANTLR Recognizer
2.3 实现Visitor
新建EvalVisitor.java:
import LabeledBaseVisitor;
import LabeledParser;
import java.util.HashMap;
import java.util.Map;
public class EvalVisitor extends LabeledBaseVisitor<Integer> {
Map<String, Integer> memory = new HashMap<String, Integer>();
@Override
public Integer visitPrintExpr(LabeledParser.PrintExprContext ctx) {
// TODO Auto-generatedmethod stub
Integer value = visit(ctx.expr()); // evaluate the exprchild
System.out.println(value); // print theresult
return 0;
}
@Override
public Integer visitAssign(LabeledParser.AssignContext ctx) {
// TODO Auto-generatedmethod stub
String id = ctx.ID().getText(); // id is left-hand side of '='
int value = visit(ctx.expr()); // compute valueof expression on right
memory.put(id, value); // store it inour memory
return value;
}
@Override
public Integer visitParens(LabeledParser.ParensContext ctx) {
// TODO Auto-generatedmethod stub
return visit(ctx.expr()); // return childexpr's value
}
@Override
public Integer visitMulDiv(LabeledParser.MulDivContext ctx) {
// TODO Auto-generatedmethod stub
int left = visit(ctx.expr(0)); // get value ofleft subexpression
int right = visit(ctx.expr(1)); // get value ofright subexpression
if ( ctx.op.getType() == LabeledParser.MUL ) return left * right;
return left / right; // must be DIV
}
@Override
public Integer visitAddSub(LabeledParser.AddSubContext ctx) {
// TODO Auto-generatedmethod stub
int left = visit(ctx.expr(0)); // get value ofleft subexpression
int right = visit(ctx.expr(1)); // get value ofright subexpression
if ( ctx.op.getType() == LabeledParser.ADD ) return left + right;
return left - right; // must be SUB
}
@Override
public Integer visitId(LabeledParser.IdContext ctx) {
// TODO Auto-generatedmethod stub
String id = ctx.ID().getText();
if ( memory.containsKey(id) ) return memory.get(id);
return 0;
}
@Override
public Integer visitInt(LabeledParser.IntContext ctx) {
// TODO Auto-generatedmethod stub
return Integer.valueOf(ctx.INT().getText());
}
@Override
public Integer visitDouble(LabeledParser.DoubleContext ctx) {
// TODO Auto-generatedmethod stub
// FIXME should be Double
return Integer.valueOf(ctx.DOUBLE().getText());
}
}
2.4 错误监听
新建CalcErrorListener.java
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import java.util.Collections;
import java.util.List;
public class CalcErrorListener extends BaseErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line, int charPositionInLine, String msg,
RecognitionException e)
{
List<String> stack = ((Parser)recognizer).getRuleInvocationStack(); Collections.reverse(stack);
System.err.println("Rule stack: "+stack);
System.err.println("Line "+line+":"+charPositionInLine+" at "+offendingSymbol+": "+msg);
}
}
2.5 书写主函数
新建Calc.java。
import LabeledLexer;
import LabeledParser;
import CalcErrorListener;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.FileInputStream;
import java.io.InputStream;
public class Calc {
public static void main(String[] args) throws Exception {
String inputFile = "/Users/***/Desktop/Calculator/src/main/java/success";
InputStream is = System.in;
if ( inputFile!=null ) is = new FileInputStream(inputFile);
//读取标准输入
ANTLRInputStream input = new ANTLRInputStream(is);
//进行词法分析
LabeledLexer lexer = new LabeledLexer(input);
//创建token流
CommonTokenStream tokens = new CommonTokenStream(lexer);
//进行语法分析
LabeledParser parser = new LabeledParser(tokens);
//移除原有错误监听
parser.removeErrorListeners();
//添加自定义错误监听
parser.addErrorListener(new CalcErrorListener());
//构建语法树
ParseTree tree = parser.prog();
//创建visitor并进行访问
EvalVisitor eval = new EvalVisitor();
eval.visit(tree);
}
}
2.6 测试
1. 正确用例
新建success文件:
a=1;
b=2;
println(a+b);
println(9);
运行项目,输出如下:
3
9
2. 错误用例
新建fail文件:
prin;
修改主函数,运行项目,输出如下:
Rule stack: [prog, stat]
Line 1:4 at [@1,4:4=';',<2>,1:4]: mismatched input ';' expecting '='
三、 问题
- 浮点数返回时出现类型不兼容报错
Error:(68, 19) java: EvalVisitor中的visitDouble(analyzer.LabeledParser.DoubleContext)无法实现analyzer.LabeledVisitor中的visitDouble(analyzer.LabeledParser.DoubleContext)
返回类型java.lang.Double与java.lang.Integer不兼容
四、 参考链接
-
《The Definitive ANTLR 4 Reference》
https://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference
-
《antlr小试牛刀》
https://w-angler.com/blog/2/35
版权声明:本文为MyIgnorance原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。