parsley.expr.precedence.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2020 Parsley Contributors
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package parsley.expr
import parsley.Parsley
import parsley.combinator.choice
/** This object is used to construct precedence parsers from either a `Prec` or many `Ops[A, A]`.
*
* Contained within this object are three different shapes of `apply` functions: they allows for
* the construction of a precedence parser from either: a collection of levels of operators and atoms,
* in either weak-to-strong or strong-to-weak orderings; or a heterogeneous precedence table [[Prec `Prec`]].
*
* @since 2.2.0
* @group Precedence
*/
object precedence {
/** This combinator builds an expression parser given a collection of homogeneous atoms and operators.
*
* An expression parser will be formed by parsing `atom0` and the remaining `atoms` at
* the base of the table. Then `lvlTightest` will be the level containing the tightest
* operators. The remaining `lvls` will be from tightest-to-weakest binding. All levels
* must consume and produce the same type.
*
* @example {{{
* scala> import parsley.Parsley, parsley.character.{char, digit}
* scala> import parsley.expr.{Ops, InfixL, precedence}
* scala> val expr = precedence(digit.map(_.asDigit))
* (Ops(InfixL)(char('*').as(_ * _)),
* Ops(InfixL)(char('+').as(_ + _), char('-').as(_ - _)))
* scala> expr.parse("1+8*7+4")
* val res0 = Success(61)
* }}}
*
* @tparam A the type of the expression parsers generated by this combinator.
* @param atom0 the first atom at the base of the table.
* @param atoms the remaining atoms at the base of the table.
* @param lvlTightest the tightest binding operators.
* @param lvls the remaining levels of operators, ordered '''tightest-to-weakest'''.
* @return an expression parser for the described precedence table.
* @since 3.0.0
*/
def apply[A](atom0: Parsley[A], atoms: Parsley[A]*)(lvlTightest: Ops[A, A], lvls: Ops[A, A]*): Parsley[A] = {
apply(lvls.foldLeft[Prec[A]](new Level(Atoms(atom0, atoms: _*), lvlTightest))(new Level(_, _)))
}
/** This combinator builds an expression parser given a collection of homogeneous atoms and operators.
*
* An expression parser will be formed by parsing `atom0` and the remaining `atoms` at
* the base of the table. Then `lvlWeakest` will be the level containing the weakest
* operators at the outermost layer of the table. The remaining `lvls` will be from
* tightest-to-weakest binding. All levels must consume and produce the same type.
*
* @example {{{
* scala> import parsley.Parsley, parsley.character.{char, digit}
* scala> import parsley.expr.{Ops, InfixL, precedence}
* scala> val expr = precedence[Int](Ops(InfixL)(char('+').as(_ + _), char('-').as(_ - _))),
* Ops(InfixL)(char('*').as(_ * _))
* (digit.map(_.asDigit)))
* scala> expr.parse("1+8*7+4")
* val res0 = Success(61)
* }}}
*
* Note that the type ascription on `precedence` is needed for this example, to avoid specifying
* the argument types of the operators in the table: this wouldn't be required with the tightest-to-weakest
* variant, as the inference is better on the atoms.
*
* @tparam A the type of the expression parsers generated by this combinator.
* @param lvlWeakest the weakest binding operators.
* @param lvls the remaining levels of operators, ordered '''weakest-to-tightest'''.
* @param atom0 the first atom at the base of the table.
* @param atoms the remaining atoms at the base of the table.
* @return an expression parser for the described precedence table.
* @since 4.0.0
*/
def apply[A](lvlWeakest: Ops[A, A], lvls: Ops[A, A]*)(atom0: Parsley[A], atoms: Parsley[A]*): Parsley[A] = {
val (lvlTightest +: lvls_) = (lvlWeakest +: lvls).reverse: @unchecked
apply(atom0, atoms: _*)(lvlTightest, lvls_ : _*)
}
/** This combinator builds an expression parser given a heterogeneous precedence table.
*
* An expression parser will be formed by collapsing the given precedence table
* layer-by-layer. Since this table is heterogeneous, each level of the table
* produces a difference type, which is then consumed by the next level above.
*
* @example This is overkill for this particular example, as each layer has the same type:
* it would be better to use one of the other forms of `precedence` for simplicity. This
* is best used in conjunction with `SOps` or `GOps`; or a mix of `SOps`, `GOps`, and `Ops`.
* {{{
* scala> import parsley.Parsley, parsley.character.{char, digit}
* scala> import parsley.expr.{Atoms, Ops, InfixL, precedence}
* scala> val expr = precedence(Atoms(digit.map(_.asDigit)) :+
* Ops[Int](InfixL)(char('*').as(_ * _)) :+
* Ops[Int](InfixL)(char('+').as(_ + _), char('-').as(_ - _)))
* scala> expr.parse("1+8*7+4")
* val res0 = Success(61)
* }}}
*
* @tparam A the type of the expression parsers generated by this combinator.
* @param table the description of the heterogeneous table, where each level can vary in output and input types.
* @return an expression parser for the described precedence table.
* @see [[Prec `Prec`]] and its subtypes for a description of how the types work.
* @since 4.0.0
*/
def apply[A](table: Prec[A]): Parsley[A] = crushLevels(table)
private def crushLevels[A](lvls: Prec[A]): Parsley[A] = lvls match {
case Atoms(atom0, atoms @ _*) => choice((atom0 +: atoms): _*)
case Level(lvls, ops) => ops.chain(crushLevels(lvls))
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy