Antlr v4入门教程和实践

一.ANTRL 是什么

当我们实现一种语言时,我们需要构建读取句子(sentence)的应用,并对输入中的元素做出反应。如果应用计算或执行句子,我们就叫它解释器(interpreter),包括计算器、配置文件读取器、Python解释器都属于解释器。如果我们将句子转换成另一种语言,我们就叫它翻译器(translator),像Java到C#的翻译器和编译器都属于翻译器。不管是解释器还是翻译器,应用首先都要识别出所有有效的句子、词组、字词组等,识别语言的程序就叫解析器(parser)或语法分析器(syntax analyzer)。我们学习的重点就是如何实现自己的解析器,去解析我们的目标语言,像DSL语言、配置文件、自定义SQL等等。

手动编写解析器是非常繁琐的,所以我们有了ANTLR。只需编写ANTLR的语法文件,描述我们要解析的语言的语法,之后ANTLR就会自动生成能解析这种语言的解析器。也就是说,ANTLR是一种能写出程序的程序。而用来声明我们语言的ANTLR语言的语法,就是元语言(meta-language)。

二.ANTRL 语法

文件结构

/** Optional javadoc style comment */
grammar Name; 
options {...}
import ... ;

tokens {...}
channels {...} // lexer only
@actionName {...}

rule1 // parser and lexer rules, possibly intermingled
...
ruleN

grammar 声明语法头,类似于java类的定义

grammar  SPL;

options 选项,如语言选项,输出选项,回溯选项,记忆选项等等

 

options { output=AST;  language=Java; }

options { tokenVocab=MySqlLexer; }

rule 表示规则,以 “:” 开始, “;” 结束, 多规则以 "|" 分隔

ID : [a-zA-Z0-9|'_']+ ;    //数字 
STR:'\'' ('\'\'' | ~('\''))* '\''; 
WS: [ \t\n\r]+ -> skip ; // 系统级规则 ,即忽略换行与空格

sqlStatement
    : ddlStatement 
    | dmlStatement     | transactionStatement
    | replicationStatement     | preparedStatement
    | administrationStatement     | utilityStatement
    ;

注释

  • 单行、多行、javadoc风格
  • javadoc风格只能在开头使用
/** 
 * This grammar is an example illustrating the three kinds
 * of comments.
 */
grammar T;

/* a multi-line
  comment
*/

/** This rule matches a declarator for my language */

decl : ID ; // match a variable name

标识符

  • 符号(Token)名大写开头
  • 解析规则(Parser rule)名小写开头,后面可以跟字母、数字、下划线
ID, LPAREN, RIGHT_CURLY // token names
expr, simpleDeclarator, d2, header_file // rule names

ANTLR 语法识别一般分为二个阶段:

1.词法分析阶段 (lexical analysis)

对应的分析程序叫做 lexer ,负责将符号(token)分组成符号类(token class or token type)

2.解析阶段

根据词法,构建出一棵分析树(parse tree)或叫语法树(syntax tree)

Antlr v4入门教程和实践_第1张图片

3.递归下降解析器

 ANTLR生成的解析器叫做递归下降解析器(recursive-descent parser),属于自顶向下解析器(top-down parser)的一种。递归下降指的就是解析过程是从语法树的根开始向叶子(token)递归.

。还是以前面的赋值表达式解析为例,其递归下降解析器的代码大概是下面这个样子:

Antlr v4入门教程和实践_第2张图片

Assign很简单,直接顺序读取输入字符,不用做任何选择。相比之下,根结点Stat要复杂一些,因为它有多种选择。解析时,要向前看(lookahead)一些字符才能确认走哪个分支代码,有时甚至要读取完所有输入才能预测出,而ANTLR默默为我们处理了一切!

三. 解析树上的应用

如下图所示,解析树的叶子节点指向Token流中的Token,而Token中的起止字符索引指向字符流,而非拷贝子字符串。而像空格这种不与任何Token相关的字符会直接被Lexer丢弃掉。

Antlr v4入门教程和实践_第3张图片

ANTLR为每个Rule都会生成一个Context对象,它会记录识别时的所有信息。

四.树遍历

ANTLR提供了Listener和Visitor两种遍历机制。

Listener是全自动化的,ANTLR会主导深度优先遍历过程,我们只需处理各种事件就可以了。而Visitor则提供了可控的遍历方式,我们可以自行决定是否显示地调用子结点的visit方法。

Antlr v4入门教程和实践_第4张图片

String sql = "DELETE FROM T1 WHERE COL1 = TRUE AND (COL2 - COL3 <= (SELECT COUNT(*) FROM T2) OR MAINCOL/2 > 100.2);".toUpperCase();
final MySqlLexer mySqlLexer = new MySqlLexer(CharStreams.fromString(sql));
//字符组成单词(token)
final CommonTokenStream commonTokenStream = new CommonTokenStream(mySqlLexer);
//词法分析:将负责将符号(token)分组成符号类
final MySqlParser mySqlParser = new MySqlParser(commonTokenStream);
//根据词法,构建出一棵分析树(parse tree)或叫语法树(syntax tree)
final ParseTree tree = mySqlParser.root();
//遍历树节
ParseTreeWalker walker = new ParseTreeWalker(); // create standard walker
ExtractInterfaceListener extractor = new ExtractInterfaceListener(parser);
walker.walk(extractor, tree); // initiate walk of tree with listener

Antlr v4入门教程和实践_第5张图片

String sql = "DELETE FROM T1 WHERE COL1 = TRUE AND (COL2 - COL3 <= (SELECT COUNT(*) FROM T2) OR MAINCOL/2 > 100.2);".toUpperCase();
final MySqlLexer mySqlLexer = new MySqlLexer(CharStreams.fromString(sql));
//字符组成单词(token)
final CommonTokenStream commonTokenStream = new CommonTokenStream(mySqlLexer);
//词法分析:将负责将符号(token)分组成符号类
final MySqlParser mySqlParser = new MySqlParser(commonTokenStream);
//根据词法,构建出一棵分析树(parse tree)或叫语法树(syntax tree)
final MySqlParser.RootContext selectStatementContext = mySqlParser.root();
//遍历树节点
MySqlParserBaseVisitor visitor = new MySqlParserBaseVisitor();
visitor.visit(selectStatementContext);

四.实践

准备工作

1 安装IDE插件

我这里使用的是Intellij IDEA,所以就去Plugins中搜“ANTLR v4 grammar plugin”插件,重启IDEA即可使用。如果想在IDE外使用,需要下载ANTLR包,是JAVA写成的,后面在IDEA中的各种操作都可以手动执行命令来完成。

2 编写.g4文件

创建一个文件,后缀名是g4,只有这样在文件上点右键才能看到ANTLR插件的菜单。

/*
这个grammar的名称为ArrayInt ,必须与文件名相同
*/
grammar ArrayInt;

/* 一条rule  符号(token)分组*/
init : '{' value (',' value)* '}' ;
/* 一条rule*/
value : init
         | INT
         ;
/* lexer 词法分析规则*/
INT : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;

3.自动生成代码

在.g4文件上右键就能看到ANTLR插件的两个菜单,分别用来配置ANTLR生成工具的参数(在命令行中都有对应)和触发生成文件。首先选配置菜单,将目录选择到main/java或test/java。注意:ANTLR会自动根据Package/namespace的配置,生成出包的文件夹,不用预先创建出来。

Antlr v4入门教程和实践_第6张图片

之后就点生成菜单,于是就在我们配置的目录下,自动生成出的如下代码:

Antlr v4入门教程和实践_第7张图片

4 构建应用代码

有了生成好的解析器,我们就可以在它上面构建出好玩的应用了。

在开始编写应用代码之前,我们要引入ANTLR运行时。因为我们的解析器其实只是一堆回调hook,真正的通用解析流程实现是在ANTLR runtime包中。所以,以Maven为例ANTLR v4的依赖是:


    org.antlr
    antlr4-runtime
    4.8
String arrayInt = "{1,2,3}";
final ArrayIntLexer arrayIntLexer = new ArrayIntLexer(CharStreams.fromString(arrayInt));
//字符组成单词(token)
final CommonTokenStream commonTokenStream = new CommonTokenStream(arrayIntLexer);
//词法分析:将负责将符号(token)分组成符号类
final ArrayIntParser arrayIntParser = new ArrayIntParser(commonTokenStream);
//根据词法,构建出一棵分析树(parse tree)或叫语法树(syntax tree)
final ArrayIntParser.InitContext selectStatementContext = arrayIntParser.init();
//遍历树节点
ArrayIntBaseVisitor visitor = new ArrayIntBaseVisitor();
visitor.visit(selectStatementContext);

你可能感兴趣的:(antlr)