antlr的使用
我从以下几个问题入手介绍ANTLR的相关知识。
1 ANTLR是什么?
ANTLR, ANother Tool for Language Recognition, 是一个可以接受含有语法描述的语言描述符并且生成程序能够识别这些语言所产生的句子。作为一个翻译程序的 一部分,你可以给你的语法附上简单的操作符和行为并且告诉ANTLR如何构造AST并且如何输出它们。ANTLR知道如何使用Java,C++,C#或者Python来生成它们。
ANTLR知道如何构造识别程序并且将语法结构应用到三种不同的输入上:(i) 字符流, (ii) 标记(token)流,(iii) 二维树结构。本质上这些对应了词法分析器(Lexer),解析器(Parser)和Tree Walker。用于制定这些语法(Grammar)的句法(Syntax),被称 为meta-language,在所有情况下是独立的。
一旦你适应了ANTLR或者相应的工具,你将会以另一种眼光来看待编程。很多任务期待一种不同于传统编程语言流派的语言解决方案。举个例子,这些课程的笔记就是用TML编写 的,Terence's Markup Language。我讨厌输入HTML所以我用ANTLR编写了一个简单的翻译程序来转换文本成为HTML或者PDF或者其他我讨厌直接编写的东西。
(以上摘自http://www.cs.usfca.edu/~parrt/course/652/lectures/antlr.html)
2 ANTLR能做些什么?
ANTLR仅仅是一个工具!它帮你通过自动生成单调乏味的组件来构造程序,但并不试图让你创造一个完整的编译器,举个例子,单行的描述。
3 ANTLR怎么搭建环境?
我安装的是插件形式的ANTLR,它的版本为2.7.6.
当其他插件安装一样,比较简单。
或者:
1 、从 http://www.antlr.org/ 上下载 antlr-x.x.x.jar
2 、配置环境变量: classpath=.;x:\jdk\lib\tools.jar;x:\antlr-x.x.x.jar
3 、在命令提示符环境里在 t.g 所在目录下执行:
java antlr.Tool t.g
4 ANTLR语法文件怎么写?
4.1 Antlr 的主要类:
Antlr 中有主要类有两种(其实还有一种 TreeLexer )
Lexer: 文法分析器类。主要用于把读入的字节流根据规则分段。既把长面条根据你要的尺寸切成一段一段:)并不对其作任何修改。
Parser: 解析器类。主要用于处理经过 Lexer 处理后的各段。一些具体的操作都在这里。
4.2 Antlr 文法文件形式:
Antlr 文件是 *.g 形式,即以 g 为后缀名。
例如: t.g
class P extends Parser;
startRule
: n:NAME
{System.out.println("Hi there, "+n.getText());}
class L extends Lexer;
// one-or-more letters followed by a newline
NAME: ( 'a'……'z'|'A'……'Z' )+ NEWLINE
NEWLINE
: '\r' '\n' // DOS
| '\n' // UNIX 网管u家u.bitsCN.com
;
4.3 具体成分分析:
1 、总体结构
Class P extends Parser
Class L extends Lexer
两行同 JAVA 继承一样, P 继承 Parser 类; L 继承 Lexer 类。每个 .g 文件只能各有一个。
2 、 Lexer 类分析
一般按照:
类型名:匹配的具体规则的形式构成。是分隔字节流的依据。同时可以看到里面可以互相引用。如本例中的类型名 NEWLINE 出现在 NEW 的匹配规则中。
4.4 Parser 类分析
一般按照
起始规则名:
规则实例名:类型名或规则名
{Java 语句……; };
……
的形式构成。
起始规则名:任意。
规则实例名:就象 Java 中“ String s ;”的 s 一样。规则实例名用于在之后的 JAVA 语句中调用。
类型名或规则名:可以是在 Lexer 中定义的类型名,也可以是 Parser 中定义的规则名。感觉就像是 int 与 Integer 的区别。
Java 语句:指当满足当前规则时所执行的语句。 Antlr 会自动嵌入生成的 java 类中。
三、生成 Java 类
会在当前目录下生成如下文件:
L.java : Lexer 文法分析器 java 类。
P.java : Parser 解析器 java 类。
PTokenTypes.java : Lexer 中定义的类型具体化,供 Parser 解析器调用。
PTokenTypes.txt :当外部的(如 t2.g )要调用当前的类型或规则时要用到本文件。
4.5 执行
1 、编写 Main 类
import java.io.*;
class Main {
public static void main(String[] args) {
try {
L lexer = new L(new DataInputStream(System.in));
P parser = new P(lexer);
parser.startRule();
} catch(Exception e) {
System.err.println("exception: "+e);
}
5 再从编写一个表达式解析器文件介绍一下ANTLR语法:
表达式是几乎所有高级编程语言中,都会出现的重要组成部分。因此,如何准确的理解一个表达式,可以说是各种不同的语言所共同面临的问题。表达式千变万化,真正要想正确解释C/C++那样的复杂表达式,是非常困难的,我们这里只从最简单的表达式做起。
假设一个表达式中,只有常量,没有变量。所有的运算只有:“+”、“-”、“*”、“/”、“^”、“%”六种,而且没有括号,只有整数,没有小数。就这么简单,要解释这样的表达式,我们如果要想自己写个程序来解释,只怕也是非常麻烦的吧,而且要写一个功能全面且BUG很少的解释器势必要大费周折。
现在有了ANTLR,我们只需要将定义写清楚,程序就会自动帮我们生成了。可以先下载一个人家现成的文件来看看:expression.g,然后再antlr expression.g生成一堆java文件。
接下来的步骤和前面的也差不多,建一个Main.java,
import java.io.*;
import antlr.CommonAST;
import antlr.collections.AST;
import antlr.debug.misc.ASTFrame;
public class Main {
public static void main(String args[]) {
try {
DataInputStream input = new DataInputStream(System.in);
ExpressionLexer lexer = new ExpressionLexer(input);
ExpressionParser parser = new ExpressionParser(lexer);
parser.expr();
CommonAST parseTree = (CommonAST)parser.getAST();
System.out.println(parseTree.toStringList());
ASTFrame frame = new ASTFrame("The tree", parseTree);
frame.setVisible(true);
ExpressionTreeWalker walker = new ExpressionTreeWalker();
double r = walker.expr(parseTree);
System.out.println("Value: "+r);
} catch(Exception e) { System.err.println("Exception: "+e); }
}
}
执行这个Main.class,输入个表达式给它试一试,比如: 1+2-3*4/5^6;系统应该就能给出正确的答案了:
( - ( + 1 2 ) ( / ( * 3 4 ) ( ^ 5 6 ) ) ) ;
Value: 2.999232
咱们来一句一句的解释一下这个expression.g文件。还是从最简单的几行开始:
class ExpressionLexer extends Lexer; //这是用来声明一个词法分析器,名字叫
//ExpressionLexer
PLUS : '+' ; //加号
MINUS : '-' ; //减号
MUL : '*' ; //乘号
DIV : '/' ; //除号
MOD : '%' ; //求余
POW : '^' ; //开方
SEMI : ';' ; //结束号
//上面这些都太简单了,简直就不需要说明
protected DIGIT : '0'..'9' ; //数字,这是一个受保护的单词,只能被
//词法分析器内部使用
INT : (DIGIT)+ ; //出现了一次以上的数字的词,就是整数,
//它通过受保护的单词:“数字”来定义自己。
//如果DIGIT不是被保护的单词,则词法分析器就会
//无法分辨究竟是数字还是整数了
接下来看语法分析器的代码:
class ExpressionParser extends Parser; //定义一个语法分析器,名字叫ExpressionParser
options { buildAST=true; } //告诉ANTLR,要帮我生成一棵抽象语法树,
//留着以后有用
//接下来的部分就非常复杂了,主要是多出来了两个特殊的符号“!”、“^”
//这两个符号,不是EBNF原有的,而是ANTLR为了生成AST而增加的符号
//“!”,是告诉AST生成程序,不要把自己算进去
//“^”,是告诉AST生成程序,把这个符号,放在一颗树的根部,或者一颗子树的根部
//另外,“*”表示出现0次以上,“+”表示出现一次以上,“?”表示出现0或1次
/*以下为定义一个语法的规则,一般采用递归的形式来规定。
即就是它描述了一个表达式的表达形式。解析和计算表达式的方式有很多种。在使用递归下降的解析器时,表达式被视为递归的数据结构—— 表达式由其本身来定义。假定表达式只能使用+、-、*、/和圆括号,那么所有的表达式可以用下面的规则来定义:
表达式 项 [+项] [–项]
项因数[*因数] [/因数]
因数变量、数字或者(表达式)
方括号里面表示可选元素,而箭头表示箭头前面的元素由箭头后面的元素定义产生。实际上,该规则通常被称为表达式的生成规则。因此,对于项的定义可以这样表述:“项由因数乘以因数或者因数除以因数产生。”需要注意的是,运算符的优先级已经隐含在表达式的定义中。 */
expr : sumExpr SEMI!; //“;”作为结束符,不放入AST
sumExpr : prodExpr ((PLUS^|MINUS^) prodExpr)* ; //“+”“-”作为计算符号
//放在树的顶部
prodExpr : powExpr ((MUL^|DIV^|MOD^) powExpr)* ; //剩下的就不解释了,都能明白的
powExpr : atom (POW^ atom)? ;
atom : INT ;
再来看AST计算器的代码。这“AST计算器”是我起的名字,也就是通过对一个生成的抽象语法树,递归求值,得到最后的结果。
{import java.lang.Math;} //ExpressionTreeWalker要用到的
class ExpressionTreeWalker extends TreeParser; //声明一个树计算器
expr returns [double r] //有一个方法叫expr
//它的返回值是double类型
{double a,b; r=0; } //嵌入的代码,后面要用到
: #(PLUS a=expr b=expr) { r=a+b; } //以下就是计算各种算符,不用多说了
| #(MINUS a=expr b=expr) { r=a-b; }
| #(MUL a=expr b=expr) { r=a*b; }
| #(DIV a=expr b=expr) { r=a/b; }
| #(MOD a=expr b=expr) { r=a%b; }
| #(POW a=expr b=expr) { r=Math.pow(a,b); }
| i:INT { r=(double)Integer.parseInt(i.getText()); }
;
5.1 完整的expression.g文件:
class ExpressionParser extends Parser;
options { buildAST=true; }
expr : sumExpr SEMI;
sumExpr : prodExpr ((PLUS^|MINUS^) prodExpr)*;
prodExpr : powExpr ((MUL^|DIV^|MOD^) powExpr)* ;
powExpr : atom (POW^ atom)? ;
atom : INT ;
class ExpressionLexer extends Lexer;
PLUS : '+' ;
MINUS : '-' ;
MUL : '*' ;
DIV : '/' ;
MOD : '%' ;
POW : '^' ;
SEMI : ';' ;
protected DIGIT : '0'..'9' ;
INT : (DIGIT)+ ;
{import java.lang.Math;}
class ExpressionTreeWalker extends TreeParser;
expr returns [double r]
{ double a,b; r=0; }
: #(PLUS a=expr b=expr) { r=a+b; }
| #(MINUS a=expr b=expr) { r=a-b; }
| #(MUL a=expr b=expr) { r=a*b; }
| #(DIV a=expr b=expr) { r=a/b; }
| #(MOD a=expr b=expr) { r=a%b; }
| #(POW a=expr b=expr) { r=Math.pow(a,b); }
| i:INT { r=(double)Integer.parseInt(i.getText()); }
6 ANTLR使用中的一些使用心得:
1):在此法分析中,比如要描述一个“>=”与”>”时,如果用
BEQUAL:('>''=');
BIGER:”>”;
当语法文件进行此法分析的时,当扫描到一个”>”形式时,不知道是将其当BEQUAL还是当BIGER符号处理,即出现了冲突,那么可以采用以下这种形式定义:
BEQUAL:('>''=')=>('>''=')|'>'{ $setType(BIGER); };//它的形式为: (...)=>(...)|(...)。这相当于一般语言中的三元表达式:(1)?(2):(3)。如果式1为真,则返回式2的值,否则返回式3的值。
2):在ANTLR中一个规则相当与JAVA语言中的一个函数,因此它可以有传参和返回值,例如:
expr[HashMap hm] returns [String s]//即相当于JAVA中的:
public String expr(HashMap hm){…}
3):ANTLR中可以内嵌生成的目标语言 如:{import java.lang.Math;}
antlr的使用的更多相关文章
- antlr.collections.AST.getLine()I异常
antlr.collections.AST.getLine()I异常 Struts+hibernate+spring项目经常遇到问题 因为Struts自带的antlr-2.7.2.jar,比H ...
- 词法分析器Antlr
一.我们都知道编程语言在执行之前需要先进行编译,这样就可以把代码转换成机器识别的语言,这个过程就是编译. 那么它是怎么编译的呢? Java在JVM虚拟机中进行编译,javascript在Js引擎中编译 ...
- 一个基于ANTLR 4的布尔表达式语句解释器的实现
Reference The Definitive ANTLR 4 Reference, 2nd Edition. 0 Features labeled grammar definition, i.e. ...
- NoSuchMethodError: antlr.collections.AST.getLine()I
错误完整表述: Filter execution threw an exception] with root cause java.lang.NoSuchMethodError: antlr.coll ...
- 关于antlr包删除问题
在建这个网站,用户登录的时候,涉及查询问题,然后就出现了java.lang.NoSuchMethodError: antlr.collections.AST.getLine()I错误,我一脸蒙逼,后来 ...
- java.lang.NoClassDefFoundError: antlr/ANTLRException
在用Hibernate进行查询时,出现这样的错误:Exception in thread "main" java.lang.NoClassDefFoundError: antlr/ ...
- 使用Antlr实现简单的DSL
为什么要使用DSL DSL是领域专用语言,常见的DSL有SQL,CSS,Shell等等,这些DSL语言有别于其他通用语言如:C++,Java,C#,DSL常在特殊的场景或领域中使用.如下图: 领域专用 ...
- Antlr学习
参加工作之后,接触DSL领域语言,了解了编译原理. 比如Hibernate.Hive等的HQL都是基于antlr编写的 所以,如果想自己实现一套DSL语言,我们可以基于antlr做词法分析与语法分析 ...
- Weblogic环境下hibernate、antlr类加载冲突问题分析及解决方案
公司应用项目在客户部署时经常遇到此类问题,为避免实施部署时增加配置量,花了点时间找到了此问题的终极解决办法(方案二.修改org.hibernate.hql.ast.HqlLexer的源代码).在此进行 ...
- 如何用 ANTLR 4 实现自己的脚本语言?
ANTLR 是一个 Java 实现的词法/语法分析生成程序,目前最新版本为 4.5.2,支持 Java,C#,JavaScript 等语言,这里我们用 ANTLR 4.5.2 来实现一个自己的脚本语言 ...
随机推荐
- python configparser 创建ini文件,动态读取与修改配置文件,以及保存与读取字符串与QColor类型的配置
# 动态配置所需 from import ConfigParser # 获取系统语系所需 import locale # QColor 类型的传参所需 from PyQt6.QtGui import ...
- 人形动画常见IK的处理
Unity中常见人形动画IK的处理方式 本文将尝试仅使用Untiy内置的Animator来解决常见的几种运动所需的IK.也会给出核心功能的代码实现. 效果一览:b站视频 Unity中人形角色的IK I ...
- 小tips:docker 配置国内镜像地址
在配置文件daemon.json中添加国内镜像,让其下载加速. vi /etc/docker/daemon.json 如下国内镜像: { "registry-mirrors": [ ...
- RabbitMQ——死信队列介绍和应用
死信和死信队列的概念 什么是死信?简单来说就是无法被消费和处理的消息.一般生产者将消息投递到broker或者queue,消费者直接从中取出消息进行消费.但有时因为某些原因导致消息不能被消费,导致消息积 ...
- 音视频入门-7-ffmpeg小实验-v4l2 ubuntu 获取摄像头图像并进行格式转换
1. Linux内我们使用V4L2框架获取摄像头数据,由于摄像头的不同,摄像头所输出的数据格式各有不同. 考虑到YUV420P 的格式使用最广泛,我们最终将摄像头数据转为该格式. pic_dat ...
- 阿里云Tomcat7配置域名详解
一. 进入阿里云服务控制台,点击SSL证书 看到下载了么,对应着你的域名点击下载服务器类型选择Tomcat,点击下载,压缩包中包含 xxxxx__test.com.pfx, pfx-password. ...
- PRT预计算辐射传输方法
PRT(Precomputed Radiance Transfer)技术是一种用于实时渲染全局光照的方法.它通过预计算光照传输来节省时间,并能够实时重现面积光源下3D模型的全局光照效果. 由于PRT方 ...
- kali Linux 安装 AWVS 笔记
安装 AWVS 笔记 配置安装 https://www.zwnblog.com/archives/kali-an-zhuang-awvs 根据这篇文章 配置并安装出来的 设定的账号和密码 账号:adm ...
- AI网关在应用集成中起到什么作用?
现在,国内外几乎每个SaaS服务商都找到办法把大型语言模型(LLM)集成到自己的产品里.印证了那句话"每款SaaS都值得用AI重做一遍"我们暂且不讨论是否值得用AI重做,但是增加A ...
- 宏定义define的用法
#define read(x) scanf("%d",&x); 这行代码是一个宏定义,使用了 C 语言中的 #define 指令.它的作用是定义一个名为 read 的宏,用 ...