1. 程式人生 > >Antlr4實現數學四則運算

Antlr4實現數學四則運算

中文程式設計知乎專欄原文地址

基本參考https://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference 一書"Building a Calculator Using a Visitor"一節, 僅添加了數學乘除法符號的支援(×÷). 比如下面的算式:

3×2+8÷4-2×4

原始碼仍在program-in-chinese/quan5

相比上一版本語法檔案去除了空格定義. 需要深究的是優先順序問題. 是否因為"表示式 運算子=(’*’|’/’|‘×’|‘÷’) 表示式"寫在了前面才使得乘除法的優先順序在語法分析時更高.

至此, 感覺Antlr語法檔案對中文命名的支援還是不錯的. 唯一需要權宜之計的就是Token(詞)規則必須要大寫開頭, 因此採用了字首"T"):

grammar 圈5;
程式
 : 表示式
 ;

表示式
 : 表示式 運算子=('*'|'/'|'×'|'÷') 表示式 	#乘除
   | 表示式 運算子=('+'|'-') 表示式 		#加減
   | T數					#數
 ;

T數
 : [0-9]+
 ;
T加 : '+';
T減 : '-';
T乘 : '*';
T數乘: '×';
T除 : '/';
T數除: '÷';

第一次嘗試#標號的輔助功能. 一個"表示式"語法規則生成了三個Visitor方法(如下), 訪問器仍比較簡單. 注: 語法規則中要麼所有分支都有標號, 要麼都沒有. 不然生成分析器時報錯:

public class 定製訪問器 extends5BaseVisitor<節點> {

  @Override
  public 節點 visit數(數Context 上下文) {
    TerminalNode 數 = 上下文.T數();
    returninstanceof 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;
    }
  }

}

已經要手動跑十個測試檔案, 下面除了清理程式碼, 還需要加測試, 再加功能(應該是變數賦值).