diff --git a/src/main/scala/com/github/kmizu/scomb/SCombinator.scala b/src/main/scala/com/github/kmizu/scomb/SCombinator.scala
index 651cd5e..294b776 100644
--- a/src/main/scala/com/github/kmizu/scomb/SCombinator.scala
+++ b/src/main/scala/com/github/kmizu/scomb/SCombinator.scala
@@ -10,10 +10,10 @@ abstract class SCombinator[R] {self =>
protected var recent: Option[Failure] = None
- protected var fatal: Option[Error] = None
-
protected val locations: mutable.Map[Int, Location] = mutable.Map[Int, Location]()
+ private[this] val DefaultLabel: String = "fail"
+
lazy val space: Parser[String] = (
$(" ") | $("\t") | $("\b") | $("\f") | $("\r\n") | $("\r") | $("\n")
)
@@ -53,13 +53,12 @@ abstract class SCombinator[R] {self =>
repeat(next1) match {
case Success(result, next2) =>
Success(value::result, next2)
- case r@Error(_, _) => r
case r => throw new RuntimeException("cannot be " + r)
}
- case Failure(message, next) =>
+ case Failure(message, next, DefaultLabel) =>
Success(Nil, index)
- case f@Error(_, _) =>
- f
+ case failure@Failure(message, next, label) =>
+ failure
}
repeat(index) match {
case r@Success(_, _) => r
@@ -150,20 +149,6 @@ abstract class SCombinator[R] {self =>
}
}
- /**
- * Return the same parser except it replace the failure
- * message with message
- * @param message
- * @return
- */
- def replace(message: String): Parser[T] = parserOf{ index =>
- this(index) match {
- case r@Success(_, _) => r
- case Failure(_, index) => Failure(message, index)
- case Error(_, index) => Error(message, index)
- }
- }
-
/**
* Returns a sequential Parser consists of this
and rhs
.
*/
@@ -173,15 +158,11 @@ abstract class SCombinator[R] {self =>
right(next1) match {
case Success(value2, next2) =>
Success(new ~(value1, value2), next2)
- case failure@Failure(_, _) =>
+ case failure@Failure(_, _, _) =>
failure
- case fatal@Error(_, _) =>
- fatal
}
- case failure@Failure(message, next) =>
+ case failure@Failure(_, _, _) =>
failure
- case fatal@Error(_, _) =>
- fatal
}
}
@@ -191,13 +172,13 @@ abstract class SCombinator[R] {self =>
def ? : Parser[Option[T]] = parserOf{index =>
this(index) match {
case Success(v, i) => Success(Some(v), i)
- case Failure(message, i) => Success(None, index)
- case error@Error(_, _) => error
+ case Failure(message, i, DefaultLabel) => Success(None, index)
+ case failure@Failure(_, _, _) => failure
}
}
/**
- * Returns a alternation Parser.
+ * Returns an alternation Parser.
* It first trys this
.
* It trys rhs
iff this
failed.
* @param rhs the alternation
@@ -207,8 +188,21 @@ abstract class SCombinator[R] {self =>
def |[U >: T](rhs: Parser[U]): Parser[U] = parserOf{index =>
this(index) match {
case success@Success(_, _) => success
- case Failure(_, _) => rhs(index)
- case fatal@Error(_, _) => fatal
+ case Failure(_, _, DefaultLabel) => rhs(index)
+ case failure@Failure(_, _, _) => failure
+ }
+ }
+
+ /**
+ * Returns an alternation parser.
+ * It first trys this
.
+ * It trys rhs> iff this
failed and parameters are appropriate catchLabels.
+ */
+ def |~[U >: T](rhs: Parser[U], catchLabels: String*): Parser[U] = parserOf{index =>
+ this(index) match {
+ case success@Success(_, _) => success
+ case Failure(_, _, label) if catchLabels.contains(label) => rhs(index)
+ case failure@Failure(_, _, _) => failure
}
}
@@ -227,9 +221,7 @@ abstract class SCombinator[R] {self =>
s
case (_, s@Success(_, _)) =>
s
- case (f@Failure(_, _), _) =>
- f
- case (f@Error(_, _), _) =>
+ case (f@Failure(_, _, _), _) =>
f
}
}
@@ -248,38 +240,20 @@ abstract class SCombinator[R] {self =>
Success(value, next)
else
Failure(message, index)
- case failure@Failure(_, _) =>
+ case failure@Failure(_, _, _) =>
failure
- case fatal@Error(_, _) =>
- fatal
}
}
- /**
- * Replace the failure parser with the error parser.
- * It is used to suppress backtracks.
- * @param message the error message
- */
- def withErrorMessage(message: String): Parser[T] = parserOf{ index =>
+ def labeled(newLabel: String): Parser[T] = parserOf{index =>
this(index) match {
- case r@Success(_, _) => r
- case Failure(_, index) => Error(message, index)
- case r@Error(_, _) => r
+ case success@Success(_, _) => success
+ case Failure(message, index, oldLabel) =>
+ Failure(message, index, newLabel)
}
}
- /**
- * This method is same as `withErrorMessage()` except for
- * no message being specified.
- * @return
- */
- def commit: Parser[T] = parserOf{index =>
- this(index) match {
- case r@Success(_, _) => r
- case Failure(message, index) => Error(message, index)
- case r@Error(_, _) => r
- }
- }
+ def l(newLabel: String): Parser[T] = labeled(newLabel)
/**
* It is same as filter
@@ -293,8 +267,7 @@ abstract class SCombinator[R] {self =>
def map[U](function: T => U): Parser[U] = parserOf{index =>
this(index) match {
case Success(value, next) => Success(function(value), next)
- case failure@Failure(_, _) => failure
- case fatal@Error(_, _) => fatal
+ case failure@Failure(_, _, _) => failure
}
}
@@ -317,10 +290,8 @@ abstract class SCombinator[R] {self =>
this(index) match {
case Success(value, next) =>
function.apply(value).apply(next)
- case failure@Failure(_, _) =>
+ case failure@Failure(_, _, _) =>
failure
- case fatal@Error(_, _) =>
- fatal
}
}
}
@@ -358,11 +329,9 @@ abstract class SCombinator[R] {self =>
}
/**
- * A data constructor in the case parser failed. It can be recovered by |
operator
- * @param message the error message
- * @param index the next index
- */
- case class Failure(override val message: String, override val index: Int) extends ParseNonSuccess {
+ * A data constructor in the case parser failed with catch-labels. It can be recovered by |~
operator
+ */
+ case class Failure(override val message: String, override val index: Int, label: String) extends ParseNonSuccess {
self.recent match {
case None => self.recent = Some(this)
case Some(failure) if index >= failure.index => self.recent = Some(this)
@@ -370,14 +339,8 @@ abstract class SCombinator[R] {self =>
}
override def value: Option[Nothing] = None
}
-
- /**
- * A data constructor in the case parser failed. It must not be recovered.
- * @param message the error message
- * @param index the next index
- */
- case class Error(override val message: String, override val index: Int) extends ParseNonSuccess {
- override def value: Option[Nothing] = None
+ object Failure {
+ def apply(message: String, index: Int): Failure = Failure(message, index, DefaultLabel)
}
/**
@@ -441,8 +404,12 @@ abstract class SCombinator[R] {self =>
calculateLocations()
root(0) match {
case s@Success(_, _) => s
- case f@Failure(_, _) => recent.get
- case f@Error(_, _) => f
+ case f@Failure(_, _, label) =>
+ if(label == DefaultLabel) {
+ this.recent.get
+ } else {
+ f
+ }
}
}
@@ -454,10 +421,12 @@ abstract class SCombinator[R] {self =>
} else {
Result.Failure(locations(i), s"unconsumed input:`${current(i)}`")
}
- case Failure(message, index) =>
- Result.Failure(locations(index), message)
- case Error(message, index) =>
- Result.Failure(locations(index), message)
+ case Failure(message, index, label) =>
+ if(label == DefaultLabel) {
+ Result.Failure(locations(index), message)
+ } else {
+ Result.Failure(locations(index), s"${message} in <${label}>")
+ }
}
}
@@ -485,7 +454,7 @@ abstract class SCombinator[R] {self =>
val substring = current(index)
literal.findPrefixOf(substring) match {
case Some(prefix) => Success(prefix, index + prefix.length)
- case None => Failure(s"expected:`${literal}`", index)
+ case None => Failure(s"expected:`${literal}` actual: `${current(index)(0)}`", index)
}
}
}
@@ -508,7 +477,7 @@ abstract class SCombinator[R] {self =>
} else if(current(index).startsWith(literal)) {
Success(literal, index + literal.length)
} else {
- Failure(s"expected:`${literal}`", index)
+ Failure(s"expected:`${literal}` actual:`${current(index)(0)}`", index)
}
}
@@ -529,8 +498,7 @@ abstract class SCombinator[R] {self =>
final def not(parser: Parser[Any]): Parser[Any] = parserOf{index =>
parser(index) match {
case Success(_, _) => Failure("not expected", index)
- case Failure(_, _) => Success("", index)
- case f@Error(_, _) => f
+ case Failure(_, _, _) => Success("", index)
}
}
diff --git a/src/test/scala/com/github/kmizu/scomb/JsonSpec.scala b/src/test/scala/com/github/kmizu/scomb/JsonSpec.scala
index a41366a..ce72660 100644
--- a/src/test/scala/com/github/kmizu/scomb/JsonSpec.scala
+++ b/src/test/scala/com/github/kmizu/scomb/JsonSpec.scala
@@ -44,25 +44,25 @@ class JsonSpec extends FunSpec with DiagrammedAssertions {
lazy val jobject: P[JValue] = rule{for {
_ <- LBRACE
properties <- pair.repeat0By(COMMA)
- _ <- RBRACE
+ _ <- RBRACE.l("RBRACE")
} yield JObject(properties:_*)}
lazy val pair: P[(String, JValue)] = rule{for {
key <- string
- _ <- COLON
+ _ <- COLON.l("COLON")
value <- jvalue
} yield (key, value)}
lazy val jarray: P[JValue] = rule{for {
_ <- LBRACKET
elements <- jvalue.repeat0By(COMMA)
- _ <- RBRACKET
+ _ <- RBRACKET.l("rbracket")
} yield JArray(elements:_*)}
lazy val string: Parser[String] = rule{for {
_ <- $("\"")
contents <- ($("\\") ~ any ^^ { case _ ~ ch => escape(ch).toString} | except('"')).*
- _ <- $("\"")
+ _ <- $("\"").l("double quote")
_ <- space.*
} yield contents.mkString}
@@ -139,7 +139,15 @@ class JsonSpec extends FunSpec with DiagrammedAssertions {
it("cannot parse incorrect object") {
val failure = parse("{").asInstanceOf[Result.Failure]
assert(Location(1, 2) == failure.location)
- assert("""expected:`}` actual:EOF""" == failure.message)
+ assert("""expected:`}` actual:EOF in """ == failure.message)
+ }
+ }
+
+ describe("The JsonParser") {
+ it("cannot parse incorrect array") {
+ val failure = parse("[1, 2, ]").asInstanceOf[Result.Failure]
+ assert(Location(1, 6) == failure.location)
+ assert("""expected:`]` actual:`,` in """ == failure.message)
}
}
}
diff --git a/src/test/scala/com/github/kmizu/scomb/RegularExpressionSpec.scala b/src/test/scala/com/github/kmizu/scomb/RegularExpressionSpec.scala
index 259817a..3323885 100644
--- a/src/test/scala/com/github/kmizu/scomb/RegularExpressionSpec.scala
+++ b/src/test/scala/com/github/kmizu/scomb/RegularExpressionSpec.scala
@@ -70,7 +70,7 @@ class RegularExpressionSpec extends FunSpec with DiagrammedAssertions {
input = "(1|9)*"
assert(parse(input) == Result.Success(Repeat(Choice(Value('1'), Value('9')))))
input = "*"
- assert(parse(input) == Result.Failure(Location(1, 1), "expected:`\\`"))
+ assert(parse(input) == Result.Failure(Location(1, 1), "expected:`\\` actual:`*`"))
}
}
}