diff --git a/src/main/scala/scala/util/parsing/combinator/Parsers.scala b/src/main/scala/scala/util/parsing/combinator/Parsers.scala index 16754646..7cd9afce 100644 --- a/src/main/scala/scala/util/parsing/combinator/Parsers.scala +++ b/src/main/scala/scala/util/parsing/combinator/Parsers.scala @@ -314,6 +314,36 @@ trait Parsers { def ~! [U](p: => Parser[U]): Parser[~[T, U]] = OnceParser{ (for(a <- this; b <- commit(p)) yield new ~(a,b)).named("~!") } + + /** A parser combinator for non-back-tracking sequential composition which only keeps the right result. + * + * `p ~>! q` succeeds if `p` succeds and `q` succeds on the input left over by `p`. + * In case of failure, no back-tracking is performed (in an earlier parser produced by the `|` combinator). + * + * @param q a parser that will be executed after `p` (this parser) succeeds -- evaluated at most once, and only when necessary + * @return a `Parser` that -- on success -- reutrns the result of `q`. + * The resulting parser fails if either `p` or `q` fails, this failure is fatal. + */ + @migration("The call-by-name argument is evaluated at most once per constructed Parser object, instead of on every need that arises during parsing.", "2.9.0") + def ~>! [U](q: => Parser[U]): Parser[U] = { lazy val p = q // lazy argument + OnceParser { (for(a <- this; b <- commit(p)) yield b).named("~>!") } + } + + /** A parser combinator for non-back-tracking sequential composition which only keeps the left result. + * + * `p <~! q` succeeds if `p` succeds and `q` succeds on the input left over by `p`. + * In case of failure, no back-tracking is performed (in an earlier parser produced by the `|` combinator). + * + * @param q a parser that will be executed after `p` (this parser) succeeds -- evaluated at most once, and only when necessary + * @return a `Parser` that -- on success -- reutrns the result of `p`. + * The resulting parser fails if either `p` or `q` fails, this failure is fatal. + */ + @migration("The call-by-name argument is evaluated at most once per constructed Parser object, instead of on every need that arises during parsing.", "2.9.0") + def <~! [U](q: => Parser[U]): Parser[T] = { lazy val p = q // lazy argument + OnceParser { (for(a <- this; b <- commit(p)) yield a).named("<~!") } + } + + /** A parser combinator for alternative composition. * * `p | q` succeeds if `p` succeeds or `q` succeeds. diff --git a/src/test/scala/scala/util/parsing/combinator/RegexParsersTest.scala b/src/test/scala/scala/util/parsing/combinator/RegexParsersTest.scala index f97b02bb..969f29b4 100644 --- a/src/test/scala/scala/util/parsing/combinator/RegexParsersTest.scala +++ b/src/test/scala/scala/util/parsing/combinator/RegexParsersTest.scala @@ -38,6 +38,33 @@ class RegexParsersTest { assertEquals(result(5), extractResult(parseAll(q, "5"))) } + @Test + def parserSkippingResult: Unit = { + object parser extends RegexParsers { + def quote = "\"" + def string = """[a-zA-Z]*""".r + type ResultType = String + def p: Parser[ResultType] = quote ~> string <~ quote + def q: Parser[ResultType] = quote ~>! string <~! quote + def halfQuoted = quote ~ string ^^ { case q ~ s => q + s } + } + import parser._ + val failureLq = parseAll(p, "\"asdf").asInstanceOf[Failure] + val failureRq = parseAll(p, "asdf\"").asInstanceOf[Failure] + val failureQBacktrackL = parseAll(q | quote, "\"").asInstanceOf[Error] + val failureQBacktrackR = parseAll(q | halfQuoted, "\"asdf").asInstanceOf[Error] + + val successP = parseAll(p, "\"asdf\"").get + assertEquals(successP, "asdf") + val successPBacktrackL = parseAll(p | quote, "\"").get + assertEquals(successPBacktrackL, "\"") + val successPBacktrackR = parseAll(p | halfQuoted, "\"asdf").get + assertEquals(successPBacktrackR, "\"asdf") + + val successQ = parseAll(q, "\"asdf\"").get + assertEquals(successQ, "asdf") + } + @Test def parserFilter: Unit = { object parser extends RegexParsers {