parsley.internal.machine.errors.DefuncError.scala Maven / Gradle / Ivy
/* SPDX-FileCopyrightText: © 2021 Parsley Contributors
* SPDX-License-Identifier: BSD-3-Clause
*/
package parsley.internal.machine.errors
import parsley.XAssert._
import parsley.internal.errors.{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
// these case classes. This means that every error message created will be done in a single
// O(1) allocation, avoiding anything to do with the underlying sets, options etc.
/** This structure represents a collection of operations that build up a
* `ParseError`. This is done to allow for the operations to be performed
* lazily, or potentially not at all if the error message is discarded
* or a merge occurs with a different error kind. It also allows for the
* conversion to `ParseError` to make use of mutable collections, which
* would otherwise be unsound.
*/
private [machine] sealed abstract class DefuncError {
// Stores the bit-packed flags below, starting from bit 0 upwards
private [errors] val flags: Byte
/** Is this error a trivial error, or is it fancy? */
private [machine] final def isTrivialError: Boolean = (flags & DefuncError.TrivialErrorMask) != 0
/** Does this error, if its trivial, have any expected items? */
private [machine] final def isExpectedEmpty: Boolean = (flags & DefuncError.ExpectedEmptyMask) != 0
/** Is this error protected from amendment via the `amend` combinator? */
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
/** The offset at which this error occured */
private [machine] val offset: Int
/** This function forces the lazy defunctionalised structure into a final `ParseError` value. */
private [machine] def asParseError(implicit itemBuilder: ErrorItemBuilder): ParseError
// Operations: these are the smart constructors for the hint operations, which will reduce the number of objects in the binary
// they all perform some form of simplification step to avoid unnecesary allocations
/** This operation merges two errors.
*
* The errors must have the same offset, with the deeper of the two
* taking precedence if not. Otherwise, if they are equal offset, errors
* can only be merged when they are both the same kind: if they are not,
* fancy errors take precedence over trivial errors.
*
* @param err the error to merge with this one
* @return either the merged error, or one of the two originals
*/
private [machine] def merge(err: DefuncError): DefuncError
/** This operation adds the currently available hints as expected
* items into an error message. This is only applicable for trivial
* errors.
*
* @param hints the hints to add to the message
* @return the new error with hints
* @note hints should only be incorporated when they are at the same offset
*/
private [machine] def withHints(hints: DefuncHints): DefuncError
/** This operation adds a reason to a trivial error.
*
* @param reason the reason to add to the message
* @return the error with the reason incorporated
* @note reasons are kept in-order
*/
private [machine] def withReason(reason: String): DefuncError
/** This operation replaces the expected labels in this error message
* by the given label. This can only happen when the offset of
* this error message matches the given offset: this should be the
* offset on entry to the `label` ''combinator'''.
*
* If the label given is `""`, then this label is not emitted into
* the error message, having the effect of removing the underlying
* labels.
*
* @param label the name to replace the expected labels with
* @param offset the offset that the label is applicable at
* @return an error message that incorporates the relevant errors
*/
private [machine] def label(label: String, offset: Int): DefuncError
/** This operation changes the offset, line, and column number that
* an error appears to occur at. The effect of this operation is
* suppressed by `entrench`, however.
*
* @param offset the new offset of the error
* @param line the new line of the error
* @param col the new column of the error
* @return a new error that has been amended
*/
private [machine] def amend(offset: Int, line: Int, col: Int): DefuncError
/** This operation resists the changes attempted by an `amend` operation
* ''above'' it, without affecting the messages below.
*
* @return an entrenched error message
*/
private [machine] def entrench: DefuncError
/** This operation undoes the `amend` protection provided by an underlying entrenched error.
*
* @return a non-entrenched error message
*/
private [machine] def dislodge: DefuncError
/** This operation sets this error message to be considered as a lexical
* error message, which means that it will not perform lexical extraction
* within the builder, instead opting to extract a token via raw input.
* This only happens if the error occured at an offset ''greater'' than
* the provided offset (which is the beginning of where the token started
* parsing).
*
* @param offset the offset the error must have occured deeper than
* @return a lexical error message
*/
private [machine] def markAsLexical(offset: Int): DefuncError
}
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
}
/** Represents partially evaluated trivial errors */
private [errors] sealed abstract class TrivialDefuncError extends DefuncError {
private [machine] final override def asParseError(implicit itemBuilder: ErrorItemBuilder): TrivialError = {
val errorBuilder = new TrivialErrorBuilder(offset, !itemBuilder.inRange(offset), lexicalError)
makeTrivial(errorBuilder)
errorBuilder.mkError
}
private [errors] def makeTrivial(builder: TrivialErrorBuilder): Unit
/** This method collects up the error labels of this error message into the given `HintState`.
*
* This is important for `DefuncHints`, which generates a hints set for previously recovered
* errors. This is a more efficient way of computing this information as it ignores the other
* data processed during the formation of a regular error message.
*
* @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 =>
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 =>
self.hints.collect(collector)
self.err.collectHints(collector)
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 [machine] final override def merge(err: DefuncError): DefuncError = {
val cmp = Integer.compareUnsigned(this.offset, err.offset)
if (cmp > 0) this
else if (cmp < 0) err
else err match {
case err: TrivialDefuncError => new TrivialMergedErrors(this, err)
case err => err
}
}
private [machine] final override def withHints(hints: DefuncHints): TrivialDefuncError = {
if (hints.nonEmpty) new WithHints(this, hints)
else this
}
private [machine] final override def withReason(reason: String): TrivialDefuncError = new WithReason(this, reason)
private [machine] final override def label(label: String, offset: Int): TrivialDefuncError = {
if (this.offset == offset) new WithLabel(this, label)
else this
}
private [machine] final override def amend(offset: Int, line: Int, col: Int): TrivialDefuncError = {
if (!this.entrenched) new TrivialAmended(offset, line, col, this)
else this
}
private [machine] final override def entrench: TrivialDefuncError = this match {
case self: TrivialDislodged => new TrivialEntrenched(self.err)
case self if !self.entrenched => new TrivialEntrenched(this)
case self => self
}
private [machine] final override def dislodge: TrivialDefuncError = this match {
case self: TrivialEntrenched => self.err
case self if self.entrenched => new TrivialDislodged(this)
case self => self
}
private [machine] final override def markAsLexical(offset: Int): TrivialDefuncError = {
if (Integer.compareUnsigned(this.offset, offset) > 0) new TrivialLexical(this)
else this
}
}
/** Represents partially evaluated fancy errors */
private [errors] sealed abstract class FancyDefuncError extends DefuncError {
private [machine] final override def asParseError(implicit itemBuilder: ErrorItemBuilder): FancyError = {
val builder = new FancyErrorBuilder(offset, lexicalError)
makeFancy(builder)
builder.mkError
}
private [errors] def makeFancy(builder: FancyErrorBuilder): Unit
private [machine] final override def merge(err: DefuncError): DefuncError = {
val cmp = Integer.compareUnsigned(this.offset, err.offset)
if (cmp > 0) this
else if (cmp < 0) err
else err match {
case err: FancyDefuncError => new FancyMergedErrors(this, err)
case _ => this
}
}
private [machine] final override def withHints(hints: DefuncHints): FancyDefuncError = this
private [machine] final override def withReason(reason: String): FancyDefuncError = this
private [machine] final override def label(label: String, offset: Int): FancyDefuncError = this
private [machine] final override def amend(offset: Int, line: Int, col: Int): FancyDefuncError = {
if (!this.entrenched) new FancyAmended(offset, line, col, this)
else this
}
private [machine] final override def entrench: FancyDefuncError = this match {
case self: FancyDislodged => new FancyEntrenched(self.err)
case self if !self.entrenched => new FancyEntrenched(this)
case self => self
}
private [machine] final override def dislodge: FancyDefuncError = this match {
case self: FancyEntrenched => self.err
case self if self.entrenched => new FancyDislodged(this)
case self => self
}
private [machine] final override def markAsLexical(offset: Int): FancyDefuncError = {
if (Integer.compareUnsigned(this.offset, offset) > 0) new FancyLexical(this)
else this
}
}
/** 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 */
private [errors] val line: Int
/** The column number the error occurred at */
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
/** The error items produced by this error */
private [errors] def expectedIterable: Iterable[ExpectItem]
/** Adds the reasons and errors (or any other work) after the position and unexpected updates.
* By default, this will just add the expected messages found in the `expectedIterable`.
*
* @note the default can, and should, be overriden for efficiency or to add more information
*/
private [errors] def addLabelsAndReasons(builder: TrivialErrorBuilder): Unit = builder ++= expectedIterable
/** Default implementation of `makeTrivial`, which updates the position of the error, and the
* size of the unexpected token. This calls `addLabelsAndReasons` to complete any work.
*
* @note the default implementation should be overriden for EmptyErrors, which must not update the unexpected!
*/
private [errors] override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
builder.pos_=(line, col)
builder.updateUnexpected(unexpectedWidth)
addLabelsAndReasons(builder)
}
}
private [machine] final class ClassicExpectedError(val offset: Int, val line: Int, val col: Int, val expected: Option[ExpectItem], val unexpectedWidth: Int)
extends BaseError {
override final val flags = if (expected.isEmpty) (DefuncError.ExpectedEmptyMask | DefuncError.TrivialErrorMask).toByte else DefuncError.TrivialErrorMask
override def expectedIterable: Iterable[ExpectItem] = expected
}
private [machine] final class ClassicExpectedErrorWithReason(val offset: Int, val line: Int, val col: Int,
val expected: Option[ExpectItem], val reason: String, val unexpectedWidth: Int) extends BaseError {
override final val flags = if (expected.isEmpty) (DefuncError.ExpectedEmptyMask | DefuncError.TrivialErrorMask).toByte else DefuncError.TrivialErrorMask
override def expectedIterable: Iterable[ExpectItem] = expected
override def addLabelsAndReasons(builder: TrivialErrorBuilder): Unit = {
builder += expected
builder += reason
}
}
private [machine] final class ClassicUnexpectedError(val offset: Int, val line: Int, val col: Int, val expected: Option[ExpectItem],
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 def addLabelsAndReasons(builder: TrivialErrorBuilder): Unit = {
builder += expected
builder.updateUnexpected(unexpected)
}
}
private [machine] 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
override def makeFancy(builder: FancyErrorBuilder): Unit = {
builder.pos_=(line, col)
builder ++= msgs
builder.updateCaretWidth(caretWidth)
}
}
private [machine] final class EmptyError(val offset: Int, val line: Int, val col: Int, val unexpectedWidth: Int) extends BaseError {
override final val flags = (DefuncError.ExpectedEmptyMask | DefuncError.TrivialErrorMask).toByte
override def expectedIterable: Iterable[ExpectItem] = None
override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
builder.pos_=(line, col)
builder.updateEmptyUnexpected(unexpectedWidth)
}
}
private [machine] final class EmptyErrorWithReason(val offset: Int, val line: Int, val col: Int, val reason: String, val unexpectedWidth: Int)
extends BaseError {
override final val flags: Byte = (DefuncError.ExpectedEmptyMask | DefuncError.TrivialErrorMask).toByte
override def expectedIterable: Iterable[ExpectItem] = None
override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
builder.pos_=(line, col)
builder.updateEmptyUnexpected(unexpectedWidth)
builder += reason
}
}
private [machine] final class MultiExpectedError(val offset: Int, val line: Int, val col: Int, val expected: Set[ExpectItem], val unexpectedWidth: Int)
extends BaseError {
override final val flags = if (expected.isEmpty) (DefuncError.ExpectedEmptyMask | DefuncError.TrivialErrorMask).toByte else DefuncError.TrivialErrorMask
override def expectedIterable: Iterable[ExpectItem] = expected
}
private [errors] final class TrivialMergedErrors private [errors] (val err1: TrivialDefuncError, val err2: TrivialDefuncError) extends TrivialDefuncError {
override final val flags = (err1.flags & err2.flags).toByte
assume(err1.offset == err2.offset, "two errors only merge when they have matching offsets")
val offset = err1.offset //Math.max(err1.offset, err2.offset)
override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
err1.makeTrivial(builder)
err2.makeTrivial(builder)
}
}
private [errors] final class FancyMergedErrors private [errors] (val err1: FancyDefuncError, val err2: FancyDefuncError) extends FancyDefuncError {
override final val flags = (err1.flags & err2.flags).toByte
assume(err1.offset == err2.offset, "two errors only merge when they have matching offsets")
override val offset = err1.offset //Math.max(err1.offset, err2.offset)
override def makeFancy(builder: FancyErrorBuilder): Unit = {
err1.makeFancy(builder)
err2.makeFancy(builder)
}
}
private [errors] final class WithHints private [errors] (val err: TrivialDefuncError, val hints: DefuncHints) extends TrivialDefuncError {
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
override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
err.makeTrivial(builder)
builder.whenAcceptingExpected {
val size = hints.updateExpectedsAndGetSize(builder)
builder.updateUnexpected(size)
}
}
}
private [errors] final class WithReason private [errors] (val err: TrivialDefuncError, val reason: String) extends TrivialDefuncError {
override final val flags = err.flags
val offset = err.offset
override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
err.makeTrivial(builder)
builder += reason
}
}
private [errors] final class WithLabel private [errors] (val err: TrivialDefuncError, val label: String) extends TrivialDefuncError {
override final val flags = {
if (label.isEmpty) (err.flags | DefuncError.ExpectedEmptyMask).toByte
else (err.flags & ~DefuncError.ExpectedEmptyMask).toByte
}
val offset = err.offset
override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
builder.ignoreExpected {
err.makeTrivial(builder)
}
if (label.nonEmpty) builder += ExpectDesc(label)
}
}
private [errors] final class TrivialAmended private [errors] (val offset: Int, val line: Int, val col: Int, val err: TrivialDefuncError)
extends TrivialDefuncError {
assume(!err.entrenched, "an amendment will only occur on unentrenched errors")
override final val flags = err.flags
override def makeTrivial(builder: TrivialErrorBuilder): Unit = {
err.makeTrivial(builder)
// this should happen after the sub-error has been proceeded
builder.pos_=(line, col)
}
}
private [errors] final class FancyAmended private [errors] (val offset: Int, val line: Int, val col: Int, val err: FancyDefuncError) extends FancyDefuncError {
assume(!err.entrenched, "an amendment will only occur on unentrenched errors")
override final val flags = err.flags
override def makeFancy(builder: FancyErrorBuilder): Unit = {
err.makeFancy(builder)
builder.pos_=(line, col)
}
}
private [errors] final class TrivialEntrenched private [errors] (val err: TrivialDefuncError) extends TrivialDefuncError {
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 {
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 FancyEntrenched private [errors] (val err: FancyDefuncError) extends FancyDefuncError {
override final val flags = (err.flags | DefuncError.EntrenchedMask).toByte
override val offset = err.offset
override def makeFancy(builder: FancyErrorBuilder): Unit = err.makeFancy(builder)
}
private [errors] final class FancyDislodged private [errors] (val err: FancyDefuncError) extends FancyDefuncError {
override final val flags = (err.flags & ~DefuncError.EntrenchedMask).toByte
override val offset = err.offset
override def makeFancy(builder: FancyErrorBuilder): Unit = err.makeFancy(builder)
}
private [errors] final class TrivialLexical private [errors] (val err: TrivialDefuncError) extends TrivialDefuncError {
override final val flags = (err.flags | DefuncError.LexicalErrorMask).toByte
override val offset = err.offset
override def makeTrivial(builder: TrivialErrorBuilder): Unit = err.makeTrivial(builder)
}
private [errors] final class FancyLexical private [errors] (val err: FancyDefuncError) extends FancyDefuncError {
override final val flags = (err.flags | DefuncError.LexicalErrorMask).toByte
override val offset = err.offset
override def makeFancy(builder: FancyErrorBuilder): Unit = err.makeFancy(builder)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy