如何安裝
由於是在MAC OS 下面,所以跟着官網的教程,直接copy5行代碼搞定
$ cd /usr/local/lib
$ sudo curl -O http://www.antlr.org/download/antlr-4.7-complete.jar
$ export CLASSPATH=".:/usr/local/lib/antlr-4.7-complete.jar:$CLASSPATH"
$ alias antlr4='java -jar /usr/local/lib/antlr-4.7-complete.jar'
$ alias grun='java org.antlr.v4.gui.TestRig'
但是經歷多了,就會發現
vi ~/.bash_profile
把與環境相關的內容都copy進來,這樣的話重啟計算機后仍能生效
[esc]
:wq
保存退出
source ~/.bash_profile
更新環境變量。
好了,現在就可以進行初步的操作了。
grun
這個命令的基本格式為
grun xxx.g4 __garmmar_begin [參數] [資源文件]+
其中xxx.g4為語法文件
__garmmar_begin 為語法開始內容
首先列出一些比較重要的參數:
- -tokens 打印出記號流
- -tree 以LISP風格的文本形式打印出語法分析樹
- -gui 在對話框中可視化地顯示出語法分析樹。
- -ps file.ps 在PostScript中生成一個可視化的語法分析樹表示,並把它存儲在file.ps文件中
- -encoding _name 指定輸入文件編碼
- -trace 在進入/退出規則前打印規則名稱和當前記號
- -diagnostics分析時打開診斷信息。此生成消息僅用於異常情況,如二義性
- -SLL使用更快但稍弱的分析策略
-tokens
-tree
-gui
-ps
這個功能就我發現雙擊這個ps文件后會出一個pdf文件。
其他參數由於暫時涉及不到,就暫時沒有嘗試。
二義性
在處理二義性方面的問題時。ANTLR通過選擇涉及決定的第一個選項來解決二義性。
並且在面對下面的問題時,會“智能地選擇合理結果”
BEGIN : 'begin';
ID : [a-zA-Z]+;
在遇到begin時,會用第一個規則進行匹配,如果遇到了類似beging、abegin這樣的都會用第二個規則進行匹配。
Visitor和Listener
ANTLR在它的運行庫中為兩種樹遍歷機制提供支持。默認下ANTLR生成語法分析樹和Listener接口,並在其中定義了回調方法,用於響應被內建的樹遍歷器的觸發。
在Listener和Visitor機制之間最大的不同是:Listener方法被ANTLR提供的遍歷器對象調用; 而Visitor方法必須顯式的調用visit方法遍歷它們的子節點,在一個節點的子節點上如果忘記調 用visit方法就意味着那些子樹沒有得到訪問。
在這次學習中,是用Visitor實現了一個計算器。首先上計算器的語法:
grammar Calc;
prog : stat+;
stat : expr # printExpr
| ID '=' expr # assign
| 'print(' ID ')' # print
;
expr : <assoc=right> expr '^' expr # power
| expr op=(MUL|DIV) expr # MulDiv
| expr op=(ADD|SUB) expr # AddSub
| sign=(ADD|SUB)?NUMBER # number
| ID # id
| '(' expr ')' # parens
;
ID : [a-zA-Z]+;
NUMBER : [0-9]+('.'([0-9]+)?)?
| [0-9]+;
COMMENT : '/*' .*? '*/' -> skip;
LINE_COMMENT : '//' .*? '\r'? '\n' -> skip;
WS : [ \t\r\n]+ -> skip;
MUL : '*';
DIV : '/';
ADD : '+';
SUB : '-';
我覺得在這個文法中有一些細節值得強調,一個是運算符的優先級,第二個是 #號后面的東西有什么用,最后就是<assoc=right>
這個東西。
運算符優先級類如加減乘除這些基本法則在Antlr中已經自動幫你處理,就參考我上面寫的expr,有時候並不意味着你這樣寫
....
| expr op=(MUL|DIV) expr
| expr op=(ADD|SUB) expr
....
就可以讓乘除先於加減,它與怎樣排列無關。
但是,Antlr會把我們的目標腳本,解析生一棵抽象語法樹,越是靠近葉子節點的地方,結合優先級越高,越是靠近根節點的地方,結合優先級越低。
在就上面的expr來談,
...
| ID
| '(' expr ')'
...
他們的優先級要高於加減乘除運算
#號有什么用呢?
在Visitor模式中,它會給你生成一些visit方法,方便你的編程。
<assoc=right>
有什么用呢?
在默認情況下,Antlr是默認從左向右結合運算符,然而像指數群這樣的運算符則是要從右向左,因此我們必須使用assoc手動指定運算符,這樣就能把2^3^4解釋成2^(3^4)。
下面上一下自己寫的EvalVisitor類。
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
public class EvalVisitor extends CalcBaseVisitor<Double> {
Map<String, Double> memory = new HashMap<String, Double>();
//id = expr
@Override
public Double visitAssign(CalcParser.AssignContext ctx){
String id = ctx.ID().getText();
Double value = visit(ctx.expr());
memory.put(id, value);
return value;
}
// expr
@Override
public Double visitPrintExpr(CalcParser.PrintExprContext ctx) {
Double value = visit(ctx.expr());
//保留兩位有數字的方法
DecimalFormat df = new DecimalFormat("#.##");
String s_value = df.format(value);
System.out.println(s_value);
return 0.0;
}
//print
@Override
public Double visitPrint(CalcParser.PrintContext ctx){
String id = ctx.ID().getText();
Double value=0.0;
if(memory.containsKey(id)) value = memory.get(id);
DecimalFormat df = new DecimalFormat("#.##");
String s_value = df.format(value);
System.out.println(s_value);
return value;
}
//Number
@Override
public Double visitNumber(CalcParser.NumberContext ctx){
int size = ctx.getChildCount();
if(size == 2){
if(ctx.sign.getType() == CalcParser.SUB){
return -1 * Double.valueOf(ctx.getChild(1).getText());
}else{
return Double.valueOf(ctx.getChild(1).getText());
}
}else{
return Double.valueOf(ctx.getChild(0).getText());
}
}
//ID
@Override
public Double visitId(CalcParser.IdContext ctx){
String id = ctx.ID().getText();
if(memory.containsKey(id)) return memory.get(id);
return 0.0;
}
//expr op=('*'|'/') expr
@Override
public Double visitMulDiv(CalcParser.MulDivContext ctx) {
Double left = visit(ctx.expr(0));
Double right = visit(ctx.expr(1));
if(ctx.op.getType() == CalcParser.MUL){
return left * right;
}else{
if(right == 0 || right == 0.0){
System.out.println("Divisor can not be zero");
return 0.0;
}else{
return left / right;
}
}
}
//expr op=('+'|'-') expr
@Override
public Double visitAddSub(CalcParser.AddSubContext ctx){
Double left = visit(ctx.expr(0));
Double right = visit(ctx.expr(1));
if(ctx.op.getType() == CalcParser.ADD)
return left + right;
return left - right;
}
// '(' expr ')'
@Override
public Double visitParens(CalcParser.ParensContext ctx){
return visit(ctx.expr());
}
// '^'
@Override
public Double visitPower(CalcParser.PowerContext ctx){
Double base = visit(ctx.expr(0));
Double exponet = visit(ctx.expr(1));
return Math.pow(base, exponet);
}
}
還有Calc.java類,為開始類
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.InputStream;
public class Calc {
public static void main(String[] args) throws Exception {
CharStream input;
if(args.length == 1) {
String fileName = String.valueOf(args[0]);
input = CharStreams.fromFileName(fileName);
}else if(args.length > 1 || args.length < 0){
throw new Exception("the number of arguments is false, Please only give the source file or nothing and then you input your text");
}else {
InputStream is = System.in;
input = CharStreams.fromStream(is);
}
CalcLexer lexer = new CalcLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CalcParser parser = new CalcParser(tokens);
ParseTree tree = parser.prog();
EvalVisitor eval = new EvalVisitor();
eval.visit(tree);
System.out.println(tree.toStringTree(parser));
}
}
最后通過下面的命令便可以運行
antlr4 -no-listener -visitor Calc.g4
javac *.java
java Calc 或 java Calc calc.txt
grun Calc prog -gui calc.txt #可看生成樹
具體代碼及相關學習書籍在這摸我