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

laika.parse.Parsed.scala Maven / Gradle / Ivy

/*
 * Copyright 2012-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package laika.parse

/** Represents the result of a `Parser`, a value of type `T` in case of success,
  * a message in case of failure as well as the `SourceCursor` for the remaining input.
  *
  *  @author Jens Halm
  */
sealed abstract class Parsed[+T] {

  /** The context representing the remaining input
    * left over by the parser that produced this result.
    */
  val next: SourceCursor

  /** Indicates whether this results represents a successful
    * parser invocation.
    */
  val isSuccess: Boolean

  /** Indicates whether this results represents an unsuccessful
    * parser invocation.
    */
  def isFailure: Boolean = !isSuccess

  /** The result as an Option, empty in case of failure.
    */
  def toOption: Option[T]

  /** The result as an Either, a Left in case of failure.
    */
  def toEither: Either[String, T]

  /** Returns the result value from the parser invocation if the
    * parser succeeded or otherwise the specified fallback value.
    */
  def getOrElse[B >: T](default: => B): B = toOption.getOrElse(default)

  /** Returns this `Parsed` instance if the parser succeeded or
    * otherwise the specified fallback instance.
    */
  def orElse[U >: T](default: => Parsed[U]): Parsed[U]

  /** Builds a new `Parsed` instance by applying the specified function
    * to the result of this instance.
    */
  def map[U](f: T => U): Parsed[U]

}

/** The success case of `Parsed` containing the result and the remaining input.
  */
case class Success[+T](result: T, next: SourceCursor) extends Parsed[T] {

  val isSuccess = true

  def toOption: Option[T] = Some(result)

  def toEither: Either[String, T] = Right(result)

  def orElse[U >: T](default: => Parsed[U]): Parsed[U] = this

  def map[U](f: T => U) = Success(f(result), next)

  override def toString = s"[${next.position}] parsed: $result"

}

/**  The failure case of `Parsed` containing an error message and the remaining input.
  *
  *  Implementation note:
  *  The message property is of type `Message`, to allow for lazy message creation.
  *  The former SDK parser combinators which this API is partially inspired by contained
  *  a lot of unnecessary string concatenations for messages which were then never read.
  *  This implementation avoids this extra cost and the result is measurable
  *  (about 15% performance gain for a typical Markdown document for example).
  *
  *  @param msgProvider  A provider that produces an error message for this failure based on its SourceCursor
  *  @param next         The unconsumed input at the point where the failing parser started
  *  @param maxOffset    The offset position the parser could successfully read to before failing
  */
case class Failure(msgProvider: Message, next: SourceCursor, maxOffset: Int)
    extends Parsed[Nothing] {

  private lazy val failureContext = next.consume(maxOffset - next.offset)

  /** The message specifying the cause of the failure.
    */
  lazy val message: String = msgProvider.message(failureContext)

  val isSuccess = false

  def toOption: Option[Nothing] = None

  def toEither: Either[String, Nothing] = Left(message)

  def orElse[U >: Nothing](default: => Parsed[U]): Parsed[U] = default match {
    case s: Success[_] => s
    case f: Failure    => if (f.maxOffset > maxOffset) f else this
  }

  def map[U](f: Nothing => U): Failure = this

  override def toString: String = {
    val pointOfFailure = failureContext.position
    s"[$pointOfFailure] failure: $message\n\n${pointOfFailure.lineContentWithCaret}"
  }

}

object Failure {

  @inline def apply(msgProvider: Message, next: SourceCursor): Failure =
    apply(msgProvider, next, next.offset)

}

/** Represents a lazy failure message.
  * Implementations can use the specified `SourceCursor` to construct
  * the actual message, e.g. for adding parts of the input to the message.
  */
trait Message {

  def message(source: SourceCursor): String

}

/** Message companion providing several pre-built messages
  * and factory methods.
  */
object Message {

  val UnexpectedEOF: Message = fixed("Unexpected end of input")

  val ExpectedFailure: Message = fixed("Expected failure, but parser succeeded")

  val ExpectedEOF: Message = fixed("Expected end of input")

  val ExpectedStart: Message = fixed("Expected start of input")

  val ExpectedEOL: Message = fixed("Expected end of line")

  private class MessageFactory[T](f: T => String) extends (T => Message) {

    def apply(value: T): Message = new Message {

      def message(source: SourceCursor): String = f(value)

    }

  }

  /** Builds a message instance for a fixed string,
    * independent of the parser context.
    */
  def fixed(msg: String): Message = new Message {

    def message(source: SourceCursor): String = msg

  }

  /** Builds a message instance for the specified factory function.
    */
  def forContext(f: SourceCursor => String): Message = new Message {
    def message(source: SourceCursor): String = f(source)
  }

  /** Builds a factory function that produces new messages
    * based on some arbitrary input type.
    * This allows to pre-capture some context for the message
    * that does not relate to the parser context.
    */
  def forRuntimeValue[T](f: T => String): T => Message = new MessageFactory(f)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy