Skip to content

Commit 9142938

Browse files
soyaSethTisue
soya
authored andcommitted
[backport] backport scala#234 and scala#327 to 1.1.x branch
fixes scala#233 on 1.1.x branch
1 parent 9d75808 commit 9142938

File tree

5 files changed

+146
-35
lines changed

5 files changed

+146
-35
lines changed

Diff for: build.sbt

+9-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,15 @@ lazy val parserCombinators = crossProject(JVMPlatform, JSPlatform, NativePlatfor
4242
case _ => file(dir.getPath ++ "-2.11-2.12")
4343
}
4444
}
45-
}
45+
},
46+
mimaBinaryIssueFilters ++= {
47+
import com.typesafe.tools.mima.core._, ProblemFilters._
48+
Seq(
49+
// these are safe to exclude because they're `private[combinator]`
50+
exclude[ReversedMissingMethodProblem]("scala.util.parsing.combinator.Parsers.Success"),
51+
exclude[ReversedMissingMethodProblem]("scala.util.parsing.combinator.Parsers.selectLastFailure"),
52+
)
53+
},
4654
)
4755
.jvmSettings(
4856
OsgiKeys.exportPackage := Seq(s"scala.util.parsing.*;version=${version.value}"),

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

+62-27
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,21 @@ trait Parsers {
133133
* @param next The parser's remaining input
134134
*/
135135
case class Success[+T](result: T, override val next: Input) extends ParseResult[T] {
136-
def map[U](f: T => U) = Success(f(result), next)
137-
def mapPartial[U](f: PartialFunction[T, U], error: T => String): ParseResult[U]
138-
= if(f.isDefinedAt(result)) Success(f(result), next)
139-
else Failure(error(result), next)
136+
def lastFailure: Option[Failure] = None
140137

141-
def flatMapWithNext[U](f: T => Input => ParseResult[U]): ParseResult[U]
142-
= f(result)(next)
138+
def map[U](f: T => U) = Success(f(result), next, lastFailure)
139+
140+
def mapPartial[U](f: PartialFunction[T, U], error: T => String): ParseResult[U] =
141+
if(f.isDefinedAt(result)) Success(f(result), next, lastFailure)
142+
else Failure(error(result), next)
143+
144+
def flatMapWithNext[U](f: T => Input => ParseResult[U]): ParseResult[U] = f(result)(next) match {
145+
case s @ Success(result, rest) =>
146+
val failure = selectLastFailure(this.lastFailure, s.lastFailure)
147+
Success(result, rest, failure)
148+
case f: Failure => selectLastFailure(Some(f), lastFailure).get
149+
case e: Error => e
150+
}
143151

144152
def filterWithError(p: T => Boolean, error: T => String, position: Input): ParseResult[T] =
145153
if (p(result)) this
@@ -188,10 +196,16 @@ trait Parsers {
188196
/** The toString method of a Failure yields an error message. */
189197
override def toString = "["+next.pos+"] failure: "+msg+"\n\n"+next.pos.longString
190198

191-
def append[U >: Nothing](a: => ParseResult[U]): ParseResult[U] = { val alt = a; alt match {
192-
case Success(_, _) => alt
193-
case ns: NoSuccess => if (alt.next.pos < next.pos) this else alt
194-
}}
199+
def append[U >: Nothing](a: => ParseResult[U]): ParseResult[U] = {
200+
val alt = a
201+
202+
alt match {
203+
case s @ Success(result, rest) =>
204+
val failure = selectLastFailure(Some(this), s.lastFailure)
205+
Success(result, rest, failure)
206+
case ns: NoSuccess => if (alt.next.pos < next.pos) this else alt
207+
}
208+
}
195209
}
196210

197211
/** The fatal failure case of ParseResult: contains an error-message and
@@ -210,6 +224,19 @@ trait Parsers {
210224
def Parser[T](f: Input => ParseResult[T]): Parser[T]
211225
= new Parser[T]{ def apply(in: Input) = f(in) }
212226

227+
private[combinator] def Success[U](res: U, next: Input, failure: Option[Failure]): Success[U] =
228+
new Success(res, next) { override val lastFailure: Option[Failure] = failure }
229+
230+
private[combinator] def selectLastFailure(failure0: Option[Failure], failure1: Option[Failure]): Option[Failure] =
231+
(failure0, failure1) match {
232+
case (Some(f0), Some(f1)) =>
233+
if(f0.next.pos < f1.next.pos) Some(f1)
234+
else Some(f0)
235+
case (Some(f0), _) => Some(f0)
236+
case (_, Some(f1)) => Some(f1)
237+
case _ => None
238+
}
239+
213240
def OnceParser[T](f: Input => ParseResult[T]): Parser[T] with OnceParser[T]
214241
= new Parser[T] with OnceParser[T] { def apply(in: Input) = f(in) }
215242

@@ -630,7 +657,7 @@ trait Parsers {
630657
*/
631658
def acceptIf(p: Elem => Boolean)(err: Elem => String): Parser[Elem] = Parser { in =>
632659
if (in.atEnd) Failure("end of input", in)
633-
else if (p(in.first)) Success(in.first, in.rest)
660+
else if (p(in.first)) Success(in.first, in.rest, None)
634661
else Failure(err(in.first), in)
635662
}
636663

@@ -649,7 +676,7 @@ trait Parsers {
649676
*/
650677
def acceptMatch[U](expected: String, f: PartialFunction[Elem, U]): Parser[U] = Parser{ in =>
651678
if (in.atEnd) Failure("end of input", in)
652-
else if (f.isDefinedAt(in.first)) Success(f(in.first), in.rest)
679+
else if (f.isDefinedAt(in.first)) Success(f(in.first), in.rest, None)
653680
else Failure(expected+" expected", in)
654681
}
655682

@@ -682,7 +709,7 @@ trait Parsers {
682709
* @param v The result for the parser
683710
* @return A parser that always succeeds, with the given result `v`
684711
*/
685-
def success[T](v: T) = Parser{ in => Success(v, in) }
712+
def success[T](v: T) = Parser{ in => Success(v, in, None) }
686713

687714
/** A helper method that turns a `Parser` into one that will
688715
* print debugging information to stdout before and after
@@ -747,19 +774,24 @@ trait Parsers {
747774
lazy val p = p0 // lazy argument
748775
val elems = new ListBuffer[T]
749776

750-
def continue(in: Input): ParseResult[List[T]] = {
777+
def continue(in: Input, failure: Option[Failure]): ParseResult[List[T]] = {
751778
val p0 = p // avoid repeatedly re-evaluating by-name parser
752-
@tailrec def applyp(in0: Input): ParseResult[List[T]] = p0(in0) match {
753-
case Success(x, rest) => elems += x ; applyp(rest)
779+
@tailrec def applyp(in0: Input, failure: Option[Failure]): ParseResult[List[T]] = p0(in0) match {
780+
case s @ Success(x, rest) =>
781+
val selectedFailure = selectLastFailure(s.lastFailure, failure)
782+
elems += x
783+
applyp(rest, selectedFailure)
754784
case e @ Error(_, _) => e // still have to propagate error
755-
case _ => Success(elems.toList, in0)
785+
case f: Failure =>
786+
val selectedFailure = selectLastFailure(failure, Some(f))
787+
Success(elems.toList, in0, selectedFailure)
756788
}
757789

758-
applyp(in)
790+
applyp(in, failure)
759791
}
760792

761793
first(in) match {
762-
case Success(x, rest) => elems += x ; continue(rest)
794+
case s @ Success(x, rest) => elems += x ; continue(rest, s.lastFailure)
763795
case ns: NoSuccess => ns
764796
}
765797
}
@@ -779,14 +811,14 @@ trait Parsers {
779811
val elems = new ListBuffer[T]
780812
val p0 = p // avoid repeatedly re-evaluating by-name parser
781813

782-
@tailrec def applyp(in0: Input): ParseResult[List[T]] =
783-
if (elems.length == num) Success(elems.toList, in0)
814+
@tailrec def applyp(in0: Input, failure: Option[Failure]): ParseResult[List[T]] =
815+
if (elems.length == num) Success(elems.toList, in0, failure)
784816
else p0(in0) match {
785-
case Success(x, rest) => elems += x ; applyp(rest)
817+
case s @ Success(x, rest) => elems += x ; applyp(rest, s.lastFailure)
786818
case ns: NoSuccess => ns
787819
}
788820

789-
applyp(in)
821+
applyp(in, None)
790822
}
791823

792824
/** A parser generator for non-empty repetitions.
@@ -868,7 +900,7 @@ trait Parsers {
868900
def not[T](p: => Parser[T]): Parser[Unit] = Parser { in =>
869901
p(in) match {
870902
case Success(_, _) => Failure("Expected failure", in)
871-
case _ => Success((), in)
903+
case _ => Success((), in, None)
872904
}
873905
}
874906

@@ -882,7 +914,7 @@ trait Parsers {
882914
*/
883915
def guard[T](p: => Parser[T]): Parser[T] = Parser { in =>
884916
p(in) match{
885-
case s@ Success(s1,_) => Success(s1, in)
917+
case s@ Success(s1,_) => Success(s1, in, s.lastFailure)
886918
case e => e
887919
}
888920
}
@@ -897,7 +929,7 @@ trait Parsers {
897929
*/
898930
def positioned[T <: Positional](p: => Parser[T]): Parser[T] = Parser { in =>
899931
p(in) match {
900-
case Success(t, in1) => Success(if (t.pos == NoPosition) t setPos in.pos else t, in1)
932+
case s @ Success(t, in1) => Success(if (t.pos == NoPosition) t setPos in.pos else t, in1, s.lastFailure)
901933
case ns: NoSuccess => ns
902934
}
903935
}
@@ -915,7 +947,10 @@ trait Parsers {
915947
def apply(in: Input) = p(in) match {
916948
case s @ Success(out, in1) =>
917949
if (in1.atEnd) s
918-
else Failure("end of input expected", in1)
950+
else s.lastFailure match {
951+
case Some(failure) => failure
952+
case _ => Failure("end of input expected", in1)
953+
}
919954
case ns => ns
920955
}
921956
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ trait RegexParsers extends Parsers {
9494
j += 1
9595
}
9696
if (i == s.length)
97-
Success(source.subSequence(start, j).toString, in.drop(j - offset))
97+
Success(source.subSequence(start, j).toString, in.drop(j - offset), None)
9898
else {
9999
val found = if (start == source.length()) "end of source" else "'"+source.charAt(start)+"'"
100100
Failure("'"+s+"' expected but "+found+" found", in.drop(start - offset))
@@ -111,7 +111,8 @@ trait RegexParsers extends Parsers {
111111
(r findPrefixMatchOf (new SubSequence(source, start))) match {
112112
case Some(matched) =>
113113
Success(source.subSequence(start, start + matched.end).toString,
114-
in.drop(start + matched.end - offset))
114+
in.drop(start + matched.end - offset),
115+
None)
115116
case None =>
116117
val found = if (start == source.length()) "end of source" else "'"+source.charAt(start)+"'"
117118
Failure("string matching regex '"+r+"' expected but "+found+" found", in.drop(start - offset))

Diff for: shared/src/test/scala/scala/util/parsing/combinator/JavaTokenParsersTest.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class JavaTokenParsersTest {
4545
def parseFailure(s: String, errorColPos: Int): Unit = {
4646
val parseResult = parseAll(ident, s)
4747
parseResult match {
48-
case Failure(_, next) =>
48+
case Failure(msg, next) =>
4949
val pos = next.pos
5050
assertEquals(1, pos.line)
5151
assertEquals(errorColPos, pos.column)
@@ -65,7 +65,7 @@ class JavaTokenParsersTest {
6565
parseFailure("with-s", 5)
6666
// we♥scala
6767
parseFailure("we\u2665scala", 3)
68-
parseFailure("with space", 6)
68+
parseFailure("with space", 5)
6969
}
7070

7171
@Test
@@ -87,7 +87,7 @@ class JavaTokenParsersTest {
8787
case e @ Failure(message, next) =>
8888
assertEquals(next.pos.line, 1)
8989
assertEquals(next.pos.column, 7)
90-
assert(message.endsWith(s"end of input expected"))
90+
assert(message.endsWith("string matching regex '(?i)AND' expected but 's' found"))
9191
case _ => sys.error(parseResult1.toString)
9292
}
9393

@@ -111,7 +111,7 @@ class JavaTokenParsersTest {
111111
case Failure(message, next) =>
112112
assertEquals(next.pos.line, 1)
113113
assertEquals(next.pos.column, 1)
114-
assert(message.endsWith(s"end of input expected"))
114+
assert(message.endsWith(s"identifier expected but '-' found"))
115115
case _ => sys.error(parseResult.toString)
116116
}
117117

Diff for: shared/src/test/scala/scala/util/parsing/combinator/RegexParsersTest.scala

+68-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
package scala.util.parsing.combinator
1414

1515
import org.junit.Test
16-
import org.junit.Assert.assertEquals
16+
import org.junit.Assert.{ assertEquals, assertTrue }
1717

1818
class RegexParsersTest {
1919
@Test
@@ -112,4 +112,71 @@ class RegexParsersTest {
112112
val success = parseAll(twoWords, "first second").asInstanceOf[Success[(String, String)]]
113113
assertEquals(("second", "first"), success.get)
114114
}
115+
116+
@Test
117+
def hierarchicalRepSuccess: Unit = {
118+
case class Node(a: String, b: String)
119+
120+
object parser extends RegexParsers {
121+
def top: Parser[List[List[Node]]] = rep(nodes)
122+
def nodes: Parser[List[Node]] = "{" ~> rep(node) <~ "}"
123+
def node: Parser[Node] = "[a-z]+".r ~ ":" ~ "[a-z]+".r ^^ { case a ~ _ ~ b => Node(a, b) }
124+
}
125+
126+
import parser._
127+
128+
val success0 = parseAll(top, "{ a : b c : d}").get
129+
assertEquals(List(List(Node("a", "b"), Node("c", "d"))), success0)
130+
val success1 = parseAll(top, "{ a : b } { c : d }").get
131+
assertEquals(List(List(Node("a", "b")), List(Node("c", "d"))), success1)
132+
val success2 = parseAll(top, "{} {}").get
133+
assertEquals(List(List(), List()), success2)
134+
val success3 = parseAll(top, "").get
135+
assertEquals(List(), success3)
136+
}
137+
138+
@Test
139+
def hierarchicalRepFailure: Unit = {
140+
case class Node(a: String, b: String)
141+
142+
object parser extends RegexParsers {
143+
def top: Parser[List[List[Node]]] = rep(nodes)
144+
def nodes: Parser[List[Node]] = "{" ~> rep(node) <~ "}"
145+
def node: Parser[Node] = "[a-z]+".r ~ ":" ~ "[a-z]+".r ^^ { case a ~ _ ~ b => Node(a, b) }
146+
}
147+
148+
def test(src: String, expect: String, column: Int): Unit = {
149+
import parser._
150+
val result = parseAll(top, src)
151+
result match {
152+
case Failure(msg, next) =>
153+
assertEquals(column, next.pos.column)
154+
assertEquals(expect, msg)
155+
case _ =>
156+
sys.error(result.toString)
157+
}
158+
}
159+
160+
test("{ a : b c : }", "string matching regex '[a-z]+' expected but '}' found", 13)
161+
test("{", "'}' expected but end of source found", 2)
162+
}
163+
164+
@Test
165+
def ifElseTest: Unit = {
166+
object parser extends RegexParsers {
167+
def top: Parser[List[Unit]] = rep(ifelse)
168+
def ifelse: Parser[Unit] = "IF" ~ condition ~ "THEN" ~ "1"~ "END" ^^ { _ => }
169+
def condition: Parser[String] = "TRUE" | "FALSE"
170+
}
171+
172+
import parser._
173+
val res = parseAll(top, "IF FALSE THEN 1 IF TRUE THEN 1 END")
174+
res match {
175+
case Failure(msg, next) =>
176+
assertEquals(17, next.pos.column)
177+
assertEquals("'END' expected but 'I' found", msg)
178+
case _ =>
179+
sys.error(res.toString)
180+
}
181+
}
115182
}

0 commit comments

Comments
 (0)