2011-04-27 4 views
8

최근 산술 표현식에 알맞은 문법을 찾고 있었지만, 예를 들어 pow(..., ...)을 무시하고 사소한 표현식을 찾았습니다. 그런 다음 혼자서 시도했지만 때로는 예상대로 작동하지 않았습니다. 예를 들어 표현식 앞에 단 하나의 -을 허용하고 수정하지 못했습니다. 아마도 누군가 내 현재의 접근 방식을 살펴보고이를 향상시킬 수 있습니다. 게다가 나는 산술 표현식을 파싱 할 수있는 공통된 작업이기 때문에 다른 사람들이 활용할 수 있다고 생각한다.산술 표현식 문법 및 구문 분석기

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 

어떤 개선 제안 :

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 
} 

그래서 하나는 다음과 같은 평가를 할 수 있습니까? 올바른 문법을 사용합니까, 아니면 사용자가 구문 분석 할 수없는 산술 표현식을 유효한 형태로 입력하기까지는 시간 문제입니까?
(연산자 우선 순위에 대한 What's?) 더 나은 성능을 위해

+0

예 : 'E'로 시작하는 userFct는 이전에 'math.E'가 일치했기 때문에 구문 분석 오류가 발생합니다. 어떻게 이것을 막을 수 있습니까? 아니면'Parser [Double]'과'|'를 조합 할 때 올바른 우선 순위가 될까요? –

+2

이 코드는 피터 슈미츠 (Peter Schmitz)와 매우 흡사합니다. 당신은 Github에있는 도서관에 보관해야합니다. 그러면 나는 당신에게 나의 무모를 줄 수 있습니다. 나는 작업중인 프로젝트의 시작점으로 사용하고 있습니다. – Jason

+0

@ Jason 고맙습니다. 시간이 있으면 Github에 게시 할 예정이지만 자유롭게 사용하고 코드를 개선하여 사용할 수 있습니다. 나는 문법이 옳은지 여전히 스스로에게 묻기 때문에 개선을보기를 고대하고있다. –

답변

2

나는 파서를 정의 할 때 private def 대신 private lazy val을 사용하는 것이 좋습니다. 그렇지 않으면 파서가 참조 될 때마다 다시 작성됩니다.

좋은 코드 BTW.

+0

맞아, 고마워. 코딩하는 동안 그것을 시도했지만 그것은 여러 입력에 파서를 실행하면 작동하지 않았다. 나는 파서가 한 번만 사용될 수 있다고 생각했기 때문에 Scala에서 프로그래밍하기와 비슷한'def'를 선택했습니다. 이제는 '게으른 val'과 함께 작동합니다. –

1

그럼 아마 루프에서 변수를 추가 : 평가지도를 전달하는

import scala.math._ 
import scala.util.parsing.combinator._ 
import scala.util.Random 

class FormulaParser(val variables: Set[String] = Set(), 
        val constants: Map[String, Double] = Map(), 
        val unary: Map[String, Double => Double] = Map(), 
        val binary: Map[String, (Double, Double) => 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](
    "sqrt" -> (sqrt(_)), "abs" -> (abs(_)), "floor" -> (floor(_)), "ceil" -> (ceil(_)), "ln" -> (math.log(_)), "round" -> (round(_).toDouble), "signum" -> (signum(_)) 
    ) ++ unary 
    private val binaryOps1 = Map[String, (Double, Double) => Double](
     "+" -> (_ + _), "-" -> (_ - _), "*" -> (_ * _), "/" -> (_/_), "^" -> (pow(_, _)) 
    ) 
    private val binaryOps2 = Map[String, (Double, Double) => Double](
     "max" -> (max(_, _)), "min" -> (min(_, _)) 
    ) ++ binary 

    type Argument = Map[String, Double] 
    type Formula = Argument => Double 

    private def fold(d: Formula, l: List[~[String, Formula]]) = l.foldLeft(d) { case (d1, op ~ d2) => arg => binaryOps1(op)(d1(arg), d2(arg))} 
    private implicit def set2Parser[V](s: Set[String]) = s.map(_ ^^ identity).reduceLeft(_ | _) 
    private implicit def map2Parser[V](m: Map[String, V]) = m.keys.map(_ ^^ identity).reduceLeft(_ | _) 
    private def expression: Parser[Formula] = sign ~ term ~ rep(("+" | "-") ~ term) ^^ { case s ~ t ~ l => fold(arg => s * t(arg), l)} 
    private def sign: Parser[Double] = opt("+" | "-") ^^ { case None => 1; case Some("+") => 1; case Some("-") => -1} 
    private def term: Parser[Formula] = longFactor ~ rep(("*" | "/") ~ longFactor) ^^ { case d ~ l => fold(d, l)} 
    private def longFactor: Parser[Formula] = shortFactor ~ rep("^" ~ shortFactor) ^^ { case d ~ l => fold(d, l)} 
    private def shortFactor: Parser[Formula] = fpn | sign ~ (constant | variable | rnd | unaryFct | binaryFct | userFct | "(" ~> expression <~ ")") ^^ { case s ~ x => arg => s * x(arg)} 
    private def constant: Parser[Formula] = allConstants ^^ (name => arg => allConstants(name)) 
    private def variable: Parser[Formula] = variables ^^ (name => arg => arg(name)) 
    private def rnd: Parser[Formula] = "rnd" ~> "(" ~> fpn ~ "," ~ fpn <~ ")" ^^ { case x ~ _ ~ y => (arg: Argument) => require(y(arg) > x(arg)); x(arg) + (y(arg) - x(arg)) * random.nextDouble} | "rnd" ^^ { _ => arg => random.nextDouble} 
    private def fpn: Parser[Formula] = floatingPointNumber ^^ (value => arg => value.toDouble) 
    private def unaryFct: Parser[Formula] = unaryOps ~ "(" ~ expression ~ ")" ^^ { case op ~ _ ~ d ~ _ => arg => unaryOps(op)(d(arg))} 
    private def binaryFct: Parser[Formula] = binaryOps2 ~ "(" ~ expression ~ "," ~ expression ~ ")" ^^ { case op ~ _ ~ d1 ~ _ ~ d2 ~ _ => arg => binaryOps2(op)(d1(arg), d2(arg))} 
    private def userFct: Parser[Formula] = userFcts ~ "(" ~ (expression ^^ (_.toString) | ident) <~ ")" ^^ { case fct ~ _ ~ x => arg => userFcts(fct)(x)} 
    def evaluate(formula: String) = parseAll(expression, formula).get 
} 

그래서 지금 당신은 당신이 할 수 있습니다 :

val formulaParser = new FormulaParser(Set("x"), unary = Map(
    "sin" -> (math.sin(_)), "cos" -> (math.cos(_)), "tan" -> (math.tan(_)) 
)) 
val formula = formulaParser.evaluate("sin(x)^x") 
val function: Double => Double = x => formula(Map("x" -> x)) 
println(function(5.5)) 

당신이 볼 수 있듯이, 나는 또한 추가 매개 변수를 단항 및 이진 함수를 추가합니다.

그런 좋은 코드를 보내 주셔서 감사합니다!