开源语法分析器antlr4入门初探

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.执行原来代码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值