antlr简单表达式语言入门

一、基本介绍

antlr是一款适合拿来开发领域特定语言(DSL)的工具。它可以根据开发人员定义的词法和文法生成词法分析器(lexer)程序,语法分析器(parser)程序。如果没有antlr这样的工具,就要自己写底层的词法分析,语法分析,语义分析,中间代码生成等复杂的工作。antlr降低了dsl开发的复杂度。

ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It’s widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build and walk parse trees.

antlr的github地址
antlr官网传送门

antlr有别于编译器,它只做了compiler的前三个步骤
antlr简单表达式语言入门_第1张图片

二、准备工作

  1. maven依赖,我的示例代码是基于4.5.3的,目前最新的是4.8.1版本
<dependency>
    <groupId>org.antlrgroupId>
    <artifactId>antlr4artifactId>
    <version>4.5.3version>
dependency>
  1. IDE插件
    antlr会根据g4文件生成词法分析和语法分析的代码,但是要配合插件使用。
    ANTLR v4 IDEA插件地址

这里需要注意的是,antlr的maven版本和插件支持的版本需要保持一致,否则代码运行时会报错。用的时候注意比对下版本。

插件自动生成的代码会校验jar包版本,校验代码如下:

static {
	RuntimeMetaData.checkVersion("4.5.3", RuntimeMetaData.VERSION);
}

三、g4文件

g4文件里定义的是dsl语言的词法和文法规则,antlr插件会根据g4文件里的词法文法规则生成解析代码。标准的g4文件格式如下

/** Optional javadoc style comment */
grammar Name; ①
options {...}
import ... ;
 	
tokens {...}
channels {...} // lexer only
@actionName {...}
 	 
rule1 // parser and lexer rules, possibly intermingled
...
ruleN

语法名字和文件名字必须一致。options,import, tokens, channels, @actionName都是可选的。rule是词法或者文法规则,必须定义。

简单表达式语言的语法定义

grammar Math;

prog : stat+;

// 文法定义
stat: expr NEWLINE          # printExpr
    | ID '=' expr NEWLINE   # assign
    | NEWLINE               # blank
    ;

expr:  expr op=('*'|'/') expr   # MulDiv
| expr op=('+'|'-') expr        # AddSub
| INT                           # int
| ID                            # id
| '(' expr ')'                  # parens
;

// 词法定义
MUL : '*' ; 
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip;

antlr插件的测试功能

输入一个满足文法定义的句子,antlr能生成语法树的可视图,十分有用。
antlr简单表达式语言入门_第2张图片

antlr的语法分析

语法分析的作用

根据给定文法,将一个句子构造出语法树(parse tree)

语法树的构造方法

  1. 自底向上构造语法树 (bottom-up parsing),由输入句子推导出开始符号。如LR语法分析,LALR,LR(1),这类分析手段是主流分析法。
  2. 自顶向下构造语法树(top-down parsing),从开始符号S推导出句子。如LL语法分析

antlr采用的是自顶向下的左递归推导分析。左递归容易遇到歧义情况,对此antlr会对输入的左递归文法改造非左递归的文法,同时引入优先级进行语法制导的翻译。这里不赘述,有兴趣的可以看antlr的文档 https://github.com/antlr/antlr4/blob/master/doc/left-recursion.md

四、antlr的遍历机制

antlr提供两种方式遍历语法树(syntax tree)

  • listener方式
    监听器方式需要与ParseTreeWalker一起使用
  • visitor方式
    访问器模式可以自己定义访问顺序

五、完整示例demo

  1. 定义g4文件,见上文

  2. 用插件生成lexer和parser的代码
    antlr简单表达式语言入门_第3张图片
    此时,antlr工具已经为你生成好了代码。把代码整体移入你的工程里。
    antlr简单表达式语言入门_第4张图片

  3. 选择visitor遍历模式,实现MathBaseVisitor类
    生成好的MathBaseVisitor是一个MathVisitor的空实现

package antlr;

import antlr.gen.math.MathBaseVisitor;
import antlr.gen.math.MathParser;
import com.google.common.collect.Maps;

import java.util.Map;

/**
 * 后根遍历序
 *
 * @author lvsheng
 * @date 2018/10/5
 **/
public class MathVisitorImpl extends MathBaseVisitor<Integer> {
	
	/**
	 * 存储变量值
	 */
	private Map<String, Integer> memory = Maps.newHashMap();
	
	@Override
	public Integer visitAssign(MathParser.AssignContext ctx) {
		String  id    = ctx.ID().getText();
		Integer value = visit(ctx.expr());
		this.memory.put(id, value);
		return value;
		
	}
	
	@Override
	public Integer visitPrintExpr(MathParser.PrintExprContext ctx) {
		Integer value = visit(ctx.expr());
		System.out.println(value);
		return value;
	}
	
	@Override
	public Integer visitInt(MathParser.IntContext ctx) {
		return Integer.valueOf(ctx.INT().getText());
	}
	
	@Override
	public Integer visitId(MathParser.IdContext ctx) {
		String id = ctx.ID().getText();
		if (memory.containsKey(id)) {
			return memory.get(id);
		}
		
		return 0;
	}
	
	@Override
	public Integer visitMulDiv(MathParser.MulDivContext ctx) {
		Integer left  = visit(ctx.expr(0));
		Integer right = visit(ctx.expr(1));
		
		if (ctx.op.getType() == MathParser.MUL) {
			return left * right;
		} else {
			return left / right;
		}
		
	}
	
	@Override
	public Integer visitAddSub(MathParser.AddSubContext ctx) {
		Integer left  = visit(ctx.expr(0));
		Integer right = visit(ctx.expr(1));
		
		if (ctx.op.getType() == MathParser.ADD) {
			return left + right;
		} else {
			return left - right;
		}
	}
	
	@Override
	public Integer visitParens(MathParser.ParensContext ctx) {
		return visit(ctx.expr());
	}
}

  1. 客户端调用,测试
package antlr;

/**
 * @author lvsheng
 * @date 2018/10/5
 **/
import antlr.gen.math.MathLexer;
import antlr.gen.math.MathParser;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

public class MathTest {
	
	public static void main(String[] args) {
		//String            simpleInput = " 3 * 4+ 6 / 2\r\n";
		String            sentence    = "a = 5\r\n b = 6\r\n a * b + 3 - ( 5 + 10 )\r\n";
		ANTLRInputStream  inputStream = new ANTLRInputStream(sentence);
		MathLexer         lexer       = new MathLexer(inputStream);
		CommonTokenStream tokenStream = new CommonTokenStream(lexer);
		MathParser        parser      = new MathParser(tokenStream);
		// 生成解析树
		ParseTree       parseTree = parser.prog();
		MathVisitorImpl visitor   = new MathVisitorImpl();
		// 遍历解析树
		Integer rtn = visitor.visit(parseTree);
		System.out.println("result : " + rtn.toString());
	}
}

你可能感兴趣的:(Java框架)