Skip to content

Commit 85c033f

Browse files
committed
Merge pull request #38 from JanBessai/master
Add non backtracking versions of ~> and <~
2 parents 875302a + c8f4a4a commit 85c033f

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

src/main/scala/scala/util/parsing/combinator/Parsers.scala

+30
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,36 @@ trait Parsers {
314314
def ~! [U](p: => Parser[U]): Parser[~[T, U]]
315315
= OnceParser{ (for(a <- this; b <- commit(p)) yield new ~(a,b)).named("~!") }
316316

317+
318+
/** A parser combinator for non-back-tracking sequential composition which only keeps the right result.
319+
*
320+
* `p ~>! q` succeeds if `p` succeds and `q` succeds on the input left over by `p`.
321+
* In case of failure, no back-tracking is performed (in an earlier parser produced by the `|` combinator).
322+
*
323+
* @param q a parser that will be executed after `p` (this parser) succeeds -- evaluated at most once, and only when necessary
324+
* @return a `Parser` that -- on success -- reutrns the result of `q`.
325+
* The resulting parser fails if either `p` or `q` fails, this failure is fatal.
326+
*/
327+
@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")
328+
def ~>! [U](q: => Parser[U]): Parser[U] = { lazy val p = q // lazy argument
329+
OnceParser { (for(a <- this; b <- commit(p)) yield b).named("~>!") }
330+
}
331+
332+
/** A parser combinator for non-back-tracking sequential composition which only keeps the left result.
333+
*
334+
* `p <~! q` succeeds if `p` succeds and `q` succeds on the input left over by `p`.
335+
* In case of failure, no back-tracking is performed (in an earlier parser produced by the `|` combinator).
336+
*
337+
* @param q a parser that will be executed after `p` (this parser) succeeds -- evaluated at most once, and only when necessary
338+
* @return a `Parser` that -- on success -- reutrns the result of `p`.
339+
* The resulting parser fails if either `p` or `q` fails, this failure is fatal.
340+
*/
341+
@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")
342+
def <~! [U](q: => Parser[U]): Parser[T] = { lazy val p = q // lazy argument
343+
OnceParser { (for(a <- this; b <- commit(p)) yield a).named("<~!") }
344+
}
345+
346+
317347
/** A parser combinator for alternative composition.
318348
*
319349
* `p | q` succeeds if `p` succeeds or `q` succeeds.

src/test/scala/scala/util/parsing/combinator/RegexParsersTest.scala

+27
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,33 @@ class RegexParsersTest {
3838
assertEquals(result(5), extractResult(parseAll(q, "5")))
3939
}
4040

41+
@Test
42+
def parserSkippingResult: Unit = {
43+
object parser extends RegexParsers {
44+
def quote = "\""
45+
def string = """[a-zA-Z]*""".r
46+
type ResultType = String
47+
def p: Parser[ResultType] = quote ~> string <~ quote
48+
def q: Parser[ResultType] = quote ~>! string <~! quote
49+
def halfQuoted = quote ~ string ^^ { case q ~ s => q + s }
50+
}
51+
import parser._
52+
val failureLq = parseAll(p, "\"asdf").asInstanceOf[Failure]
53+
val failureRq = parseAll(p, "asdf\"").asInstanceOf[Failure]
54+
val failureQBacktrackL = parseAll(q | quote, "\"").asInstanceOf[Error]
55+
val failureQBacktrackR = parseAll(q | halfQuoted, "\"asdf").asInstanceOf[Error]
56+
57+
val successP = parseAll(p, "\"asdf\"").get
58+
assertEquals(successP, "asdf")
59+
val successPBacktrackL = parseAll(p | quote, "\"").get
60+
assertEquals(successPBacktrackL, "\"")
61+
val successPBacktrackR = parseAll(p | halfQuoted, "\"asdf").get
62+
assertEquals(successPBacktrackR, "\"asdf")
63+
64+
val successQ = parseAll(q, "\"asdf\"").get
65+
assertEquals(successQ, "asdf")
66+
}
67+
4168
@Test
4269
def parserFilter: Unit = {
4370
object parser extends RegexParsers {

0 commit comments

Comments
 (0)