解释器构造实践-ANTLR(三)

  • 2017年9月20日
    本节探讨主要是在JetBrain公司家的IntelliJ IDEA下使用ANTLR,并实现了课程要求的计算器实例。如有任何问题欢迎斧正。
  • 2017年9月22日
    对本节页面进行了调整。
  • 2017年9月23日
    添加了错误监听,有待进一步完善。

    • 一 在IntelliJ IDEA中配置ANTLR
      • 1 安装ANTLR插件
      • 2 新建maven项目
    • 二 计算器解析实现
      • 1 制定语法规则
      • 2 生成解析文件
      • 3 实现Visitor
      • 4 错误监听
      • 5 书写主函数
      • 6 测试
    • 三 问题
    • 四 参考链接

一、 在IntelliJ IDEA中配置ANTLR

在实际的开发应用当中,IDE能够给予我们一定的便捷。笔者习惯使用JetBrain家的IDE,故而使用IDEA来完成本次实验。

1.1 安装ANTLR插件

  1. 打开 Plugins
  2. 搜索 ANTLR
  3. 点击 Search in repository
  4. 安装 ANTLR v4 grammar plugin
  5. 如图
    解释器构造实践-ANTLR(三)_第1张图片

1.2 新建maven项目

  1. 新建 maven 项目
  2. 添加 pom.xml 依赖
    注意目前最新的antlr版本为4.7,该版本需要与之前插件中的ANTLR Tool版本相匹配。
<dependencies>
    
    <dependency>
        <groupId>org.antlrgroupId>
        <artifactId>antlr4-runtimeartifactId>
        <version>4.7version>
    dependency>
dependencies>

二、 计算器解析实现

2.1 制定语法规则

新建Labeled.g4:

grammar Labeled;

prog: stat+;
/* 使用#来对事件进行标记 */
stat: ID '=' expr ';'                   # assign
    | 'println(' expr ');'              # printExpr
;

expr: expr op=('*'|'/') expr            # MulDiv
| expr op=('+'|'-') expr                # AddSub
| DOUBLE                                # double
| INT                                   # int
| ID                                    # id
| '(' expr ')'                          # parens
;

ID : [a-zA-Z]+ ;
DOUBLE : [0-9]+ '.' [0-9]+;
INT : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

2.2 生成解析文件

  1. 配置
    右键Configure ANTLR
    解释器构造实践-ANTLR(三)_第2张图片
  2. 生成
    右键Generate ANTLR Recognizer

2.3 实现Visitor

新建EvalVisitor.java:


import LabeledBaseVisitor;
import LabeledParser;

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


public class EvalVisitor extends LabeledBaseVisitor<Integer> {
    Map memory = new HashMap();

    @Override
    public Integer visitPrintExpr(LabeledParser.PrintExprContext ctx) {
        // TODO Auto-generatedmethod stub
        Integer value = visit(ctx.expr()); // evaluate the exprchild
        System.out.println(value); // print theresult
        return 0;
    }

    @Override
    public Integer visitAssign(LabeledParser.AssignContext ctx) {
        // TODO Auto-generatedmethod stub
        String id = ctx.ID().getText(); // id is left-hand side of '='
        int value = visit(ctx.expr()); // compute valueof expression on right
        memory.put(id, value); // store it inour memory
        return value;
    }

    @Override
    public Integer visitParens(LabeledParser.ParensContext ctx) {
        // TODO Auto-generatedmethod stub
        return visit(ctx.expr()); // return childexpr's value
    }

    @Override
    public Integer visitMulDiv(LabeledParser.MulDivContext ctx) {
        // TODO Auto-generatedmethod stub
        int left = visit(ctx.expr(0)); // get value ofleft subexpression
        int right = visit(ctx.expr(1)); // get value ofright subexpression
        if ( ctx.op.getType() == LabeledParser.MUL ) return left * right;
        return left / right; // must be DIV
    }

    @Override
    public Integer visitAddSub(LabeledParser.AddSubContext ctx) {
        // TODO Auto-generatedmethod stub
        int left = visit(ctx.expr(0)); // get value ofleft subexpression
        int right = visit(ctx.expr(1)); // get value ofright subexpression
        if ( ctx.op.getType() == LabeledParser.ADD ) return left + right;
        return left - right; // must be SUB
    }

    @Override
    public Integer visitId(LabeledParser.IdContext ctx) {
        // TODO Auto-generatedmethod stub
        String id = ctx.ID().getText();
        if ( memory.containsKey(id) ) return memory.get(id);
        return 0;
    }

    @Override
    public Integer visitInt(LabeledParser.IntContext ctx) {
        // TODO Auto-generatedmethod stub
        return Integer.valueOf(ctx.INT().getText());
    }

    @Override
    public Integer visitDouble(LabeledParser.DoubleContext ctx) {
        // TODO Auto-generatedmethod stub
        // FIXME should be Double
        return Integer.valueOf(ctx.DOUBLE().getText());
    }
}

2.4 错误监听

新建CalcErrorListener.java

import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;

import java.util.Collections;
import java.util.List;

public class CalcErrorListener extends BaseErrorListener {

    @Override
    public void syntaxError(Recognizer recognizer, 
                    Object offendingSymbol,
                    int line, int charPositionInLine, String msg,
                    RecognitionException e)
    {
        List stack = ((Parser)recognizer).getRuleInvocationStack(); Collections.reverse(stack);
        System.err.println("Rule stack: "+stack);
        System.err.println("Line "+line+":"+charPositionInLine+" at "+offendingSymbol+": "+msg);
    }
}

2.5 书写主函数

新建Calc.java。


import LabeledLexer;
import LabeledParser;
import CalcErrorListener;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

import java.io.FileInputStream;
import java.io.InputStream;

public class Calc {
    public static void main(String[] args) throws Exception {
        String inputFile = "/Users/***/Desktop/Calculator/src/main/java/success";
        InputStream is = System.in;

        if ( inputFile!=null ) is = new FileInputStream(inputFile);
        //读取标准输入
        ANTLRInputStream input = new ANTLRInputStream(is);
        //进行词法分析
        LabeledLexer lexer = new LabeledLexer(input);
        //创建token流
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        //进行语法分析
        LabeledParser parser = new LabeledParser(tokens);
        //移除原有错误监听
        parser.removeErrorListeners();
        //添加自定义错误监听
        parser.addErrorListener(new CalcErrorListener());
        //构建语法树
        ParseTree tree = parser.prog();
        //创建visitor并进行访问
        EvalVisitor eval = new EvalVisitor();
        eval.visit(tree);

    }
}

2.6 测试

1. 正确用例
新建success文件:

a=1;
b=2;
println(a+b);
println(9);

运行项目,输出如下:

3
9

2. 错误用例
新建fail文件:

prin;

修改主函数,运行项目,输出如下:

Rule stack: [prog, stat]
Line 1:4 at [@1,4:4=';',<2>,1:4]: mismatched input ';' expecting '='

三、 问题

  1. 浮点数返回时出现类型不兼容报错
Error:(68, 19) java: EvalVisitor中的visitDouble(analyzer.LabeledParser.DoubleContext)无法实现analyzer.LabeledVisitor中的visitDouble(analyzer.LabeledParser.DoubleContext)
  返回类型java.lang.Double与java.lang.Integer不兼容

四、 参考链接

  1. 《The Definitive ANTLR 4 Reference》
    https://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference
  2. 《antlr小试牛刀》
    https://w-angler.com/blog/2/35

你可能感兴趣的:(Lesson)