All Downloads are FREE. Search and download functionalities are using the official Maven repository.

parsley.errors.ErrorGen.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 Parsley Contributors 
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
package parsley.errors

import parsley.Parsley

import parsley.internal.deepembedding.frontend.LazyParsley
import parsley.internal.deepembedding.singletons
import parsley.internal.errors.{RigidCaret, UnexpectDesc}
import parsley.internal.machine.errors.{DefuncError, EmptyError, ExpectedError, UnexpectedError}

import org.typelevel.scalaccompat.annotation.unused

/** This class can be used to generate hand-tuned error messages without using `flatMap`.
  *
  * This class, and its subclasses, describe special primitives that can use the results of
  * a previous parser to form an error message and then raise it. This is not something that
  * is normally possible with raw combinators, without using `flatMap`, which is expensive.
  *
  * Primarily, these are designed to be used with `filterWith`/`verifiedWith`/`preventWith`
  * but can be used in other parsers as well. See the methods of the class for information.
  *
  * @since 4.4.0
  */
sealed abstract class ErrorGen[-A] {
    /** This combinator takes a given parser and raises an error based on its returned results.
      *
      * The given parser produces a value and a width, which are used to synthesise and raise
      * an error message derived from the value with the given width. This is a safe way of using
      * `parser`, since it ensures that the result of the given parser `p` is not optimised out.
      * `errGen(p)` is similar to `withWidth(p).flatMap { case (x, w) => failCombinator(...) }`,
      * in that it generates errors in a context-sensitive way. However, this is much more efficient
      * than using the expensive `flatMap`, so it is provided as a primitive operation.
      *
      * @since 4.4.0
      */
    final def apply(p: Parsley[(A, Int)]): Parsley[Nothing] = (p <**> parser).impure

    /** This parser can be applied (postfix) to a parser returning a value and a width to generate an
      * error message tailored to them.
      *
      * This is '''not''' a generally safe operation to be performing, and should only be used
      * within a combinator that is guaranteed to use its results. The optimiser is not aware that
      * the results of the parser this will be applied to will actually be needed, and so may optimise
      * them out. Using this parser inside an arm of `select` or `branch`, say, would be safe, because
      * these combinators force the result of their condition to be generated, but `p <**> this.parser`
      * is not generally safe without a use of `impure` to guard it. This is what `apply` accomplishes
      * more safely.
      *
      * @since 4.4.0
      */
    final def parser: Parsley[((A, Int)) => Nothing] = new Parsley(internal)
    private [errors] def internal: LazyParsley[((A, Int)) => Nothing]

    /** This method can be overridden to control how wide an error is based on the value and width
      * that produces it.
      *
      * The width provides to this error generator likely comes directly from the span of the
      * parser used to produce the required result. However, this may not be entirely accurate
      * for how the user might want the error to be sized (perhaps there was whitespace, or the
      * parser consumed more input than was necessary to pin-point the problem). In these cases,
      * this method allows for custom logic to derive the actual width of the error message. By
      * default, just returns the given `width`.
      *
      * @since 4.4.0
      */
    def adjustWidth(@unused x: A, width: Int): Int = width
}

/** An error generator for ''Vanilla'' errors, which can tune the unexpected message and a
  * generated reason.
  *
  * @since 4.4.0
  */
class VanillaGen[-A] extends ErrorGen[A] {
    /** What should the unexpected component of the error message be based on the result the
      * offending parser produced?
      *
      * @since 4.4.0
      */
    def unexpected(@unused x: A): VanillaGen.UnexpectedItem = VanillaGen.EmptyItem

    /** What should the reason component of the error message be (if any) based on the result the
      * offending parser produced?
      *
      * @since 4.4.0
      */
    def reason(@unused x: A): Option[String] = None

    private [errors] override def internal: LazyParsley[((A, Int)) => Nothing] = new singletons.VanillaGen(this)
}

/** This object contain the `UnexpectedItem` classes, needed to define the `unexpected` component of `VanillaGen`.
  *
  * @since 4.4.0
  */
object VanillaGen {
    /** Base class for describing how to form the unexpected component of a vanilla error message from `VanillaGen`.
      *
      * @since 4.4.0
      */
    sealed abstract class UnexpectedItem {
        private [parsley] def makeError(offset: Int, line: Int, col: Int, caretWidth: Int): DefuncError
    }

    /** Signifies that the error generated should use whatever input was consumed by the offending parser
      * verbatim.
      *
      * @since 4.4.0
      */
    case object RawItem extends UnexpectedItem {
        private[parsley] def makeError(offset: Int, line: Int, col: Int, caretWidth: Int): DefuncError =
            new ExpectedError(offset, line, col, None, caretWidth)
    }

    /** Signifies that the error generated should not have an unexpected component at all (as in `filter`).
      *
      * @since 4.4.0
      */
    case object EmptyItem extends UnexpectedItem {
        private[parsley] def makeError(offset: Int, line: Int, col: Int, caretWidth: Int): DefuncError =
            new EmptyError(offset, line, col, caretWidth)
    }

    /** Signifies that the error generated should use the given name as the unexpected component.
      *
      * @since 4.4.0
      */
    final case class NamedItem(name: String) extends UnexpectedItem {
        private[parsley] def makeError(offset: Int, line: Int, col: Int, caretWidth: Int): DefuncError =
            new UnexpectedError(offset, line, col, None, new UnexpectDesc(name, new RigidCaret(caretWidth)))
    }
}

/** An error generate for ''Specialized'' errors, which can tune the freeform messages of the error.
  *
  * @since 4.4.0
  */
abstract class SpecializedGen[-A] extends ErrorGen[A] {
    /** What should the messages of the error message be based on the result the
      * offending parser produced?
      *
      * @since 4.4.0
      */
    def messages(x: A): Seq[String]

    private [errors] override def internal: LazyParsley[((A, Int)) => Nothing] = new singletons.SpecializedGen(this)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy