Skip to content

Commit

Permalink
Respect operator precedence while parsing
Browse files Browse the repository at this point in the history
Parser now respects operator precedence

Added unit tests to check precedence.
  • Loading branch information
hrj committed Apr 3, 2014
1 parent 5b3f5b3 commit 030f866
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 5 deletions.
26 changes: 21 additions & 5 deletions base/src/main/scala/co/uproot/abandon/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,31 @@ object AbandonParser extends StandardTokenParsers with PackratParsers {
lazy val numericParser:Parser[NumericExpr] = phrase(numericExpr)

private lazy val numericLiteralExpr:PackratParser[NumericExpr] = (number ^^ {case n => NumericLiteralExpr(n)})
private lazy val numericExpr:PackratParser[NumericExpr] = (addExpr | subExpr | mulExpr | divExpr | numericLiteralExpr | numericFunctionExpr | parenthesizedExpr | unaryNegExpr )
private lazy val numericLiteralFirstExpr:PackratParser[NumericExpr] = (numericLiteralExpr | numericExpr)
private lazy val addExpr:PackratParser[AddExpr] = ((numericExpr <~ "+") ~ numericExpr) ^^ { case a ~ b => AddExpr(a, b) }
private lazy val subExpr:PackratParser[SubExpr] = ((numericExpr <~ "-") ~ numericExpr) ^^ { case a ~ b => SubExpr(a, b) }
private lazy val mulExpr:PackratParser[MulExpr] = ((numericExpr <~ "*") ~ numericExpr) ^^ { case a ~ b => MulExpr(a, b) }
private lazy val divExpr:PackratParser[DivExpr] = ((numericExpr <~ "/") ~ numericExpr) ^^ { case a ~ b => DivExpr(a, b) }
private lazy val unaryNegExpr:PackratParser[UnaryNegExpr] = ("-" ~> numericLiteralFirstExpr) ^^ { case expr => UnaryNegExpr(expr) }
private lazy val parenthesizedExpr:PackratParser[NumericExpr] = (("(" ~> numericExpr) <~ ")") ^^ { case expr => expr }

private def mkExpr(op:String, e1:NumericExpr, e2:NumericExpr) = {
op match {
case "+" => AddExpr(e1, e2)
case "-" => SubExpr(e1, e2)
case "*" => MulExpr(e1, e2)
case "/" => DivExpr(e1, e2)
}
}

private lazy val numericExpr:PackratParser[NumericExpr] =
(term ~ termFrag) ^^ {
case t1 ~ ts => ts.foldLeft(t1){case (acc, op ~ t2) => mkExpr(op, acc, t2)}
}
private lazy val term:PackratParser[NumericExpr] =
factor ~ factorFrag ^^ {
case t1 ~ ts => ts.foldLeft(t1){case (acc, op ~ t2) => mkExpr(op, acc, t2)}
}
private lazy val termFrag:PackratParser[Seq[String ~ NumericExpr]] = (("+" | "-") ~ term)*
private lazy val factor:PackratParser[NumericExpr] = numericLiteralExpr | parenthesizedExpr | unaryNegExpr
private lazy val factorFrag:PackratParser[Seq[String ~ NumericExpr]] = (("*" | "/") ~ factor)*

private lazy val booleanExpression:PackratParser[BooleanExpr] = (trueKeyword ^^^ BooleanLiteralExpr(true)) | (falseKeyword ^^^ BooleanLiteralExpr(false))

private lazy val txFrag = ((dateFrag ~ (annotation?) ~ (payee?)) <~ eol) ~ (eolComment*) ~ (txDetails+) ^^ {
Expand Down
50 changes: 50 additions & 0 deletions base/src/test/scala/co/uproot/abandon/ParserTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,54 @@ class ParserTest extends FlatSpec with Matchers with Inside {
}
}

"parser" should "ensure precedence of operators" in {
val tests = Map(
"1 + 2 * 3" -> (bd("7"), AddExpr(nlit(1),MulExpr(nlit(2), nlit(3)))),
"1 * 2 + 3" -> (bd("5"), AddExpr(MulExpr(nlit(1), nlit(2)), nlit(3))),
"1 * 2 + 3 * 4 - 2" -> (bd("12"), SubExpr(AddExpr(MulExpr(nlit(1), nlit(2)), MulExpr(nlit(3), nlit(4))), nlit(2))),
"1 + 2 * 3 * 4 - 2" -> (bd("23"), SubExpr(AddExpr(nlit(1), MulExpr(MulExpr(nlit(2),nlit(3)), nlit(4))), nlit(2))),
"1 + 2 * -3 * 4 - 2" -> (bd("-25"), SubExpr(AddExpr(nlit(1), MulExpr(MulExpr(nlit(2),UnaryNegExpr(nlit(3))), nlit(4))), nlit(2)))
)

val context = new co.uproot.abandon.EvaluationContext[BigDecimal](Nil, Nil, null)

tests foreach {
case (testInput, expectedOutput) =>
val parseResult = AbandonParser.numericParser(scanner(testInput))

inside(parseResult) {
case AbandonParser.Success(result, _) =>
inside(result) {
case ne:NumericExpr =>
ne.evaluate(context) should be (expectedOutput._1)
ne should be(expectedOutput._2)
}
}
}
}

"parser" should "handle parenthesis correctly" in {
val tests = Map(
"1 + (2 * 3)" -> (bd("7"), AddExpr(nlit(1),MulExpr(nlit(2), nlit(3)))),
"(1 + 2) * 3" -> (bd("9"), MulExpr(AddExpr(nlit(1),nlit(2)), nlit(3))),
"1 * (2 + 3)" -> (bd("5"), MulExpr(nlit(1), AddExpr(nlit(2), nlit(3)))),
"1 * (2 + 3) * 4 - 2" -> (bd("18"), SubExpr(MulExpr(MulExpr(nlit(1), AddExpr(nlit(2), nlit(3))), nlit(4)), nlit(2)))
)

val context = new co.uproot.abandon.EvaluationContext[BigDecimal](Nil, Nil, null)

tests foreach {
case (testInput, expectedOutput) =>
val parseResult = AbandonParser.numericParser(scanner(testInput))

inside(parseResult) {
case AbandonParser.Success(result, _) =>
inside(result) {
case ne:NumericExpr =>
ne.evaluate(context) should be (expectedOutput._1)
ne should be(expectedOutput._2)
}
}
}
}
}

0 comments on commit 030f866

Please sign in to comment.