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:`*`")) } } }