Antlr4新增中文變數賦求值,括號,各種問題
阿新 • • 發佈:2019-12-31
例程(更多測試用例在此):
基數=100
基數×(基數+1)÷2
=> 求值為5050
複製程式碼
續上文Antlr4實現數學四則運算,修改的語法規則部分:
程式: 宣告+;
宣告: 表示式 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).
定製訪問器新增的部分:
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,還需更多工作. 另外一個問題,最後的表示式求值也會對變數值重複計算. 舉例:
利率=1
年增長率=1+利率
1000×年增長率×年增長率
複製程式碼
最後語法樹如下:
"年增長率"應該提前求值,以省去最後的多次計算(避免對變數重複求值 · Issue #3 · program-in-chinese/quan5)
後兩個問題已初步解決,通過在"執行器"中儲存變量表,以及將各種節點的求值方法都集中到其中. 想起來在其他有些語言實現裡也看到過類似結構(根據不同型別進行求值):
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 返回值;
}
}
複製程式碼