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

laika.parse.text.PrefixedParser.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.text

import cats.data.NonEmptySet
import laika.parse.builders.~
import laika.parse.{ Parsed, Parser, SourceCursor, SourceFragment }

/** A parser that is associated with a non-empty set of trigger
  * characters for performance optimizations.
  *
  * There is usually no need to create such a parser manually,
  * as some of the basic building blocks in `TextParsers` create such a parser
  * (e.g. the `literal`, `oneOf` or `someOf` parsers).
  *
  * This set only has only an effect when this parser is used
  * in an optimized parser for recursive spans,
  * meaning it is either registered as a top-level parser
  * (with `SpanParser.standalone` or `SpanParser.recursive`)
  * or passed to a custom span parser with `InlineParser.embed`.
  * In all other use cases this parser behaves just like plain parser.
  *
  * @author Jens Halm
  */
trait PrefixedParser[+T] extends Parser[T] { self =>

  /** The set of trigger characters that can start this parser.
    */
  def startChars: NonEmptySet[Char]

  /** The underlying parser that may be optimized based on the specified
    * start characters.
    */
  def underlying: Parser[T]

  def parse(in: SourceCursor): Parsed[T] = underlying.parse(in)

  override def ~ [U](p: Parser[U]): PrefixedParser[T ~ U] = PrefixedParser(startChars)(super.~(p))
  override def ~> [U](p: Parser[U]): PrefixedParser[U]    = PrefixedParser(startChars)(super.~>(p))
  override def <~ [U](p: Parser[U]): PrefixedParser[T]    = PrefixedParser(startChars)(super.<~(p))

  override def ~ (value: String): PrefixedParser[T ~ String] = this.~(TextParsers.literal(value))
  override def ~> (value: String): PrefixedParser[String]    = this.~>(TextParsers.literal(value))
  override def <~ (value: String): PrefixedParser[T]         = this.<~(TextParsers.literal(value))

  override def flatMap[U](f: T => Parser[U]): PrefixedParser[U] =
    PrefixedParser(startChars)(super.flatMap(f))

  override def >> [U](fq: T => Parser[U]): PrefixedParser[U] =
    PrefixedParser(startChars)(super.flatMap(fq))

  override def map[U](f: T => U): PrefixedParser[U] = PrefixedParser(startChars)(super.map(f))
  override def ^^ [U](f: T => U): PrefixedParser[U] = PrefixedParser(startChars)(super.map(f))
  override def as[U](v: => U): PrefixedParser[U]    = PrefixedParser(startChars)(super.as(v))

  override def evalMap[U](f: T => Either[String, U]): PrefixedParser[U] =
    PrefixedParser(startChars)(super.evalMap(f))

  override def collect[U, V >: T](
      f: PartialFunction[T, U],
      error: V => String = (r: V) => s"Constructor function not defined at $r"
  ): PrefixedParser[U] =
    PrefixedParser(startChars)(super.collect(f, error))

  /**  Applies the specified parser when this parser fails.
    *
    *  `a.orElse(b)` succeeds if either of the parsers succeeds.
    *
    *  This is a specialized variant of the `orElse` method of the
    *  base trait that preserves the nature of the `PrefixedParser`
    *  if both original parsers implement this trait.
    */
  def orElse[U >: T](p: => PrefixedParser[U]): PrefixedParser[U] =
    PrefixedParser(startChars ++ p.startChars)(super.orElse(p))

  /**  Applies the specified parser when this parser fails.
    *
    *  `a | b` succeeds if either of the parsers succeeds.
    *
    *  This is a specialized variant of the `|` method of the
    *  base trait that preserves the nature of the `PrefixedParser`
    *  if both original parsers implement this trait.
    */
  def | [U >: T](p: => PrefixedParser[U]): PrefixedParser[U] =
    PrefixedParser(startChars ++ p.startChars)(super.orElse(p))

  override def | (value: String)(implicit ev: T <:< String): PrefixedParser[String] =
    map(ev).orElse(TextParsers.literal(value))

  override def withCursor: PrefixedParser[(T, SourceFragment)] =
    PrefixedParser(startChars)(super.withCursor)

  override def cursor: PrefixedParser[SourceFragment] = PrefixedParser(startChars)(super.cursor)

  override def source: PrefixedParser[String] = PrefixedParser(startChars)(super.source)

}

/** Factories and utilities for creating or processing PrefixedParser instances.
  */
object PrefixedParser {

  import cats.syntax.all._

  /** Creates a new parser that is only triggered when a character in the specified
    * set is seen on the input.
    */
  def apply[U](sc: NonEmptySet[Char])(p: Parser[U]): PrefixedParser[U] = new PrefixedParser[U] {
    def startChars: NonEmptySet[Char] = sc
    override def underlying           = p
  }

  /** Creates a new parser that is only triggered when one of the specified characters
    * is seen on the input.
    */
  def apply[U](char: Char, chars: Char*)(p: Parser[U]): PrefixedParser[U] = new PrefixedParser[U] {
    def startChars: NonEmptySet[Char] = NonEmptySet.of(char, chars: _*)
    override def underlying           = p
  }

  /** Creates a mapping from start characters to their corresponding parser
    * from the specified sequence of PrefixedParsers.
    * If a character is a trigger for more than one parser they will be combined using `orElse`
    * where the parser which comes first in the sequence has higher precedence.
    */
  def mapAndMerge[T](parsers: Seq[PrefixedParser[T]]): Map[Char, Parser[T]] = parsers
    .flatMap { parserDef =>
      parserDef.startChars.toList.map(c => (c, parserDef))
    }
    .groupBy(_._1)
    .map { case (char, definitions) =>
      (char, definitions.map(_._2).reduceLeft(_ | _))
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy