antlr简单表达式语言入门
Posted bruce128
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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有别于编译器,它只做了compiler的前三个步骤
二、准备工作
- maven依赖,我的示例代码是基于4.5.3的,目前最新的是4.8.1版本
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4</artifactId>
<version>4.5.3</version>
</dependency>
- 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的语法分析
语法分析的作用
根据给定文法,将一个句子构造出语法树(parse tree)
语法树的构造方法
- 自底向上构造语法树 (bottom-up parsing),由输入句子推导出开始符号。如LR语法分析,LALR,LR(1),这类分析手段是主流分析法。
- 自顶向下构造语法树(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
-
定义g4文件,见上文
-
用插件生成lexer和parser的代码
此时,antlr工具已经为你生成好了代码。把代码整体移入你的工程里。
-
选择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());
- 客户端调用,测试
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());
以上是关于antlr简单表达式语言入门的主要内容,如果未能解决你的问题,请参考以下文章
Hive 源码解读 Driver 将 HQL 语句转换为 AST
Hive 源码解读 Driver 将 HQL 语句转换为 AST
Hive 源码解读 Driver 将 HQL 语句转换为 AST