Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3ed6570

Browse files
issue #242 implementation of repMN
1 parent 633200e commit 3ed6570

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,57 @@ trait Parsers {
792792
applyp(in)
793793
}
794794

795+
/** A parser generator for a specified range of repetitions interleaved by a
796+
* separator.
797+
*
798+
* `repN(n, m, p, s)` uses `p` at least `m` times and up to `n` times, interleaved
799+
* with separator `s`, to parse the input
800+
* (the result is a `List` of at least `m` consecutive results of `p` and up to `n` results).
801+
*
802+
* @param m minimum number of repetitions
803+
* @param n 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 `m` and up to `n`
808+
* (and that only succeeds if `p` matches exactly at least `n` times).
809+
*/
810+
def repMN[T](m: Int, n: Int, p: Parser[T], sep: Parser[Any]): Parser[List[T]] = Parser { in =>
811+
require(0 <= m && m <= n)
812+
val mandatory = if (m == 0) success(Nil) else (p ~ repN(m - 1, sep ~> p)).map { case head ~ tail => head :: tail }
813+
val elems = new ListBuffer[T]
814+
815+
def continue(in: Input): ParseResult[List[T]] = {
816+
val p0 = sep ~> p // avoid repeatedly re-evaluating by-name parser
817+
@tailrec def applyp(in0: Input): ParseResult[List[T]] = p0(in0) match {
818+
case Success(x, rest) => elems += x; if (elems.length == n) Success(elems.toList, rest) else applyp(rest)
819+
case e @ Error(_, _) => e // still have to propagate error
820+
case _ => Success(elems.toList, in0)
821+
}
822+
823+
applyp(in)
824+
}
825+
826+
mandatory(in) match {
827+
case Success(x, rest) => elems ++= x; continue(rest)
828+
case ns: NoSuccess => ns
829+
}
830+
}
831+
832+
/** A parser generator for a specified range of repetitions.
833+
*
834+
* `repN(n, m, p)` uses `p` at least `m` times and up to `n` times to parse the input
835+
* (the result is a `List` of at least `m` consecutive results of `p` and up to `n` results).
836+
*
837+
* @param m minimum number of repetitions
838+
* @param n maximum number of repetitions
839+
* @param p a `Parser` that is to be applied successively to the input
840+
* @return A parser that returns a list of results produced by repeatedly applying `p` to the input.
841+
* The list has a size between `m` and up to `n`
842+
* (and that only succeeds if `p` matches exactly at least `n` times).
843+
*/
844+
def repMN[T](m: Int, n: Int, p: Parser[T]): Parser[List[T]] = repMN[T](m, n, p, success(()))
845+
795846
/** A parser generator for non-empty repetitions.
796847
*
797848
* `rep1sep(p, q)` repeatedly applies `p` interleaved with `q` to parse the
Lines changed: 149 additions & 0 deletions
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]] = repMN(5, 10, 'a', ',')
11+
}
12+
13+
class TestWithoutSeparator extends Parsers {
14+
type Elem = Char
15+
val csv: Parser[List[Char]] = repMN(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)

AltStyle によって変換されたページ (->オリジナル) /