Antlr4入门(二)-制作一个简单的计算器

项目准备

  1. 创建Maven项目
  2. 引入依赖
<!-- https://mvnrepository.com/artifact/org.antlr/antlr4-runtime -->
<dependency>
	<groupId>org.antlr</groupId>
	<artifactId>antlr4-runtime</artifactId>
	<version>4.7</version>
</dependency>
  1. 安装antlr插件并重启idea,我这里已经安装过了
    在这里插入图片描述

生成Java

  1. 编写Cal.g4文法
grammar Cal;

prog: stat+;  //一个程序由至少一条语句组成

/*为了以后的运算的方便性,我们需要给每一步规则打上标签,标签以”#”开头,出现在每一条规则的右边。打上标签后,antlr会为每一个规则都生成一个事件*/

stat: ID '=' expr ';' #Assign //变量赋值语句
| 'print' '(' expr ')' ';' #printExpr   //输出语句
;

expr: expr op=('*'|'/') expr #MulDiv  //表达式可以是表达式之间乘除
  | expr op=('+'|'-') expr #AddSub  //表达式可以是表达式之间加减
  | NUM #NUM    //表达式可以是一个数字
  | ID #ID  //表达式可以是一个变脸
  | '(' expr ')' #parens    //表达式可以被括号括起来
  ;

MUL:'*';
DIV:'/';
ADD:'+';
SUB:'-';

ID: [a-zA-Z][a-zA-Z0-9]*; //变量可以是数字和字母,但必须以字母开头

//负数必须要用"()"括起来
NUM: [0-9]+   //正整数
  | '(' '-' [0-9]+ ')'  //负整数
| [0-9]+'.'[0-9]+   //正浮点数
| '(' '-' [0-9]+'.'[0-9]+ ')'   //负浮点数
;

WS: [ \t\r\n] -> skip;    //跳过空格、制表符、回车、换行
  1. 配置ANTLR:右键单击文件->Configure ANTLR
    设置路径、包名,注意勾选生成visitor
    在这里插入图片描述
  2. 生成Java代码:右键单击文件->Generate ANTLR
    在这里插入图片描述
    生成的代码中,我们主要关心的是CalVisitor,但它是一个接口,CalBaseVisitor是它的简单实现。

编写实现

  1. 继承CalBaseVisitor,我们需要完善计算器的功能,为每个规则实现一个方法。
    Cal.g4中定义了七条规则的文法:printExpr、Assign、MulDiv、AddSub、NUM、ID、Parens
package output;

import java.util.HashMap;
import java.util.Map;

public class EvalVisitor extends CalBaseVisitor<Double> {
  private Map<String,Double> table;

  public EvalVisitor(){
      table=new HashMap<>();
  }

    @Override
    public Double visitPrintExpr(CalParser.PrintExprContext ctx) {
        Double value=visit(ctx.expr());
        String str=value.toString();
        int index=str.indexOf('.');
        //检查是否为整数,截取小数点后的字符串,与"0"比较
        if(str.substring(index+1).equals("0"))
            //是整数,只输出小数点之前的字符串
            System.out.println(str.substring(0,index));
        else
            //不是整数,直接输出
            System.out.println(str);
        return null;
    }

    @Override
    public Double visitAssign(CalParser.AssignContext ctx) {
        //获取ID的名字
        String id=ctx.ID().getText();
        //调用expr()遍历子树,获取结果
        Double value=visit(ctx.expr());
        //赋值给ID,存放入符号表中
        table.put(id,value);
        //返回dummy value
        return null;
    }

    @Override
    public Double visitMulDiv(CalParser.MulDivContext ctx) {
        Double left=visit(ctx.expr(0));   //左值
        Double right=visit(ctx.expr(1));  //右值
        //如果逻辑错误,获取出错地方的行号和列数
        int line,column;
        if(ctx.op.getType()==CalParser.DIV){  //如果是除法
            if(right==0.0){
                line=ctx.expr(1).start.getLine();
                column=ctx.expr(1).start.getStartIndex();
                //除数为0,抛出异常
                try{
                    throw new CalException(line,column,"Divided by zero");
                }catch (CalException e){
                    System.out.println(e.toString());
                }
                return null;
            }else
                return left/right;
        }else
            return left*right;
    }

    @Override
    public Double visitAddSub(CalParser.AddSubContext ctx) {
        Double left=visit(ctx.expr(0));
        Double right=visit(ctx.expr(1));
        if(ctx.op.getType()==CalParser.ADD)
            return left+right;
        else
            return left-right;
    }

    @Override
    public Double visitNUM(CalParser.NUMContext ctx) {
        return Double.valueOf(ctx.getText()); //直接将字符串转为数字并返回
    }

    @Override
    public Double visitID(CalParser.IDContext ctx) {
        String id=ctx.getText();
        int line,column;
        //符号表中存在这个变量并且已经赋值,直接返回
        if(table.containsKey(id))
            return table.get(id);
        else{
            //变量未初始化,抛出异常
            line=ctx.start.getLine();
            column=ctx.start.getStartIndex();
            try{
                throw new CalException(line,column,"Undefined variable:"+id);
            }catch (CalException e){
                System.out.println(e.toString());
            }
            return null;
        }
    }

    @Override
    public Double visitParens(CalParser.ParensContext ctx) {
        return visit(ctx.expr());
    }
}
  1. 其中用到的自定义异常
package output;

public class CalException extends Exception {
  private int line;
  private int column;
  private String msg;

  public CalException(int line,int column,String msg){
      this.line=line;
      this.column=column;
      this.msg=msg;
  }

  @Override
  public String toString() {
      return msg+",line:"+line+",column:"+column;
  }
}

案例测试

  1. 再resources下准备一个some_cases.txt
a=(10.44*356+1.28)/2-1024*1.6;
b=a*2-a/2;
c123=a+b*b/5-(a-a*2)/b;
print(a);
print(b);
print(c123);
print(1+2-3*4/5);
  1. 编写主程序测试自己的case
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;

import java.io.FileInputStream;
import java.io.IOException;
import output.*;
public class MyCalculator {
    public static void main(String[] args) throws IOException {
        String fileName= "some_cases.txt";
        String filePath = MyCalculator.class.getClassLoader().getResource(fileName).getPath();
        //创建输入文件流
        FileInputStream inputStream=new FileInputStream(filePath);
        //转化为字符流
        CharStream input= CharStreams.fromStream(inputStream);
        //创建词法分析器
        CalLexer lexer=new CalLexer(input);
        //获取Token集
        CommonTokenStream tokenStream=new CommonTokenStream(lexer);
        //创建语法分析器
        CalParser parser=new CalParser(tokenStream);
        //分析语法
        ParseTree tree=parser.prog();
        //遍历语法树,输出结果
        EvalVisitor visitor=new EvalVisitor();
        //当遍历树时如果出错,会返回空指针,在这里捕获
        try{
            visitor.visit(tree);
        }catch (NullPointerException e){
            System.out.println("oops, we have some problem");
        }
    }
}

运行后获得的结果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44112790/article/details/110631227