diff --git a/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala
index efe12579..4f50225e 100644
--- a/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala
+++ b/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala
@@ -792,6 +792,42 @@ trait Parsers {
       applyp(in)
     }
 
+  /** A parser generator for a specified range of repetitions interleaved by a
+   * separator.
+   *
+   *  `repNM(n, m, p, s)` uses `p` at least `n` times and up to `m` times, interleaved
+   *  with separator `s`, to parse the input
+   *  (the result is a `List` of at least `n` consecutive results of `p` and up to `m` results).
+   *
+   * @param n   minimum number of repetitions
+   * @param m   maximum number of repetitions
+   * @param p   a `Parser` that is to be applied successively to the input
+   * @param sep a `Parser` that interleaves with p
+   * @return    A parser that returns a list of results produced by repeatedly applying `p` interleaved
+   *            with `sep` to the input. The list has a size between `n` and up to `m`
+   *            (and that only succeeds if `p` matches at least `n` times).
+   */
+  def repNM[T](n: Int, m: Int, p: Parser[T], sep: Parser[Any] = success(())): Parser[List[T]] = Parser { in =>
+    val mandatory = if (n == 0) success(Nil) else (p ~ repN(n - 1, sep ~> p)).map { case head ~ tail => head :: tail }
+    val elems = new ListBuffer[T]
+
+    def continue(in: Input): ParseResult[List[T]] = {
+      val p0 = sep ~> p // avoid repeatedly re-evaluating by-name parser
+      @tailrec def applyp(in0: Input): ParseResult[List[T]] = p0(in0) match {
+        case Success(x, rest) => elems += x; if (elems.length == m) Success(elems.toList, rest) else applyp(rest)
+        case e @ Error(_, _) => e // still have to propagate error
+        case _ => Success(elems.toList, in0)
+      }
+
+      applyp(in)
+    }
+
+    mandatory(in) match {
+      case Success(x, rest) => elems ++= x; continue(rest)
+      case ns: NoSuccess => ns
+    }
+  }
+
   /** A parser generator for non-empty repetitions.
    *
    *  `rep1sep(p, q)` repeatedly applies `p` interleaved with `q` to parse the
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/gh242.scala b/shared/src/test/scala/scala/util/parsing/combinator/gh242.scala
new file mode 100644
index 00000000..2fe21170
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/gh242.scala
@@ -0,0 +1,149 @@
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+import scala.util.parsing.combinator.Parsers
+import scala.util.parsing.input.CharSequenceReader
+
+class gh242 {
+  class TestWithSeparator extends Parsers {
+    type Elem = Char
+    val csv: Parser[List[Char]] = repNM(5, 10, 'a', ',')
+  }
+
+  class TestWithoutSeparator extends Parsers {
+    type Elem = Char
+    val csv: Parser[List[Char]] = repNM(5, 10, 'a')
+  }
+
+  @Test
+  def testEmpty(): Unit = {
+    val tstParsers = new TestWithSeparator
+    val s = new CharSequenceReader("")
+    val expectedFailure = """[1.1] failure: end of input
+                            |
+                            |
+                            |^""".stripMargin
+    assertEquals(expectedFailure, tstParsers.csv(s).toString)
+  }
+
+  @Test
+  def testBelowMinimum(): Unit = {
+    val tstParsers = new TestWithSeparator
+    val s = new CharSequenceReader("a,a,a,a")
+    val expectedFailure = """[1.8] failure: end of input
+                            |
+                            |a,a,a,a
+                            |       ^""".stripMargin
+    assertEquals(expectedFailure, tstParsers.csv(s).toString)
+  }
+
+  @Test
+  def testMinimum(): Unit = {
+    val tstParsers = new TestWithSeparator
+    val s = new CharSequenceReader("a,a,a,a,a")
+    val expected = List.fill[Char](5)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(9, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+
+  @Test
+  def testInRange(): Unit = {
+    val tstParsers = new TestWithSeparator
+    val s = new CharSequenceReader("a,a,a,a,a,a,a,a")
+    val expected = List.fill[Char](8)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(15, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+
+  @Test
+  def testMaximum(): Unit = {
+    val tstParsers = new TestWithSeparator
+    val s = new CharSequenceReader("a,a,a,a,a,a,a,a,a,a")
+    val expected = List.fill[Char](10)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(19, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+
+  @Test
+  def testAboveMaximum(): Unit = {
+    val tstParsers = new TestWithSeparator
+    val s = new CharSequenceReader("a,a,a,a,a,a,a,a,a,a,a,a")
+    val expected = List.fill[Char](10)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(19, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+
+  @Test
+  def testEmptyWithoutSep(): Unit = {
+    val tstParsers = new TestWithoutSeparator
+    val s = new CharSequenceReader("")
+    val expectedFailure = """[1.1] failure: end of input
+                            |
+                            |
+                            |^""".stripMargin
+    assertEquals(expectedFailure, tstParsers.csv(s).toString)
+  }
+
+  @Test
+  def testBelowMinimumWithoutSep(): Unit = {
+    val tstParsers = new TestWithoutSeparator
+    val s = new CharSequenceReader("aaaa")
+    val expectedFailure = """[1.5] failure: end of input
+                            |
+                            |aaaa
+                            |    ^""".stripMargin
+    assertEquals(expectedFailure, tstParsers.csv(s).toString)
+  }
+
+  @Test
+  def testMinimumWithoutSep(): Unit = {
+    val tstParsers = new TestWithoutSeparator
+    val s = new CharSequenceReader("aaaaa")
+    val expected = List.fill[Char](5)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(5, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+
+  @Test
+  def testInRangeWithoutSep(): Unit = {
+    val tstParsers = new TestWithoutSeparator
+    val s = new CharSequenceReader("aaaaaaaa")
+    val expected = List.fill[Char](8)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(8, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+
+  @Test
+  def testMaximumWithoutSep(): Unit = {
+    val tstParsers = new TestWithoutSeparator
+    val s = new CharSequenceReader("aaaaaaaaaa")
+    val expected = List.fill[Char](10)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(10, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+
+  @Test
+  def testAboveMaximumWithoutSep(): Unit = {
+    val tstParsers = new TestWithoutSeparator
+    val s = new CharSequenceReader("aaaaaaaaaaaa")
+    val expected = List.fill[Char](10)('a')
+    val actual = tstParsers.csv(s)
+    assertEquals(10, actual.next.offset)
+    assert(actual.successful)
+    assertEquals(expected, actual.get)
+  }
+}