利用ANTLR4实现一个简单的四则运算计算器

ANTLR4介绍

ANTLR能够自动地帮助你完成词法分析和语法分析的工作, 免去了手写去写词法分析器和语法分析器的麻烦

它是基于LL(k)的, 以递归下降的方式进行工作.ANTLR v4还支持多种目标语言。本文用java来写代码。

总结一下:ANTRL能自动完成语法分析和词法分析过程,并生产框架代码,让我们写相关过程的时候只需要往固定位置添加代码即可。大大简便了语法分析词法分析的过程。

ANTLR4安装配置

因为用IDEA,所以直接介绍在IDEA中怎么安装,在IDEA中安装ANTLR4相关插件即可。然后MAVEN引用下

```
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4</artifactId>
<version>4.5.2</version>
</dependency>
```

ANTLR4 语法描述文件

ANTLR4有专门的语法来构建整个过程


grammar Expr; 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 : '*' ; // assigns token name to '*' used above in grammar
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip;

相关语法很简单, 整体来说一个原则,递归下降。 即定义一个表达式(如expr),可以循环调用直接也可以调用其他表达式,但是最终肯定会有一个最核心的表达式不能再继续往下调用了。

以上代码在真正执行的时候会生成一棵抽象语法树,选择“prog”然后->"Test Rule prog", 输入测试数据“(1 + 2)+3-4*5”,然后我们会就可以看到一棵语法树了。

相关生成的java代码

整个语法文件的目的是为了让antlr生产相关的java代码。 我们先设置下生成visitor, 然后,他会生成如下几个文件:

  1. ExprParser
  2. ExprLexer
  3. ExprBaseVistor
  4. ExprVisitor

ExprLexer 是词法分析器, ExprParser是语法分析器。 一个语言的解析过程一般过程是 词法分析-->语法分析。这是ANTLR4为我们生成的框架代码, 而我们唯一要做的是自己实现一个Vistor,一般从ExprBaseVistor继承即可。

ANTLR 会为ExprBaseVistor 从定义的symoble文件如“#printExpr, #assign” ,自动生成相应的还是,然后就实现这些还是就可以实现我们的功能了。 如:

    @Override
public Integer visitAssign(ExprParser.AssignContext ctx) {
String id = ctx.ID().getText();
Integer value = visit(ctx.expr());
this.memory.put(id, value);
return value; } @Override
public Integer visitInt(ExprParser.IntContext ctx) {
return Integer.valueOf(ctx.INT().getText());
} @Override
public Integer visitMulDiv(ExprParser.MulDivContext ctx) {
Integer left = visit(ctx.expr(0));
Integer right = visit(ctx.expr(1)); if (ctx.op.getType() == ExprParser.MUL){
return left * right;
}else{
return left / right;
} }

解释下Context的应用, Context 可以通过 expr(i) 取上下文的子内容。

然后就可以用如下方式是使用了:

    public static void main(String [] args) throws IOException {
ANTLRInputStream inputStream = new ANTLRInputStream("1 + 2 + 3 * 4+ 6 / 2");
ExprLexer lexer = new ExprLexer(inputStream); CommonTokenStream tokenStream = new CommonTokenStream(lexer);
ExprParser parser = new ExprParser(tokenStream);
ParseTree parseTree = parser.prog();
EvalVisitor visitor = new EvalVisitor();
Integer rtn = visitor.visit(parseTree);
System.out.println("#result#"+rtn.toString());
}

运行一下,可以得到正确的结果了。

分析下整个过程

好神奇。 我们来分析下整个过程是如何实现的。

首先Antlr4会根据相关的语法文件生成ExprParser类,其内容是由 其语法内容决定的。如上的语法中有三个表达式:prog,stat,expr,所以就生成了三个函数:

    public final ProgContext prog() throws RecognitionException {
ProgContext _localctx = new ProgContext(_ctx, getState());
enterRule(_localctx, 0, RULE_prog);
try {
enterOuterAlt(_localctx, 1);
{
setState(6);
stat();
}
}
catch (RecognitionException re) {
_localctx.exception = re;
_errHandler.reportError(this, re);
_errHandler.recover(this, re);
}
finally {
exitRule();
}
return _localctx;
}
    public final StatContext stat() throws RecognitionException {
StatContext _localctx = new StatContext(_ctx, getState());
enterRule(_localctx, 2, RULE_stat);
...

stat过程是真正的语法分析过程, 他会把相应的token填上不同的StatContext.

整个语法解析的过程就是 prop -> stat ->expr。

在语法文件中有MUL,DIV 等几个关键字, Antlr会自动识别其是否有子项调用如果没有则这样定义:

    public static final int
T__0=1, T__1=2, T__2=3, MUL=4, DIV=5, ADD=6, SUB=7, ID=8, INT=9, NEWLINE=10,
WS=11;
public static final int
RULE_prog = 0, RULE_stat = 1, RULE_expr = 2;

有了parser, 下一个疑问就是parser如何和我们写的visitor联系起来的。 这就要借助于一个非常重要的概念:Context.

因为语法文件中有8个symbol ,所以会对于生成不同的Context.

最终返回出去:

    ParseTree parseTree = parser.prog();
EvalVisitor visitor = new EvalVisitor();
Integer rtn = visitor.visit(parseTree);

一个典型的Context是这样实现的:

    public static class IntContext extends ExprContext {
public TerminalNode INT() { return getToken(ExprParser.INT, 0); }
public IntContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof ExprVisitor ) return ((ExprVisitor<? extends T>)visitor).visitInt(this);
else return visitor.visitChildren(this);
}
}

特别关注 accept 的实现。

看下 visitor的实现

    public T visit(ParseTree tree) {
return tree.accept(this);
}

典型的visitor模式的实现。 以上这个流程是:

  1. 通过parser返回一个xxContext的树
  2. 在visitor中调用 xxContent的accept方法
  3. xxContext 调用visitor的具体实现方法: 如:visitMulDiv
  4. 在实现vistor方法时候,注意如果还有chilContent,继续往下。

总结

Antlr4 屏蔽了语法分析和词法分析的细节。大大简化了开发的工作量。 而且使用简单方便。对比 Boost.Spirit,简直一个是自动挡的汽车,一个是飞机。

利用ANTLR4实现一个简单的四则运算计算器的更多相关文章

  1. php实现一个简单的四则运算计算器

    php实现一个简单的四则运算计算器(还不支持括号的优先级).利用栈这种数据结构来计算表达式很赞. 这里可以使用栈的结构,由于php的数组“天然”就有栈的特性,这里直接就利用了数组.当然可以使用栈结构写 ...

  2. 完成一段简单的Python程序,用于实现一个简单的加减乘除计算器功能

    #!/bin/usr/env python#coding=utf-8'''完成一段简单的Python程序,用于实现一个简单的加减乘除计算器功能'''try: a=int(raw_input(" ...

  3. 作业1开发一个简单的python计算器

    开发一个简单的python计算器 实现加减乘除及拓号优先级解析 用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568 ...

  4. 老男孩python作业5-开发一个简单的python计算器

    开发一个简单的python计算器 实现加减乘除及拓号优先级解析 用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568 ...

  5. 利用 nodeJS 搭建一个简单的Web服务器(转)

    下面的代码演示如何利用 nodeJS 搭建一个简单的Web服务器: 1. 文件 WebServer.js: //-------------------------------------------- ...

  6. 利用VS2008发布一个简单的webservice

    一个开发好的webservice,怎样发布出去,供其他电脑访问呢? 本文将介绍如何发布一个简单的webservice,其中的内容都是在网上查看别人文章,自己仿照着做了一遍,因此,难免会发生错误,如果发 ...

  7. 利用java实现一个简单的远程监控程序

    一般的远程监控软件都是用c或者c++等语言开发的,而使用java如何来实现相同的功能呢. 首先我们先介绍一下一个简单的远程监控程序的实现原理. 功能一,远程屏幕监视 (1) 必须要有监控端与被监控端, ...

  8. 利用jmeter做一个简单的性能测试并进行参数化设置

    1.新增一个线程组,并在下面添加基本原件,包括:监听器.http请求默认值和一个事务控制器 在http请求默认值中填写 ip 地址和端口号,协议类型默认为http 2.添加代理服务器,以便之后进行录制 ...

  9. Asp.NetMVC利用LigerUI搭建一个简单的后台管理详解(函登录验证)

    上一篇 Asp.Net 中Grid详解两种方法使用LigerUI加载数据库数据填充数据分页  了解了LigerUI 中Grid的基本用法  现在结合上一篇的内容做一个简单的后台管理,当然也有前台的页面 ...

随机推荐

  1. 使用 yum 安装 virtualbox 虚拟机

    我的环境是centos7,所以也可以说是在centos7下使用yum安装virtualbox,不过对于其他的LINUX发行版方法都一样. 下面的操作都是在命令行中进行的. 1.首先需要配置yum的源. ...

  2. 解决echsop兼容jquery(transport.js的冲突)的问题

    方案一: 本人亲测过,可以用.有的人说需要删除js目录下的gobal.js文件,否则依然会冲突.我没删除也解决了冲突. 1.加入JSON2.js文件 原因很简单,transport修改Object是为 ...

  3. int(*f)(int)

    int(*f)(int): 为指向函数的指针变量的定义方法,其中f为指向函数的指针变量,第一个int为函数返回值类型,第二个int为函数的形参类型.

  4. LoadRunner使用技巧-IP欺骗的使用

    设置IP欺骗的原因         1.当某个IP的访问过于频繁,或者访问量过大是,服务器会拒绝访问请求,这时候通过IP欺骗可以增加访问频率和访问量,以达到压力测试的效果. 2.某些服务器配置了负载均 ...

  5. JavaScript 函数声明,函数表达式,匿名函数,立即执行函数之区别

    函数声明:function fnName () {-};使用function关键字声明一个函数,再指定一个函数名,叫函数声明. 函数表达式 var fnName = function () {-};使 ...

  6. python 2.43 升级到2.7

    [root@GW1 bin]# lsb_release -aLSB Version: :core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3. ...

  7. erlang文章博客推荐

    博客排名不分先后. 诸霸,http://blog.yufeng.info/. 淘宝核心系统资深技术专家,真的是专家,了解erlang已不久局限于erlang,而且现在也一直在更新技术博文,和陈晧一样. ...

  8. python中matplotlib画折线图实例(坐标轴数字、字符串混搭及标题中文显示)

    最近在用python中的matplotlib画折线图,遇到了坐标轴 "数字+刻度" 混合显示.标题中文显示.批量处理等诸多问题.通过学习解决了,来记录下.如有错误或不足之处,望请指 ...

  9. selenium python的使用(二)

    1.selenium获取到的信息是 把页面加载完毕之后 获取异步加载的html源码 html=driver.find_element_by_xpath("/html").get_a ...

  10. android 三步实现沉浸式 简单到无法想象

    今天产品来看进度,说ios状态栏可以改颜色,以前竟然也没注意过,看了美团 ,扣扣的实现, 才注意到.着手开始做.网上借鉴了点 ,各种乱,整理了下  .希望可以帮到大家 . [转载请标明出处] 前提:  ...