前面文章我們學習了編譯器前端的詞法和語法分析工具,本篇我們來看看如何藉助 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 類
把這部分代碼放到你的 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
ADD 和 SUB 兩個操作符換成其他的符號。
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進行語法分析。