parsley.internal.deepembedding.AlternativeEmbedding.scala Maven / Gradle / Ivy
The newest version!
package parsley.internal.deepembedding
import ContOps.{result, ContAdapter}
import parsley.internal.machine.instructions
import parsley.internal.errors.{ErrorItem, Raw, Desc}
import scala.annotation.tailrec
import scala.collection.mutable
import scala.language.higherKinds
// TODO: Tablification is too aggressive. It appears that `optional` is being compiled to jumptable
private [parsley] final class <|>[A, B](_p: =>Parsley[A], _q: =>Parsley[B]) extends Binary[A, B, B](_p, _q)((l, r) => s"($l <|> $r)", <|>.empty) {
override val numInstrs = 3
override def optimise: Parsley[B] = (left, right) match {
// left catch law: pure x <|> p = pure x
case (u: Pure[B @unchecked], _) => u
// alternative law: empty <|> p = p
case (Empty, v) => v
// alternative law: p <|> empty = p
case (u: Parsley[B @unchecked], Empty) => u
// associative law: (u <|> v) <|> w = u <|> (v <|> w)
case ((u: Parsley[T]) <|> (v: Parsley[A @unchecked]), w) =>
left = u.asInstanceOf[Parsley[A]]
right = <|>[A, B](v, w).optimise
this
case _ => this
}
// TODO: Refactor
override def codeGen[Cont[_, +_], R](implicit ops: ContOps[Cont, R], instrs: InstrBuffer, state: CodeGenState): Cont[R, Unit] = tablify(this, Nil) match {
// If the tablified list is single element, that implies that this should be generated as normal!
case _::Nil => left match {
case Attempt(u) => right match {
case Pure(x) =>
val handler = state.freshLabel()
instrs += new instructions.PushHandlerAndState(handler, true, false)
u.codeGen |> {
instrs += new instructions.Label(handler)
instrs += new instructions.AlwaysRecoverWith[B](x)
}
case v =>
val handler = state.freshLabel()
val skip = state.freshLabel()
val merge = state.freshLabel()
instrs += new instructions.PushHandlerAndState(handler, true, false)
u.codeGen >> {
instrs += new instructions.Label(handler)
instrs += new instructions.JumpGoodAttempt(skip, merge)
v.codeGen |> {
instrs += new instructions.Label(merge)
instrs += instructions.MergeErrors
instrs += new instructions.Label(skip)
}
}
}
case u => right match {
case Pure(x) =>
val handler = state.freshLabel()
val skip = state.freshLabel()
instrs += new instructions.InputCheck(handler, true)
u.codeGen |> {
instrs += new instructions.JumpGood(skip)
instrs += new instructions.Label(handler)
instrs += new instructions.RecoverWith[B](x)
instrs += new instructions.Label(skip)
}
case v =>
val handler = state.freshLabel()
val skip = state.freshLabel()
val merge = state.freshLabel()
instrs += new instructions.InputCheck(handler, true)
u.codeGen >> {
instrs += new instructions.JumpGood(skip)
instrs += new instructions.Label(handler)
instrs += new instructions.Catch(merge)
v.codeGen |> {
instrs += new instructions.Label(merge)
instrs += instructions.MergeErrors
instrs += new instructions.Label(skip)
}
}
}
}
// In case of None'd list, the codeGen cont continues by codeGenning that p, else we are done for this tree, call cont!
case tablified =>
// This list is backwards :)
val needsDefault = tablified.head._2.isDefined
val end = state.freshLabel()
val default = state.freshLabel()
val merge = state.freshLabel()
val (roots, leads, ls, size, expecteds) = foldTablified(tablified, state, mutable.Map.empty, Nil, Nil, 0, mutable.Set.empty)
//println(leads, tablified)
instrs += new instructions.JumpTable(leads, ls, default, merge, size, expecteds)
codeGenRoots(roots, ls, end) >> {
instrs += new instructions.Catch(merge) //This instruction is reachable as default - 1
instrs += new instructions.Label(default)
if (needsDefault) {
instrs += instructions.Empty
instrs += new instructions.Label(merge)
instrs += instructions.MergeErrors
result(instrs += new instructions.Label(end))
}
else {
tablified.head._1.codeGen |> {
instrs += new instructions.Label(merge)
instrs += instructions.MergeErrors
instrs += new instructions.Label(end)
}
}
}
}
def codeGenRoots[Cont[_, +_], R](roots: List[List[Parsley[_]]], ls: List[Int], end: Int)
(implicit ops: ContOps[Cont, R], instrs: InstrBuffer, state: CodeGenState): Cont[R, Unit] = roots match {
case root::roots_ =>
instrs += new instructions.Label(ls.head)
codeGenAlternatives(root) >> {
instrs += new instructions.JumpGood(end)
codeGenRoots(roots_, ls.tail, end)
}
case Nil => result(())
}
def codeGenAlternatives[Cont[_, +_], R](alts: List[Parsley[_]])
(implicit ops: ContOps[Cont, R], instrs: InstrBuffer, state: CodeGenState): Cont[R, Unit] = (alts: @unchecked) match {
case alt::Nil => alt.codeGen
case Attempt(alt)::alts_ =>
val handler = state.freshLabel()
val skip = state.freshLabel()
val merge = state.freshLabel()
instrs += new instructions.PushHandlerAndState(handler, true, false)
alt.codeGen >> {
instrs += new instructions.Label(handler)
instrs += new instructions.JumpGoodAttempt(skip, merge)
codeGenAlternatives(alts_) |> {
instrs += new instructions.Label(merge)
instrs += instructions.MergeErrors
instrs += new instructions.Label(skip)
}
}
case alt::alts_ =>
val handler = state.freshLabel()
val skip = state.freshLabel()
val merge = state.freshLabel()
instrs += new instructions.InputCheck(handler, true)
alt.codeGen >> {
instrs += new instructions.JumpGood(skip)
instrs += new instructions.Label(handler)
instrs += new instructions.Catch(merge)
codeGenAlternatives(alts_) |> {
instrs += new instructions.Label(merge)
instrs += instructions.MergeErrors
instrs += new instructions.Label(skip)
}
}
}
// TODO: Refactor
@tailrec def foldTablified(tablified: List[(Parsley[_], Option[Parsley[_]])], labelGen: CodeGenState,
roots: mutable.Map[Char, List[Parsley[_]]],
leads: List[Char],
labels: List[Int],
size: Int,
expecteds: mutable.Set[ErrorItem]):
(List[List[Parsley[_]]], List[Char], List[Int], Int, Set[ErrorItem]) = tablified match {
case (_, None)::tablified_ => foldTablified(tablified_, labelGen, roots, leads, labels, size, expecteds)
case (root, Some(lead))::tablified_ =>
val (c: Char, expected: ErrorItem, _size: Int) = lead match {
case ct@CharTok(d) => (d, ct.expected.fold[ErrorItem](Raw(d))(Desc(_)), 1)
case st@StringTok(s) => (s.head, st.expected.fold[ErrorItem](Raw(s))(Desc(_)), s.size)
case st@Specific(s) => (s.head, Desc(s), s.size)
case op@MaxOp(o) => (o.head, Desc(o), o.size)
case sl: StringLiteral => ('"', Desc("string"), 1)
case RawStringLiteral => ('"', Desc("string"), 1)
}
expecteds += expected
if (roots.contains(c)) {
roots(c) = root::roots(c)
foldTablified(tablified_, labelGen, roots, leads, labelGen.freshLabel() :: labels, Math.max(size, _size), expecteds)
}
else {
roots(c) = root::Nil
foldTablified(tablified_, labelGen, roots, c::leads, labelGen.freshLabel() :: labels, Math.max(size, _size), expecteds)
}
case Nil => (leads.map(roots(_)), leads, labels, size, expecteds.toSet)
}
@tailrec private def tablable(p: Parsley[_]): Option[Parsley[_]] = p match {
// CODO: Numeric parsers by leading digit (This one would require changing the foldTablified function a bit)
case t@(_: CharTok | _: StringTok | _: Specific | _: StringLiteral | RawStringLiteral | _: MaxOp) => Some(t)
case Attempt(t) => tablable(t)
case (_: Pure[_]) <*> t => tablable(t)
case Lift2(_, t, _) => tablable(t)
case Lift3(_, t, _, _) => tablable(t)
case t <*> _ => tablable(t)
case t *> _ => tablable(t)
case t <* _ => tablable(t)
case _ => None
}
@tailrec private [deepembedding] def tablify(p: Parsley[_], acc: List[(Parsley[_], Option[Parsley[_]])]): List[(Parsley[_], Option[Parsley[_]])] = p match {
case u <|> v =>
val leading = tablable(u)
if (leading.isDefined) tablify(v, (u, leading)::acc)
else (p, None)::acc
case _ => (p, tablable(p))::acc
}
}
private [parsley] object Empty extends Singleton[Nothing]("empty", instructions.Empty) with MZero
private [deepembedding] object <|> {
def empty[A, B]: A <|> B = new <|>(???, ???)
def apply[A, B](left: Parsley[A], right: Parsley[B]): A <|> B = empty.ready(left, right)
def unapply[A, B](self: A <|> B): Some[(Parsley[A], Parsley[B])] = Some((self.left, self.right))
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy