中文編程 "知乎專欄" 原文 "地址" 基本參考https://pragprog.com/book/tpantlr2/the definitive antlr 4 reference 一書"Building a Calculator Using a Visitor"一節, 僅添加了數學乘除法符號的支 ...
基本參考https://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference 一書"Building a Calculator Using a Visitor"一節, 僅添加了數學乘除法符號的支持(×÷). 比如下麵的算式:
3×2+8÷4-2×4
相比上一版本語法文件去除了空格定義. 需要深究的是優先順序問題. 是否因為"表達式 運算符=('*'|'/'|'×'|'÷') 表達式"寫在了前面才使得乘除法的優先順序在語法分析時更高.
至此, 感覺Antlr語法文件對中文命名的支持還是不錯的. 唯一需要權宜之計的就是Token(詞)規則必須要大寫開頭, 因此採用了首碼"T"):
grammar 圈5;
程式
: 表達式
;
表達式
: 表達式 運算符=('*'|'/'|'×'|'÷') 表達式 #乘除
| 表達式 運算符=('+'|'-') 表達式 #加減
| T數 #數
;
T數
: [0-9]+
;
T加 : '+';
T減 : '-';
T乘 : '*';
T數乘: '×';
T除 : '/';
T數除: '÷';
第一次嘗試#標號的輔助功能. 一個"表達式"語法規則生成了三個Visitor方法(如下), 訪問器仍比較簡單. 註: 語法規則中要麼所有分支都有標號, 要麼都沒有. 不然生成分析器時報錯:
public class 定製訪問器 extends 圈5BaseVisitor<節點> {
@Override
public 節點 visit數(數Context 上下文) {
TerminalNode 數 = 上下文.T數();
return 數 instanceof ErrorNode ? null : new 數節點(數.getText());
}
@Override
public 節點 visit加減(加減Context 上下文) {
表達式節點 節點 = new 表達式節點();
節點.運算符 = 上下文.運算符.getType() == 圈5Parser.T加 ? 運算符號.加 : 運算符號.減;
節點.左子節點 = visit(上下文.表達式(0));
節點.右子節點 = visit(上下文.表達式(1));
return 節點;
}
@Override
public 節點 visit乘除(乘除Context 上下文) {
表達式節點 節點 = new 表達式節點();
int 運算符 = 上下文.運算符.getType();
節點.運算符 = (運算符 == 圈5Parser.T乘 || 運算符 == 圈5Parser.T數乘) ? 運算符號.乘 : 運算符號.除;
節點.左子節點 = visit(上下文.表達式(0));
節點.右子節點 = visit(上下文.表達式(1));
return 節點;
}
}
語法樹中稍微複雜一點的"表達式"節點, 代碼很冗餘:
public class 表達式節點 extends 節點 {
public 運算符號 運算符;
@Override
public Object 求值() {
if (運算符.equals(運算符號.加)) {
return (int)(左子節點.求值()) + ((int)右子節點.求值());
} else if (運算符.equals(運算符號.減)) {
return (int)(左子節點.求值()) - ((int)右子節點.求值());
} else if (運算符.equals(運算符號.乘)) {
return (int)(左子節點.求值()) * ((int)右子節點.求值());
} else if (運算符.equals(運算符號.除)) {
return (int)(左子節點.求值()) / ((int)右子節點.求值());
} else {
return null;
}
}
}
已經要手動跑十個測試文件, 下麵除了清理代碼, 還需要加測試, 再加功能(應該是變數賦值).