博客 / 詳情

返回

【編譯原理】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.antlr</groupId>
            <artifactId>antlr4-runtime</artifactId>
            <version>4.10</version>
        </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<Integer> {
    @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; i<args.length; i++) {
            if (args[i].equals("-input")) {
                input = args[++i];
            }
        }

        if (input == null) {
            System.out.println("args:  -input <expression>");
            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進行語法分析。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.