简单的四则运算(三)Antlr的实现

前面的文章介绍了如何写一个非常简单的解释器去完成四则运算。文章的最后也提到用 Antlr 可以帮助你完成 Lexer 和 Parser。(我老是分不清词法分析和语法分析,不知为何我国的翻译的专业词汇总是这样的) 。这篇文章就是介绍如何用这些 Antlr 工具完成四则运算(当然你要按官网的步骤先完成安装)。Antlr 真心大赞!

官网的其实就有一个例子了,正好就是四则运算。

创建一个 Expr.g4 文件

grammar Expr;		
prog:	(expr NEWLINE)* ;
expr:	expr ('*'|'/') expr
    |	expr ('+'|'-') expr
    |	INT
    |	'(' expr ')'
    ;
NEWLINE : [\r\n]+ ;
INT     : [0-9]+ ;
复制代码

用 antlr 根据文法生成 java 文件

antlr4 Expr.g4
复制代码

javac Expr*.java 编译 java 文件后

grun Expr prog -gui 后,输入四则运算的表达式1*2+4/2 ,再回车按CTRL-D。可以可视化地看到抽象树了

但官网的Sample的例子,没有遍历树和求值的。下面我们一起来完成一下。

首先给表达式的后面标记一些东西,这样 antlr 会在生成的用于遍历抽象树的接口文件中抛出这些函数,方便我们求值。

其次,引入 op 变量更友好地标记运算符。在Visitor 的上下文(ctx)中可以,ctx.op 这样直接获取到运算符。

最后,添加些常量(你可以 ExprParser.MUL),方便对比

grammar Expr;		
prog:	expr NEWLINE ;
expr:	expr op=('*'|'/') expr # MulDiv
    |	expr op=('+'|'-') expr # AddSub
    |	INT					# int
    |	'(' expr ')'		# parens
    ;
NEWLINE : [\r\n]+ ;
INT     : [0-9]+ ;

/*创建常量方便引入*/
MUL: '*';
DIV: '/';
Add: '+';
SUB: '-';
复制代码

然后用 Expr.g4 文件生成 java 文件。

antlr4 -no-listener -visitor Expr.g4
复制代码

生成的 ExprVisitor 文件中就有我们刚才标记的函数

public interface ExprVisitor<T> extends ParseTreeVisitor<T> {
	/**
	 * Visit a parse tree produced by {@link ExprParser#prog}.
	 * @param ctx the parse tree
	 * @return the visitor result
	 */
	T visitProg(ExprParser.ProgContext ctx);
	/**
	 * Visit a parse tree produced by the {@code MulDiv}
	 * labeled alternative in {@link ExprParser#expr}.
	 * @param ctx the parse tree
	 * @return the visitor result
	 */
	T visitMulDiv(ExprParser.MulDivContext ctx);
	/**
	 * Visit a parse tree produced by the {@code AddSub}
	 * labeled alternative in {@link ExprParser#expr}.
	 * @param ctx the parse tree
	 * @return the visitor result
	 */
	T visitAddSub(ExprParser.AddSubContext ctx);
	/**
	 * Visit a parse tree produced by the {@code prens}
	 * labeled alternative in {@link ExprParser#expr}.
	 * @param ctx the parse tree
	 * @return the visitor result
	 */
	T visitPrens(ExprParser.PrensContext ctx);
	/**
	 * Visit a parse tree produced by the {@code int}
	 * labeled alternative in {@link ExprParser#expr}.
	 * @param ctx the parse tree
	 * @return the visitor result
	 */
	T visitInt(ExprParser.IntContext ctx);
}
复制代码

然后我们创建用于求值的文件 EvalVisitor

public class EvalVisitor extends ExprBaseVisitor<Integer> {

    @Override
    public Integer visitProg(ExprParser.ProgContext ctx) {
        Integer value = visit(ctx.expr());
        System.out.println(value);
        return value;
    }

    /* expr ('*'|'/') expr */
    @Override
    public Integer visitMulDiv(ExprParser.MulDivContext ctx) {
        int left = visit(ctx.expr(0));
        int right = visit(ctx.expr(1));
        if (ctx.op.getType() == ExprParser.MUL){
            return left*right;
        }
        return left/right;
    }

    /* expr ('+'|'-') expr */
    @Override
    public Integer visitAddSub(ExprParser.AddSubContext ctx) {
        int left = visit(ctx.expr(0));
        int right = visit(ctx.expr(1));
        if (ctx.op.getType() == ExprParser.Add){
            return left+right;
        }
        return left-right;
    }


    /* INT */
    @Override
    public Integer visitInt(ExprParser.IntContext ctx) {
        return Integer.valueOf(ctx.INT().getText());
    }


    /* '(' expr ')' */
    @Override
    public Integer visitParens(ExprParser.ParensContext ctx) {
        return visit(ctx.expr());
    }
}
复制代码

具体的 REPL 实现如下:

import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while (true) {
            System.out.print(">");
            String command = scan.nextLine() + "\n";
            try {
                if (command.length() == 0) {
                    continue;
                }
                ExprLexer lexer = new ExprLexer(CharStreams.fromString(command));
                CommonTokenStream tokens = new CommonTokenStream(lexer);
                ExprParser parser = new ExprParser(tokens);
                ExprParser.ProgContext tree = parser.prog();
                EvalVisitor eval = new EvalVisitor();
                eval.visit(tree);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
        }
    }
}
复制代码

当然来到这里,可能会觉得一脸懵逼,不知道在干啥。

其实 Antlr 是做了 Parser 的部分,和生成一些可以快捷遍历抽象树的接口。我们实现了接口了就可以遍历抽象树求值了。

至于为何基本只要写规则就可以完成,因为这些规则就是本身就是用来描述语言的,对规则进行 Lexer、Parser 得到的中间表示(IR),遍历中间表示就可以将之转换成其他的语言(java、c),其实相当于是一个翻译。

这些不需要太在意。可以自动完成的东西就自动完成就好,编译原理很多人就是在 Parser 上栽倒就不想学后面的 IR、codegen 之类,其实那些部分更有意思,更重要。Parser 这种东西可以明白原理就好了,自动生成就可以了。

以上就是这篇文章的全部内容。


参考资料

《ANTLR 4权威指南》

原文在 blog.zhangguojian.com/2018/10/14/…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值