ANTLR(pronounced Antler) 是一个语言识别工具,Another Tool forLanguage Recognition 的缩写。ANTLR由旧金山大学(University of San Francisco)的教授 Terence Parr 开发并维护的,其始于1989年,到了现在过了20多年,一直都是一个很活跃的项目。

ANTLR 一般用于构建  Domain-Specific Languages (DSL)。用户编写好特定语言的语法文件后,ANTLR 会根据该语法文件生成相应的源代码来识别该语言。ANTLR 3.4 (截至2011-10-15最新的版本) 支持的编程语言(runtime)包括:ActionScript, Csharp2, Delphi, JavaScript, Perl5, Ruby , C, CSharp3,  Java, ObjC,Python。其中 C runtime 由 Jim Idle 维护,C
runtime 也是本文关注的部分。在早期的版本中提供有C++的runtime,但是在最新的版本中只提供 C 语言的 runtime。

很多的开源的软件都使用了ANTLR 作为自己的DSL 解析工具,比如: Hibernate,一个在javaEE 中运用非常广泛的ORM 框架,使用ANTLR解析 HQL –一种类似于SQL 的面向对象的数据库查询语言;Apache Hive,一个建于Hadoop 之上的数据仓库查询语言,使用ANTLR解析HiveQL;TOra,Toolkit for Oracle,一个用 Qt写的Oracle 数据库管理工具,使用ANTLR解析Oracle SQL 和 PL/SQL。还有其他很多的软件也使用了ANTLR,比如Esper,StreamBase,这两个都是用 Java 写的数据流处理引擎。

本文主要基于一个例子简单的讲解如果使用 ANTLR,介绍一些ANTLR的基本概念,本文不包括语言识别的理论知识,和ANTLR的一些高级应用,想了解这些知识应该学习编译理论相关知识,并阅读Terence Parr编写的The Definitive ANTLR Reference 。

ANTLR支持生成词法分析器,语法解析器和树解析器,同时也支持把代码和语法规则混合在一起编写,个人觉得两者混合在一起写对于开发会很方便,可是这样会影响语法的可读性,所以在本文中语法与代码不会混合在一起写。

本文将一步步的从头开始使用C语言构造一个简单的StreamSQL 解析器。该StreamSQL 解析器实现对 CREATE INPUT STREAM,CREATE OUTPUT STREAM,CREATE SCHEMA,CREATE WINDOW,SELECT 等语句的解析。

工具准备

1.首先下载 ANTLR3.4。http://www.antlr.org/download.html  我们要下载的是ANTLR 3.4 distribution (Source for tools, targets, complete binaries)

2. 下载 ANTLRWorks。 ANTLRWorks 是一个可视化的开发工具,为语法的编写和调试提供了很大的方便。

3. 下载 ANTLR v3 plugin for eclipse。这是一个Eclipse的插件,可以在Eclipse 里为ANTLR语法文件提供语法高亮,也可以作为语法的调试工具,不过它的调试功能没有 ANTLRWorks 强大。

语法描述

我们要解析的语言如下表所示。

CREATE INPUT STREAM Packet PacketTuple;

CREATE OUTPUT STREAM Aggregate AggregateTuple;

CREATE SCHEMA PacketTuple (time INT,price DOUBLE );

CREATE SCHEMA AggregateTuple(

time INT,

maxprice DOUBLE ,

currenttime INT);

CREATE WINDOW Dimension1(SIZE 180 ADVANCE 10 ON time);

SELECT max(price) AS maxprice, max(time) AS currenttime

FROM Packet[Dimension1] INTO Aggregate;

用来解析上面语言的ANTLR语法。

grammar StreamSQL;

options {

language=C;

ASTLabelType=pANTLR3_BASE_TREE;

output=AST;

}

tokens {

TOK_CREATE_SCHEMA;

TOK_CREATE_STREAM;

TOK_CREATE_WINDOW;

TOK_SELECT;

TOK_SCHEMA_LIST;

TOK_NAME_TYPE;

TOK_SELEXPR;

TOK_SELITEM;

TOK_SELLIST;

}

statement

: selectStatement EOF

| createStatement EOF

;

selectStatement

: KW_SELECT selectList

KW_FROM instreamName=Identifier LSQUARE windowName=Identifier RSQUARE KW_INTO outstreamName=Identifier

-> ^(KW_SELECT selectList $instreamName $windowName $outstreamName)

;

selectList

: selectColumn (COMMA selectColumn)*

-> ^(TOK_SELLIST selectColumn+)

;

selectColumn

: selectItem

| selectExpression

;

selectItem

: Identifier KW_AS Identifier

-> ^(TOK_SELITEM Identifier Identifier)

;

selectExpression

: functionName=Identifier LPAREN itemName=Identifier RPAREN KW_AS asName=Identifier

-> ^(TOK_SELEXPR $functionName $itemName $asName)

;

createStatement

: KW_CREATE KW_SCHEMA Identifier schemaList

-> ^(TOK_CREATE_SCHEMA Identifier schemaList)

| KW_CREATE streamType KW_STREAM streamName=Identifier schemaName=Identifier

-> ^(TOK_CREATE_STREAM streamType $streamName $schemaName)

| KW_CREATE KW_WINDOW

windowName=Identifier LPAREN KW_SIZE Number KW_ADVANCE Number KW_ON onWhat=Identifier RPAREN

-> ^(TOK_CREATE_WINDOW $windowName Number Number $onWhat)

;

schemaList

: LPAREN columnNameType (COMMA columnNameType)* RPAREN

-> ^(TOK_SCHEMA_LIST columnNameType+)

;

streamType

: (KW_INPUT | KW_OUTPUT)

;

columnNameType

: coluName=Identifier dataType

-> ^(TOK_NAME_TYPE $coluName dataType)

;

dataType

: KW_INT

| KW_DOUBLE

;

// Keywords

KW_FROM : 'FROM';

KW_AS : 'AS';

KW_SELECT : 'SELECT';

KW_ON : 'ON';

KW_CREATE: 'CREATE';

KW_INT: 'INT';

KW_DOUBLE: 'DOUBLE';

KW_INTO: 'INTO';

KW_SCHEMA: 'SCHEMA';

KW_INPUT: 'INPUT';

KW_OUTPUT: 'OUTPUT';

KW_STREAM: 'STREAM';

KW_WINDOW: 'WINDOW';

KW_SIZE: 'SIZE';

KW_ADVANCE: 'ADVANCE';

// Operators

// NOTE: if you add a new function/operator, add it to sysFuncNames so that describe function _FUNC_ will work.

DOT : '.'; // generated as a part of Number rule

COMMA : ',' ;

SEMICOLON : ';' ;

LPAREN : '(' ;

RPAREN : ')' ;

LSQUARE : '[' ;

RSQUARE : ']' ;

MINUS : '-';

PLUS : '+';

// LITERALS

fragment

Letter

: 'a'..'z' | 'A'..'Z'

;

fragment

Digit

: '0'..'9'

;

fragment

Exponent

: 'e' ( PLUS|MINUS )? (Digit)+

;

fragment

Number

: (Digit)+ ( DOT (Digit)* (Exponent)? | Exponent)?

;

Identifier

: (Letter | Digit) (Letter | Digit | '_')*

;

WS  :  (' '|'\r'|'\t'|'\n') {$channel=HIDDEN;}

;

将该语法文件保存,文件名为 StreamSQL.g 文件名必须与语法名相同。

java -cp /opt/java_lib/antlr-3.4-complete.jar org.antlr.Tool   StreamSQL.g

运行上面的命令,ANTLR就会生成相应的词法和语法解析代码。

ls | egrep Lexer\|Parser\|tokens

StreamSQLLexer.c

StreamSQLLexer.h

StreamSQLParser.c

StreamSQLParser.h

StreamSQL.tokens

代码实现

pANTLR3_INPUT_STREAM input;

pSQLLexer lxr;

pANTLR3_COMMON_TOKEN_STREAM tstream;

pSQLParser psr;

const char * inputString = sqlStatement.c_str();

input = antlr3StringStreamNew((uint8_t *) inputString, ANTLR3_ENC_UTF8,

strlen(inputString), (uint8_t *) "test_statement");

lxr = SQLLexerNew(input);

tstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT,

TOKENSOURCE(lxr));

psr = SQLParserNew(tstream);

SQLParser_statement_return statementAST = psr->statement(psr);

/* get the AST root */

pANTLR3_BASE_TREE root = statementAST.tree;

pANTLR3_BASE_TREE treeNode;

treeNode = (pANTLR3_BASE_TREE) root->getChild(root, 0);

ANTLR3_UINT32 treeType = treeNode->getType(treeNode);

string result;

switch (treeType)

{

case TOK_CREATE_SCHEMA:

result = parseCreateSchema(treeNode);

break;

case TOK_CREATE_STREAM:

result = parseCreateStream(treeNode);

break;

case TOK_CREATE_WINDOW:

result = parseCreateWindow(treeNode);

break;

case TOK_SELECT:

result = parseSelect(treeNode);

break;

default:

WARN << "Unknown tree type... " << treeType;

break;

}

input->close(input);

lxr->free(lxr);

tstream->free(tstream);

psr->free(psr);

ANTLR 的 runtime 会根据输入生成对应的抽象语法树AST,我们只要从根节点遍历该AST 即可以完成该语句的解析。下图为CREATE SCHMEA 的一个抽象语法树:

未完成的章节

1. 出错提示:当遇到无法解析的语句时,应该可以给出提示在那一行那一个字符解析的时候出错了。

2. ANTLR的高级特性:backtracking, memoization, syntactic predicates, LL(*) parsing

 

同类的工具

1. flex/bison  PostgreSQL 用的是这个。

2. JavaCC

3. Yacc  MySQL 用的是这个

4. Lemon 一个小巧的词法语法解析器,SQLite 用的是这个。

参考资料

1. The Definitive ANTLR Reference Building Domain-Specific Languages – Terence Parr

2. http://www.antlr.org/wiki/display/ANTLR3/ANTLR+3+Wiki+Home

3. http://en.wikipedia.org/wiki/ANTLR

ANTLR3 简介及示例的更多相关文章

  1. Android查缺补漏(IPC篇)-- 进程间通讯之Socket简介及示例

    本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8425736.html 进程间通讯篇系列文章目录: Android查缺补漏(IP ...

  2. IdentityServer4 中文文档 -6- (简介)示例服务器和测试

    IdentityServer4 中文文档 -6- (简介)示例服务器和测试 原文:http://docs.identityserver.io/en/release/intro/test.html 目 ...

  3. Ruby简介,附带示例程序

    Ruby语言是日本人松本行弘于1993年器开始着手研发,经历2年时间,发布了Ruby语言的第一个版本:0.95版.     Ruby是一种非常简介的解释性语言,一种纯粹的面向对象编程语言,甚至比Jav ...

  4. IText简介及示例

    一.iText简介 iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库.通过iText不仅可以生成PDF或rtf的文档,而且可以将XML.Html文 ...

  5. Unity 3(一):简介与示例

    本文关注以下方面(环境为VS2012..Net Framework 4.5以及Unity 3): Ioc/DI简介: Unity简单示例 一.Ioc/DI简介 IoC 即 Inversion of C ...

  6. JavaScript简介及示例

    JavaScript简介及使用 一.简介 JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛 ...

  7. Lambda表达式 简介 语法 示例

    Lambda 表达式也称为闭包,是匿名类的简短形式.Lambda 表达式简化了[单一抽象方法声明接口]的使用,因此 lambda 表达式也称为功能接口. 在 Java SE 7 中,单一方法接口可使用 ...

  8. Spring IO Platform简介及示例

    什么是Spring IO Platform Spring IO Platform,简单的可以认为是一个依赖维护平台,该平台将相关依赖汇聚到一起,针对每个依赖,都提供了一个版本号: 这些版本对应的依赖都 ...

  9. Lambda表达式 简介 语法 示例 匿名内部类

    在AS中使用 Lambda 表达式 Demo地址:https://github.com/baiqiantao/MultiTypeTest.git Gradle(Project级别)中添加classpa ...

  10. 003-unity3d 物理引擎简介以及示例

    一.概述 物理引擎就是模拟真实世界中物体碰撞.跌落等反应的引擎,通过ballence.愤怒的小鸟等理解.Unity3D的物理引擎使用的是Nvidia的PhysX. 物理引擎是一个计算机程序模拟牛顿力学 ...

随机推荐

  1. JVM笔记二双亲委派机制

    JVM笔记二双亲委派机制 JVM双亲委派机制,简单来说:我爸是李刚,有事找我爸.用三个字来说:往上捅.不信?咱们一起看看. ​ JVM的双亲委派机制 JVM类加载器是什么机制?为什么使用这种机制(这种 ...

  2. 【JS设计模式笔记】神奇的魔术师-简单工厂模式(创建型)

    简单工厂模式(Simple Factory):又叫静态工厂方法,由一个工厂对象决定创建某一种产品对象类的实例.主要用来创建同一类对象. 第一次需求 开发一个登录模块的需求,用户名输入框如果输入的内容不 ...

  3. CSS – Flex

    前言 Flex 诞生在 Float 之后, Grid 之前, 它主要是取代 Float 来实现布局. 而它没有 cover 到的地方则由 Grid 弥补. 所以当前, 我们做布局时, 几乎不用 Flo ...

  4. react-pdf预览在线PDF的使用

    1.在react项目中安装react-pdf依赖包 建议安装8.0.2版本的react-pdf,如果安装更高版本的可能出现一些浏览器的兼容性问题: npm install react-pdf@8.0. ...

  5. Windbg常用命令及分析套路

    自己也在使用windbg分析问题,但是属于刚入门所以转发下大神的总结:https://www.cnblogs.com/fj365/p/13295453.html 常用 !threadpool 查看线程 ...

  6. 2024.09.18初赛模拟MX-S/P6029记录

    MX-S 太简单了,没啥难度.\yiw $ 1, 3, 5, 7, 9 $ 的二叉搜索树棵数是卡特兰数. P6029 题意 给定一张有 $ n $ 个点,$ m $ 条边的图.可以任意交换途中两条边的 ...

  7. 基于SqlAlchemy+Pydantic+FastApi的Python开发框架

    随着大环境的跨平台需求越来越多,对与开发环境和实际运行环境都有跨平台的需求,Python开发和部署上都是跨平台的,本篇随笔介绍基于SqlAlchemy+Pydantic+FastApi的Python开 ...

  8. C#/.NET/.NET Core技术前沿周刊 | 第 8 期(2024年10.01-10.06)

    前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...

  9. Thinkphp原生验证码的使用

    Thinkphp原生验证码的使用 一. 获取验证码 public function verifyCode(){ $captcha = new \think\captcha\Captcha(); $ca ...

  10. eBPF 概述:第 3 部分:软件开发生态

    1. 前言 在本系列的第 1 部分和第 2 部分中,我们对 eBPF 虚拟机进行了简洁的深入研究.阅读上述部分并不是理解第 3 部分的必修课,尽管很好地掌握了低级别的基础知识确实有助于更好地理解高级别 ...