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

parsley.internal.machine.errors.DefuncBuilders.scala Maven / Gradle / Ivy

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

import scala.collection.mutable

import parsley.XAssert._

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

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

/** When building a true `TrivialError` from a `TrivialDefuncError`, this builder is used to keep track of the
  * components that will make up the final error. This allows for more efficient construction of the object,
  * as it can internally use mutable collections, before finally freezing them into an immutable form.
  *
  * @param offset the offset that the error being built occured at
  * @param outOfRange whether or not this error occured at the end of the input or not
  */
private [errors] final class TrivialErrorBuilder(presentationOffset: Int, outOfRange: Boolean, lexicalError: Boolean) {
    private var line: Int = _
    private var col: Int = _
    private val expecteds = mutable.Set.empty[ExpectItem]
    private var unexpected = EmptyItem
    private val reasons = mutable.Set.empty[String]
    private var acceptingExpected = true

    /** Updates the position of the error message.
      *
      * @param line the new line number
      * @param col the new column number
      */
    def pos_=(line: Int, col: Int): Unit = {
        this.line = line
        this.col = col
    }
    /** Updates the width of the raw unexpected token generated by the error: this may expand its width,
      * but will not shrink it. If a raw unexpected token is not going to be generated, this has no
      * effect.
      *
      * If this error is `outOfRange`, `EndOfInput` is generated instead.
      *
      * @param width
      */
    def updateUnexpected(width: Int): Unit = {
        assume(width != 0 || this.unexpected.pickHigher(EmptyItem) == this.unexpected, s"trying to pick empty item never does anything (${this.unexpected} becomes ${this.unexpected.pickHigher(EmptyItem)})")
        //if (width == 0) updateUnexpected(EmptyItem)
        if (width != 0) {
            if (outOfRange) updateUnexpected(new Other(EndOfInput))
            else updateUnexpected(new Raw(width))
        }
    }
    /** Updates the unexpected token generated by the error, so long as this new item takes precedence
      * over the old one.
      *
      * @param 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
      */
    def updateEmptyUnexpected(width: Int): Unit = updateUnexpected(new NoItem(width))
    private def updateUnexpected(item: BuilderUnexpectItem): Unit = this.unexpected = this.unexpected.pickHigher(item)
    /** If this error is accepting new expected items, adds all the given items into the error
      *
      * @param expecteds the items to add
      */
    def ++=(expecteds: Iterable[ExpectItem]): Unit = if (acceptingExpected) this.expecteds ++= expecteds
    /** Adds the given reason onto the end of the errors reasons.
      *
      * @param reason the reason to add
      */
    def +=(reason: String): Unit = this.reasons += reason
    /** Builds the final error within the context of some builder for error items.
      *
      * @param itemBuilder a builder for constructing unexpected error items from the raw input of the parse
      * @return the final error message
      */
    def mkError(implicit itemBuilder: ErrorItemBuilder): TrivialError = {
        new TrivialError(presentationOffset, line, col, unexpected.toErrorItem(presentationOffset), expecteds.toSet, reasons.toSet, lexicalError)
    }
    /** Performs some given action only when this builder is currently accepting new expected items
      *
      * @param action the action to perform
      */
    def whenAcceptingExpected(action: =>Unit): Unit = if (acceptingExpected) action
    /** For the duration of the given action, all expected items that are provided to the builder
      * will be ignored.
      *
      * @param action the action to perform, during which no expected items can be accepted
      */
    def ignoreExpected(action: =>Unit): Unit = {
        val old = acceptingExpected
        acceptingExpected = false
        action
        acceptingExpected = old
    }

    /** Makes a `HintCollector` for collecting any hints from a `DefuncHints`
      * directly into this builder's expected error items.
      */
    def makeHintCollector: HintCollector = new HintCollector(expecteds)
}
private [errors] object TrivialErrorBuilder {
    final def EmptyItem: BuilderUnexpectItem = new NoItem(0)
    private [TrivialErrorBuilder] sealed abstract class BuilderUnexpectItem {
        def pickHigher(other: BuilderUnexpectItem): BuilderUnexpectItem
        protected [TrivialErrorBuilder] def pickRaw(other: Raw): BuilderUnexpectItem
        protected [TrivialErrorBuilder] def pickOther(other: Other): Other
        protected [TrivialErrorBuilder] def pickNoItem(other: NoItem): BuilderUnexpectItem
        def toErrorItem(offset: Int)(implicit builder: ErrorItemBuilder): Either[Int, UnexpectItem]
    }
    private [TrivialErrorBuilder] final class Raw(val size: Int) extends BuilderUnexpectItem {
        assume(size > 0, "Raw should not try and represent 0-width tokens")
        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.pickRaw(this)
        final override def pickNoItem(other: NoItem): Raw = if (this.size >= other.width) this else new Raw(other.width)
        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 = {
            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 = {
            if (underlying.isFlexible) new Other(underlying.widen(other.width))
            else this
        }
        def toErrorItem(offset: Int)(implicit builder: ErrorItemBuilder): Either[Int, UnexpectItem] = Right(underlying)
    }
    private [TrivialErrorBuilder] class NoItem(val width: Int) extends BuilderUnexpectItem {
        assume(width >= 0, "NoItem should not try and represent negative tokens")
        final def pickHigher(other: BuilderUnexpectItem): BuilderUnexpectItem = other.pickNoItem(this)
        final override def pickRaw(other: Raw): Raw = other.pickNoItem(this)
        final override def pickOther(other: Other): Other = other.pickNoItem(this)
        final override def pickNoItem(other: NoItem): NoItem = if (this.width > other.width) this else other
        def toErrorItem(offset: Int)(implicit builder: ErrorItemBuilder): Either[Int, UnexpectItem] = Left(width)
    }
}

/** When building a true `FancyError` from a `FancyDefuncError`, this builder is used to keep track of the
  * components that will make up the final error. This allows for more efficient construction of the object,
  * as it can internally use mutable collections, before finally freezing them into an immutable form.
  *
  * @param offset the offset that the error being built occured at
  */
private [errors] final class FancyErrorBuilder(presentationOffset: Int) {
    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.
      *
      * @param line the new line number
      * @param col the new column number
      */
    def pos_=(line: Int, col: Int): Unit = {
        this.line = line
        this.col = col
    }

    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.
      *
      * @param msgs the messages to add
      */
    def ++=(msgs: Seq[String]): Unit = this.msgs ++= msgs
    /** Builds the final error. */
    def mkError: FancyError = {
        new FancyError(presentationOffset, line, col, msgs.toList.distinct, caretWidth)
    }
}

/** This collects up hints generated by the ghosts of old error messages,
  * which are usually piped directly into a builder's expected messages.
  *
  * @param hints the place to collect the items into
  */
private [errors] final class HintCollector(hints: mutable.Set[ExpectItem]) {
    /** Creates a new collector that starts empty */
    def this() = this(mutable.Set.empty)

    private var width = Option.empty[Int]

    /** Adds a new hint into the collector. */
    def +=(hint: ExpectItem): Unit = this.hints += hint
    /** Adds several hints into the collector. */
    def ++=(hints: Iterable[ExpectItem]): Unit = this.hints ++= hints

    /** Gets the width of the unexpected token that may have accompanied the original hints */
    def unexpectWidth: Option[Int] = width
    /** Updates the width of the unexpected token that may have accompanied these hints: this
      * can only get wider.
      */
    def updateWidth(sz: Int): Unit = width = Some(Math.max(width.getOrElse(0), sz))

    /** Generates an immutable snapshot of this collector */
    def mkSet: Set[ExpectItem] = this.hints.toSet
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy