diff --git a/parsley/shared/src/main/scala/parsley/errors/combinator.scala b/parsley/shared/src/main/scala/parsley/errors/combinator.scala index 1dfd725b4..1357299a8 100644 --- a/parsley/shared/src/main/scala/parsley/errors/combinator.scala +++ b/parsley/shared/src/main/scala/parsley/errors/combinator.scala @@ -112,7 +112,7 @@ object combinator { * @since 3.1.0 * @group adj */ - def amend[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorAmend(p.internal)) + def amend[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorAmend(p.internal, partial = false)) /** This combinator prevents the action of any enclosing `amend` on the errors generated by the given * parser. @@ -144,6 +144,8 @@ object combinator { // TODO: Documentation and testing ahead of future release private [parsley] def dislodge[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorDislodge(p.internal)) private [parsley] def amendThenDislodge[A](p: Parsley[A]): Parsley[A] = dislodge(amend(p)) + private [parsley] def partialAmend[A](p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ErrorAmend(p.internal, partial = true)) + private [parsley] def partialAmendThenDislodge[A](p: Parsley[A]): Parsley[A] = dislodge(partialAmend(p)) /** This combinator marks any errors within the given parser as being ''lexical errors''. * diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/ErrorEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/ErrorEmbedding.scala index fca356166..9ec2deaf1 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/ErrorEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/ErrorEmbedding.scala @@ -36,10 +36,10 @@ private [deepembedding] final class ErrorExplain[A](val p: StrictParsley[A], rea // $COVERAGE-ON$ } -private [deepembedding] final class ErrorAmend[A](val p: StrictParsley[A]) extends ScopedUnaryWithState[A, A](false) { +private [deepembedding] final class ErrorAmend[A](val p: StrictParsley[A], partial: Boolean) extends ScopedUnaryWithState[A, A](false) { override val instr: instructions.Instr = instructions.PopHandlerAndState override def instrNeedsLabel: Boolean = false - override def handlerLabel(state: CodeGenState): Int = state.getLabel(instructions.AmendAndFail) + override def handlerLabel(state: CodeGenState): Int = state.getLabel(instructions.AmendAndFail(partial)) // $COVERAGE-OFF$ final override def pretty(p: String): String = s"amend($p)" // $COVERAGE-ON$ diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala index 2b1cdc184..517c10369 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala @@ -12,8 +12,8 @@ private [parsley] final class ErrorExplain[A](p: LazyParsley[A], reason: String) override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorExplain(p, reason) } -private [parsley] final class ErrorAmend[A](p: LazyParsley[A]) extends Unary[A, A](p) { - override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorAmend(p) +private [parsley] final class ErrorAmend[A](p: LazyParsley[A], partial: Boolean) extends Unary[A, A](p) { + override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorAmend(p, partial) } private [parsley] final class ErrorEntrench[A](p: LazyParsley[A]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorEntrench(p) diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/ErrorInstrs.scala b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/ErrorInstrs.scala index f9d119dde..bae8c05ae 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/ErrorInstrs.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/ErrorInstrs.scala @@ -86,10 +86,10 @@ private [internal] class ApplyReasonAndFail(reason: String) extends Instr { // $COVERAGE-ON$ } -private [internal] object AmendAndFail extends Instr { +private [internal] class AmendAndFail private (partial: Boolean) extends Instr { override def apply(ctx: Context): Unit = { ensureHandlerInstruction(ctx) - ctx.errs.error = ctx.errs.error.amend(ctx.states.offset, ctx.states.line, ctx.states.col) + ctx.errs.error = ctx.errs.error.amend(if (partial) ctx.offset else ctx.states.offset, ctx.states.line, ctx.states.col) ctx.states = ctx.states.tail ctx.fail() } @@ -98,6 +98,11 @@ private [internal] object AmendAndFail extends Instr { override def toString: String = "AmendAndFail" // $COVERAGE-ON$ } +private [internal] object AmendAndFail { + private [this] val partial = new AmendAndFail(partial = true) + private [this] val full = new AmendAndFail(partial = false) + def apply(partial: Boolean): AmendAndFail = if (partial) this.partial else this.full +} private [internal] object EntrenchAndFail extends Instr { override def apply(ctx: Context): Unit = { diff --git a/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplTyped.scala b/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplTyped.scala index f2dfebf67..01a7aaeef 100644 --- a/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplTyped.scala +++ b/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplTyped.scala @@ -9,6 +9,14 @@ private [parsley] object FilterOps { if (full) combinator.amendThenDislodge(p) else p } + def amendThenDislodgeOrPartial[A](full: Boolean)(p: Parsley[A]): Parsley[A] = { + if (full) combinator.amendThenDislodge(p) + else combinator.partialAmendThenDislodge(p) + } + def entrench[A](full: Boolean)(p: Parsley[A]): Parsley[A] = { + if (full) combinator.entrench(p) + else p + } } /** This trait, and its subclasses, can be used to configure how filters should be used within the `Lexer`. @@ -46,12 +54,12 @@ abstract class SpecialisedMessage[A](fullAmend: Boolean) extends SpecialisedFilt def message(x: A): Seq[String] private [parsley] final override def filter(p: Parsley[A])(f: A => Boolean) = FilterOps.amendThenDislodge(fullAmend) { - p.guardAgainst { + FilterOps.entrench(fullAmend)(p).guardAgainst { case x if !f(x) => message(x) } } private [parsley] final override def collect[B](p: Parsley[A])(f: PartialFunction[A, B]) = FilterOps.amendThenDislodge(fullAmend) { - p.collectMsg(message(_))(f) + FilterOps.entrench(fullAmend)(p).collectMsg(message(_))(f) } private [parsley] final override def injectLeft[B] = new SpecialisedMessage[Either[A, B]](fullAmend) { def message(xy: Either[A, B]) = { @@ -80,7 +88,7 @@ abstract class Unexpected[A](fullAmend: Boolean) extends VanillaFilterConfig[A] def unexpected(x: A): String private [parsley] final override def filter(p: Parsley[A])(f: A => Boolean) = FilterOps.amendThenDislodge(fullAmend) { - p.unexpectedWhen { + FilterOps.entrench(fullAmend)(p).unexpectedWhen { case x if !f(x) => unexpected(x) } } @@ -111,7 +119,7 @@ abstract class Because[A](fullAmend: Boolean) extends VanillaFilterConfig[A] { s def reason(x: A): String private [parsley] final override def filter(p: Parsley[A])(f: A => Boolean) = FilterOps.amendThenDislodge(fullAmend) { - p.filterOut { + FilterOps.entrench(fullAmend)(p).filterOut { case x if !f(x) => reason(x) } } @@ -146,12 +154,11 @@ abstract class UnexpectedBecause[A](fullAmend: Boolean) extends VanillaFilterCon */ def reason(x: A): String - private [parsley] final override def filter(p: Parsley[A])(f: A => Boolean) = FilterOps.amendThenDislodge(fullAmend) { - combinator.amendThenDislodge { - position.internalOffsetSpan(combinator.entrench(p)).flatMap { case (os, x, oe) => - if (f(x)) combinator.unexpected(oe - os, this.unexpected(x)).explain(reason(x)) - else pure(x) - } + // TODO: factor this combinator out with the "Great Move" in 4.2 + private [parsley] final override def filter(p: Parsley[A])(f: A => Boolean) = FilterOps.amendThenDislodgeOrPartial(fullAmend) { + position.internalOffsetSpan(combinator.entrench(p)).flatMap { case (os, x, oe) => + if (f(x)) combinator.unexpected(oe - os, this.unexpected(x)).explain(reason(x)) + else pure(x) } } private [parsley] final override def injectLeft[B] = new UnexpectedBecause[Either[A, B]](fullAmend) { diff --git a/parsley/shared/src/main/scala/parsley/token/errors/VerifiedAndPreventativeErrors.scala b/parsley/shared/src/main/scala/parsley/token/errors/VerifiedAndPreventativeErrors.scala index 4b293bfa5..d05d9abf9 100644 --- a/parsley/shared/src/main/scala/parsley/token/errors/VerifiedAndPreventativeErrors.scala +++ b/parsley/shared/src/main/scala/parsley/token/errors/VerifiedAndPreventativeErrors.scala @@ -23,8 +23,9 @@ object UnexpectedZeroDot { def apply(unexpected: String): PreventDotIsZeroConfig = new UnexpectedZeroDot(unexpected) } +// TODO: factor this combinator out with the "Great Move" in 4.2 private final class UnexpectedZeroDotWithReason private (unexpected: String, reason: String) extends PreventDotIsZeroConfig { - private [token] override def apply(p: Parsley[Boolean]): Parsley[Boolean] = combinator.amendThenDislodge { + private [token] override def apply(p: Parsley[Boolean]): Parsley[Boolean] = combinator.partialAmendThenDislodge { position.internalOffsetSpan(combinator.entrench(p)).flatMap { case (os, x, oe) => if (x) combinator.unexpected(oe - os, unexpected).explain(reason) else pure(x)