From eb6285277c2efb06ffd62f0263daa63074532047 Mon Sep 17 00:00:00 2001 From: Jamie Willis Date: Sun, 21 May 2023 21:32:16 +0100 Subject: [PATCH] Added CaretWidth datatype, which will be used to deal with fail that can have caret width affected --- .../scala/parsley/errors/combinator.scala | 6 ++-- .../singletons/ErrorEmbedding.scala | 5 +-- .../parsley/internal/errors/CaretWidth.scala | 10 ++++++ .../parsley/internal/machine/Context.scala | 4 +-- .../machine/errors/DefuncBuilders.scala | 4 +-- .../internal/machine/errors/DefuncError.scala | 7 ++-- .../machine/instructions/ErrorInstrs.scala | 6 ++-- .../instructions/IntrinsicInstrs.scala | 4 +-- .../token/errors/ConfigImplTyped.scala | 4 +-- .../machine/errors/DefuncErrorTests.scala | 35 ++++++++++--------- 10 files changed, 50 insertions(+), 35 deletions(-) create mode 100644 parsley/shared/src/main/scala/parsley/internal/errors/CaretWidth.scala diff --git a/parsley/shared/src/main/scala/parsley/errors/combinator.scala b/parsley/shared/src/main/scala/parsley/errors/combinator.scala index 33f0e3aa0..1f8bbe008 100644 --- a/parsley/shared/src/main/scala/parsley/errors/combinator.scala +++ b/parsley/shared/src/main/scala/parsley/errors/combinator.scala @@ -6,6 +6,7 @@ package parsley.errors import parsley.Parsley import parsley.internal.deepembedding.{frontend, singletons} +import parsley.internal.errors.{CaretWidth, FlexibleCaret, RigidCaret} /** This module contains combinators that can be used to directly influence error messages of parsers. * @@ -52,7 +53,7 @@ object combinator { * @since 3.0.0 * @group fail */ - def fail(msg0: String, msgs: String*): Parsley[Nothing] = fail(1, msg0, msgs: _*) + def fail(msg0: String, msgs: String*): Parsley[Nothing] = fail(new FlexibleCaret(1), msg0, msgs: _*) /** This combinator consumes no input and fails immediately with the given error messages. * @@ -70,7 +71,8 @@ object combinator { * @since 4.0.0 * @group fail */ - def fail(caretWidth: Int, msg0: String, msgs: String*): Parsley[Nothing] = new Parsley(new singletons.Fail(caretWidth, (msg0 +: msgs): _*)) + def fail(caretWidth: Int, msg0: String, msgs: String*): Parsley[Nothing] = fail(new RigidCaret(caretWidth), msg0, msgs: _*) + private def fail(caretWidth: CaretWidth, msg0: String, msgs: String*): Parsley[Nothing] = new Parsley(new singletons.Fail(caretWidth, (msg0 +: msgs): _*)) /** This combinator consumes no input and fails immediately, setting the unexpected component * to the given item. diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala index 90c0e6a2c..7a30b83a6 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala @@ -4,13 +4,14 @@ package parsley.internal.deepembedding.singletons import parsley.internal.deepembedding.backend.MZero +import parsley.internal.errors.CaretWidth import parsley.internal.machine.instructions -private [parsley] final class Fail(width: Int, msgs: String*) extends Singleton[Nothing] with MZero { +private [parsley] final class Fail(width: CaretWidth, msgs: String*) extends Singleton[Nothing] with MZero { // $COVERAGE-OFF$ override def pretty: String = s"fail(${msgs.mkString(", ")})" // $COVERAGE-ON$ - override def instr: instructions.Instr = new instructions.Fail(width: Int, msgs: _*) + override def instr: instructions.Instr = new instructions.Fail(width, msgs: _*) } private [parsley] final class Unexpected(msg: String, width: Int) extends Singleton[Nothing] with MZero { diff --git a/parsley/shared/src/main/scala/parsley/internal/errors/CaretWidth.scala b/parsley/shared/src/main/scala/parsley/internal/errors/CaretWidth.scala new file mode 100644 index 000000000..4ac5a5d91 --- /dev/null +++ b/parsley/shared/src/main/scala/parsley/internal/errors/CaretWidth.scala @@ -0,0 +1,10 @@ +/* SPDX-FileCopyrightText: © 2023 Parsley Contributors + * SPDX-License-Identifier: BSD-3-Clause + */ +package parsley.internal.errors + +private [parsley] sealed abstract class CaretWidth { + def width: Int +} +private [parsley] class FlexibleCaret(val width: Int) extends CaretWidth +private [parsley] class RigidCaret(val width: Int) extends CaretWidth diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/Context.scala b/parsley/shared/src/main/scala/parsley/internal/machine/Context.scala index d50a9e857..2a7647172 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/Context.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/Context.scala @@ -11,7 +11,7 @@ import parsley.Result import parsley.Success import parsley.errors.ErrorBuilder -import parsley.internal.errors.{ExpectItem, LineBuilder, UnexpectDesc} +import parsley.internal.errors.{CaretWidth, ExpectItem, LineBuilder, UnexpectDesc} import parsley.internal.machine.errors.{ ClassicExpectedError, ClassicFancyError, ClassicUnexpectedError, DefuncError, DefuncHints, EmptyHints, ErrorItemBuilder, MultiExpectedError @@ -200,7 +200,7 @@ private [parsley] final class Context(private [machine] var instrs: Array[Instr] } } - private [machine] def failWithMessage(caretWidth: Int, msgs: String*): Unit = this.fail(new ClassicFancyError(offset, line, col, caretWidth, msgs: _*)) + private [machine] def failWithMessage(caretWidth: CaretWidth, msgs: String*): Unit = this.fail(new ClassicFancyError(offset, line, col, caretWidth, msgs: _*)) private [machine] def unexpectedFail(expected: Option[ExpectItem], unexpected: UnexpectDesc): Unit = { this.fail(new ClassicUnexpectedError(offset, line, col, expected, unexpected)) } diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncBuilders.scala b/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncBuilders.scala index 37b8721c2..9c30d08f7 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncBuilders.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncBuilders.scala @@ -5,7 +5,7 @@ package parsley.internal.machine.errors import scala.collection.mutable -import parsley.internal.errors.{EndOfInput, ExpectItem, FancyError, TrivialError, UnexpectItem} +import parsley.internal.errors.{EndOfInput, ExpectItem, FancyError, TrivialError, UnexpectDesc, UnexpectItem} import TrivialErrorBuilder.{BuilderUnexpectItem, NoItem, Other, Raw} @@ -48,7 +48,7 @@ private [errors] final class TrivialErrorBuilder(offset: Int, outOfRange: Boolea * * @param item */ - def updateUnexpected(item: UnexpectItem): Unit = updateUnexpected(new Other(item)) + def updateUnexpected(item: UnexpectDesc): Unit = updateUnexpected(new Other(item)) /** Updates the unexpected (but empty) token generated by the error, so long as no other error item * has been used. * @param width diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncError.scala b/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncError.scala index c22f0ef10..26152d56e 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncError.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncError.scala @@ -5,7 +5,7 @@ package parsley.internal.machine.errors import parsley.XAssert._ -import parsley.internal.errors.{ExpectDesc, ExpectItem, FancyError, ParseError, TrivialError, UnexpectDesc} +import parsley.internal.errors.{CaretWidth, ExpectDesc, ExpectItem, FancyError, ParseError, TrivialError, UnexpectDesc} // This file contains the defunctionalised forms of the error messages. // Essentially, whenever an error is created in the machine, it should make use of one of @@ -293,12 +293,13 @@ private [parsley] final class ClassicUnexpectedError(val offset: Int, val line: builder.updateUnexpected(unexpected) } } -private [parsley] final class ClassicFancyError(val offset: Int, val line: Int, val col: Int, caretWidth: Int, val msgs: String*) extends FancyDefuncError { + +private [parsley] final class ClassicFancyError(val offset: Int, val line: Int, val col: Int, caretWidth: CaretWidth, val msgs: String*) extends FancyDefuncError { override final val flags = DefuncError.ExpectedEmptyMask override def makeFancy(builder: FancyErrorBuilder): Unit = { builder.pos_=(line, col) builder ++= msgs - builder.updateCaretWidth(caretWidth) + builder.updateCaretWidth(caretWidth.width) } } private [parsley] final class EmptyError(val offset: Int, val line: Int, val col: Int, val unexpectedWidth: Int) extends BaseError { 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 f490f4913..9f37cf2c3 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 @@ -3,7 +3,7 @@ */ package parsley.internal.machine.instructions -import parsley.internal.errors.UnexpectDesc +import parsley.internal.errors.{CaretWidth, RigidCaret, UnexpectDesc} import parsley.internal.machine.Context import parsley.internal.machine.XAssert._ import parsley.internal.machine.errors.{ClassicExpectedError, ClassicExpectedErrorWithReason, ClassicFancyError, EmptyError} @@ -141,7 +141,7 @@ private [internal] object SetLexicalAndFail extends Instr { // $COVERAGE-ON$ } -private [internal] final class Fail(width: Int, msgs: String*) extends Instr { +private [internal] final class Fail(width: CaretWidth, msgs: String*) extends Instr { override def apply(ctx: Context): Unit = { ensureRegularInstruction(ctx) ctx.failWithMessage(width, msgs: _*) @@ -177,7 +177,7 @@ private [internal] class MakeVerifiedError private (msggen: Either[Any => Seq[St val caretWidth = ctx.offset - state.offset val x = ctx.stack.upeek val err = msggen match { - case Left(f) => new ClassicFancyError(state.offset, state.line, state.col, caretWidth, f(x): _*) + case Left(f) => new ClassicFancyError(state.offset, state.line, state.col, new RigidCaret(caretWidth), f(x): _*) case Right(Some(f)) => new ClassicExpectedErrorWithReason(state.offset, state.line, state.col, None, f(x), caretWidth) case Right(None) => new ClassicExpectedError(state.offset, state.line, state.col, None, caretWidth) } diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/IntrinsicInstrs.scala b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/IntrinsicInstrs.scala index b61b1b866..768dbe110 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/IntrinsicInstrs.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/IntrinsicInstrs.scala @@ -7,7 +7,7 @@ import scala.annotation.tailrec import parsley.token.errors.LabelConfig -import parsley.internal.errors.{EndOfInput, ExpectDesc, ExpectItem, UnexpectDesc} +import parsley.internal.errors.{EndOfInput, ExpectDesc, ExpectItem, RigidCaret, UnexpectDesc} import parsley.internal.machine.Context import parsley.internal.machine.XAssert._ import parsley.internal.machine.errors.{ClassicFancyError, ClassicUnexpectedError, DefuncError, EmptyError, EmptyErrorWithReason} @@ -248,7 +248,7 @@ private [internal] final class GuardAgainst(pred: PartialFunction[Any, Seq[Strin if (pred.isDefinedAt(ctx.stack.upeek)) { val state = ctx.states val caretWidth = ctx.offset - state.offset - ctx.fail(new ClassicFancyError(state.offset, state.line, state.col, caretWidth, pred(ctx.stack.upop()): _*)) + ctx.fail(new ClassicFancyError(state.offset, state.line, state.col, new RigidCaret(caretWidth), pred(ctx.stack.upop()): _*)) } else ctx.inc() ctx.states = ctx.states.tail 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 84af7a6a2..b6392da9a 100644 --- a/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplTyped.scala +++ b/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplTyped.scala @@ -7,7 +7,7 @@ import parsley.Parsley import parsley.XCompat.unused import parsley.errors.combinator, combinator.ErrorMethods -import parsley.internal.errors.UnexpectDesc +import parsley.internal.errors.{RigidCaret, UnexpectDesc} import parsley.internal.machine.errors.{ClassicFancyError, ClassicUnexpectedError, DefuncError, EmptyError, EmptyErrorWithReason} /** This trait, and its subclasses, can be used to configure how filters should be used within the `Lexer`. @@ -53,7 +53,7 @@ abstract class SpecialisedMessage[A] extends SpecialisedFilterConfig[A] { self = } private [parsley] final override def collect[B](p: Parsley[A])(f: PartialFunction[A, B]) = p.collectMsg(message(_))(f) private [parsley] final override def mkError(offset: Int, line: Int, col: Int, caretWidth: Int, x: A): DefuncError = { - new ClassicFancyError(offset, line, col, caretWidth, message(x): _*) + new ClassicFancyError(offset, line, col, new RigidCaret(caretWidth), message(x): _*) } // $COVERAGE-OFF$ diff --git a/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncErrorTests.scala b/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncErrorTests.scala index fc0243ae3..4587e5270 100644 --- a/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncErrorTests.scala +++ b/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncErrorTests.scala @@ -5,7 +5,7 @@ package parsley.internal.machine.errors import parsley.ParsleyTest -import parsley.internal.errors.{TrivialError, FancyError, ExpectRaw, ExpectDesc, EndOfInput, UnexpectDesc} +import parsley.internal.errors.{TrivialError, FancyError, ExpectRaw, ExpectDesc, EndOfInput, RigidCaret, UnexpectDesc} import MockedBuilders.mockedErrorItemBuilder @@ -41,12 +41,12 @@ class DefuncErrorTests extends ParsleyTest { } "ClassicFancyError" should "evaluate to FancyError" in { - val err = new ClassicFancyError(0, 0, 0, 1, "") + val err = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "") err.isTrivialError shouldBe false err.asParseError shouldBe a [FancyError] } it should "always be empty" in { - new ClassicFancyError(0, 0, 0, 1, "hi").isExpectedEmpty shouldBe true + new ClassicFancyError(0, 0, 0, new RigidCaret(1), "hi").isExpectedEmpty shouldBe true } "EmptyError" should "evaluate to TrivialError" in { @@ -77,36 +77,37 @@ class DefuncErrorTests extends ParsleyTest { err.asParseError shouldBe a [TrivialError] } they should "be a trivial error if one trivial child is further than the other fancy child" in { - val err1 = new EmptyError(1, 0, 0, 0).merge(new ClassicFancyError(0, 0, 0, 1, "")) + val err1 = new EmptyError(1, 0, 0, 0).merge(new ClassicFancyError(0, 0, 0, new RigidCaret(1), "")) err1.isTrivialError shouldBe true err1.asParseError shouldBe a [TrivialError] - val err2 = new ClassicFancyError(0, 0, 0, 1, "").merge(new EmptyError(1, 0, 0, 0)) + val err2 = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").merge(new EmptyError(1, 0, 0, 0)) err2.isTrivialError shouldBe true err2.asParseError shouldBe a [TrivialError] } + // TODO: tests for flexible carets they should "be a fancy error in any other case" in { - val err1 = new EmptyError(0, 0, 0, 0).merge(new ClassicFancyError(0, 0, 0, 1, "")) + val err1 = new EmptyError(0, 0, 0, 0).merge(new ClassicFancyError(0, 0, 0, new RigidCaret(1), "")) err1.isTrivialError shouldBe false err1.asParseError shouldBe a [FancyError] - val err2 = new ClassicFancyError(0, 0, 0, 1, "").merge(new EmptyError(0, 0, 0, 0)) + val err2 = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").merge(new EmptyError(0, 0, 0, 0)) err2.isTrivialError shouldBe false err2.asParseError shouldBe a [FancyError] - val err3 = new EmptyError(0, 0, 0, 0).merge(new ClassicFancyError(1, 0, 0, 1, "")) + val err3 = new EmptyError(0, 0, 0, 0).merge(new ClassicFancyError(1, 0, 0, new RigidCaret(1), "")) err3.isTrivialError shouldBe false err3.asParseError shouldBe a [FancyError] - val err4 = new ClassicFancyError(1, 0, 0, 1, "").merge(new EmptyError(0, 0, 0, 0)) + val err4 = new ClassicFancyError(1, 0, 0, new RigidCaret(1), "").merge(new EmptyError(0, 0, 0, 0)) err4.isTrivialError shouldBe false err4.asParseError shouldBe a [FancyError] - val err5 = new ClassicFancyError(0, 0, 0, 1, "").merge(new ClassicFancyError(0, 0, 0, 1, "")) + val err5 = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").merge(new ClassicFancyError(0, 0, 0, new RigidCaret(1), "")) err5.isTrivialError shouldBe false err5.asParseError shouldBe a [FancyError] - val err6 = new ClassicFancyError(1, 0, 0, 1, "").merge(new ClassicFancyError(0, 0, 0, 1, "")) + val err6 = new ClassicFancyError(1, 0, 0, new RigidCaret(1), "").merge(new ClassicFancyError(0, 0, 0, new RigidCaret(1), "")) err6.isTrivialError shouldBe false err6.asParseError shouldBe a [FancyError] } @@ -127,7 +128,7 @@ class DefuncErrorTests extends ParsleyTest { err.asParseError shouldBe a [TrivialError] } it should "support fancy errors as not trivial" in { - val err = new ClassicFancyError(0, 0, 0, 1, "").withHints(EmptyHints) + val err = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").withHints(EmptyHints) err.isTrivialError shouldBe false err.asParseError shouldBe a [FancyError] } @@ -142,7 +143,7 @@ class DefuncErrorTests extends ParsleyTest { err.asParseError shouldBe a [TrivialError] } it should "support fancy errors as not trivial" in { - val err = new ClassicFancyError(0, 0, 0, 1, "").withReason("") + val err = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").withReason("") err.isTrivialError shouldBe false err.asParseError shouldBe a [FancyError] } @@ -157,7 +158,7 @@ class DefuncErrorTests extends ParsleyTest { err.asParseError shouldBe a [TrivialError] } it should "support fancy errors as not trivial" in { - val err = new ClassicFancyError(0, 0, 0, 1, "").label("", 0) + val err = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").label("", 0) err.isTrivialError shouldBe false err.asParseError shouldBe a [FancyError] } @@ -182,7 +183,7 @@ class DefuncErrorTests extends ParsleyTest { errOut.offset shouldBe 10 } it should "work for fancy errors too" in { - val err = new ClassicFancyError(0, 0, 0, 1, "").amend(10, 10, 10) + val err = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").amend(10, 10, 10) val errOut = err.asParseError errOut.col shouldBe 10 errOut.line shouldBe 10 @@ -198,7 +199,7 @@ class DefuncErrorTests extends ParsleyTest { errOut.offset shouldBe 0 } it should "work for fancy errors too" in { - val err = new ClassicFancyError(0, 0, 0, 1, "").entrench.amend(10, 10, 10) + val err = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").entrench.amend(10, 10, 10) val errOut = err.asParseError err.entrenched shouldBe true errOut.col shouldBe 0 @@ -216,7 +217,7 @@ class DefuncErrorTests extends ParsleyTest { err2.offset shouldBe 10 } it should "work for fancy errors too" in { - val err = new ClassicFancyError(0, 0, 0, 1, "").entrench + val err = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").entrench require(err.entrenched) err.dislodge.entrenched shouldBe false val err2 = err.dislodge.amend(10, 10, 10).asParseError