diff --git a/parsley/shared/src/main/scala/parsley/errors/combinator.scala b/parsley/shared/src/main/scala/parsley/errors/combinator.scala index 33f0e3aa0..8d7b21693 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. @@ -83,7 +85,7 @@ object combinator { * @return a parser that fails producing an error with `item` as the unexpected token. * @group fail */ - def unexpected(item: String): Parsley[Nothing] = unexpected(1, item) + def unexpected(item: String): Parsley[Nothing] = unexpected(new FlexibleCaret(1), item) /** This combinator consumes no input and fails immediately, setting the unexpected component * to the given item. @@ -97,7 +99,8 @@ object combinator { * @return a parser that fails producing an error with `item` as the unexpected token. * @group fail */ - def unexpected(caretWidth: Int, item: String): Parsley[Nothing] = new Parsley(new singletons.Unexpected(item, caretWidth)) + def unexpected(caretWidth: Int, item: String): Parsley[Nothing] = unexpected(new RigidCaret(caretWidth), item) + private def unexpected(caretWidth: CaretWidth, item: String): Parsley[Nothing] = new Parsley(new singletons.Unexpected(item, caretWidth)) /** This combinator adjusts any error messages generated by the given parser so that they * occur at the position recorded on entry to this combinator (effectively as if no 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..fa3e35153 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,16 +4,17 @@ 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 { +private [parsley] final class Unexpected(msg: String, width: CaretWidth) extends Singleton[Nothing] with MZero { // $COVERAGE-OFF$ override def pretty: String = s"unexpected($msg)" // $COVERAGE-ON$ 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..4afbd54c8 --- /dev/null +++ b/parsley/shared/src/main/scala/parsley/internal/errors/CaretWidth.scala @@ -0,0 +1,15 @@ +/* SPDX-FileCopyrightText: © 2023 Parsley Contributors + * SPDX-License-Identifier: BSD-3-Clause + */ +package parsley.internal.errors + +private [parsley] sealed abstract class CaretWidth { + def width: Int + def isFlexible: Boolean +} +private [parsley] class FlexibleCaret(val width: Int) extends CaretWidth { + def isFlexible: Boolean = true +} +private [parsley] class RigidCaret(val width: Int) extends CaretWidth { + def isFlexible: Boolean = false +} diff --git a/parsley/shared/src/main/scala/parsley/internal/errors/ErrorItem.scala b/parsley/shared/src/main/scala/parsley/internal/errors/ErrorItem.scala index 912c2aab7..1169b3f19 100644 --- a/parsley/shared/src/main/scala/parsley/internal/errors/ErrorItem.scala +++ b/parsley/shared/src/main/scala/parsley/internal/errors/ErrorItem.scala @@ -7,17 +7,19 @@ import parsley.XAssert._ import parsley.errors, errors.{ErrorBuilder, Token, TokenSpan} private [internal] sealed abstract class ErrorItem -private [internal] sealed trait UnexpectItem extends ErrorItem { +private [internal] sealed abstract class UnexpectItem extends ErrorItem { private [internal] def formatUnexpect(lexicalError: Boolean)(implicit builder: ErrorBuilder[_]): (builder.Item, TokenSpan) private [internal] def higherPriority(other: UnexpectItem): Boolean protected [errors] def lowerThanRaw(other: UnexpectRaw): Boolean protected [errors] def lowerThanDesc(other: UnexpectDesc): Boolean + private [internal] def isFlexible: Boolean + private [internal] def widen(caret: Int): UnexpectItem } private [parsley] sealed trait ExpectItem extends ErrorItem { private [internal] def formatExpect(implicit builder: ErrorBuilder[_]): builder.Item } -private [internal] final case class UnexpectRaw(cs: Iterable[Char], amountOfInputParserWanted: Int) extends UnexpectItem { +private [internal] final case class UnexpectRaw(val cs: Iterable[Char], val amountOfInputParserWanted: Int) extends UnexpectItem { assert(cs.nonEmpty, "we promise that unexpectedToken never receives empty input") private [internal] def formatUnexpect(lexicalError: Boolean)(implicit builder: ErrorBuilder[_]): (builder.Item, TokenSpan) = { builder.unexpectedToken(cs, amountOfInputParserWanted, lexicalError) match { @@ -28,33 +30,43 @@ private [internal] final case class UnexpectRaw(cs: Iterable[Char], amountOfInpu private [internal] override def higherPriority(other: UnexpectItem): Boolean = other.lowerThanRaw(this) protected [errors] override def lowerThanRaw(other: UnexpectRaw): Boolean = this.amountOfInputParserWanted < other.amountOfInputParserWanted protected [errors] override def lowerThanDesc(other: UnexpectDesc): Boolean = true + private [internal] override def isFlexible: Boolean = true + private [internal] override def widen(caret: Int): UnexpectItem = this.copy(amountOfInputParserWanted = math.max(caret, amountOfInputParserWanted)) } private [parsley] final case class ExpectRaw(cs: String) extends ExpectItem { + def this(c: Char) = this(s"$c") private [internal] def formatExpect(implicit builder: ErrorBuilder[_]): builder.Item = builder.raw(cs) } -private [parsley] object ExpectRaw { - def apply(c: Char): ExpectRaw = new ExpectRaw(s"$c") -} private [parsley] final case class ExpectDesc(msg: String) extends ExpectItem { assert(msg.nonEmpty, "Desc cannot contain empty things!") private [internal] def formatExpect(implicit builder: ErrorBuilder[_]): builder.Item = builder.named(msg) } -private [parsley] final case class UnexpectDesc(msg: String, width: Int) extends UnexpectItem { +private [parsley] final case class UnexpectDesc(msg: String, val width: CaretWidth) extends UnexpectItem { assert(msg.nonEmpty, "Desc cannot contain empty things!") // FIXME: When this is formatted, the width should really be normalised to the number of code points... this information is not readily available private [internal] def formatUnexpect(lexicalError: Boolean)(implicit builder: ErrorBuilder[_]): (builder.Item, TokenSpan) = - (builder.named(msg), TokenSpan.Width(width)) + (builder.named(msg), TokenSpan.Width(width.width)) private [internal] override def higherPriority(other: UnexpectItem): Boolean = other.lowerThanDesc(this) protected [errors] override def lowerThanRaw(other: UnexpectRaw): Boolean = false - protected [errors] override def lowerThanDesc(other: UnexpectDesc): Boolean = this.width < other.width + protected [errors] override def lowerThanDesc(other: UnexpectDesc): Boolean = { + if (this.isFlexible != other.isFlexible) !other.isFlexible + else this.width.width < other.width.width + } + private [internal] override def isFlexible: Boolean = width.isFlexible + private [internal] override def widen(caret: Int): UnexpectItem = { + assert(width.isFlexible, "can only widen flexible carets!") + this.copy(width = new FlexibleCaret(math.max(width.width, caret))) + } } -private [internal] case object EndOfInput extends UnexpectItem with ExpectItem { +private [internal] object EndOfInput extends UnexpectItem with ExpectItem { private [internal] def formatExpect(implicit builder: ErrorBuilder[_]): builder.Item = builder.endOfInput private [internal] def formatUnexpect(lexicalError: Boolean)(implicit builder: ErrorBuilder[_]): (builder.Item, TokenSpan) = (builder.endOfInput, TokenSpan.Width(1)) private [internal] override def higherPriority(other: UnexpectItem): Boolean = true protected [errors] override def lowerThanRaw(other: UnexpectRaw): Boolean = false protected [errors] override def lowerThanDesc(other: UnexpectDesc): Boolean = false + private [internal] override def isFlexible: Boolean = false + private [internal] override def widen(caret: Int): UnexpectItem = this } 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..fce30dbaf 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,9 @@ 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/Builders.scala b/parsley/shared/src/main/scala/parsley/internal/machine/errors/Builders.scala index 03a6c3774..20b505648 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/errors/Builders.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/errors/Builders.scala @@ -6,7 +6,7 @@ package parsley.internal.machine.errors import parsley.internal.errors.{UnexpectItem, UnexpectRaw} private [machine] abstract class ErrorItemBuilder { - final private [errors] def apply(offset: Int, size: Int): UnexpectItem = UnexpectRaw(iterableFrom(offset), size) + final private [errors] def apply(offset: Int, size: Int): UnexpectItem = new UnexpectRaw(iterableFrom(offset), size) private [errors] def inRange(offset: Int): Boolean 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..8044f0845 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,9 @@ package parsley.internal.machine.errors import scala.collection.mutable -import parsley.internal.errors.{EndOfInput, ExpectItem, FancyError, TrivialError, UnexpectItem} +import parsley.XAssert._ + +import parsley.internal.errors.{CaretWidth, EndOfInput, ExpectItem, FancyError, TrivialError, UnexpectDesc, UnexpectItem} import TrivialErrorBuilder.{BuilderUnexpectItem, NoItem, Other, Raw} @@ -48,7 +50,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 @@ -115,13 +117,16 @@ private [errors] object TrivialErrorBuilder { private [TrivialErrorBuilder] final class Raw(val size: Int) extends BuilderUnexpectItem { final def pickHigher(other: BuilderUnexpectItem): BuilderUnexpectItem = other.pickRaw(this) final override def pickRaw(other: Raw): Raw = if (this.size > other.size) this else other - final override def pickOther(other: Other): Other = other + final override def pickOther(other: Other): Other = other.pickRaw(this) final override def pickNoItem(other: NoItem): Raw = this def toErrorItem(offset: Int)(implicit builder: ErrorItemBuilder): Either[Int, UnexpectItem] = Right(builder(offset, size)) } private [TrivialErrorBuilder] final class Other(val underlying: UnexpectItem) extends BuilderUnexpectItem { final def pickHigher(other: BuilderUnexpectItem): BuilderUnexpectItem = other.pickOther(this) - final override def pickRaw(other: Raw): Other = this + final override def pickRaw(other: Raw): Other = { + if (underlying.isFlexible) new Other(underlying.widen(other.size)) + else this + } final override def pickOther(other: Other): Other = if (this.underlying.higherPriority(other.underlying)) this else other final override def pickNoItem(other: NoItem): Other = this def toErrorItem(offset: Int)(implicit builder: ErrorItemBuilder): Either[Int, UnexpectItem] = Right(underlying) @@ -145,6 +150,7 @@ private [errors] final class FancyErrorBuilder(offset: Int, lexicalError: Boolea private var line: Int = _ private var col: Int = _ private var caretWidth: Int = 0 + private var flexibleCaret: Boolean = true private val msgs = mutable.ListBuffer.empty[String] /** Updates the position of the error message. @@ -157,7 +163,19 @@ private [errors] final class FancyErrorBuilder(offset: Int, lexicalError: Boolea this.col = col } - def updateCaretWidth(width: Int): Unit = this.caretWidth = math.max(this.caretWidth, width) + def updateCaretWidth(width: Int): Unit = { + assume(flexibleCaret, "if we are updating direct from a TrivialError, we better be flexible!") + this.caretWidth = math.max(this.caretWidth, width) + } + def updateCaretWidth(width: CaretWidth): Unit = { + // if they match, just take the max + if (width.isFlexible == this.flexibleCaret) this.caretWidth = math.max(this.caretWidth, width.width) + // if they don't match and we are rigid, then we override the flexible caret, otherwise do nothing + else if (!width.isFlexible) { + this.caretWidth = width.width + this.flexibleCaret = false + } + } /** Adds a collection of new error message lines into this error. * 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..6c0017398 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 @@ -30,6 +30,8 @@ private [parsley] sealed abstract class DefuncError { private [machine] final def entrenched: Boolean = (flags & DefuncError.EntrenchedMask) != 0 /** Is this error created while parsing a lexical token? */ private [machine] final def lexicalError: Boolean = (flags & DefuncError.LexicalErrorMask) != 0 + /** Is the caret on this error flexible (only relevant for fancy errors) **/ + private [machine] final def flexibleCaret: Boolean = (flags & DefuncError.FlexibleCaretMask) != 0 /** The offset at which this error occured */ private [machine] val offset: Int /** This function forces the lazy defunctionalised structure into a final `ParseError` value. */ @@ -112,11 +114,15 @@ private [parsley] sealed abstract class DefuncError { */ private [machine] def markAsLexical(offset: Int): DefuncError } +// These are not covered by coverage because they are all inlined private [errors] object DefuncError { - private [errors] final val TrivialErrorMask: Byte = 1 << 0 - private [errors] final val ExpectedEmptyMask: Byte = 1 << 1 - private [errors] final val EntrenchedMask: Byte = 1 << 2 - private [errors] final val LexicalErrorMask: Byte = 1 << 3 + // $COVERAGE-OFF$ + private [errors] final val TrivialErrorMask = 1 << 0 + private [errors] final val ExpectedEmptyMask = 1 << 1 + private [errors] final val EntrenchedMask = 1 << 2 + private [errors] final val LexicalErrorMask = 1 << 3 + private [errors] final val FlexibleCaretMask = 1 << 4 + // $COVERAGE-ON$ } /** Represents partially evaluated trivial errors */ @@ -137,23 +143,26 @@ private [errors] sealed abstract class TrivialDefuncError extends DefuncError { * @param state the hint state that is collecting up the expected items * @note this function should be tail-recursive! */ - // TODO: Factor all the duplicated cases out? private [errors] final def collectHints(collector: HintCollector): Unit = this match { - case self: BaseError => + case self: BaseError => collector ++= self.expectedIterable collector.updateWidth(self.unexpectedWidth) - case self: WithLabel => if (self.label.nonEmpty) collector += ExpectDesc(self.label) - case self: WithReason => self.err.collectHints(collector) - case self: WithHints => + case self: WithLabel => if (self.label.nonEmpty) collector += new ExpectDesc(self.label) + case self: WithHints => self.hints.collect(collector) self.err.collectHints(collector) + case self: TrivialTransitive => self.err.collectHints(collector) // the WithLabel and WithHint cases must be above case self: TrivialMergedErrors => self.err1.collectHints(collector) self.err2.collectHints(collector) - case self: TrivialAmended => self.err.collectHints(collector) - case self: TrivialEntrenched => self.err.collectHints(collector) - case self: TrivialDislodged => self.err.collectHints(collector) - case self: TrivialLexical => self.err.collectHints(collector) + } + + private [errors] final def adjustCaret(builder: FancyErrorBuilder): Unit = this match { + case self: BaseError => builder.updateCaretWidth(self.unexpectedWidth) + case self: TrivialTransitive => self.err.adjustCaret(builder) + case self: TrivialMergedErrors => + self.err1.adjustCaret(builder) + self.err2.adjustCaret(builder) } private [machine] final override def merge(err: DefuncError): DefuncError = { @@ -209,8 +218,9 @@ private [errors] sealed abstract class FancyDefuncError extends DefuncError { if (cmp > 0) this else if (cmp < 0) err else err match { - case err: FancyDefuncError => new FancyMergedErrors(this, err) - case _ => this + case err: FancyDefuncError => new FancyMergedErrors(this, err) + case err: TrivialDefuncError if flexibleCaret => new FancyAdjustCaret(this, err) + case _ => this } } @@ -237,6 +247,9 @@ private [errors] sealed abstract class FancyDefuncError extends DefuncError { } } +private [errors] sealed abstract class TrivialTransitive extends TrivialDefuncError { + val err: TrivialDefuncError +} /** This is the common supertype of all "regular" trivial errors: those that result from failures as opposed to operations on errors. */ private [errors] sealed abstract class BaseError extends TrivialDefuncError { /** The line number the error occurred at */ @@ -245,7 +258,7 @@ private [errors] sealed abstract class BaseError extends TrivialDefuncError { private [errors] val col: Int /** The size of the unexpected token demanded by this error */ private [errors] def unexpectedWidth: Int - // def expected: IterableOnce[ErrorItem] // TODO: when 2.12 is dropped this will work better + // private [errors] def expected: IterableOnce[ErrorItem] // TODO: when 2.12 is dropped this will work better /** The error items produced by this error */ private [errors] def expectedIterable: Iterable[ExpectItem] @@ -286,15 +299,18 @@ private [parsley] final class ClassicUnexpectedError(val offset: Int, val line: val unexpected: UnexpectDesc) extends BaseError { override final val flags = if (expected.isEmpty) (DefuncError.ExpectedEmptyMask | DefuncError.TrivialErrorMask).toByte else DefuncError.TrivialErrorMask override def expectedIterable: Iterable[ExpectItem] = expected - override private [errors] def unexpectedWidth: Int = unexpected.width + override private [errors] def unexpectedWidth: Int = unexpected.width.width override def makeTrivial(builder: TrivialErrorBuilder): Unit = { builder.pos_=(line, col) builder += expected builder.updateUnexpected(unexpected) } } -private [parsley] final class ClassicFancyError(val offset: Int, val line: Int, val col: Int, caretWidth: Int, val msgs: String*) extends FancyDefuncError { - override final val flags = DefuncError.ExpectedEmptyMask + +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 = + if (caretWidth.isFlexible) (DefuncError.ExpectedEmptyMask | DefuncError.FlexibleCaretMask).toByte else DefuncError.ExpectedEmptyMask override def makeFancy(builder: FancyErrorBuilder): Unit = { builder.pos_=(line, col) builder ++= msgs @@ -345,7 +361,17 @@ private [errors] final class FancyMergedErrors private [errors] (val err1: Fancy } } -private [errors] final class WithHints private [errors] (val err: TrivialDefuncError, val hints: DefuncHints) extends TrivialDefuncError { +private [errors] final class FancyAdjustCaret private [errors] (val err: FancyDefuncError, val caretAdjuster: TrivialDefuncError) extends FancyDefuncError { + override final val flags = err.flags + assume(err.offset == caretAdjuster.offset, "two errors only merge when they have matching offsets") + override val offset = err.offset + override def makeFancy(builder: FancyErrorBuilder): Unit = { + err.makeFancy(builder) + caretAdjuster.adjustCaret(builder) + } +} + +private [errors] final class WithHints private [errors] (val err: TrivialDefuncError, val hints: DefuncHints) extends TrivialTransitive { assume(!hints.isEmpty, "WithHints will always have non-empty hints") override final val flags = (err.flags & ~DefuncError.ExpectedEmptyMask).toByte //err.isExpectedEmpty && hints.isEmpty override val offset = err.offset @@ -357,7 +383,7 @@ private [errors] final class WithHints private [errors] (val err: TrivialDefuncE } } -private [errors] final class WithReason private [errors] (val err: TrivialDefuncError, val reason: String) extends TrivialDefuncError { +private [errors] final class WithReason private [errors] (val err: TrivialDefuncError, val reason: String) extends TrivialTransitive { override final val flags = err.flags val offset = err.offset override def makeTrivial(builder: TrivialErrorBuilder): Unit = { @@ -366,7 +392,7 @@ private [errors] final class WithReason private [errors] (val err: TrivialDefunc } } -private [errors] final class WithLabel private [errors] (val err: TrivialDefuncError, val label: String) extends TrivialDefuncError { +private [errors] final class WithLabel private [errors] (val err: TrivialDefuncError, val label: String) extends TrivialTransitive { override final val flags = { if (label.isEmpty) (err.flags | DefuncError.ExpectedEmptyMask).toByte else (err.flags & ~DefuncError.ExpectedEmptyMask).toByte @@ -376,12 +402,12 @@ private [errors] final class WithLabel private [errors] (val err: TrivialDefuncE builder.ignoreExpected { err.makeTrivial(builder) } - if (label.nonEmpty) builder += ExpectDesc(label) + if (label.nonEmpty) builder += new ExpectDesc(label) } } private [errors] final class TrivialAmended private [errors] (val offset: Int, val line: Int, val col: Int, val err: TrivialDefuncError) - extends TrivialDefuncError { + extends TrivialTransitive { assume(!err.entrenched, "an amendment will only occur on unentrenched errors") override final val flags = err.flags override def makeTrivial(builder: TrivialErrorBuilder): Unit = { @@ -400,13 +426,13 @@ private [errors] final class FancyAmended private [errors] (val offset: Int, val } } -private [errors] final class TrivialEntrenched private [errors] (val err: TrivialDefuncError) extends TrivialDefuncError { +private [errors] final class TrivialEntrenched private [errors] (val err: TrivialDefuncError) extends TrivialTransitive { override final val flags = (err.flags | DefuncError.EntrenchedMask).toByte override val offset = err.offset override def makeTrivial(builder: TrivialErrorBuilder): Unit = err.makeTrivial(builder) } -private [errors] final class TrivialDislodged private [errors] (val err: TrivialDefuncError) extends TrivialDefuncError { +private [errors] final class TrivialDislodged private [errors] (val err: TrivialDefuncError) extends TrivialTransitive { override final val flags = (err.flags & ~DefuncError.EntrenchedMask).toByte override val offset = err.offset override def makeTrivial(builder: TrivialErrorBuilder): Unit = err.makeTrivial(builder) @@ -424,7 +450,7 @@ private [errors] final class FancyDislodged private [errors] (val err: FancyDefu override def makeFancy(builder: FancyErrorBuilder): Unit = err.makeFancy(builder) } -private [errors] final class TrivialLexical private [errors] (val err: TrivialDefuncError) extends TrivialDefuncError { +private [errors] final class TrivialLexical private [errors] (val err: TrivialDefuncError) extends TrivialTransitive { override final val flags = (err.flags | DefuncError.LexicalErrorMask).toByte override val offset = err.offset override def makeTrivial(builder: TrivialErrorBuilder): Unit = err.makeTrivial(builder) diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncHints.scala b/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncHints.scala index 1229b6024..949bf251b 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncHints.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/errors/DefuncHints.scala @@ -38,7 +38,7 @@ private [machine] sealed abstract class DefuncHints { final private [errors] def collect(collector: HintCollector): Unit = { this match { case EmptyHints => - case self: ReplaceHint => collector += ExpectDesc(self.label) + case self: ReplaceHint => collector += new ExpectDesc(self.label) case self: MergeHints => self.oldHints.collect(collector) self.newHints.collect(collector) 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..1e71ece3d 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: _*) @@ -151,8 +151,8 @@ private [internal] final class Fail(width: Int, msgs: String*) extends Instr { // $COVERAGE-ON$ } -private [internal] final class Unexpected(msg: String, width: Int) extends Instr { - private [this] val unexpected = UnexpectDesc(msg, width) +private [internal] final class Unexpected(msg: String, width: CaretWidth) extends Instr { + private [this] val unexpected = new UnexpectDesc(msg, width) override def apply(ctx: Context): Unit = { ensureRegularInstruction(ctx) ctx.unexpectedFail(None, unexpected) @@ -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..89ee00e61 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 @@ -269,7 +269,7 @@ private [internal] final class UnexpectedWhen(pred: PartialFunction[Any, (String val state = ctx.states val caretWidth = ctx.offset - state.offset val (unex, reason) = pred(ctx.stack.upop()) - val err = new ClassicUnexpectedError(state.offset, state.line, state.col, None, new UnexpectDesc(unex, caretWidth)) + val err = new ClassicUnexpectedError(state.offset, state.line, state.col, None, new UnexpectDesc(unex, new RigidCaret(caretWidth))) ctx.fail(reason.fold[DefuncError](err)(err.withReason(_))) } else ctx.inc() diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/TokenInstrs.scala b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/TokenInstrs.scala index 465ff4917..f123ca7ac 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/TokenInstrs.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/TokenInstrs.scala @@ -9,7 +9,7 @@ import parsley.XAssert._ import parsley.token.descriptions.SpaceDesc import parsley.token.errors.ErrorConfig -import parsley.internal.errors.{ExpectDesc, ExpectItem, UnexpectDesc} +import parsley.internal.errors.{ExpectDesc, ExpectItem, RigidCaret, UnexpectDesc} import parsley.internal.machine.Context import parsley.internal.machine.XAssert._ @@ -221,7 +221,7 @@ private [internal] final class TokenNonSpecific(name: String, unexpectedIllegal: private def ensureLegal(ctx: Context, tok: String) = { if (illegal(tok)) { ctx.offset -= tok.length - ctx.unexpectedFail(expected = expected, unexpected = new UnexpectDesc(unexpectedIllegal(tok), tok.length)) + ctx.unexpectedFail(expected = expected, unexpected = new UnexpectDesc(unexpectedIllegal(tok), new RigidCaret(tok.length))) } else { ctx.col += tok.length diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/token/TextInstructions.scala b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/token/TextInstructions.scala index 85b33ff16..4ed8f6e4a 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/token/TextInstructions.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/token/TextInstructions.scala @@ -16,7 +16,7 @@ import parsley.internal.machine.errors.{EmptyError, MultiExpectedError} import parsley.internal.machine.instructions.Instr private [internal] final class EscapeMapped(escTrie: Trie[Int], caretWidth: Int, expecteds: Set[ExpectItem]) extends Instr { - def this(escTrie: Trie[Int], escs: Set[String]) = this(escTrie, escs.view.map(_.length).max, escs.map(ExpectRaw(_))) + def this(escTrie: Trie[Int], escs: Set[String]) = this(escTrie, escs.view.map(_.length).max, escs.map(new ExpectRaw(_))) // Do not consume input on failure, it's possible another escape sequence might share a lead override def apply(ctx: Context): Unit = { ensureRegularInstruction(ctx) @@ -67,10 +67,10 @@ private [machine] abstract class EscapeSomeNumber(radix: Int) extends Instr { } protected val expected: Some[ExpectDesc] = radix match { - case 10 => Some(ExpectDesc("digit")) - case 16 => Some(ExpectDesc("hexadecimal digit")) - case 8 => Some(ExpectDesc("octal digit")) - case 2 => Some(ExpectDesc("bit")) + case 10 => Some(new ExpectDesc("digit")) + case 16 => Some(new ExpectDesc("hexadecimal digit")) + case 8 => Some(new ExpectDesc("octal digit")) + case 2 => Some(new ExpectDesc("bit")) } protected val pred: Char => Boolean = radix match { 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..719af4576 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$ @@ -91,7 +91,7 @@ abstract class Unexpected[A] extends VanillaFilterConfig[A] { self => case x if !f(x) => unexpected(x) } private [parsley] final override def mkError(offset: Int, line: Int, col: Int, caretWidth: Int, x: A): DefuncError = { - new ClassicUnexpectedError(offset, line, col, None, new UnexpectDesc(unexpected(x), caretWidth)) + new ClassicUnexpectedError(offset, line, col, None, new UnexpectDesc(unexpected(x), new RigidCaret(caretWidth))) } // $COVERAGE-OFF$ @@ -172,7 +172,7 @@ abstract class UnexpectedBecause[A] extends VanillaFilterConfig[A] { self => case x if !f(x) => (unexpected(x), reason(x)) } private [parsley] final override def mkError(offset: Int, line: Int, col: Int, caretWidth: Int, x: A): DefuncError = { - new ClassicUnexpectedError(offset, line, col, None, new UnexpectDesc(unexpected(x), caretWidth)).withReason(reason(x)) + new ClassicUnexpectedError(offset, line, col, None, new UnexpectDesc(unexpected(x), new RigidCaret(caretWidth))).withReason(reason(x)) } // $COVERAGE-OFF$ diff --git a/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplUntyped.scala b/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplUntyped.scala index 0044c2f25..a58efd84a 100644 --- a/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplUntyped.scala +++ b/parsley/shared/src/main/scala/parsley/token/errors/ConfigImplUntyped.scala @@ -51,7 +51,7 @@ trait ExplainConfig extends LabelWithExplainConfig private final class Label private[errors] (val label: String) extends LabelConfig { private [parsley] final override def apply[A](p: Parsley[A]) = p.label(label) - private [parsley] final override def asExpectDesc = Some(ExpectDesc(label)) + private [parsley] final override def asExpectDesc = Some(new ExpectDesc(label)) private [parsley] final override def asExpectDesc(@unused otherwise: String) = asExpectDesc private [parsley] final override def asExpectItem(@unused raw: String) = asExpectDesc private [parsley] final override def orElse(config: LabelWithExplainConfig) = config match { @@ -86,8 +86,8 @@ private final class Reason private[errors] (val reason: String) extends Explain require(reason.nonEmpty, "reason cannot be empty, use `Label` instead") private [parsley] final override def apply[A](p: Parsley[A]) = p.explain(reason) private [parsley] final override def asExpectDesc = None - private [parsley] final override def asExpectDesc(otherwise: String) = Some(ExpectDesc(otherwise)) - private [parsley] final override def asExpectItem(raw: String) = Some(ExpectRaw(raw)) + private [parsley] final override def asExpectDesc(otherwise: String) = Some(new ExpectDesc(otherwise)) + private [parsley] final override def asExpectItem(raw: String) = Some(new ExpectRaw(raw)) private [parsley] final override def orElse(config: LabelWithExplainConfig) = config match { case l: Label => new LabelAndReason(l.label, reason) case lr: LabelAndReason => new LabelAndReason(lr.label, reason) @@ -104,7 +104,7 @@ object Reason { private final class LabelAndReason private[errors] (val label: String, val reason: String) extends LabelWithExplainConfig { private [parsley] final override def apply[A](p: Parsley[A]) = p.label(label).explain(reason) - private [parsley] final override def asExpectDesc = Some(ExpectDesc(label)) + private [parsley] final override def asExpectDesc = Some(new ExpectDesc(label)) private [parsley] final override def asExpectDesc(@unused otherwise: String) = asExpectDesc private [parsley] final override def asExpectItem(@unused raw: String) = asExpectDesc private [parsley] final override def orElse(config: LabelWithExplainConfig) = this @@ -129,8 +129,8 @@ object LabelAndReason { object NotConfigured extends LabelConfig with ExplainConfig with LabelWithExplainConfig { private [parsley] final override def apply[A](p: Parsley[A]) = p private [parsley] final override def asExpectDesc = None - private [parsley] final override def asExpectDesc(otherwise: String) = Some(ExpectDesc(otherwise)) - private [parsley] final override def asExpectItem(raw: String) = Some(ExpectRaw(raw)) + private [parsley] final override def asExpectDesc(otherwise: String) = Some(new ExpectDesc(otherwise)) + private [parsley] final override def asExpectItem(raw: String) = Some(new ExpectRaw(raw)) private [parsley] final override def orElse(config: LabelWithExplainConfig) = config private [parsley] final override def orElse(config: LabelConfig) = config } 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..f6890ef22 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, FlexibleCaret, ExpectRaw, ExpectDesc, EndOfInput, RigidCaret, UnexpectDesc} import MockedBuilders.mockedErrorItemBuilder @@ -31,22 +31,43 @@ class DefuncErrorTests extends ParsleyTest { } "ClassicUnexpectedError" should "evaluate to TrivialError" in { - val err = new ClassicUnexpectedError(0, 0, 0, None, UnexpectDesc("oops", 1)) + val err = new ClassicUnexpectedError(0, 0, 0, None, new UnexpectDesc("oops", new RigidCaret(1))) err.isTrivialError shouldBe true err.asParseError shouldBe a [TrivialError] } it should "only be empty when its label is" in { - new ClassicUnexpectedError(0, 0, 0, None, UnexpectDesc("oops", 1)).isExpectedEmpty shouldBe true - new ClassicUnexpectedError(0, 0, 0, Some(ExpectDesc("oops")), UnexpectDesc("oops", 1)).isExpectedEmpty shouldBe false + new ClassicUnexpectedError(0, 0, 0, None, new UnexpectDesc("oops", new RigidCaret(1))).isExpectedEmpty shouldBe true + new ClassicUnexpectedError(0, 0, 0, Some(new ExpectDesc("oops")), new UnexpectDesc("oops", new RigidCaret(1))).isExpectedEmpty shouldBe false + } + it should "allow for flexible and rigid carets" in { + val err = new ClassicExpectedError(0, 0, 0, None, 5) + val errRigid = new ClassicUnexpectedError(0, 0, 0, None, new UnexpectDesc("oops", new RigidCaret(1))) + val errFlex1 = new ClassicUnexpectedError(0, 0, 0, None, new UnexpectDesc("oops", new FlexibleCaret(1))) + val errFlex2 = new ClassicUnexpectedError(0, 0, 0, None, new UnexpectDesc("oops", new FlexibleCaret(6))) + val pRigid = errRigid.merge(err).asParseError + pRigid shouldBe a [TrivialError] + pRigid.asInstanceOf[TrivialError].unexpected.fold(identity, _.formatUnexpect(false)._2.toCaretLength(0, 10, Nil)) shouldBe 1 + val pFlex1 = errFlex1.merge(err).asParseError + pFlex1 shouldBe a [TrivialError] + pFlex1.asInstanceOf[TrivialError].unexpected.fold(identity, _.formatUnexpect(false)._2.toCaretLength(0, 10, Nil)) shouldBe 5 + val pFlex2 = errFlex2.merge(err).asParseError + pFlex2 shouldBe a [TrivialError] + pFlex2.asInstanceOf[TrivialError].unexpected.fold(identity, _.formatUnexpect(false)._2.toCaretLength(0, 10, Nil)) shouldBe 6 + val pFlex3 = errFlex1.merge(errFlex2).asParseError + pFlex3 shouldBe a [TrivialError] + pFlex3.asInstanceOf[TrivialError].unexpected.fold(identity, _.formatUnexpect(false)._2.toCaretLength(0, 10, Nil)) shouldBe 6 + val pFlex4 = errRigid.merge(errFlex1).asParseError + pFlex4 shouldBe a [TrivialError] + pFlex4.asInstanceOf[TrivialError].unexpected.fold(identity, _.formatUnexpect(false)._2.toCaretLength(0, 10, Nil)) shouldBe 1 } "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 +98,45 @@ 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] } 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, 4).merge(new ClassicFancyError(0, 0, 0, new RigidCaret(1), "")) err1.isTrivialError shouldBe false - err1.asParseError shouldBe a [FancyError] + err1.flexibleCaret shouldBe false + val perr1 = err1.asParseError + perr1 shouldBe a [FancyError] + perr1.asInstanceOf[FancyError].caretWidth shouldBe 1 - val err2 = new ClassicFancyError(0, 0, 0, 1, "").merge(new EmptyError(0, 0, 0, 0)) + val err2 = new ClassicFancyError(0, 0, 0, new FlexibleCaret(1), "").merge(new EmptyError(0, 0, 0, 4)) err2.isTrivialError shouldBe false - err2.asParseError shouldBe a [FancyError] + err2.flexibleCaret shouldBe true + val perr2 = err2.asParseError + perr2 shouldBe a [FancyError] + perr2.asInstanceOf[FancyError].caretWidth shouldBe 4 - 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 FlexibleCaret(3), "").merge(new ClassicFancyError(0, 0, 0, new RigidCaret(1), "")) err5.isTrivialError shouldBe false - err5.asParseError shouldBe a [FancyError] + err5.flexibleCaret shouldBe false + val perr5 = err5.asParseError + perr5 shouldBe a [FancyError] + perr5.asInstanceOf[FancyError].caretWidth shouldBe 1 - 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] } @@ -117,8 +147,8 @@ class DefuncErrorTests extends ParsleyTest { new ClassicExpectedError(0, 0, 0, Some(EndOfInput), 1).merge(new ClassicExpectedError(0, 0, 0, Some(EndOfInput), 1)).isExpectedEmpty shouldBe false } they should "contain all the expecteds from both branches when appropriate" in { - val err = new MultiExpectedError(0, 0, 0, Set(ExpectRaw("a"), ExpectRaw("b")), 1).merge(new MultiExpectedError(0, 0, 0, Set(ExpectRaw("b"), ExpectRaw("c")), 1)) - err.asParseError.asInstanceOf[TrivialError].expecteds should contain only (ExpectRaw("a"), ExpectRaw("b"), ExpectRaw("c")) + val err = new MultiExpectedError(0, 0, 0, Set(new ExpectRaw("a"), new ExpectRaw("b")), 1).merge(new MultiExpectedError(0, 0, 0, Set(new ExpectRaw("b"), new ExpectRaw("c")), 1)) + err.asParseError.asInstanceOf[TrivialError].expecteds should contain only (new ExpectRaw("a"), new ExpectRaw("b"), new ExpectRaw("c")) } "WithHints" should "be trivial if its child is" in { @@ -127,7 +157,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 +172,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,20 +187,20 @@ 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] } it should "be empty if the label is empty and not otherwise" in { new EmptyError(0, 0, 0, 0).label("", 0).isExpectedEmpty shouldBe true new EmptyError(0, 0, 0, 0).label("a", 0).isExpectedEmpty shouldBe false - new ClassicExpectedError(0, 0, 0, Some(ExpectDesc("x")), 1).label("", 0).isExpectedEmpty shouldBe true - new ClassicExpectedError(0, 0, 0, Some(ExpectDesc("x")), 1).label("a", 0).isExpectedEmpty shouldBe false + new ClassicExpectedError(0, 0, 0, Some(new ExpectDesc("x")), 1).label("", 0).isExpectedEmpty shouldBe true + new ClassicExpectedError(0, 0, 0, Some(new ExpectDesc("x")), 1).label("a", 0).isExpectedEmpty shouldBe false } it should "replace all expected" in { - val errShow = new MultiExpectedError(0, 0, 0, Set(ExpectRaw("a"), ExpectRaw("b")), 1).label("x", 0) - val errHide = new MultiExpectedError(0, 0, 0, Set(ExpectRaw("a"), ExpectRaw("b")), 1).label("", 0) - errShow.asParseError.expecteds should contain only (ExpectDesc("x")) + val errShow = new MultiExpectedError(0, 0, 0, Set(new ExpectRaw("a"), new ExpectRaw("b")), 1).label("x", 0) + val errHide = new MultiExpectedError(0, 0, 0, Set(new ExpectRaw("a"), new ExpectRaw("b")), 1).label("", 0) + errShow.asParseError.expecteds should contain only (new ExpectDesc("x")) errHide.asParseError.expecteds shouldBe empty } @@ -182,7 +212,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 +228,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 +246,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 diff --git a/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncHintsTests.scala b/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncHintsTests.scala index 68d907716..6110550bf 100644 --- a/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncHintsTests.scala +++ b/parsley/shared/src/test/scala/parsley/internal/machine/errors/DefuncHintsTests.scala @@ -10,7 +10,7 @@ import parsley.internal.errors.ExpectDesc class DefuncHintsTests extends ParsleyTest { def mkErr(labels: String*): DefuncError = { assert(labels.nonEmpty) - new MultiExpectedError(0, 0, 0, labels.map(ExpectDesc(_)).toSet, 1) + new MultiExpectedError(0, 0, 0, labels.map(new ExpectDesc(_)).toSet, 1) } "EmptyHints" should "have size 0" in { @@ -29,12 +29,12 @@ class DefuncHintsTests extends ParsleyTest { } it should "replace the hints under it" in { val hints = EmptyHints.addError(mkErr("a", "c")).addError(mkErr("b")).rename("hi") - hints.toSet should contain only (ExpectDesc("hi")) + hints.toSet should contain only (new ExpectDesc("hi")) } "MergeHints" should "ensure all elements from both hints" in { val hints1 = EmptyHints.addError(mkErr("a")).addError(mkErr("b")) val hints2 = EmptyHints.addError(mkErr("c")).addError(mkErr("d")) - hints1.merge(hints2).toSet should contain only (ExpectDesc("a"), ExpectDesc("b"), ExpectDesc("c"), ExpectDesc("d")) + hints1.merge(hints2).toSet should contain only (new ExpectDesc("a"), new ExpectDesc("b"), new ExpectDesc("c"), new ExpectDesc("d")) } }