Skip to content

Commit

Permalink
Removed the type casts within the expr machinery (#173)
Browse files Browse the repository at this point in the history
* Removed the asInstanceOf by allowing the Fixity to construct the Ops with no pattern matching

* Further improved by removing pattern matches
  • Loading branch information
j-mie6 authored Mar 2, 2023
1 parent 6ab0f51 commit defdf31
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 34 deletions.
10 changes: 9 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ inThisBuild(List(
ProblemFilters.exclude[DirectMissingMethodProblem]("parsley.errors.combinator#ErrorMethods.unexpected"),
ProblemFilters.exclude[MissingClassProblem]("parsley.token.errors.FilterOps"),
ProblemFilters.exclude[MissingClassProblem]("parsley.token.errors.FilterOps$"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("parsley.token.predicate#CharPredicate.asInternalPredicate")
ProblemFilters.exclude[ReversedMissingMethodProblem]("parsley.token.predicate#CharPredicate.asInternalPredicate"),
// Expression refactor
ProblemFilters.exclude[ReversedMissingMethodProblem]("parsley.expr.Fixity.chain"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("parsley.expr.Ops.chain"),
ProblemFilters.exclude[MissingClassProblem]("parsley.expr.Lefts*"),
ProblemFilters.exclude[MissingClassProblem]("parsley.expr.Rights*"),
ProblemFilters.exclude[MissingClassProblem]("parsley.expr.NonAssocs*"),
ProblemFilters.exclude[MissingClassProblem]("parsley.expr.Prefixes*"),
ProblemFilters.exclude[MissingClassProblem]("parsley.expr.Postfixes*"),
),
tlVersionIntroduced := Map(
"2.13" -> "1.5.0",
Expand Down
8 changes: 8 additions & 0 deletions parsley/shared/src/main/scala/parsley/expr/Fixity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
package parsley.expr

import parsley.Parsley

/**
* Denotes the fixity and associativity of an operator. Importantly, it also specifies the type of the
* of the operations themselves.
Expand All @@ -11,6 +13,7 @@ package parsley.expr
*/
sealed trait Fixity {
type Op[A, B]
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B]
}

/**
Expand All @@ -20,6 +23,7 @@ sealed trait Fixity {
*/
case object InfixL extends Fixity {
override type Op[-A, B] = (B, A) => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.left1(p, op)
}

/**
Expand All @@ -29,6 +33,7 @@ case object InfixL extends Fixity {
*/
case object InfixR extends Fixity {
override type Op[-A, B] = (A, B) => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.right1(p, op)
}

/**
Expand All @@ -38,6 +43,7 @@ case object InfixR extends Fixity {
*/
case object Prefix extends Fixity {
override type Op[A, B] = B => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.prefix(op, p)
}

/**
Expand All @@ -47,6 +53,7 @@ case object Prefix extends Fixity {
*/
case object Postfix extends Fixity {
override type Op[A, B] = B => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.postfix(p, op)
}

/**
Expand All @@ -56,4 +63,5 @@ case object Postfix extends Fixity {
*/
case object InfixN extends Fixity {
override type Op[-A, +B] = (A, A) => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.nonassoc(p, op)
}
11 changes: 5 additions & 6 deletions parsley/shared/src/main/scala/parsley/expr/Ops.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,8 @@ import parsley.Parsley
* @group Table
*/
sealed abstract class Ops[-A, B] {
private [expr] val wrap: A => B
private [expr] def chain(p: Parsley[A]): Parsley[B]
}
private [expr] case class Lefts[A, B](ops: Parsley[InfixL.Op[A, B]]*)(implicit override val wrap: A => B) extends Ops[A, B]
private [expr] case class Rights[A, B](ops: Parsley[InfixR.Op[A, B]]*)(implicit override val wrap: A => B) extends Ops[A, B]
private [expr] case class Prefixes[A, B](ops: Parsley[Prefix.Op[A, B]]*)(implicit override val wrap: A => B) extends Ops[A, B]
private [expr] case class Postfixes[A, B](ops: Parsley[Postfix.Op[A, B]]*)(implicit override val wrap: A => B) extends Ops[A, B]
private [expr] case class NonAssocs[A, B](ops: Parsley[InfixN.Op[A, B]]*)(implicit override val wrap: A => B) extends Ops[A, B]

/** This helper object is used to build values of `Ops[A, A]`, for homogeneous precedence parsing.
*
Expand All @@ -47,4 +42,8 @@ object Ops {
* @since 2.2.0
*/
def apply[A](fixity: Fixity)(ops: Parsley[fixity.Op[A, A]]*): Ops[A, A] = GOps[A, A](fixity)(ops: _*)

private [expr] def apply[A, B](fixity: Fixity)(op: Parsley[fixity.Op[A, B]])(implicit wrap: A => B): Ops[A, B] = new Ops[A, B] {
private [expr] def chain(p: Parsley[A]): Parsley[B] = fixity.chain(p, op)
}
}
9 changes: 2 additions & 7 deletions parsley/shared/src/main/scala/parsley/expr/SmartOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package parsley.expr

import parsley.Parsley
import parsley.combinator.choice

/** This helper object builds values of `Ops[A, B]`, for fully heterogeneous precedence parsing.
*
Expand Down Expand Up @@ -31,13 +32,7 @@ object GOps {
* @note currently a bug in scaladoc incorrect displays this functions type, it should be: `fixity.Op[A, B]`, NOT `Op[A, B]`.
* @since 2.2.0
*/
def apply[A, B](fixity: Fixity)(ops: Parsley[fixity.Op[A, B]]*)(implicit wrap: A => B): Ops[A, B] = fixity match {
case InfixL => Lefts[A, B](ops.asInstanceOf[Seq[Parsley[InfixL.Op[A, B]]]]: _*)
case InfixR => Rights[A, B](ops.asInstanceOf[Seq[Parsley[InfixR.Op[A, B]]]]: _*)
case Prefix => Prefixes[A, B](ops.asInstanceOf[Seq[Parsley[Prefix.Op[A, B]]]]: _*)
case Postfix => Postfixes[A, B](ops.asInstanceOf[Seq[Parsley[Postfix.Op[A, B]]]]: _*)
case InfixN => NonAssocs[A, B](ops.asInstanceOf[Seq[Parsley[InfixN.Op[A, B]]]]: _*)
}
def apply[A, B](fixity: Fixity)(ops: Parsley[fixity.Op[A, B]]*)(implicit wrap: A => B): Ops[A, B] = Ops[A, B](fixity)(choice(ops: _*))(wrap)
}

/** This helper object builds values of `Ops[A, B]` where `A <: B`, for subtyped heterogeneous precedence parsing.
Expand Down
14 changes: 13 additions & 1 deletion parsley/shared/src/main/scala/parsley/expr/infix.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ package parsley.expr

import scala.annotation.implicitNotFound

import parsley.Parsley
import parsley.Parsley, Parsley.notFollowedBy
import parsley.errors.combinator.ErrorMethods
import parsley.implicits.zipped.Zipped2

import parsley.internal.deepembedding.frontend

Expand Down Expand Up @@ -174,4 +176,14 @@ object infix {
*/
def left[A, B, C >: B](p: Parsley[A], op: =>Parsley[(C, A) => B], x: C)
(implicit @implicitNotFound("Please provide a wrapper function from ${A} to ${C}") wrap: A => C): Parsley[C] = left1(p, op).getOrElse(x)

// Private Helpers (maybe expose these in future?)
private [expr] def prefix[A, B](op: Parsley[B => B], p: Parsley[A])(implicit wrap: A => B): Parsley[B] =
chain.prefix(op, parsley.XCompat.applyWrap(wrap)(p))
private [expr] def postfix[A, B](p: Parsley[A], op: Parsley[B => B])(implicit wrap: A => B): Parsley[B] =
chain.postfix(parsley.XCompat.applyWrap(wrap)(p), op)
private [expr] def nonassoc[A, B](p: Parsley[A], op: Parsley[(A, A) => B])(implicit wrap: A => B): Parsley[B] = {
val guardNonAssoc = notFollowedBy(op).explain("non-associative operators cannot be chained together")
p <**> ((op, p).zipped((f, y) => f(_, y)) </> wrap) <* guardNonAssoc
}
}
21 changes: 2 additions & 19 deletions parsley/shared/src/main/scala/parsley/expr/precedence.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
*/
package parsley.expr

import parsley.Parsley, Parsley.notFollowedBy
import parsley.Parsley
import parsley.combinator.choice
import parsley.errors.combinator.ErrorMethods
import parsley.implicits.zipped.Zipped2

/** This object is used to construct precedence parsers from either a `Prec` or many `Ops[A, A]`.
*
Expand Down Expand Up @@ -108,24 +106,9 @@ object precedence {
*/
def apply[A](table: Prec[A]): Parsley[A] = crushLevels(table)

private def convertOperators[A, B](atom: Parsley[A], opList: Ops[A, B]): Parsley[B] = {
implicit val wrap: A => B = opList.wrap
opList match {
case Lefts(ops @ _*) => infix.left1(atom, choice(ops: _*))
case Rights(ops @ _*) => infix.right1(atom, choice(ops: _*))
case Prefixes(ops @ _*) => chain.prefix(choice(ops: _*), parsley.XCompat.applyWrap(wrap)(atom))
// FIXME: Postfix operators which are also binary ops may fail, how can we work around this?
case Postfixes(ops @ _*) => chain.postfix(parsley.XCompat.applyWrap(wrap)(atom), choice(ops: _*))
case NonAssocs(ops @ _*) =>
val op = choice(ops: _*)
val guardNonAssoc = notFollowedBy(op).explain("non-associative operators cannot be chained together")
atom <**> ((op, atom).zipped((f, y) => f(_, y)) </> wrap) <* guardNonAssoc
}
}

private def crushLevels[A](lvls: Prec[A]): Parsley[A] = lvls match {
case Atoms(atom0, atoms @ _*) => choice((atom0 +: atoms): _*)
case Level(lvls, ops) => convertOperators(crushLevels(lvls), ops)
case Level(lvls, ops) => ops.chain(crushLevels(lvls))
}

}

0 comments on commit defdf31

Please sign in to comment.