Skip to content

Commit

Permalink
Fancy errors now have caret flexiblity if the caret is not manually s…
Browse files Browse the repository at this point in the history
…pecified: filters remain rigid
  • Loading branch information
j-mie6 committed May 21, 2023
1 parent eb62852 commit ff052d0
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ 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
}
private [parsley] class FlexibleCaret(val width: Int) extends CaretWidth
private [parsley] class RigidCaret(val width: Int) extends CaretWidth
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ package parsley.internal.machine.errors

import scala.collection.mutable

import parsley.internal.errors.{EndOfInput, ExpectItem, FancyError, TrivialError, UnexpectDesc, UnexpectItem}
import parsley.XAssert._

import parsley.internal.errors.{CaretWidth, EndOfInput, ExpectItem, FancyError, TrivialError, UnexpectDesc, UnexpectItem}

import TrivialErrorBuilder.{BuilderUnexpectItem, NoItem, Other, Raw}

Expand Down Expand Up @@ -145,6 +147,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.
Expand All @@ -157,7 +160,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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -117,6 +119,7 @@ private [errors] object DefuncError {
private [errors] final val ExpectedEmptyMask: Byte = 1 << 1
private [errors] final val EntrenchedMask: Byte = 1 << 2
private [errors] final val LexicalErrorMask: Byte = 1 << 3
private [errors] final val FlexibleCaretMask: Byte = 1 << 4
}

/** Represents partially evaluated trivial errors */
Expand All @@ -137,23 +140,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 += 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 = {
Expand Down Expand Up @@ -209,8 +215,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
}
}

Expand All @@ -237,6 +244,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 */
Expand All @@ -245,7 +255,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]

Expand Down Expand Up @@ -295,11 +305,12 @@ private [parsley] final class ClassicUnexpectedError(val offset: Int, val line:
}

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 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
builder.updateCaretWidth(caretWidth.width)
builder.updateCaretWidth(caretWidth)
}
}
private [parsley] final class EmptyError(val offset: Int, val line: Int, val col: Int, val unexpectedWidth: Int) extends BaseError {
Expand Down Expand Up @@ -346,7 +357,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
Expand All @@ -358,7 +379,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 = {
Expand All @@ -367,7 +388,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
Expand All @@ -382,7 +403,7 @@ private [errors] final class WithLabel private [errors] (val err: TrivialDefuncE
}

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 = {
Expand All @@ -401,13 +422,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)
Expand All @@ -425,7 +446,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package parsley.internal.machine.errors

import parsley.ParsleyTest

import parsley.internal.errors.{TrivialError, FancyError, ExpectRaw, ExpectDesc, EndOfInput, RigidCaret, UnexpectDesc}
import parsley.internal.errors.{TrivialError, FancyError, FlexibleCaret, ExpectRaw, ExpectDesc, EndOfInput, RigidCaret, UnexpectDesc}

import MockedBuilders.mockedErrorItemBuilder

Expand Down Expand Up @@ -85,15 +85,20 @@ class DefuncErrorTests extends ParsleyTest {
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, new RigidCaret(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, new RigidCaret(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, new RigidCaret(1), ""))
err3.isTrivialError shouldBe false
Expand All @@ -103,9 +108,12 @@ class DefuncErrorTests extends ParsleyTest {
err4.isTrivialError shouldBe false
err4.asParseError shouldBe a [FancyError]

val err5 = new ClassicFancyError(0, 0, 0, new RigidCaret(1), "").merge(new ClassicFancyError(0, 0, 0, new RigidCaret(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, new RigidCaret(1), "").merge(new ClassicFancyError(0, 0, 0, new RigidCaret(1), ""))
err6.isTrivialError shouldBe false
Expand Down

0 comments on commit ff052d0

Please sign in to comment.