Skip to content

Commit dfac69c

Browse files
author
soya
committed
Fix scala#203
This change means revival of lastNoSuccessVar(deleted by scala#108). However, in this time, a new variable(`lastFailure` in `Success` class) is immutable(i.e. this variable does not means revival of side effects). That is why, probably, this change does not break referentially transparent.
1 parent cc56de2 commit dfac69c

File tree

4 files changed

+137
-34
lines changed

4 files changed

+137
-34
lines changed

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

+63-28
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,22 @@ trait Parsers {
136136
* @param result The parser's output
137137
* @param next The parser's remaining input
138138
*/
139-
case class Success[+T](result: T, override val next: Input) extends ParseResult[T] {
140-
def map[U](f: T => U) = Success(f(result), next)
141-
def mapPartial[U](f: PartialFunction[T, U], error: T => String): ParseResult[U]
142-
= if(f.isDefinedAt(result)) Success(f(result), next)
143-
else Failure(error(result), next)
139+
abstract case class Success[+T](result: T, override val next: Input) extends ParseResult[T] {
140+
val lastFailure: Option[Failure]
144141

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

148156
def filterWithError(p: T => Boolean, error: T => String, position: Input): ParseResult[T] =
149157
if (p(result)) this
@@ -192,10 +200,16 @@ trait Parsers {
192200
/** The toString method of a Failure yields an error message. */
193201
override def toString = s"[${next.pos}] failure: $msg\n\n${next.pos.longString}"
194202

195-
def append[U >: Nothing](a: => ParseResult[U]): ParseResult[U] = { val alt = a; alt match {
196-
case Success(_, _) => alt
197-
case ns: NoSuccess => if (alt.next.pos < next.pos) this else alt
198-
}}
203+
def append[U >: Nothing](a: => ParseResult[U]): ParseResult[U] = {
204+
val alt = a
205+
206+
alt match {
207+
case s @ Success(result, rest) =>
208+
val failure = selectLastFailure(Some(this), s.lastFailure)
209+
Success(result, rest, failure)
210+
case ns: NoSuccess => if (alt.next.pos < next.pos) this else alt
211+
}
212+
}
199213
}
200214

201215
/** The fatal failure case of ParseResult: contains an error-message and
@@ -214,6 +228,19 @@ trait Parsers {
214228
def Parser[T](f: Input => ParseResult[T]): Parser[T]
215229
= new Parser[T]{ def apply(in: Input) = f(in) }
216230

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

@@ -633,7 +660,7 @@ trait Parsers {
633660
*/
634661
def acceptIf(p: Elem => Boolean)(err: Elem => String): Parser[Elem] = Parser { in =>
635662
if (in.atEnd) Failure("end of input", in)
636-
else if (p(in.first)) Success(in.first, in.rest)
663+
else if (p(in.first)) Success(in.first, in.rest, None)
637664
else Failure(err(in.first), in)
638665
}
639666

@@ -652,7 +679,7 @@ trait Parsers {
652679
*/
653680
def acceptMatch[U](expected: String, f: PartialFunction[Elem, U]): Parser[U] = Parser{ in =>
654681
if (in.atEnd) Failure("end of input", in)
655-
else if (f.isDefinedAt(in.first)) Success(f(in.first), in.rest)
682+
else if (f.isDefinedAt(in.first)) Success(f(in.first), in.rest, None)
656683
else Failure(expected+" expected", in)
657684
}
658685

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

690717
/** A helper method that turns a `Parser` into one that will
691718
* print debugging information to stdout before and after
@@ -750,19 +777,24 @@ trait Parsers {
750777
lazy val p = p0 // lazy argument
751778
val elems = new ListBuffer[T]
752779

753-
def continue(in: Input): ParseResult[List[T]] = {
780+
def continue(in: Input, failure: Option[Failure]): ParseResult[List[T]] = {
754781
val p0 = p // avoid repeatedly re-evaluating by-name parser
755-
@tailrec def applyp(in0: Input): ParseResult[List[T]] = p0(in0) match {
756-
case Success(x, rest) => elems += x ; applyp(rest)
782+
@tailrec def applyp(in0: Input, failure: Option[Failure]): ParseResult[List[T]] = p0(in0) match {
783+
case s @ Success(x, rest) =>
784+
val selectedFailure = selectLastFailure(s.lastFailure, failure)
785+
elems += x
786+
applyp(rest, selectedFailure)
757787
case e @ Error(_, _) => e // still have to propagate error
758-
case _ => Success(elems.toList, in0)
788+
case f: Failure =>
789+
val selectedFailure = selectLastFailure(failure, Some(f))
790+
Success(elems.toList, in0, selectedFailure)
759791
}
760792

761-
applyp(in)
793+
applyp(in, failure)
762794
}
763795

764796
first(in) match {
765-
case Success(x, rest) => elems += x ; continue(rest)
797+
case s @ Success(x, rest) => elems += x ; continue(rest, s.lastFailure)
766798
case ns: NoSuccess => ns
767799
}
768800
}
@@ -782,14 +814,14 @@ trait Parsers {
782814
val elems = new ListBuffer[T]
783815
val p0 = p // avoid repeatedly re-evaluating by-name parser
784816

785-
@tailrec def applyp(in0: Input): ParseResult[List[T]] =
786-
if (elems.length == num) Success(elems.toList, in0)
817+
@tailrec def applyp(in0: Input, failure: Option[Failure]): ParseResult[List[T]] =
818+
if (elems.length == num) Success(elems.toList, in0, failure)
787819
else p0(in0) match {
788-
case Success(x, rest) => elems += x ; applyp(rest)
820+
case s @ Success(x, rest) => elems += x ; applyp(rest, s.lastFailure)
789821
case ns: NoSuccess => ns
790822
}
791823

792-
applyp(in)
824+
applyp(in, None)
793825
}
794826

795827
/** A parser generator for non-empty repetitions.
@@ -871,7 +903,7 @@ trait Parsers {
871903
def not[T](p: => Parser[T]): Parser[Unit] = Parser { in =>
872904
p(in) match {
873905
case Success(_, _) => Failure("Expected failure", in)
874-
case _ => Success((), in)
906+
case _ => Success((), in, None)
875907
}
876908
}
877909

@@ -885,7 +917,7 @@ trait Parsers {
885917
*/
886918
def guard[T](p: => Parser[T]): Parser[T] = Parser { in =>
887919
p(in) match{
888-
case s@ Success(s1,_) => Success(s1, in)
920+
case s@ Success(s1,_) => Success(s1, in, s.lastFailure)
889921
case e => e
890922
}
891923
}
@@ -900,7 +932,7 @@ trait Parsers {
900932
*/
901933
def positioned[T <: Positional](p: => Parser[T]): Parser[T] = Parser { in =>
902934
p(in) match {
903-
case Success(t, in1) => Success(if (t.pos == NoPosition) t setPos in.pos else t, in1)
935+
case s @ Success(t, in1) => Success(if (t.pos == NoPosition) t setPos in.pos else t, in1, s.lastFailure)
904936
case ns: NoSuccess => ns
905937
}
906938
}
@@ -918,7 +950,10 @@ trait Parsers {
918950
def apply(in: Input) = p(in) match {
919951
case s @ Success(out, in1) =>
920952
if (in1.atEnd) s
921-
else Failure("end of input expected", in1)
953+
else s.lastFailure match {
954+
case Some(failure) => failure
955+
case _ => Failure("end of input expected", in1)
956+
}
922957
case ns => ns
923958
}
924959
}

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))

shared/src/test/scala/scala/util/parsing/combinator/JavaTokenParsersTest.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class JavaTokenParsersTest {
3333
def parseFailure(s: String, errorColPos: Int): Unit = {
3434
val parseResult = parseAll(ident, s)
3535
parseResult match {
36-
case Failure(_, next) =>
36+
case Failure(msg, next) =>
3737
val pos = next.pos
3838
assertEquals(1, pos.line)
3939
assertEquals(errorColPos, pos.column)
@@ -53,7 +53,7 @@ class JavaTokenParsersTest {
5353
parseFailure("with-s", 5)
5454
// we♥scala
5555
parseFailure("we\u2665scala", 3)
56-
parseFailure("with space", 6)
56+
parseFailure("with space", 5)
5757
}
5858

5959
@Test
@@ -75,7 +75,7 @@ class JavaTokenParsersTest {
7575
case e @ Failure(message, next) =>
7676
assertEquals(next.pos.line, 1)
7777
assertEquals(next.pos.column, 7)
78-
assert(message.endsWith(s"end of input expected"))
78+
assert(message.endsWith("string matching regex '(?i)AND' expected but 's' found"))
7979
case _ => sys.error(parseResult1.toString)
8080
}
8181

@@ -99,7 +99,7 @@ class JavaTokenParsersTest {
9999
case Failure(message, next) =>
100100
assertEquals(next.pos.line, 1)
101101
assertEquals(next.pos.column, 1)
102-
assert(message.endsWith(s"end of input expected"))
102+
assert(message.endsWith(s"identifier expected but '-' found"))
103103
case _ => sys.error(parseResult.toString)
104104
}
105105

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

+67
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,71 @@ class RegexParsersTest {
100100
val success = parseAll(twoWords, "first second").asInstanceOf[Success[(String, String)]]
101101
assertEquals(("second", "first"), success.get)
102102
}
103+
104+
@Test
105+
def hierarchicalRepSuccess: Unit = {
106+
case class Node(a: String, b: String)
107+
108+
object parser extends RegexParsers {
109+
def top: Parser[List[List[Node]]] = rep(nodes)
110+
def nodes: Parser[List[Node]] = "{" ~> rep(node) <~ "}"
111+
def node: Parser[Node] = "[a-z]+".r ~ ":" ~ "[a-z]+".r ^^ { case a ~ _ ~ b => Node(a, b) }
112+
}
113+
114+
import parser._
115+
116+
val success0 = parseAll(top, "{ a : b c : d}").get
117+
assertEquals(List(List(Node("a", "b"), Node("c", "d"))), success0)
118+
val success1 = parseAll(top, "{ a : b } { c : d }").get
119+
assertEquals(List(List(Node("a", "b")), List(Node("c", "d"))), success1)
120+
val success2 = parseAll(top, "{} {}").get
121+
assertEquals(List(List(), List()), success2)
122+
val success3 = parseAll(top, "").get
123+
assertEquals(List(), success3)
124+
}
125+
126+
@Test
127+
def hierarchicalRepFailure: Unit = {
128+
case class Node(a: String, b: String)
129+
130+
object parser extends RegexParsers {
131+
def top: Parser[List[List[Node]]] = rep(nodes)
132+
def nodes: Parser[List[Node]] = "{" ~> rep(node) <~ "}"
133+
def node: Parser[Node] = "[a-z]+".r ~ ":" ~ "[a-z]+".r ^^ { case a ~ _ ~ b => Node(a, b) }
134+
}
135+
136+
def test(src: String, expect: String, column: Int): Unit = {
137+
import parser._
138+
val result = parseAll(top, src)
139+
result match {
140+
case Failure(msg, next) =>
141+
assertEquals(column, next.pos.column)
142+
assertEquals(expect, msg)
143+
case _ =>
144+
sys.error(result.toString)
145+
}
146+
}
147+
148+
test("{ a : b c : }", "string matching regex '[a-z]+' expected but '}' found", 13)
149+
test("{", "'}' expected but end of source found", 2)
150+
}
151+
152+
@Test
153+
def ifElseTest: Unit = {
154+
object parser extends RegexParsers {
155+
def top: Parser[List[Unit]] = ifelse*
156+
def ifelse: Parser[Unit] = "IF" ~ condition ~ "THEN" ~ "1"~ "END" ^^ { _ => }
157+
def condition: Parser[String] = "TRUE" | "FALSE"
158+
}
159+
160+
import parser._
161+
val res = parseAll(top, "IF FALSE THEN 1 IF TRUE THEN 1 END")
162+
res match {
163+
case Failure(msg, next) =>
164+
assertEquals(17, next.pos.column)
165+
assertEquals("'END' expected but 'I' found", msg)
166+
case _ =>
167+
sys.error(res.toString)
168+
}
169+
}
103170
}

0 commit comments

Comments
 (0)