自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從Java走進(jìn)Scala:構(gòu)建計(jì)算器 結(jié)合解析器組合子和case類(lèi)

開(kāi)發(fā) 后端
在構(gòu)建了 AST 模式和基本前端解析器 之后(用于獲取文本和生成適合解釋的對(duì)象圖形),作者在這篇文章中將這些知識(shí)無(wú)縫地整合起來(lái)(雖然有點(diǎn)麻煩)。然后他將推薦一些適合 DSL 語(yǔ)言及其解釋器的擴(kuò)展。

本文繼續(xù)探索 Scala 的語(yǔ)言和庫(kù)支持,我們將改造一下計(jì)算器 DSL 并最終 “完成它”。DSL 本身有點(diǎn)簡(jiǎn)單 — 一個(gè)簡(jiǎn)單的計(jì)算器,目前為止只支持 4 個(gè)基本數(shù)學(xué)運(yùn)算符。但要記住,我們的目標(biāo)是創(chuàng)建一些可擴(kuò)展的、靈活的對(duì)象,并且以后可以輕松增強(qiáng)它們以支持新的功能。

繼續(xù)上次的討論……

說(shuō)明一下,目前我們的 DSL 有點(diǎn)零亂。我們有一個(gè)抽象語(yǔ)法樹(shù)(Abstract Syntax Tree ),它由大量 case 類(lèi)組成……

清單 1. 后端(AST)

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   private[calcdsl] abstract class Expr  
  6.   private[calcdsl]  case class Variable(name : String) extends Expr  
  7.   private[calcdsl]  case class Number(value : Double) extends Expr  
  8.   private[calcdsl]  case class UnaryOp(operator : String, arg : Expr) extends Expr  
  9.   private[calcdsl]  case class BinaryOp(operator : String, left : Expr, right : Expr)  
  10.    extends Expr  
  11.  
  12. }  

……對(duì)此我們可以提供類(lèi)似解釋器的行為,它能最大限度地簡(jiǎn)化數(shù)學(xué)表達(dá)式……

清單 2. 后端(解釋器)

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     def simplify(e: Expr): Expr = {  
  8.       // first simplify the subexpressions  
  9.       val simpSubs = e match {  
  10.         // Ask each side to simplify  
  11.         case BinaryOp(op, left, right) => BinaryOp(op, simplify(left), simplify(right))  
  12.         // Ask the operand to simplify  
  13.         case UnaryOp(op, operand) => UnaryOp(op, simplify(operand))  
  14.         // Anything else doesn't have complexity (no operands to simplify)  
  15.         case _ => e  
  16.       }  
  17.  
  18.       // now simplify at the top, assuming the components are already simplified  
  19.       def simplifyTop(x: Expr) = x match {  
  20.         // Double negation returns the original value  
  21.         case UnaryOp("-", UnaryOp("-", x)) => x  
  22.     
  23.         // Positive returns the original value  
  24.         case UnaryOp("+", x) => x  
  25.     
  26.         // Multiplying x by 1 returns the original value  
  27.         case BinaryOp("*", x, Number(1)) => x  
  28.     
  29.         // Multiplying 1 by x returns the original value  
  30.         case BinaryOp("*", Number(1), x) => x  
  31.     
  32.         // Multiplying x by 0 returns zero  
  33.         case BinaryOp("*", x, Number(0)) => Number(0)  
  34.     
  35.         // Multiplying 0 by x returns zero  
  36.         case BinaryOp("*", Number(0), x) => Number(0)  
  37.     
  38.         // Dividing x by 1 returns the original value  
  39.         case BinaryOp("/", x, Number(1)) => x  
  40.     
  41.         // Dividing x by x returns 1  
  42.         case BinaryOp("/", x1, x2) if x1 == x2 => Number(1)  
  43.     
  44.         // Adding x to 0 returns the original value  
  45.         case BinaryOp("+", x, Number(0)) => x  
  46.     
  47.         // Adding 0 to x returns the original value  
  48.         case BinaryOp("+", Number(0), x) => x  
  49.     
  50.         // Anything else cannot (yet) be simplified  
  51.         case e => e  
  52.       }  
  53.       simplifyTop(simpSubs)  
  54.     }  
  55.     
  56.     def evaluate(e : Expr) : Double =  
  57.     {  
  58.       simplify(e) match {  
  59.         case Number(x) => x  
  60.         case UnaryOp("-", x) => -(evaluate(x))  
  61.         case BinaryOp("+", x1, x2) => (evaluate(x1) + evaluate(x2))  
  62.         case BinaryOp("-", x1, x2) => (evaluate(x1) - evaluate(x2))  
  63.         case BinaryOp("*", x1, x2) => (evaluate(x1) * evaluate(x2))  
  64.         case BinaryOp("/", x1, x2) => (evaluate(x1) / evaluate(x2))  
  65.       }  
  66.     }  
  67.   }  
  68. }  

……我們使用了一個(gè)由 Scala 解析器組合子構(gòu)建的文本解析器,用于解析簡(jiǎn)單的數(shù)學(xué)表達(dá)式……

清單 3. 前端

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     object ArithParser extends JavaTokenParsers  
  8.     {  
  9.       def expr: Parser[Any] = term ~ rep("+"~term | "-"~term)  
  10.       def term : Parser[Any] = factor ~ rep("*"~factor | "/"~factor)  
  11.       def factor : Parser[Any] = floatingPointNumber | "("~expr~")"   
  12.         
  13.       def parse(text : String) =  
  14.       {  
  15.         parseAll(expr, text)  
  16.       }  
  17.     }  
  18.       
  19.     // ...  
  20.   }  
  21. }  

……但在進(jìn)行解析時(shí),由于解析器組合子當(dāng)前被編寫(xiě)為返回 Parser[Any] 類(lèi)型,所以會(huì)生成 String 和 List 集合,實(shí)際上應(yīng)該讓解析器返回它需要的任意類(lèi)型(我們可以看到,此時(shí)是一個(gè) String 和 List 集合)。

要讓 DSL 成功,解析器需要返回 AST 中的對(duì)象,以便在解析完成時(shí),執(zhí)行引擎可以捕獲該樹(shù)并對(duì)它執(zhí)行 evaluate()。對(duì)于該前端,我們需要更改解析器組合子實(shí)現(xiàn),以便在解析期間生成不同的對(duì)象。

#p#

清理語(yǔ)法

對(duì)解析器做的第一個(gè)更改是修改其中一個(gè)語(yǔ)法。在原來(lái)的解析器中,可以接受像 “5 + 5 + 5” 這樣的表達(dá)式,因?yàn)檎Z(yǔ)法中為表達(dá)式(expr)和術(shù)語(yǔ)(term)定義了 rep() 組合子。但如果考慮擴(kuò)展,這可能會(huì)引起一些關(guān)聯(lián)性和操作符優(yōu)先級(jí)問(wèn)題。以后的運(yùn)算可能會(huì)要求使用括號(hào)來(lái)顯式給出優(yōu)先級(jí),以避免這類(lèi)問(wèn)題。因此第一個(gè)更改是將語(yǔ)法改為要求在所有表達(dá)式中加 “()”。

回想一下,這應(yīng)該是我一開(kāi)始就需要做的事情;事實(shí)上,放寬限制通常比在以后添加限制容易(如果最后不需要這些限制),但是解決運(yùn)算符優(yōu)先級(jí)和關(guān)聯(lián)性問(wèn)題比這要困難得多。如果您不清楚運(yùn)算符的優(yōu)先級(jí)和關(guān)聯(lián)性;那么讓我大致概述一下我們所處的環(huán)境將有多復(fù)雜??紤] Java 語(yǔ)言本身和它支持的各種運(yùn)算符(如 Java 語(yǔ)言規(guī)范中所示)或一些關(guān)聯(lián)性難題(來(lái)自 Bloch 和 Gafter 提供的 Java Puzzlers),您將發(fā)現(xiàn)情況不容樂(lè)觀(guān)。

因此,我們需要逐步解決問(wèn)題。首先是再次測(cè)試語(yǔ)法:

清單 4. 采用括號(hào)

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     // ...  
  8.       
  9.     object OldAnyParser extends JavaTokenParsers  
  10.     {  
  11.       def expr: Parser[Any] = term ~ rep("+"~term | "-"~term)  
  12.       def term : Parser[Any] = factor ~ rep("*"~factor | "/"~factor)  
  13.       def factor : Parser[Any] = floatingPointNumber | "("~expr~")"   
  14.         
  15.       def parse(text : String) =  
  16.       {  
  17.         parseAll(expr, text)  
  18.       }  
  19.     }  
  20.     object AnyParser extends JavaTokenParsers  
  21.     {  
  22.       def expr: Parser[Any] = (term~"+"~term) | (term~"-"~term) | term  
  23.       def term : Parser[Any] = (factor~"*"~factor) | (factor~"/"~factor) | factor  
  24.       def factor : Parser[Any] = "(" ~> expr <~ ")" | floatingPointNumber  
  25.         
  26.       def parse(text : String) =  
  27.       {  
  28.         parseAll(expr, text)  
  29.       }  
  30.     }  
  31.       
  32.     // ...  
  33.   }  
  34. }  

我已經(jīng)將舊的解析器重命名為 OldAnyParser,添加左邊的部分是為了便于比較;新的語(yǔ)法由 AnyParser 給出;注意它將 expr 定義為 term + term、term - term,或者一個(gè)獨(dú)立的 term,等等。另一個(gè)大的變化是 factor 的定義,現(xiàn)在它使用另一種組合子 ~> 和 <~ 在遇到 ( 和 ) 字符時(shí)有效地拋出它們。

因?yàn)檫@只是一個(gè)臨時(shí)步驟,所以我不打算創(chuàng)建一系列單元測(cè)試來(lái)查看各種可能性。不過(guò)我仍然想確保該語(yǔ)法的解析結(jié)果符合預(yù)期,所以我在這里編寫(xiě)一個(gè)不是很正式的測(cè)試:

清單 5. 測(cè)試解析器的非正式測(cè)試

  1. package com.tedneward.calcdsl.test  
  2. {  
  3.   class CalcTest  
  4.   {  
  5.     import org.junit._, Assert._  
  6.       
  7.     // ...  
  8.       
  9.     _cnnew1@Test def parse =  
  10.     {  
  11.       import Calc._  
  12.         
  13.       val expressions = List(  
  14.         "5",  
  15.         "(5)",  
  16.         "5 + 5",  
  17.         "(5 + 5)",  
  18.         "5 + 5 + 5",  
  19.         "(5 + 5) + 5",  
  20.         "(5 + 5) + (5 + 5)",  
  21.         "(5 * 5) / (5 * 5)",  
  22.         "5 - 5",  
  23.         "5 - 5 - 5",  
  24.         "(5 - 5) - 5",  
  25.         "5 * 5 * 5",  
  26.         "5 / 5 / 5",  
  27.         "(5 / 5) / 5" 
  28.       )  
  29.         
  30.       for (x <- expressions)  
  31.         System.out.println(x + " = " + AnyParser.parse(x))  
  32.     }  
  33.   }  

請(qǐng)記住,這純粹是出于教學(xué)目的(也許有人會(huì)說(shuō)我不想為產(chǎn)品代碼編寫(xiě)測(cè)試,但我確實(shí)沒(méi)有在編寫(xiě)產(chǎn)品代碼,所以我不需要編寫(xiě)正式的測(cè)試。這只是為了方便教學(xué))。但是,運(yùn)行這個(gè)測(cè)試后,得到的許多結(jié)果與標(biāo)準(zhǔn)單元測(cè)試結(jié)果文件相符,表明沒(méi)有括號(hào)的表達(dá)式(5 + 5 + 5)執(zhí)行失敗,而有括號(hào)的表達(dá)式則會(huì)執(zhí)行成功。真是不可思議!

不要忘了給解析測(cè)試加上注釋。更好的方法是將該測(cè)試完全刪除。這是一個(gè)臨時(shí)編寫(xiě)的測(cè)試,而且我們都知道,真正的 Jedi 只在研究或防御時(shí)使用這些源代碼,而不在這種情況中使用。

#p#

清理語(yǔ)法

現(xiàn)在我們需要再次更改各種組合子的定義。回顧一下上一篇文章,expr、term 和 factor 函數(shù)中的每一個(gè)實(shí)際上都是 BNF 語(yǔ)句,但注意每一個(gè)函數(shù)返回的都是一個(gè)解析器泛型,參數(shù)為 Any(Scala 類(lèi)型系統(tǒng)中一個(gè)基本的超類(lèi)型,從其名稱(chēng)就可以知道它的作用:指示可以包含任何對(duì)象的潛在類(lèi)型或引用);這表明組合子可以根據(jù)需要返回任意類(lèi)型。我們已經(jīng)看到,在默認(rèn)情況下,解析器可以返回一個(gè) String,也可以返回一個(gè) List(如果您還不信的話(huà),可以在運(yùn)行的測(cè)試中加入臨時(shí)測(cè)試。這也會(huì)看到同樣的結(jié)果)。

要將它更改為生成 case 類(lèi) AST 層次結(jié)構(gòu)的實(shí)例(Expr 對(duì)象),組合子的返回類(lèi)型必須更改為 Parser[Expr]。如果讓它自行更改,編譯將會(huì)失??;這三個(gè)組合子知道如何獲取 String,但不知道如何根據(jù)解析的內(nèi)容生成 Expr 對(duì)象。為此,我們使用了另一個(gè)組合子,即 ^^ 組合子,它以一個(gè)匿名函數(shù)為參數(shù),將解析的結(jié)果作為一個(gè)參數(shù)傳遞給該匿名函數(shù)。

如果您和許多 Java 開(kāi)發(fā)人員一樣,那么就要花一點(diǎn)時(shí)間進(jìn)行解析,讓我們查看一下實(shí)際效果:

清單 6. 產(chǎn)品組合子

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     object ExprParser extends JavaTokenParsers  
  8.     {  
  9.       def expr: Parser[Expr] =  
  10.         (term ~ "+" ~ term) ^^ { case lhs~plus~rhs => BinaryOp("+", lhs, rhs) } |  
  11.         (term ~ "-" ~ term) ^^ { case lhs~minus~rhs => BinaryOp("-", lhs, rhs) } |  
  12.         term   
  13.  
  14.       def term: Parser[Expr] =  
  15.         (factor ~ "*" ~ factor) ^^ { case lhs~times~rhs => BinaryOp("*", lhs, rhs) } |  
  16.         (factor ~ "/" ~ factor) ^^ { case lhs~div~rhs => BinaryOp("/", lhs, rhs) } |  
  17.         factor  
  18.  
  19.       def factor : Parser[Expr] =  
  20.         "(" ~> expr <~ ")" |  
  21.         floatingPointNumber ^^ {x => Number(x.toFloat) }  
  22.         
  23.       def parse(text : String) = parseAll(expr, text)  
  24.     }  
  25.     
  26.     def parse(text : String) =  
  27.       ExprParser.parse(text).get  
  28.  
  29.     // ...  
  30.   }  
  31.     
  32.   // ...  
  33. }  

^^ 組合子接收一個(gè)匿名函數(shù),其解析結(jié)果(例如,假設(shè)輸入的是 5 + 5,那么解析結(jié)果將是 ((5~+)~5))將會(huì)被單獨(dú)傳遞并得到一個(gè)對(duì)象 — 在本例中,是一個(gè)適當(dāng)類(lèi)型的 BinaryObject。請(qǐng)?jiān)俅巫⒁饽J狡ヅ涞膹?qiáng)大功能;我將表達(dá)式的左邊部分與 lhs 實(shí)例綁定在一起,將 + 部分與(未使用的)plus 實(shí)例綁定在一起,該表達(dá)式的右邊則與 rhs 綁定,然后我分別使用 lhs 和 rhs 填充 BinaryOp 構(gòu)造函數(shù)的左邊和右邊。

現(xiàn)在運(yùn)行代碼(記得注釋掉臨時(shí)測(cè)試),單元測(cè)試集會(huì)再次產(chǎn)生所有正確的結(jié)果:我們以前嘗試的各種表達(dá)式不會(huì)再失敗,因?yàn)楝F(xiàn)在解析器生成了派生 Expr 對(duì)象。前面已經(jīng)說(shuō)過(guò),不進(jìn)一步測(cè)試解析器是不負(fù)責(zé)任的,所以讓我們添加更多的測(cè)試(包括我之前在解析器中使用的非正式測(cè)試):

清單 7. 測(cè)試解析器(這次是正式的)

  1. package com.tedneward.calcdsl.test  
  2. {  
  3.   class CalcTest  
  4.   {  
  5.     import org.junit._, Assert._  
  6.       
  7.     // ...  
  8.       
  9.     @Test def parseAnExpr1 =  
  10.       assertEquals(  
  11.         Number(5),  
  12.         Calc.parse("5")  
  13.       )  
  14.     @Test def parseAnExpr2 =  
  15.       assertEquals(  
  16.         Number(5),  
  17.         Calc.parse("(5)")  
  18.       )  
  19.     @Test def parseAnExpr3 =  
  20.       assertEquals(  
  21.         BinaryOp("+", Number(5), Number(5)),  
  22.         Calc.parse("5 + 5")  
  23.       )  
  24.     @Test def parseAnExpr4 =  
  25.       assertEquals(  
  26.         BinaryOp("+", Number(5), Number(5)),  
  27.         Calc.parse("(5 + 5)")  
  28.       )  
  29.     @Test def parseAnExpr5 =  
  30.       assertEquals(  
  31.         BinaryOp("+", BinaryOp("+", Number(5), Number(5)), Number(5)),  
  32.         Calc.parse("(5 + 5) + 5")  
  33.       )  
  34.     @Test def parseAnExpr6 =  
  35.       assertEquals(  
  36.         BinaryOp("+", BinaryOp("+", Number(5), Number(5)), BinaryOp("+", Number(5),  
  37.                  Number(5))),  
  38.         Calc.parse("(5 + 5) + (5 + 5)")  
  39.       )  
  40.       
  41.     // other tests elided for brevity  
  42.   }  

讀者可以再增加一些測(cè)試,因?yàn)槲铱赡苈┑粢恍┎怀R?jiàn)的情況(與 Internet 上的其他人結(jié)對(duì)編程是比較好的)。

完成最后一步

假設(shè)解析器正按照我們想要的方式在工作 — 即生成 AST — 那么現(xiàn)在只需要根據(jù) AST 對(duì)象的計(jì)算結(jié)果來(lái)完善解析器。這很簡(jiǎn)單,只需向 Calc 添加代碼,如清單 8 所示……

清單 8. 真的完成啦!

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     // ...  
  8.       
  9.     def evaluate(text : String) : Double = evaluate(parse(text))  
  10.   }  
  11. }  

……同時(shí)添加一個(gè)簡(jiǎn)單的測(cè)試,確保 evaluate("1+1") 返回 2.0……

清單 9. 最后,看一下 1 + 1 是否等于 2

  1. package com.tedneward.calcdsl.test  
  2. {  
  3.   class CalcTest  
  4.   {  
  5.     import org.junit._, Assert._  
  6.       
  7.     // ...  
  8.       
  9.     @Test def add1 =  
  10.       assertEquals(Calc.evaluae("1 + 1"), 2.0)  
  11.   }  

……然后運(yùn)行它,一切正常!

#p#

擴(kuò)展 DSL 語(yǔ)言

如果完全用 Java 代碼編寫(xiě)同一個(gè)計(jì)算器 DSL,而沒(méi)有碰到我遇到的問(wèn)題(在不構(gòu)建完整的 AST 的情況下遞歸式地計(jì)算每一個(gè)片段,等等),那么似乎它是另一種能夠解決問(wèn)題的語(yǔ)言或工具。但以這種方式構(gòu)建語(yǔ)言的強(qiáng)大之處會(huì)在擴(kuò)展性上得到體現(xiàn)。

例如,我們向這種語(yǔ)言添加一個(gè)新的運(yùn)算符,即 ^ 運(yùn)算符,它將執(zhí)行求冪運(yùn)算;也就是說(shuō),2 ^ 2 等于 2 的平方 或 4。向 DSL 語(yǔ)言添加這個(gè)運(yùn)算符需要一些簡(jiǎn)單步驟。

首先,您必須考慮是否需要更改 AST。在本例中,求冪運(yùn)算符是另一種形式的二進(jìn)制運(yùn)算符,所以使用現(xiàn)有 BinaryOp case 類(lèi)就可以。無(wú)需對(duì) AST 進(jìn)行任何更改。

其次,必須修改 evaluate 函數(shù),以使用 BinaryOp("^", x, y) 執(zhí)行正確的操作;這很簡(jiǎn)單,只需添加一個(gè)嵌套函數(shù)(因?yàn)椴槐卦谕獠靠吹竭@個(gè)函數(shù))來(lái)實(shí)際計(jì)算指數(shù),然后向模式匹配添加必要的代碼行,如下所示:

清單 10. 稍等片刻

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     // ...  
  8.       
  9.     def evaluate(e : Expr) : Double =  
  10.     {  
  11.       def exponentiate(base : Double, exponent : Double) : Double =  
  12.         if (exponent == 0)   
  13.           1.0 
  14.         else 
  15.           base * exponentiate(base, exponent - 1)  
  16.  
  17.       simplify(e) match {  
  18.         case Number(x) => x  
  19.         case UnaryOp("-", x) => -(evaluate(x))  
  20.         case BinaryOp("+", x1, x2) => (evaluate(x1) + evaluate(x2))  
  21.         case BinaryOp("-", x1, x2) => (evaluate(x1) - evaluate(x2))  
  22.         case BinaryOp("*", x1, x2) => (evaluate(x1) * evaluate(x2))  
  23.         case BinaryOp("/", x1, x2) => (evaluate(x1) / evaluate(x2))  
  24.         case BinaryOp("^", x1, x2) => exponentiate(evaluate(x1), evaluate(x2))  
  25.       }  
  26.     }  
  27.   }  
  28. }  

注意,這里我們只使用 6 行代碼就有效地向系統(tǒng)添加了求冪運(yùn)算,同時(shí)沒(méi)有對(duì) Calc 類(lèi)進(jìn)行任何表面更改。這就是封裝!

(在我努力創(chuàng)建最簡(jiǎn)單求冪函數(shù)時(shí),我故意創(chuàng)建了一個(gè)有嚴(yán)重 bug 的版本 —— 這是為了讓我們關(guān)注語(yǔ)言,而不是實(shí)現(xiàn)。也就是說(shuō),看看哪位讀者能夠找到 bug。他可以編寫(xiě)發(fā)現(xiàn) bug 的單元測(cè)試,然后提供一個(gè)無(wú) bug 的版本)。

但是在向解析器添加這個(gè)求冪函數(shù)之前,讓我們先測(cè)試這段代碼,以確保求冪部分能正常工作:

清單 11. 求平方

  1. package com.tedneward.calcdsl.test  
  2. {  
  3.   class CalcTest  
  4.   {  
  5.     // ...  
  6.       
  7.     @Test def evaluateSimpleExp =  
  8.     {  
  9.       val expr =  
  10.         BinaryOp("^", Number(4), Number(2))  
  11.       val results = Calc.evaluate(expr)  
  12.       // (4 ^ 2) => 16  
  13.       assertEquals(16.0, results)  
  14.     }  
  15.     @Test def evaluateComplexExp =  
  16.     {  
  17.       val expr =  
  18.         BinaryOp("^",  
  19.           BinaryOp("*", Number(2), Number(2)),  
  20.           BinaryOp("/", Number(4), Number(2)))  
  21.       val results = Calc.evaluate(expr)  
  22.       // ((2 * 2) ^ (4 / 2)) => (4 ^ 2) => 16  
  23.       assertEquals(16.0, results)  
  24.     }  
  25.   }  

運(yùn)行這段代碼確??梢郧髢纾ê雎晕抑疤岬降?bug),這樣就完成了一半的工作。

最后一個(gè)更改是修改語(yǔ)法,讓它接受新的求冪運(yùn)算符;因?yàn)榍髢绲膬?yōu)先級(jí)與乘法和除法的相同,所以最簡(jiǎn)單的做法是將它放在 term 組合子中:

清單 12. 完成了,這次是真的!

  1. package com.tedneward.calcdsl  
  2. {  
  3.   // ...  
  4.  
  5.   object Calc  
  6.   {  
  7.     // ...  
  8.       
  9.     object ExprParser extends JavaTokenParsers  
  10.     {  
  11.       def expr: Parser[Expr] =  
  12.         (term ~ "+" ~ term) ^^ { case lhs~plus~rhs => BinaryOp("+", lhs, rhs) } |  
  13.         (term ~ "-" ~ term) ^^ { case lhs~minus~rhs => BinaryOp("-", lhs, rhs) } |  
  14.         term   
  15.  
  16.       def term: Parser[Expr] =  
  17.         (factor ~ "*" ~ factor) ^^ { case lhs~times~rhs => BinaryOp("*", lhs, rhs) } |  
  18.         (factor ~ "/" ~ factor) ^^ { case lhs~div~rhs => BinaryOp("/", lhs, rhs) } |  
  19.         (factor ~ "^" ~ factor) ^^ { case lhs~exp~rhs => BinaryOp("^", lhs, rhs) } |  
  20.         factor  
  21.  
  22.       def factor : Parser[Expr] =  
  23.         "(" ~> expr <~ ")" |  
  24.         floatingPointNumber ^^ {x => Number(x.toFloat) }  
  25.         
  26.       def parse(text : String) = parseAll(expr, text)  
  27.     }  
  28.       
  29.   // ...  
  30.   }  
  31. }  
  32.    

當(dāng)然,我們需要對(duì)這個(gè)解析器進(jìn)行一些測(cè)試……

清單 13. 再求平方

  1. package com.tedneward.calcdsl.test  
  2. {  
  3.   class CalcTest  
  4.   {  
  5.     // ...  
  6.       
  7.     @Test def parseAnExpr17 =  
  8.       assertEquals(  
  9.         BinaryOp("^", Number(2), Number(2)),  
  10.         Calc.parse("2 ^ 2")  
  11.       )  
  12.     @Test def parseAnExpr18 =  
  13.       assertEquals(  
  14.         BinaryOp("^", Number(2), Number(2)),  
  15.         Calc.parse("(2 ^ 2)")  
  16.       )  
  17.     @Test def parseAnExpr19 =  
  18.       assertEquals(  
  19.         BinaryOp("^", Number(2),  
  20.           BinaryOp("+", Number(1), Number(1))),  
  21.         Calc.parse("2 ^ (1 + 1)")  
  22.       )  
  23.     @Test def parseAnExpr20 =  
  24.       assertEquals(  
  25.         BinaryOp("^", Number(2), Number(2)),  
  26.         Calc.parse("2 ^ (2)")  
  27.       )  
  28.   }  

……運(yùn)行并通過(guò)后,還要進(jìn)行最后一個(gè)測(cè)試,看一切是否能正常工作:

清單 14. 從 String 到平方

  1. package com.tedneward.calcdsl.test  
  2. {  
  3.   class CalcTest  
  4.   {  
  5.     // ...  
  6.       
  7.     @Test def square1 =  
  8.       assertEquals(Calc.evaluate("2 ^ 2"), 4.0)  
  9.   }  

成功啦!

結(jié)束語(yǔ)

顯然,還要做更多工作才能使這門(mén)簡(jiǎn)單的語(yǔ)言變得更好;不管您對(duì)該語(yǔ)言的各個(gè)部分測(cè)試(AST、解析器、簡(jiǎn)化引擎,等等)感覺(jué)如何,僅僅將該語(yǔ)言編寫(xiě)為基于解釋器的對(duì)象都可以通過(guò)更少的代碼來(lái)實(shí)現(xiàn)(也可能更快,這取決于您的熟練程度),甚至可以動(dòng)態(tài)地計(jì)算表達(dá)式的值,而不是將它們轉(zhuǎn)換為 AST 后再進(jìn)行計(jì)算。

向系統(tǒng)添加另一種運(yùn)算符是非常簡(jiǎn)單的。該語(yǔ)言的設(shè)計(jì)也使它的擴(kuò)展非常容易,擴(kuò)展時(shí)不需要修改很多代碼。事實(shí)上,我們可以通過(guò)許多增強(qiáng)來(lái)演示該方法的內(nèi)在靈活性:

◆我們可以從使用 Doubles 轉(zhuǎn)向使用 BigDecimals 或 BigIntegers,而不是用 java.math 包(以允許進(jìn)行更大和/或更準(zhǔn)確的計(jì)算)。

◆我們可以在語(yǔ)言中支持十進(jìn)制數(shù)(當(dāng)前解析器中不支持)。

◆我們可以使用單詞(“sin”、“cos”、“tan” 等)而不是符號(hào)來(lái)添加運(yùn)算符。

◆我們甚至可以添加變量符號(hào)(“x = y + 12”)并接受 Map 作為 evaluate() 函數(shù)的參數(shù),該函數(shù)包含每個(gè)變量的初始值。

◆更重要的是,DSL 完全隱藏在 Calc 類(lèi)后面,這意味著從 Java 代碼調(diào)用它與從 Scala 調(diào)用它一樣簡(jiǎn)單。所以即使在沒(méi)有完全采用 Scala 作為首選語(yǔ)言

◆項(xiàng)目中,也可以用 Scala 編寫(xiě)部分系統(tǒng)(那些最適合使用函數(shù)性/對(duì)象混合語(yǔ)言的部分),而且 Java 開(kāi)發(fā)人員可以輕松地調(diào)用它們。

【相關(guān)閱讀】

  1. Scala編程語(yǔ)言專(zhuān)題
  2. 從Java走進(jìn)Scala:構(gòu)建計(jì)算器 解析器組合子入門(mén)
  3. 從Java走進(jìn)Scala:簡(jiǎn)單的計(jì)算器 case類(lèi)和模式匹配
  4. 從Java走進(jìn)Scala:包和訪(fǎng)問(wèn)修飾符
  5. 從Java走進(jìn)Scala:使用元組、數(shù)組和列表
責(zé)任編輯:yangsai 來(lái)源: IBMDW
相關(guān)推薦

2009-06-19 11:42:09

Scala計(jì)算器解析

2009-06-19 11:13:47

Scalacase類(lèi)模式匹配

2009-02-04 17:32:03

ibmdwJavaScala

2009-06-16 17:54:38

Scala類(lèi)語(yǔ)法語(yǔ)義

2009-09-28 11:01:39

從Java走進(jìn)Scal

2009-08-21 16:17:25

ScalaTwitter API

2009-06-17 13:57:25

Scala元組數(shù)組

2009-06-17 11:44:22

Scala控制結(jié)構(gòu)

2009-06-19 10:51:39

Scalapackage訪(fǎng)問(wèn)修飾符

2009-07-15 10:14:25

Scala并發(fā)性

2009-12-09 09:15:47

從Java走進(jìn)ScalTwitter API

2019-07-05 08:39:39

GoSQL解析器

2020-12-02 10:13:45

JacksonJDK解析器

2011-09-16 14:13:15

Windows7計(jì)算器

2009-10-14 11:14:38

ScitterScalaTwitter

2009-07-08 15:35:18

Case類(lèi)Scala

2009-03-19 09:26:05

RSS解析器MagpieRSS

2009-06-16 17:09:17

Scala面向?qū)ο?/a>函數(shù)編程

2021-03-26 09:37:12

Java開(kāi)發(fā)代碼

2022-09-08 11:35:45

Python表達(dá)式函數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)