antrl4学习(三)——用idea实现简单计算器
来源:互联网 发布:淘宝网店开店流程 编辑:程序博客网 时间:2024/06/01 08:27
antrl4学习(三)——用idea实现简单计算器
为简单起见,我们只允许基本的算术操作符(加减乘除)、圆括号、整数以及变量出现。我们的算术表达式限制浮点数的使用,只允许整数出现。
首先,新建一个Model,命名为Rules,在其中创建新文件,命名为LabeledExpr.g4
其次,设计词法规则和语法规则
词法规则:
/* 词法规则 */
ID: [a-zA-Z]+; //匹配标识符
INT: [0-9]+; //匹配整数
NEWLINE:'\r'?'\n'; //告诉语法分析器一个新行的开始
WS: [ \t]+ ->skip; //丢弃空白字符
为了后续的计算的方便性,我们再定义一下加减乘除。
MUL:'*';
DIV:'/';
ADD:'+';
SUB:'-'
语法规则:
首先我们要定义表达式expr.
表达式可以是数字(INT),也可以是标识符(ID),也可以是加减乘除表达式,也可以是括号表达式。所以我们的expr的定义如下:
expr:expr op=('*'|'/')expr
|expr op=('+'|'-')expr
|INT
|ID
|'('expr')'
;
接着我们要定义语句stat
语句可以是表达式+换行符,可以是声明变量的语句,也可以是空行
定义如下:
stat:exprNEWLINE
|ID'='exprNEWLINE
|NEWLINE
;
最后定义我们的program.即多个语句
prog:stat+;
好了至此我们的语法和语义规则就定义完成了。
接下来为了以后的运算的方便性,我们还需要给每一步规则打上标签,标签以”#”开头,出现在每一条规则的右边。打上标签后,antlr会为每一个规则都生成一个事件。(这里看不懂的暂时先记下,看到后面的代码就明白了这步的作用了)。于是我们的整个g4文件定义如下。
grammarLabeledExpr;
/* 起始规则,语法分析的起点 */
prog:stat+;
stat:exprNEWLINE #printExpr
|ID'='exprNEWLINE #assign
|NEWLINE #blank
;
expr:expr op=('*'|'/')expr #MulDiv
|expr op=('+'|'-')expr #AddSub
|INT #int
|ID #id
|'('expr')' #parens
;
/* 词法规则 */
ID: [a-zA-Z]+; //匹配标识符
INT: [0-9]+; //匹配整数
NEWLINE:'\r'?'\n'; //告诉语法分析器一个新行的开始
WS: [ \t]+ ->skip; //丢弃空白字符
MUL:'*';
DIV:'/';
ADD:'+';
SUB:'-'
好了之后我们来验证一下语法是否正确
右击prog,选择Test Rule prog
在左下角的框中输入表达式,在右下角的框中会显示语法树
然后右击LabeledExpr.g4,点击configure ANTLR
我做了如图设置,记得选取最后面的第二框,这样会自动生成visitor的文件,之后想要手动遍历语法树的时候会用到
然后在右击g4文件,点击generate ANTRL recognizer,
之后,antlr会自动生成文件。这时,我们会发现它多给我们生成了两个java文件。LabeledExprBaseVisitor.java、LabeledExprVisitor.java。而原来生成的两个listener文件在这个项目中暂时用不到,可以删掉。至于visitor和listener有什么区别,由于是入门,我们在这里先不做详细的介绍,不过从其名字上我们可以大致看出他们的区别。
Listener---监听。当有事件发生时即可自动进行相关操作,强调自动
Visitor----访问。需要我们手动进行遍历,强调手动。
好了至此我们已经全部完成了antlr项目的构建,接下来我们要新建一个java project,然后把生成好的4个java文件和两个token文件拷贝过去。
有得同学可能会想,能不能直接在antlr项目下新建一个java文件来书写我们的代码,答案是否定的,antlr项目并不允许我们去直接新建class。所以我们首先需要新建一个java project,这里我们取名为Calculator,然后,然后将之前生成的文件都放到项目中,在这里,我新建app文件夹,将生成的文件(src文件夹中的所有文件)都放入其中。
这时候我们发现所有的java文件的图标上有红色图标,而且引用这些文件都会显示无法找到。
这是因为这些文件都不是源文件,在src源文件夹里的java文件都“看不见”那些文件。
因此,右击app文件夹,如图,选择source file
其次,随意打开一个java文件,发现全是报错,因为没有给项目添加antrl依赖,现在我们添加依赖,右击file,点击Progect Struture
再点击Modules,在点击dependencies
点击右侧的加号(注意是右侧的)
选择JARS or directors,然后找到之前下载的antrl4的jar包
点击ok,再看文件,我们发现报错都消失了。
现在继续项目,我们先看一下刚才生成的两个Visitor.
import org.antlr.v4.runtime.tree.ParseTreeVisitor;
/**
* This interface defines a complete generic visitor for a parse tree produced
* by {@linkLabeledExprParser}.
*
* @param<T>The return type of the visit operation. Use {@linkVoid} for
* operations with no return type.
*/
public interface LabeledExprVisitor<T>extendsParseTreeVisitor<T>{
/**
* Visit a parse tree produced by {@linkLabeledExprParser#prog}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitProg(LabeledExprParser.ProgContextctx);
/**
* Visit a parse tree produced by the {@codeprintExpr}
* labeled alternative in {@linkLabeledExprParser#stat}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitPrintExpr(LabeledExprParser.PrintExprContextctx);
/**
* Visit a parse tree produced by the {@codeassign}
* labeled alternative in {@linkLabeledExprParser#stat}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitAssign(LabeledExprParser.AssignContextctx);
/**
* Visit a parse tree produced by the {@codeblank}
* labeled alternative in {@linkLabeledExprParser#stat}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitBlank(LabeledExprParser.BlankContextctx);
/**
* Visit a parse tree produced by the {@codeparens}
* labeled alternative in {@linkLabeledExprParser#expr}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitParens(LabeledExprParser.ParensContextctx);
/**
* Visit a parse tree produced by the {@codeMulDiv}
* labeled alternative in {@linkLabeledExprParser#expr}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitMulDiv(LabeledExprParser.MulDivContextctx);
/**
* Visit a parse tree produced by the {@codeAddSub}
* labeled alternative in {@linkLabeledExprParser#expr}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitAddSub(LabeledExprParser.AddSubContextctx);
/**
* Visit a parse tree produced by the {@codeid}
* labeled alternative in {@linkLabeledExprParser#expr}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitId(LabeledExprParser.IdContextctx);
/**
* Visit a parse tree produced by the {@codeint}
* labeled alternative in {@linkLabeledExprParser#expr}.
* @paramctxthe parse tree
* @returnthe visitor result
*/
TvisitInt(LabeledExprParser.IntContextctx);
}
看看我们这几个方法的名字,PrintExpr, Assign,Blank......有没有感觉很熟悉。没错这就是我们刚才在g4文件的时候为每一条规则的后面打上了tag.
而另一个visitor,LabeledExprBaseVisitor.java则是对本接口的一个简单的实现,我们后续的操作需要继承LabeledExprBaseVisitor.java来实现。
Visitor的作用顾名思义就是对整个语法树进行遍历,然后进行相关操作。
现在我们进行对语法树的操作,需要重写每一个方法。
在src文件夹中新建class EvalVisitor,开始编辑该类
我们先声明一个map.用于存放变量与其对应的值。
Map<String,Integer>memory=newHashMap<String,Integer>();
@Override
public IntegervisitAssign(LabeledExprParser.AssignContextctx) {
//TODO Auto-generatedmethod stub
Stringid=ctx.ID().getText();// id is left-hand side of '='
intvalue=visit(ctx.expr());// compute valueof expression on right
memory.put(id,value);// store it in our memory
returnvalue;
}
这个实现很简单,获取id,获取value(通过expr()计算表达式的值),然后放入map。
@Override
public IntegervisitPrintExpr(LabeledExprParser.PrintExprContextctx) {
//TODO Auto-generatedmethod stub
Integervalue=visit(ctx.expr());// evaluate the exprchild
System.out.println(value);// print theresult
return0;// return dummyvalue
}
这个实现就是计算通过expr()计算表达式的值,然后打印出来。
@Override
public IntegervisitInt(LabeledExprParser.IntContextctx) {
//TODO Auto-generatedmethod stub
returnInteger.valueOf(ctx.INT().getText());
}
这个实现就是如果碰见了INT,就直接返回它的数值。
@Override
public IntegervisitParens(LabeledExprParser.ParensContextctx) {
//TODO Auto-generatedmethod stub
returnvisit(ctx.expr());// return childexpr's value
}
碰到了这样的带有括号的表达式,返回括号内的值。
@Override
public IntegervisitId(LabeledExprParser.IdContextctx) {
//TODO Auto-generatedmethod stub
Stringid=ctx.ID().getText();
if (memory.containsKey(id) )return memory.get(id);
return 0;
}
检测ID,如果map中含有这个ID,就返回其对应的value值。
@Override
public IntegervisitMulDiv(LabeledExprParser.MulDivContextctx) {
//TODO Auto-generatedmethod stub
intleft=visit(ctx.expr(0));// get value ofleft subexpression
intright=visit(ctx.expr(1));// get value ofright subexpression
if( ctx.op.getType()==LabeledExprParser.MUL)returnleft*right;
return left/right;// must be DIV
}
@Override
public IntegervisitAddSub(LabeledExprParser.AddSubContextctx) {
//TODO Auto-generatedmethod stub
intleft=visit(ctx.expr(0));// get value ofleft subexpression
intright=visit(ctx.expr(1));// get value ofright subexpression
if( ctx.op.getType()==LabeledExprParser.ADD)returnleft+right;
return left-right;// must be SUB
}
上面两个函数即是我们的加减乘除的核心运算了。我们再来看一下我们的语法规则。以乘除为例.expr op=(‘*’|’/’) expr.
在上述式子中我们有两个expr。第一个代表我们运算的第一个值,第二个代表我们运算的第二个值,中间是运算符 ‘*’或者‘/’。所以我们要先获取两个值,然后判断计算符‘op’到底是加号还是减号,由于我们刚才在g4文件中已经定义了,ADD,MULTIPLY等四个值,现在直接可以使用LabeledExprParser.MULTIPLY来获取其运算符。
好了至此我们所有的函数都已重写完毕。整体的代码如下。
/**
* Created by think on 2017/9/21.
*/
import java.util.HashMap;
import java.util.Map;
public class EvalVisitorextendsLabeledExprBaseVisitor<Integer>{
Map<String,Integer>memory=newHashMap<String,Integer>();
@Override
publicIntegervisitPrintExpr(LabeledExprParser.PrintExprContextctx) {
//TODO Auto-generatedmethod stub
Integervalue=visit(ctx.expr());// evaluate the exprchild
System.out.println(value);// print theresult
return0;// return dummyvalue
}
@Override
publicIntegervisitAssign(LabeledExprParser.AssignContextctx) {
//TODO Auto-generatedmethod stub
Stringid=ctx.ID().getText();// id is left-hand side of '='
intvalue=visit(ctx.expr());// compute valueof expression on right
memory.put(id,value);// store it in our memory
returnvalue;
}
@Override
publicIntegervisitBlank(LabeledExprParser.BlankContextctx) {
//TODO Auto-generatedmethod stub
return super.visitBlank(ctx);
}
@Override
publicIntegervisitParens(LabeledExprParser.ParensContextctx) {
//TODO Auto-generatedmethod stub
returnvisit(ctx.expr());// return childexpr's value
}
@Override
publicIntegervisitMulDiv(LabeledExprParser.MulDivContextctx) {
//TODO Auto-generatedmethod stub
intleft=visit(ctx.expr(0));// get value ofleft subexpression
intright=visit(ctx.expr(1));// get value ofright subexpression
if( ctx.op.getType()==LabeledExprParser.MUL)returnleft*right;
return left/right;// must be DIV
}
@Override
publicIntegervisitAddSub(LabeledExprParser.AddSubContextctx) {
//TODO Auto-generatedmethod stub
intleft=visit(ctx.expr(0));// get value ofleft subexpression
intright=visit(ctx.expr(1));// get value ofright subexpression
if( ctx.op.getType()==LabeledExprParser.ADD)returnleft+right;
return left-right;// must be SUB
}
@Override
publicIntegervisitId(LabeledExprParser.IdContextctx) {
//TODO Auto-generatedmethod stub
Stringid=ctx.ID().getText();
if (memory.containsKey(id) )return memory.get(id);
return 0;
}
@Override
publicIntegervisitInt(LabeledExprParser.IntContextctx) {
//TODO Auto-generatedmethod stub
returnInteger.valueOf(ctx.INT().getText());
}
}
最后我们来书写一个主程序来启动代码。
/**
* Created by think on 2017/9/21.
*/
import java.io.FileInputStream;
import java.io.InputStream;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class Main{
public static voidmain(String[] args)throws Exception{
// create a CharStream thatreads from standard input
StringinputFile="data.txt";
InputStreamis=System.in;
if ( inputFile!=null) is=newFileInputStream(inputFile);
ANTLRInputStreaminput=newANTLRInputStream(is);
LabeledExprLexerlexer=newLabeledExprLexer(input);
CommonTokenStreamtokens=newCommonTokenStream(lexer);
LabeledExprParserparser=newLabeledExprParser(tokens);
ParseTree tree=parser.prog();// parse
EvalVisitoreval=newEvalVisitor();
eval.visit(tree);
}
}
单击运行,会发现有一个文件报方法重载错误,注释该方法后再运行,会报错ANTLR Tool version 4.7 used for code generation does not match the current runtime 4.5.3.
这是因为IDEA的antlr4插件只支持4.7,因此我们需要重新下载antlr-4.7-complete.jar,重新配置环境变量,然后给项目添加插件和依赖,这时会报Fail to load plugin descriptor from file antlr-4.7-complete.jar,这时,添加插件的方法改为Browse respositories,搜索到插件再安装,最后重新添加项目依赖,改为4.7版本的jar包
我们的data文件的内容及运行结果如图
这样,简单的计算器就完成了
参考链接:http://blog.csdn.net/yuexiang321/article/details/52770283
- antrl4学习(三)——用idea实现简单计算器
- Unity3d学习笔记2——GUI实现简单计算器
- Android学习—计算器实现
- C# WindowForm 三句话实现简单计算器
- C#实现计算器(简单计算器)
- JSP学习之初识JSP(实现简单的计算器)
- 编译原理—(从零开始)用flex、bison实现一个简单的计算器
- 用javascript实现简单计算器
- 《简单计算器实现(c#)》
- android学习——简单的三位数字计算器算法的实现
- 简单的图形用户界面—简单计算器的实现
- 简单工厂模式——MFC计算器实现(连续操作)
- 线性布局小实现——计算器的简单布局
- [每日一学]MFC学习笔记——简单计算器
- C#学习——简单的计算器程序
- java学习—— 制作简单的计算器
- JAVA学习之简单计算器——V1.0
- Qt4学习(1)——制作简单的计算器界面
- LeetCode编程练习
- hadoop2.7伪分布安装配置
- LeetCode编程练习
- 谷歌扩展程序开发常用工具(Develop Tools)
- LeetCode编程练习
- antrl4学习(三)——用idea实现简单计算器
- LeetCode编程练习
- C++开发uwp的坑
- LeetCode编程练习
- Android——WiFi类
- 父子上下文的关系以及对子上下文容器中Action进行AOP(事务)的思想 (important)
- Chrome像 Firefox/IE 一样拖拉收藏网页
- 同一个文件Linux环境下和windows环境下md5_file处理出来的结果不一致
- VodeoCapture读取.avi视频报错