例程(更多測試用例在此):javascript
基數=100
基數×(基數+1)÷2
=> 求值爲5050
複製代碼
續上文Antlr4實現數學四則運算, 修改的語法規則部分:java
程序: 聲明+;
聲明: 表達式 T新行 #求值
| T變量名 '=' 表達式 T新行 #賦值
| T新行 #空行
;
表達式: 表達式 運算符=('*'|'/'|'×'|'÷') 表達式 #乘除
| 表達式 運算符=('+'|'-') 表達式 #加減
| T數 #數
| T變量名 #變量
| '(' 表達式 ')' #括號
;
T變量名: ('a' .. 'z' | 'A' .. 'Z' | '\u4E00'..'\u9FA5' | '\uF900'..'\uFA2D')+;
T新行: '\r'?'\n';
複製代碼
很明顯, 變量名的範圍仍需擴展, 好比數字就不支持, 並且這個字符範圍應該有些過大(詳見Validate a JavaScript function name), 待修正(變量字符範圍 · Issue #1 · program-in-chinese/quan5).git
定製訪問器添加的部分:github
private static Map<String, 節點> 變量值表 = new HashMap<>();
// 如下爲聲明部分
@Override
public 節點 visit賦值(賦值Context 上下文) {
String 變量名 = 上下文.T變量名().getText();
變量值表.put(變量名, visit(上下文.表達式()));
return null;
}
@Override
public 節點 visit求值(求值Context 上下文) {
return visit(上下文.表達式());
}
// 如下爲表達式部分
@Override
public 節點 visit變量(變量Context 上下文) {
String 變量名 = 上下文.T變量名().getText();
// TODO: 添加變量檢查
return 變量值表.get(變量名);
}
@Override
public 節點 visit括號(括號Context 上下文) {
return visit(上下文.表達式());
}
複製代碼
變量值表採用變量名到節點的映射, 也就是在對包含這個變量的表達式求值時纔對變量對應的表達式進行求值. 這裏沒有對變量賦值表達式進行語法樹構建 · Issue #2 · program-in-chinese/quan5, 還需更多工做. 另一個問題, 最後的表達式求值也會對變量值重複計算. 舉例:bash
利率=1
年增加率=1+利率
1000×年增加率×年增加率
複製代碼
最後語法樹以下:ide
"年增加率"應該提早求值, 以省去最後的屢次計算(避免對變量重複求值 · Issue #3 · program-in-chinese/quan5)測試
後兩個問題已初步解決, 經過在"運行器"中保存變量表, 以及將各類節點的求值方法都集中到其中. 想起來在其餘有些語言實現裏也看到過相似結構(根據不一樣類型進行求值):spa
public Object 求值(節點 節點) {
if (節點 instanceof 運算式節點) {
運算符號 運算符 = ((運算式節點)節點).運算符;
Object 左結果 = 求值(((運算式節點)節點).左子節點);
Object 右結果 = 求值(((運算式節點)節點).右子節點);
switch(運算符) {
case 加: return (int)左結果 + (int)右結果;
case 減: return (int)左結果 - (int)右結果;
case 乘: return (int)左結果 * (int)右結果;
case 除: return (int)左結果 / (int)右結果;
case 賦值:
變量值表.put(((變量節點)((運算式節點)節點).左子節點).取變量名(), 右結果);
// 順延
default:
return null;
}
} else if (節點 instanceof 變量節點) {
return 變量值表.get(((變量節點)節點).取變量名());
} else if (節點 instanceof 數節點) {
return ((數節點)節點).求值();
} else {
for(節點 子節點 : 節點.子節點) {
返回值 = 求值(子節點);
}
return 返回值;
}
}
複製代碼