• 【编译原理】Antlr 入门使用


    前面文章我们学习了编译器前端的词法和语法分析工具,本篇我们来看看如何借助 Antlr 工具,快速生成词法和语法分析代码。

    一、安装#

    mac 环境:
    1)安装

    brew install antlr
    

    2)配置 classpath
    (把 Antlr 的 JAR 文件设置到 CLASSPATH 环境变量中,以便顺利编译所生成的 Java 源代码。)

    vi ~/.bash_profile
    
    # 替换成你的 antlr jar 路径
    CLASSPATH=".:/opt/homebrew/Cellar/antlr/4.13.1/antlr-4.13.1-complete.jar:$CLASSPATH"
    
    source ~/.bash_profile
    

    有了这个玩意,你可以用很简单的方式定义好词法和语法文件,他会自动生成对应的解析文件,给你生成出 AST 来。

    你可以从生成的类文件中,看看是如何生成 AST 树的。
    对于我们之前遇到的左递归问题,它又是如何解决的,也是用循环代替递归么?

    生成 AST 树,算完成了词法分析和语法分析。
    根据这棵树做什么,就是语义分析了。

    二、开发 Java 项目#

    1、创建一个 maven 项目
    2、pom 中添加 Antlr 库

            <dependency>
                <groupId>org.antlrgroupId>
                <artifactId>antlr4-runtimeartifactId>
                <version>4.10version>
            dependency>
    

    3、编写一个 antlr 文件 Expr.g4。位置随意,可以放到 src 目录

    grammar Expr;
    
    expr: expr op=(ADD|SUB) expr      # AddSub
    | INT                             # int
    ;
    
    ADD: '+';
    SUB: '-';
    
    INT : [0-9]+ ;
    WS : [ \t]+ -> skip;
    

    4、编译项目 (这样可以生成一些antlr的解析器的类代码,方便后面编程)

    mvn compile
    

    你应该能在项目根目录看到一个 gen 文件夹,打开后里面是生成的 java 类
    image.png

    把这部分代码放到你的 src 包路径下 src/main/java/com/xxx/my_antlr_demo/antlr4
    5、编写调用代码
    EvalVisitor.java

    import com.shuofxz.my_antlr_demo.antlr4.ExprBaseVisitor;
    import com.shuofxz.my_antlr_demo.antlr4.ExprLexer;
    import com.shuofxz.my_antlr_demo.antlr4.ExprParser;
    
    public class EvalVisitor extends ExprBaseVisitor {
        @Override
        public Integer visitAddSub(ExprParser.AddSubContext ctx) {
            Integer left = visit(ctx.expr(0));  // should call "visit", not "visitChildren"
            Integer right = visit(ctx.expr(1));
            if (ctx.op.getType() == ExprLexer.ADD) {
                return left + right;
            } else {
                return left - right;
            }
        }
    
        @Override
        public Integer visitInt(ExprParser.IntContext ctx) {
            return Integer.valueOf(ctx.INT().getText());
        }
    }
    

    AppDemo.java

    import com.shuofxz.my_antlr_demo.antlr4.ExprLexer;
    import com.shuofxz.my_antlr_demo.antlr4.ExprParser;
    import org.antlr.v4.runtime.CharStreams;
    import org.antlr.v4.runtime.CodePointCharStream;
    import org.antlr.v4.runtime.CommonTokenStream;
    import org.antlr.v4.runtime.tree.ParseTree;
    
    public class AppDemo {
        public static void main(String[] args) {
            String input = null;
            // 此处把输入的参数,直接赋值了
            args = new String[2];
            args[0] = "-input";
            args[1] = "1+2+3-4";
            for (int i=0; iif (args[i].equals("-input")) {
                    input = args[++i];
                }
            }
    
            if (input == null) {
                System.out.println("args:  -input ");
                return;
            }
    
            CodePointCharStream charStream = CharStreams.fromString(input);
            ExprLexer lexer = new ExprLexer(charStream);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            ExprParser parser = new ExprParser(tokens);
            ParseTree tree = parser.expr();
            EvalVisitor visitor = new EvalVisitor();
    
            Object result = visitor.visit(tree);
            System.out.println("output=" + result);
        }
    }
    
    

    6、运行就能看到结果了。

    你可能会有疑问:
    兜了这么一大圈这有啥用呢?

    那我们把 Antrl 文件修改一下 Expr.g4
    ADDSUB 两个操作符换成其他的符号。

    grammar Expr;
    
    expr: expr op=(ADD|SUB) expr      # AddSub
    | INT                             # int
    ;
    
    ADD: '@';
    SUB: '#';
    
    INT : [0-9]+ ;
    WS : [ \t]+ -> skip;
    

    记得重新执行第四步生成代码并替换。

    然后我们可以把输入字符换为 1@2@3#4
    你大概猜到了这里就实现了类似操作符重载的功能。
    那么后面我们就可以用这个工具,实现我们自己的语法解析工具了。

    三、Antlr 中都做了什么?#

    antlr 语法文件中写的都是啥?

    • 分为两个部分:词法规则和语法规则
    • 词法规则定义了语言的基本词汇元素,即词法单元(Tokens)。它们通常包括标识符、常量、关键字和符号等。通常以大写字母开头,如 ADD、INT 等
    • 语法规则定义了语言的结构,说明了不同词法单元是如何组合起来形成语言结构的。语法规则描述了语句、表达式、声明等高级结构,如 expr。

    接下来我们解释一下关键执行步骤中都做了什么事情:

    // 将字符串转换为 antlr 能接受的 CodePointCharStream 类型
    CodePointCharStream charStream = CharStreams.fromString(input);
    
    // 创建一个词法分析器实例
    ExprLexer lexer = new ExprLexer(charStream);
    // 创建一个记号流实例
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    // 创建一个语法分析器实例
    ExprParser parser = new ExprParser(tokens);
    
    // 这是实际开始进行词法和语法分析的步骤,生成 AST
    ParseTree tree = parser.expr();
    
    // 遍历 AST。按照自己定义的 visitXxx() 方法执行实际的逻辑。
    EvalVisitor visitor = new EvalVisitor();
    Object result = visitor.visit(tree);
    
    • 词法分析器:词法分析的任务是将输入文本分割成一系列的记号(tokens),每个记号是语言中最小的有意义单元,如关键字、标识符、字面量等。
    • 记号流:用于从词法分析器中获取记号,并将它们组织成一个流,以便之后进行语法分析。
    • 语法分析器:对记号流tokens进行语法分析。
  • 相关阅读:
    boost之实用工具
    新版jadx-gui导入dex会提示Bad checksum
    Js执行上下文和执行栈
    Django基础二静态文件和ORM
    如何识别假爬虫?
    OpenAI官方吴达恩《ChatGPT Prompt Engineering 提示词工程师》(1)指南:提示LLM的原则
    初学Vue(全家桶)-第18天(vue3):Vue3介绍
    selenium(练习)提取dou yu网站上的数据
    (pytorch进阶之路三)conv2d
    vivado xpm 使用和封装
  • 原文地址:https://www.cnblogs.com/shuofxz/p/18124826