最近我正在尋找算術表達式的體面語法,但只發現了一些不重要的語法,例如忽略pow(..., ...)
。然後我自己試了一下,但有時它並沒有像預期的那樣工作。例如,我錯過了允許表達式前面的一個單一的-
並修復它。也許有人可以看看我目前的做法並改進它。此外,我認爲其他人可以利用,因爲解析算術表達式是一項常見任務。算術表達式語法和解析器
import scala.math._
import scala.util.parsing.combinator._
import scala.util.Random
class FormulaParser(val constants: Map[String,Double] = Map(), val userFcts: Map[String,String => Double] = Map(), random: Random = new Random) extends JavaTokenParsers {
require(constants.keySet.intersect(userFcts.keySet).isEmpty)
private val allConstants = constants ++ Map("E" -> E, "PI" -> Pi, "Pi" -> Pi) // shouldn´t be empty
private val unaryOps: Map[String,Double => Double] = Map(
"sqrt" -> (sqrt(_)), "abs" -> (abs(_)), "floor" -> (floor(_)), "ceil" -> (ceil(_)), "ln" -> (math.log(_)), "round" -> (round(_)), "signum" -> (signum(_))
)
private val binaryOps1: Map[String,(Double,Double) => Double] = Map(
"+" -> (_+_), "-" -> (_-_), "*" -> (_*_), "/" -> (_/_), "^" -> (pow(_,_))
)
private val binaryOps2: Map[String,(Double,Double) => Double] = Map(
"max" -> (max(_,_)), "min" -> (min(_,_))
)
private def fold(d: Double, l: List[~[String,Double]]) = l.foldLeft(d){ case (d1,op~d2) => binaryOps1(op)(d1,d2) }
private implicit def map2Parser[V](m: Map[String,V]) = m.keys.map(_ ^^ (identity)).reduceLeft(_ | _)
private def expression: Parser[Double] = sign~term~rep(("+"|"-")~term) ^^ { case s~t~l => fold(s * t,l) }
private def sign: Parser[Double] = opt("+" | "-") ^^ { case None => 1; case Some("+") => 1; case Some("-") => -1 }
private def term: Parser[Double] = longFactor~rep(("*"|"/")~longFactor) ^^ { case d~l => fold(d,l) }
private def longFactor: Parser[Double] = shortFactor~rep("^"~shortFactor) ^^ { case d~l => fold(d,l) }
private def shortFactor: Parser[Double] = fpn | sign~(constant | rnd | unaryFct | binaryFct | userFct | "("~>expression<~")") ^^ { case s~x => s * x }
private def constant: Parser[Double] = allConstants ^^ (allConstants(_))
private def rnd: Parser[Double] = "rnd"~>"("~>fpn~","~fpn<~")" ^^ { case x~_~y => require(y > x); x + (y-x) * random.nextDouble } | "rnd" ^^ { _ => random.nextDouble }
private def fpn: Parser[Double] = floatingPointNumber ^^ (_.toDouble)
private def unaryFct: Parser[Double] = unaryOps~"("~expression~")" ^^ { case op~_~d~_ => unaryOps(op)(d) }
private def binaryFct: Parser[Double] = binaryOps2~"("~expression~","~expression~")" ^^ { case op~_~d1~_~d2~_ => binaryOps2(op)(d1,d2) }
private def userFct: Parser[Double] = userFcts~"("~(expression ^^ (_.toString) | ident)<~")" ^^ { case fct~_~x => userFcts(fct)(x) }
def evaluate(formula: String) = parseAll(expression,formula).get
}
所以可以評價如下:
val formulaParser = new FormulaParser(
constants = Map("radius" -> 8D,
"height" -> 10D,
"c" -> 299792458, // m/s
"v" -> 130 * 1000/60/60, // 130 km/h in m/s
"m" -> 80),
userFcts = Map("perimeter" -> { _.toDouble * 2 * Pi }))
println(formulaParser.evaluate("2+3*5")) // 17.0
println(formulaParser.evaluate("height*perimeter(radius)")) // 502.6548245743669
println(formulaParser.evaluate("m/sqrt(1-v^2/c^2)")) // 80.00000000003415
任何改進的建議?我是否使用正確的語法,還是隻有用戶輸入一個有效的(關於我提供的函數)算術表達式才能解析的時間問題?
(運算符優先級What's?)
例如:以'E'開頭的userFct會產生一個分析錯誤,因爲'math.E'之前是匹配的。我怎樣才能防止這種情況發生,或者如何將'Parser [Double]'與'|'結合起來纔是正確的優先級? – 2011-04-27 17:01:17
這段代碼相當不錯@Peter Schmitz。你應該把它放在Github的一個圖書館,然後我可以給你我的修改。我使用它作爲我正在開發的項目的起點。 – Jason 2013-07-15 19:55:29
@Jason謝謝。如果時間允許,我會在Github上發佈它,但你可以自由地使用你的改進並使用我的代碼。我期待看到改進,因爲我仍然在問自己語法是否正確。 – 2013-07-16 11:53:04