Skip to content

Commit 4d64b03

Browse files
pgrandjeanSethTisue
authored andcommitted
issue #242 implementation of repNM for 1.2.x (#245)
1 parent 633200e commit 4d64b03

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

Diff for: shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala

+36
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,42 @@ trait Parsers {
792792
applyp(in)
793793
}
794794

795+
/** A parser generator for a specified range of repetitions interleaved by a
796+
* separator.
797+
*
798+
* `repNM(n, m, p, s)` uses `p` at least `n` times and up to `m` times, interleaved
799+
* with separator `s`, to parse the input
800+
* (the result is a `List` of at least `n` consecutive results of `p` and up to `m` results).
801+
*
802+
* @param n minimum number of repetitions
803+
* @param m maximum number of repetitions
804+
* @param p a `Parser` that is to be applied successively to the input
805+
* @param sep a `Parser` that interleaves with p
806+
* @return A parser that returns a list of results produced by repeatedly applying `p` interleaved
807+
* with `sep` to the input. The list has a size between `n` and up to `m`
808+
* (and that only succeeds if `p` matches at least `n` times).
809+
*/
810+
def repNM[T](n: Int, m: Int, p: Parser[T], sep: Parser[Any] = success(())): Parser[List[T]] = Parser { in =>
811+
val mandatory = if (n == 0) success(Nil) else (p ~ repN(n - 1, sep ~> p)).map { case head ~ tail => head :: tail }
812+
val elems = new ListBuffer[T]
813+
814+
def continue(in: Input): ParseResult[List[T]] = {
815+
val p0 = sep ~> p // avoid repeatedly re-evaluating by-name parser
816+
@tailrec def applyp(in0: Input): ParseResult[List[T]] = p0(in0) match {
817+
case Success(x, rest) => elems += x; if (elems.length == m) Success(elems.toList, rest) else applyp(rest)
818+
case e @ Error(_, _) => e // still have to propagate error
819+
case _ => Success(elems.toList, in0)
820+
}
821+
822+
applyp(in)
823+
}
824+
825+
mandatory(in) match {
826+
case Success(x, rest) => elems ++= x; continue(rest)
827+
case ns: NoSuccess => ns
828+
}
829+
}
830+
795831
/** A parser generator for non-empty repetitions.
796832
*
797833
* `rep1sep(p, q)` repeatedly applies `p` interleaved with `q` to parse the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import org.junit.Assert.assertEquals
2+
import org.junit.Test
3+
4+
import scala.util.parsing.combinator.Parsers
5+
import scala.util.parsing.input.CharSequenceReader
6+
7+
class gh242 {
8+
class TestWithSeparator extends Parsers {
9+
type Elem = Char
10+
val csv: Parser[List[Char]] = repNM(5, 10, 'a', ',')
11+
}
12+
13+
class TestWithoutSeparator extends Parsers {
14+
type Elem = Char
15+
val csv: Parser[List[Char]] = repNM(5, 10, 'a')
16+
}
17+
18+
@Test
19+
def testEmpty(): Unit = {
20+
val tstParsers = new TestWithSeparator
21+
val s = new CharSequenceReader("")
22+
val expectedFailure = """[1.1] failure: end of input
23+
|
24+
|
25+
|^""".stripMargin
26+
assertEquals(expectedFailure, tstParsers.csv(s).toString)
27+
}
28+
29+
@Test
30+
def testBelowMinimum(): Unit = {
31+
val tstParsers = new TestWithSeparator
32+
val s = new CharSequenceReader("a,a,a,a")
33+
val expectedFailure = """[1.8] failure: end of input
34+
|
35+
|a,a,a,a
36+
| ^""".stripMargin
37+
assertEquals(expectedFailure, tstParsers.csv(s).toString)
38+
}
39+
40+
@Test
41+
def testMinimum(): Unit = {
42+
val tstParsers = new TestWithSeparator
43+
val s = new CharSequenceReader("a,a,a,a,a")
44+
val expected = List.fill[Char](5)('a')
45+
val actual = tstParsers.csv(s)
46+
assertEquals(9, actual.next.offset)
47+
assert(actual.successful)
48+
assertEquals(expected, actual.get)
49+
}
50+
51+
@Test
52+
def testInRange(): Unit = {
53+
val tstParsers = new TestWithSeparator
54+
val s = new CharSequenceReader("a,a,a,a,a,a,a,a")
55+
val expected = List.fill[Char](8)('a')
56+
val actual = tstParsers.csv(s)
57+
assertEquals(15, actual.next.offset)
58+
assert(actual.successful)
59+
assertEquals(expected, actual.get)
60+
}
61+
62+
@Test
63+
def testMaximum(): Unit = {
64+
val tstParsers = new TestWithSeparator
65+
val s = new CharSequenceReader("a,a,a,a,a,a,a,a,a,a")
66+
val expected = List.fill[Char](10)('a')
67+
val actual = tstParsers.csv(s)
68+
assertEquals(19, actual.next.offset)
69+
assert(actual.successful)
70+
assertEquals(expected, actual.get)
71+
}
72+
73+
@Test
74+
def testAboveMaximum(): Unit = {
75+
val tstParsers = new TestWithSeparator
76+
val s = new CharSequenceReader("a,a,a,a,a,a,a,a,a,a,a,a")
77+
val expected = List.fill[Char](10)('a')
78+
val actual = tstParsers.csv(s)
79+
assertEquals(19, actual.next.offset)
80+
assert(actual.successful)
81+
assertEquals(expected, actual.get)
82+
}
83+
84+
@Test
85+
def testEmptyWithoutSep(): Unit = {
86+
val tstParsers = new TestWithoutSeparator
87+
val s = new CharSequenceReader("")
88+
val expectedFailure = """[1.1] failure: end of input
89+
|
90+
|
91+
|^""".stripMargin
92+
assertEquals(expectedFailure, tstParsers.csv(s).toString)
93+
}
94+
95+
@Test
96+
def testBelowMinimumWithoutSep(): Unit = {
97+
val tstParsers = new TestWithoutSeparator
98+
val s = new CharSequenceReader("aaaa")
99+
val expectedFailure = """[1.5] failure: end of input
100+
|
101+
|aaaa
102+
| ^""".stripMargin
103+
assertEquals(expectedFailure, tstParsers.csv(s).toString)
104+
}
105+
106+
@Test
107+
def testMinimumWithoutSep(): Unit = {
108+
val tstParsers = new TestWithoutSeparator
109+
val s = new CharSequenceReader("aaaaa")
110+
val expected = List.fill[Char](5)('a')
111+
val actual = tstParsers.csv(s)
112+
assertEquals(5, actual.next.offset)
113+
assert(actual.successful)
114+
assertEquals(expected, actual.get)
115+
}
116+
117+
@Test
118+
def testInRangeWithoutSep(): Unit = {
119+
val tstParsers = new TestWithoutSeparator
120+
val s = new CharSequenceReader("aaaaaaaa")
121+
val expected = List.fill[Char](8)('a')
122+
val actual = tstParsers.csv(s)
123+
assertEquals(8, actual.next.offset)
124+
assert(actual.successful)
125+
assertEquals(expected, actual.get)
126+
}
127+
128+
@Test
129+
def testMaximumWithoutSep(): Unit = {
130+
val tstParsers = new TestWithoutSeparator
131+
val s = new CharSequenceReader("aaaaaaaaaa")
132+
val expected = List.fill[Char](10)('a')
133+
val actual = tstParsers.csv(s)
134+
assertEquals(10, actual.next.offset)
135+
assert(actual.successful)
136+
assertEquals(expected, actual.get)
137+
}
138+
139+
@Test
140+
def testAboveMaximumWithoutSep(): Unit = {
141+
val tstParsers = new TestWithoutSeparator
142+
val s = new CharSequenceReader("aaaaaaaaaaaa")
143+
val expected = List.fill[Char](10)('a')
144+
val actual = tstParsers.csv(s)
145+
assertEquals(10, actual.next.offset)
146+
assert(actual.successful)
147+
assertEquals(expected, actual.get)
148+
}
149+
}

0 commit comments

Comments
 (0)