Skip to content

Commit

Permalink
Added CaretWidth datatype, which will be used to deal with fail that …
Browse files Browse the repository at this point in the history
…can have caret width affected
  • Loading branch information
j-mie6 committed May 21, 2023
1 parent 04fef59 commit eb62852
Show file tree
Hide file tree
Showing 10 changed files with 50 additions and 35 deletions.
6 changes: 4 additions & 2 deletions parsley/shared/src/main/scala/parsley/errors/combinator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* SPDX-FileCopyrightText: © 2023 Parsley Contributors <https://github.com/j-mie6/Parsley/graphs/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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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
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.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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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: _*)
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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$
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, UnexpectDesc}
import parsley.internal.errors.{TrivialError, FancyError, ExpectRaw, ExpectDesc, EndOfInput, RigidCaret, UnexpectDesc}

import MockedBuilders.mockedErrorItemBuilder

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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]
}
Expand All @@ -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]
}
Expand All @@ -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]
}
Expand All @@ -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]
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit eb62852

Please sign in to comment.