1. 环境搭建 eclipse + antlr4
Eclipse 安装antlr插件
Help > EclipseMarketplace
直接搜索antlr,如下图
选择新建项目显示,如下图,插件安装成功。
新建了项目后,我们会发现在项目根目录下会生成一个Hello.g4的文件。这个是antlr project的核心文件,打开文件如下
/**
* Define a grammar called Hello
*/
grammar Hello;
r : 'hello' ID ; // match keyword hello followed by an identifier
ID : [a-z]+ ; // match lower-case identifiers
WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
我们自己新增一个CallExpr.g4文件用作测试,一个简易的科学计算器语法描述
grammar CalExpr;
prog: stat+ ;
stat: expr NEWLINE # printExpr
| ID '=' expr NEWLINE # assign
| CLEANMEM # cleanmem
| NEWLINE # blank
;
expr: expr '!' # fac
| expr '^' expr # pow
| expr op=('*'|'/') expr # MulDiv
| expr op=('+'|'-') expr # AddSub
| NUMBER # number
| ID # id
| '(' expr ')' # parens
;
CLEANMEM: 'CLEANMEM';
FAC : '!' ;
POW : '^' ;
MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
DOT : '.' ;
ID : [a-zA-Z]+ ;
NUMBER : [0-9]+ (DOT)? [0-9]*;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip ;
选择文件,右键运行配置如下
-no-listener -visitor -encoding UTF-8
意思是不生成监听文件
运行之后,我们发现它会生成4个*.java文件和2个*.tokens 文件。如图
但这些java文件并不能直接运行,它们只是为我们提供了一些模板,下面,我们就直接拷贝这些文件到我们的工程,对它们进行继承并重写其中的方法即可。
这里eclipse + antlr4 环境就搭建完毕了。
2.antlr的应用实例,简单的科学计算器
新建一个maven工程,引入相关jar包
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
先看下搭建工程的目录结构,再详细解释
datatype包下面是我们定义的数据类型,代码如下
package com.ly.datatype;
public interface IType {
Double getValue();
void setValue(Double d);
/**
* 是否数值类型
* @return
*/
Boolean isValue();
/**
* 是否变量类型
* @return
*/
Boolean isVarb();
}
package com.ly.datatype;
public class CalNumber implements IType{
private Double _value;
public CalNumber(String str) {
_value = Double.valueOf(str);
}
@Override
public Double getValue() {
return _value;
}
@Override
public void setValue(Double d) {
_value = d;
}
@Override
public Boolean isValue() {
return true;
}
@Override
public Boolean isVarb() {
return false;
}
@Override
public String toString() {
return "CalNumber [_value=" + _value + "]";
}
}
package com.ly.datatype;
import java.util.Hashtable;
import java.util.Map;
public class CalVarb implements IType{
private String _name;
private static Map<String,CalNumber> _map = new Hashtable<>();
public CalVarb(String str) {
_name = str;
}
public static void cleanmem() {
_map.clear();
_map = new Hashtable();
}
@Override
public Double getValue() {
return _map.get(_name).getValue();
}
@Override
public void setValue(Double d) {
_map.put(_name, new CalNumber(d.toString()));
}
@Override
public Boolean isValue() {
return false;
}
@Override
public Boolean isVarb() {
return true;
}
@Override
public String toString() {
return "CalVarb [_name=" + _name + "]";
}
}
有了数据类型,核心工作就是完成vistor的编写,这里就用上了之前生产的模板文件。
新建MyCalVisitor类继承模板文件CalExprBaseVisitor,然后重写相关方法。
这里先完成id,number的获取,再完成赋值和打印方法,就可以通过简单测试了
@Override
public T visitNumber(NumberContext ctx) {
CalNumber number = new CalNumber(ctx.getText());
return (T)number;
}
@Override
public T visitId(IdContext ctx) {
CalVarb varb = new CalVarb(ctx.ID().getText());
return (T)varb;
}
@Override
public T visitPrintExpr(PrintExprContext ctx) {
IType iType = ctx.expr().accept(this);
if(iType.isValue()) {
System.out.println(iType.getValue());
}else if(iType.isVarb()) {
System.out.println(ctx.getText().trim() + " = " + iType.getValue());
}
return (T)iType;
}
这里是赋值的实现
@Override
public T visitAssign(AssignContext ctx) {
CalVarb calVarb = new CalVarb(ctx.ID().getText());
IType iType = ctx.expr().accept(this);
calVarb.setValue(iType.getValue());
return (T)calVarb;
}
后面就按照之前定义的语法规则,实现运算过程,完成的代码如下:
package com.ly.visitor;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.apache.commons.math3.util.ArithmeticUtils;
import org.apache.commons.math3.util.CombinatoricsUtils;
import com.ly.CalExprBaseVisitor;
import com.ly.CalExprParser;
import com.ly.CalExprParser.AddSubContext;
import com.ly.CalExprParser.AssignContext;
import com.ly.CalExprParser.CleanmemContext;
import com.ly.CalExprParser.ExprContext;
import com.ly.CalExprParser.FacContext;
import com.ly.CalExprParser.IdContext;
import com.ly.CalExprParser.MulDivContext;
import com.ly.CalExprParser.NumberContext;
import com.ly.CalExprParser.ParensContext;
import com.ly.CalExprParser.PowContext;
import com.ly.CalExprParser.PrintExprContext;
import com.ly.datatype.CalNumber;
import com.ly.datatype.CalVarb;
import com.ly.datatype.IType;
public class MyCalVisitor<T extends IType> extends CalExprBaseVisitor<T>{
@Override
public T visitCleanmem(CleanmemContext ctx) {
CalVarb.cleanmem();
return super.visitCleanmem(ctx);
}
@Override
public T visitNumber(NumberContext ctx) {
CalNumber number = new CalNumber(ctx.getText());
return (T)number;
}
@Override
public T visitId(IdContext ctx) {
CalVarb varb = new CalVarb(ctx.ID().getText());
return (T)varb;
}
@Override
public T visitPrintExpr(PrintExprContext ctx) {
IType iType = ctx.expr().accept(this);
if(iType.isValue()) {
System.out.println(iType.getValue());
}else if(iType.isVarb()) {
System.out.println(ctx.getText().trim() + " = " + iType.getValue());
}
return (T)iType;
}
@Override
public T visitAssign(AssignContext ctx) {
CalVarb calVarb = new CalVarb(ctx.ID().getText());
IType iType = ctx.expr().accept(this);
calVarb.setValue(iType.getValue());
return (T)calVarb;
}
@Override
public T visitFac(FacContext ctx) {
IType iType = ctx.expr().accept(this);
Double l = CombinatoricsUtils.factorialDouble(iType.getValue().intValue());
return (T)new CalNumber(l.toString());
}
@Override
public T visitParens(ParensContext ctx) {
IType iType = ctx.expr().accept(this);
return (T)iType;
}
@Override
public T visitMulDiv(MulDivContext ctx) {
ExprContext left = ctx.expr().get(0);
ExprContext right = ctx.expr().get(1);
IType leftItype = left.accept(this);
IType rightItype = right.accept(this);
Double temp = 0d;
if(ctx.op.getType() == CalExprParser.MUL) {
temp = leftItype.getValue() * rightItype.getValue();
}
if(ctx.op.getType() == CalExprParser.DIV) {
temp = leftItype.getValue() / rightItype.getValue();
}
return (T)new CalNumber(temp.toString());
}
@Override
public T visitAddSub(AddSubContext ctx) {
ExprContext left = ctx.expr().get(0);
ExprContext right = ctx.expr().get(1);
IType leftItype = left.accept(this);
IType rightItype = right.accept(this);
Double temp = 0d;
if(ctx.op.getType() == CalExprParser.ADD) {
temp = leftItype.getValue() + rightItype.getValue();
}
if(ctx.op.getType() == CalExprParser.SUB) {
temp = leftItype.getValue() - rightItype.getValue();
}
return (T)new CalNumber(temp.toString());
}
@Override
public T visitPow(PowContext ctx) {
ExprContext leftContext = ctx.expr().get(0);
ExprContext rightContext = ctx.expr().get(1);
IType leftIType = leftContext.accept(this);
IType rightIType = rightContext.accept(this);
Integer d = ArithmeticUtils.pow(leftIType.getValue().intValue(), rightIType.getValue().intValue());
return (T)new CalNumber(d.toString());
}
@Override
public T visitErrorNode(ErrorNode node) {
System.out.println(node.toString());
return super.visitErrorNode(node);
}
}
编写测试类:
package com.ly.visitor;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import com.ly.CalExprLexer;
import com.ly.CalExprParser;
import com.ly.datatype.IType;
/**
*
* @author Administrator
*
*/
public class Test {
public static void main(String[] args) {
String expr = "a=1 \n" +
"b=2 \n" +
"c=(a+b)/2 \n" +
"c\n" +
"d=a\n" +
"f=d\n" +
"f\n" +
"11\n" +
"f=a+b+c\n" +
"f\n" +
"CLEANMEM \n" +
"a=4\n" +
"a!+(2^3)+1\n";
System.out.println(expr);
CharStream stream = CharStreams.fromString(expr);
CalExprLexer lexer = new CalExprLexer(stream);
CalExprParser parser = new CalExprParser(new CommonTokenStream(lexer));
ParseTree parseTree = parser.prog();
String res = parseTree.toStringTree(parser);
System.out.println(res);
MyCalVisitor<IType> parseTreeWalker = new MyCalVisitor();
IType iType = parseTreeWalker.visit(parseTree);
System.out.println(iType.getValue());
}
}
执行结果,符合预期
a=1
b=2
c=(a+b)/2
c
d=a
f=d
f
11
f=a+b+c
f
CLEANMEM
a=4
a!+(2^3)+1
(prog (stat a = (expr 1) \n) (stat b = (expr 2) \n) (stat c = (expr (expr ( (expr (expr a) + (expr b)) )) / (expr 2)) \n) (stat (expr c) \n) (stat d = (expr a) \n) (stat f = (expr d) \n) (stat (expr f) \n) (stat (expr 11) \n) (stat f = (expr (expr (expr a) + (expr b)) + (expr c)) \n) (stat (expr f) \n) (stat CLEANMEM) (stat \n) (stat a = (expr 4) \n) (stat (expr (expr (expr (expr a) !) + (expr ( (expr (expr 2) ^ (expr 3)) ))) + (expr 1)) \n))
c = 1.5
f = 1.0
11.0
f = 4.5
33.0
33.0
至此我们用antlr编写的计算器就已经全部完成了。
--------------------------------------------
异常处理
NTLR Tool version 4.4 used for code generation does not match the current runtime version 4.9.2
原因:
这是由于ANTLR Tool version 4.4默认支持的是[antlr-4.4-complete.jar]
对策:
1.去官网下载[antlr-4.9.2-complete.jar]
http://www.antlr.org/download/
2.更改ANTLR Tool version 4.4的配置
window→preferences→ANTLR 4→Tool→Antlr Tool(add)
直接把下载的[antlr-4.9.2-complete.jar]引进去,version勾上就好了.
3.编译xxxx.g4文件
4.执行原来代码