延續前兩部分,我們將計算器 DSL 從「能跑」推向「可擴展、生產就緒」。核心架構:
Expr 密封類系,Number、Variable、UnaryOp、BinaryOp case classsimplify 化簡 + evaluate 求值原語法允許 5+5+5,擴充運算子時優先級/結合性極難處理。新語法強制所有二元運算加括號:(5+5)、((5+5)+5)。解析器組合子對應調整:
object ExprParser extends JavaTokenParsers {
def expr: Parser[Expr] =
(term ~ "+" ~ term) ^^ { case l~_~r => BinaryOp("+", l, r) } |
(term ~ "-" ~ term) ^^ { case l~_~r => BinaryOp("-", l, r) } |
term
def term: Parser[Expr] =
(factor ~ "*" ~ factor) ^^ { case l~_~r => BinaryOp("*", l, r) } |
(factor ~ "/" ~ factor) ^^ { case l~_~r => BinaryOp("/", l, r) } |
factor
def factor: Parser[Expr] =
"(" ~> expr <~ ")" | // 括號內容,丟棄括號
floatingPointNumber ^^ (x => Number(x.toFloat)) // 數字轉 Number
def parse(text: String) = parseAll(expr, text)
}
^^ 組合子接收匿名函數,將解析結果映射為 AST 節點~> / <~ 選擇性保留/丟棄解析結果@Test def parseAnExpr3 = assertEquals(
BinaryOp("+", Number(5), Number(5)),
Calc.parse("5 + 5")
)
@Test def add1 = assertEquals(2.0, Calc.evaluate("1 + 1"))
測試覆蓋解析、化簡、求值全鏈路。
def evaluate(text: String): Double = evaluate(parse(text).get)
BinaryOp("^", x, y) 復用現有節點case BinaryOp("^", x, y) => pow(evaluate(x), evaluate(y))(factor ~ "^" ~ factor) ^^ { case l~_~r => BinaryOp("^", l, r) }Calc.evaluate("2 ^ 2") == 4.0全程零修改現有核心代碼,僅在擴充點添加 6 行邏輯,展現封裝與開放封閉原則威力。
BigDecimal/BigInteger 任意精度運算x = y + 12Map[String, Double] 作為變數環境傳入 evaluateScala 解析器組合子 + Case Class + 模式匹配 = 內建 DSL 引擎。Java 項目可直接調用 Scala 編譯的 DSL 模組,無需全面遷移語言。下期將深入泛型與更多語言特性。