# lihaoyi/fastparse

Fetching contributors…
Cannot retrieve contributors at this time
143 lines (118 sloc) 4.58 KB
 package fastparse import utest._ import scala.collection.mutable /** * Demonstrates simultaneously parsing and * evaluating simple arithmetic expressions */ object MathTests extends TestSuite{ def eval(tree: (Int, Seq[(String, Int)])) = { val (base, ops) = tree ops.foldLeft(base){ case (left, (op, right)) => op match{ case "+" => left + right case "-" => left - right case "*" => left * right case "/" => left / right }} } import fastparse.all._ val number: P[Int] = P( CharIn('0'to'9').rep(1).!.map(_.toInt) ) val parens: P[Int] = P( "(" ~/ addSub ~ ")" ) val factor: P[Int] = P( number | parens ) val divMul: P[Int] = P( factor ~ (CharIn("*/").! ~/ factor).rep ).map(eval) val addSub: P[Int] = P( divMul ~ (CharIn("+-").! ~/ divMul).rep ).map(eval) val expr: P[Int] = P( addSub ~ End ) val tests = TestSuite{ 'pass { val Parsed.Success(2, _) = expr.parse("1+1") val Parsed.Success(15, _) = expr.parse("(1+1*2)+3*4") val Parsed.Success(21, _) = expr.parse("((1+1*2)+(3*4*5))/3") val Parsed.Failure(expected, failIndex, extra) = expr.parse("1+1*") assert(expected == (number | parens), failIndex == 4) } 'fail{ def check(input: String, expectedTrace: String, expectedShortTrace: String) = { val failure = expr.parse(input).asInstanceOf[Parsed.Failure] val actualTrace = failure.extra.traced.trace assert(expectedTrace.trim == actualTrace.trim) // Check iterator parsing results in a failure in the right place. Note // that we aren't checking the `.traced.trace` because that requires a // second parse which doesn't work with iterators (which get exhausted) for(chunkSize <- Seq(1, 4, 16, 64, 256, 1024)){ val failure = expr.parseIterator(input.grouped(chunkSize)).asInstanceOf[Parsed.Failure] assert(failure.msg == expectedShortTrace.trim) } } check( "(+)", """expr:1:1 / addSub:1:1 / divMul:1:1 / factor:1:1 / parens:1:1 / addSub:1:2""" + """ / divMul:1:2 / factor:1:2 / (number | parens):1:2 ..."+)" """, """ (number | parens):1 ..."+)" """ ) check( "1+-", """ expr:1:1 / addSub:1:1 / divMul:1:3 / factor:1:3 / (number | parens):1:3 ..."-" """, """ (number | parens):2 ..."-" """ ) check( "(1+(2+3x))+4", """ expr:1:1 / addSub:1:1 / divMul:1:1 / factor:1:1 / parens:1:1 / addSub:1:2""" + """ / divMul:1:4 / factor:1:4 / parens:1:4 / (")" | CharIn("+-")):1:8 ..."x))+4" """, """ ")":7 ..."x))+4" """ ) } 'instrument{ 'simple{ val callCount = mutable.Map.empty[String, Int] val instrumentFunction = (parser: Parser[_], index: Int, continuation: () => Parsed[_]) => { callCount(parser.toString) = callCount.getOrElse(parser.toString, 0) + 1 } expr.parse("((1+1*2)+(3*4*5))/3", instrument = instrumentFunction) val expectedCallCount = Map( "expr" -> 1, "addSub" -> 4, "divMul" -> 6, "factor" -> 10, "number" -> 10, "parens" -> 3 ) assert(callCount == expectedCallCount) } 'continuation{ val resultCount = mutable.Map.empty[(String, Boolean), Int] val instrumentFunction = (parser: Parser[_], index: Int, continuation: () => Parsed[_]) => { val result = continuation() val resultKey = (parser.toString, result.isInstanceOf[Parsed.Success[_]]) resultCount(resultKey) = resultCount.getOrElse(resultKey, 0) + 1 } // Good Parse expr.parse("((1+1*2)+(3*4*5))/3", instrument = instrumentFunction) val expectedResultCount = Map( ("expr", true) -> 1, ("addSub", true) -> 4, ("divMul", true) -> 6, ("factor", true) -> 10, ("number", true) -> 7, ("number", false) -> 3, ("parens", true) -> 3 ) assert(resultCount == expectedResultCount) // Bad Parse resultCount.clear() expr.parse("((1+1*2)+(3*4*))/3", instrument = instrumentFunction) val expectedResultCount2 = Map( ("expr", false) -> 1, ("addSub", true) -> 1, ("addSub", false) -> 3, ("divMul", true) -> 3, ("divMul", false) -> 3, ("factor", true) -> 6, ("factor", false) -> 3, ("number", true) -> 5, ("number", false) -> 4, ("parens", true) -> 1, ("parens", false) -> 3 ) assert(resultCount == expectedResultCount2) } } } }