Skip to content

Commit 8374aa9

Browse files
committed
feat: Flatten Choice chains across Let boundaries where possible
1 parent f22e610 commit 8374aa9

11 files changed

Lines changed: 54 additions & 22 deletions

File tree

parsley/shared/src/main/scala/parsley/internal/collection/mutable/SinglyLinkedList.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ private [internal] class SinglyLinkedList[A] private
7070
var current = start
7171
override val end = SinglyLinkedList.this.end
7272
}
73+
74+
def copy: SinglyLinkedList[A] = { val r = new SinglyLinkedList[A]; r.addAll(this); r }
7375
}
7476

7577
private [internal] object SinglyLinkedList {

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/AlternativeEmbedding.scala

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import parsley.XAssert.*
1212

1313
import parsley.internal.collection.mutable.SinglyLinkedList, SinglyLinkedList.LinkedListIterator
1414
import parsley.internal.deepembedding.ContOps, ContOps.{result, suspend, ContAdapter}
15+
import parsley.internal.deepembedding.frontend.LetMap
1516
import parsley.internal.deepembedding.singletons.*
1617
import parsley.internal.errors.{ExpectDesc, ExpectItem}
1718
import parsley.internal.machine.instructions
@@ -21,24 +22,23 @@ import Choice.*
2122
import StrictParsley.InstrBuffer
2223
// scalastyle:on underscore.import
2324

24-
// TODO: can we tabilify across a Let?
2525
// FIXME: It's annoying this doesn't work if the first thing is not tablable: let's make it more fine-grained to create groupings?
2626
private [deepembedding] final class Choice[A] private (private [backend] val alt1: StrictParsley[A],
2727
private [backend] var alt2: StrictParsley[A],
2828
private [backend] var alts: SinglyLinkedList[StrictParsley[A]]) extends StrictParsley[A] {
2929
def this(lalt: StrictParsley[A], ralt: StrictParsley[A]) = this(lalt, ralt, SinglyLinkedList.empty)
3030
def inlinable: Boolean = false
3131

32-
override def optimise: StrictParsley[A] = {
32+
override def optimise(implicit lets: LetMap): StrictParsley[A] = {
3333
// We make the assumption that nodes here are not reoptimised: as such, we can safely
3434
// assume that it is always in <|> form, with no alts on a choice (as this is the only public constructor)
3535
if (alts.nonEmpty) throw new IllegalStateException("<|> assumed, but full Choice given") // scalastyle:ignore throw
3636
if (alt2 eq Empty.Zero) alt1
3737
else alt1 match {
3838
case (u: Pure[?]) => u
3939
case Empty.Zero => alt2
40-
case ret@Choice(_, _, lalts: SinglyLinkedList[StrictParsley[A]] @unchecked) => alt2 match {
41-
case Choice(ralt1, ralt2, ralts: SinglyLinkedList[StrictParsley[A]] @unchecked) =>
40+
case FindChoice(ret@Choice(_, _, lalts: SinglyLinkedList[StrictParsley[A]] @unchecked)) => alt2 match {
41+
case FindChoice(Choice(ralt1, ralt2, ralts: SinglyLinkedList[StrictParsley[A]] @unchecked)) =>
4242
assume(!lalts.exists(_.isInstanceOf[Choice[?]]), "ralts can never contain a choice")
4343
assume(!ralts.exists(_.isInstanceOf[Choice[?]]), "lalts can never contain a choice")
4444
lalts.addOne(ralt1)
@@ -51,7 +51,7 @@ private [deepembedding] final class Choice[A] private (private [backend] val alt
5151
ret
5252
}
5353
case _ => alt2 match {
54-
case Choice(ralt1, ralt2, ralts: SinglyLinkedList[StrictParsley[A]] @unchecked) =>
54+
case FindChoice(Choice(ralt1, ralt2, ralts: SinglyLinkedList[StrictParsley[A]] @unchecked)) =>
5555
assume(!ralts.exists(_.isInstanceOf[Choice[?]]), "ralts can never contain a choice")
5656
this.alt2 = ralt1
5757
this.alts = ralts
@@ -346,6 +346,28 @@ private [backend] object Choice {
346346
case _ => false
347347
}
348348
}
349+
350+
private object FindChoice {
351+
def unapply[A](p: StrictParsley[A])(implicit lets: LetMap): Option[Choice[A]] = {
352+
@tailrec
353+
def go(p: StrictParsley[A], requiresCopy: Boolean = false): Option[Choice[A]] =
354+
p match {
355+
case Choice(alt1, alt2, alts: SinglyLinkedList[StrictParsley[A]] @unchecked) =>
356+
Some(new Choice(alt1, alt2, if (requiresCopy) alts.copy else alts))
357+
case sub: Let[?] =>
358+
val body = lets.findBody(sub)
359+
if (body.isDefined) {
360+
go(body.get.asInstanceOf[StrictParsley[A]], requiresCopy = true)
361+
} else {
362+
// Recursion point
363+
None
364+
}
365+
case _ => None
366+
}
367+
368+
go(p)
369+
}
370+
}
349371
}
350372

351373

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/ErrorEmbedding.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package parsley.internal.deepembedding.backend
77

88
import parsley.token.errors.Label
99

10+
import parsley.internal.deepembedding.frontend.LetMap
1011
import parsley.internal.deepembedding.singletons.*
1112
import parsley.internal.machine.instructions
1213

@@ -17,7 +18,7 @@ private [deepembedding] final class ErrorLabel[A](val p: StrictParsley[A], priva
1718
override def instrNeedsLabel: Boolean = false
1819
override def handlerLabel(state: CodeGenState): Int = state.getLabelForRelabelError(label +: labels)
1920
// don't need to be limited to not hidden when the thing can never internally generate hints
20-
final override def optimise: StrictParsley[A] = p match {
21+
final override def optimise(implicit lets: LetMap): StrictParsley[A] = p match {
2122
case CharTok(c, x) => new CharTok(c, x, Label(label, labels*)).asInstanceOf[StrictParsley[A]]
2223
case SupplementaryCharTok(c, x) => new SupplementaryCharTok(c, x, Label(label, labels*)).asInstanceOf[StrictParsley[A]]
2324
case StringTok(s, x) => new StringTok(s, x, Label(label, labels*)).asInstanceOf[StrictParsley[A]]

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/IterativeEmbedding.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import scala.collection.{mutable, Factory}
1010
import parsley.exceptions.NonProductiveIterationException
1111

1212
import parsley.internal.deepembedding.ContOps, ContOps.{ContAdapter, suspend}
13+
import parsley.internal.deepembedding.frontend.LetMap
1314
import parsley.internal.deepembedding.singletons.*
1415
import parsley.internal.machine.instructions
1516

1617
import StrictParsley.InstrBuffer
1718

1819
private [deepembedding] final class Many[A, C](val init: StrictParsley[mutable.Builder[A, C]], val p: StrictParsley[A]) extends Unary[A, C] {
19-
final override def optimise: StrictParsley[C] = p match {
20+
final override def optimise(implicit lets: LetMap): StrictParsley[C] = p match {
2021
case _: Pure[?] => throw new NonProductiveIterationException("many") // scalastyle:ignore throw
2122
case _ => this
2223
}
@@ -44,7 +45,7 @@ private [backend] object Many {
4445

4546
private [backend] sealed abstract class ChainLike[A](p: StrictParsley[A], op: StrictParsley[A => A]) extends StrictParsley[A] {
4647
def inlinable: Boolean = false
47-
override def optimise: StrictParsley[A] = op match {
48+
override def optimise(implicit lets: LetMap): StrictParsley[A] = op match {
4849
case _: Pure[?] => throw new NonProductiveIterationException("chain") // scalastyle:ignore throw
4950
case _: MZero => p
5051
case _ => this

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/PrecedenceEmbedding.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package parsley.internal.deepembedding.backend
77

88
import parsley.internal.deepembedding.ContOps
99
import parsley.internal.deepembedding.ContOps.{suspend, ContAdapter}
10+
import parsley.internal.deepembedding.frontend.LetMap
1011
import parsley.internal.deepembedding.singletons.Pure
1112
import parsley.internal.collection.mutable.SinglyLinkedList
1213
import parsley.internal.machine.instructions
@@ -47,7 +48,7 @@ private [deepembedding] final class Precedence[A] private (prefixAtomChoice: Str
4748

4849
private [deepembedding] final class StrictOp(val fixity: Fixity, val op: StrictParsley[Any], val prec: Int)
4950
private [deepembedding] object Precedence {
50-
def apply[A](vatoms: List[StrictParsley[Any]], vops: List[StrictOp], wraps: Array[Any => Any]): Precedence[A] = {
51+
def apply[A](vatoms: List[StrictParsley[Any]], vops: List[StrictOp], wraps: Array[Any => Any])(implicit lets: LetMap): Precedence[A] = {
5152
val maxLevel = wraps.length
5253
val atoms = unwrapChoices(vatoms).map(a => <*>(new Pure(r => new Atom(r, maxLevel)), a).optimise)
5354
val (prefixes, postfixInfixes) = vops.partition(_.fixity == Prefix)
@@ -56,7 +57,7 @@ private [deepembedding] object Precedence {
5657
new Precedence(prefixAtomChoice, postfixInfixChoice, buildPrecomputedWraps(wraps))
5758
}
5859

59-
private def buildChoiceNode[A](options: List[StrictParsley[A]]): StrictParsley[A] = options.map(_.optimise) match {
60+
private def buildChoiceNode[A](options: List[StrictParsley[A]])(implicit lets: LetMap): StrictParsley[A] = options.map(_.optimise) match {
6061
case Nil => new Fail(new FlexibleCaret(0))
6162
case p :: Nil => p
6263
case p1 :: p2 :: Nil => <|>(p1, p2)
@@ -68,7 +69,7 @@ private [deepembedding] object Precedence {
6869
case p => p :: Nil
6970
}
7071

71-
private def buildOpChoice(op: StrictOp): StrictParsley[Operator] = {
72+
private def buildOpChoice(op: StrictOp)(implicit lets: LetMap): StrictParsley[Operator] = {
7273
val opFn = op.fixity match {
7374
case InfixL => (x: Any) => new instructions.InfixLOp(x.asInstanceOf[(Any, Any) => Any], op.prec)
7475
case InfixR => (x: Any) => new instructions.InfixROp(x.asInstanceOf[(Any, Any) => Any], op.prec)

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/PrimitiveEmbedding.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import parsley.errors.ErrorBuilder
1010
import parsley.state.Ref
1111

1212
import parsley.internal.deepembedding.ContOps, ContOps.{ContAdapter, result, suspend}
13+
import parsley.internal.deepembedding.frontend.LetMap
1314
import parsley.internal.deepembedding.singletons.*
1415
import parsley.internal.machine.instructions
1516

@@ -20,7 +21,7 @@ private [deepembedding] final class Atomic[A](val p: StrictParsley[A]) extends S
2021
override val instr: instructions.Instr = instructions.PopHandlerAndState
2122
override def instrNeedsLabel: Boolean = false
2223
override def handlerLabel(state: CodeGenState): Int = state.getLabel(instructions.RestoreAndFail)
23-
override def optimise: StrictParsley[A] = p match {
24+
override def optimise(implicit lets: LetMap): StrictParsley[A] = p match {
2425
case p: CharTok[?] => p
2526
case p: Atomic[?] => p
2627
//case StringTok(s, _) if s.size == 1 => p

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/SelectiveEmbedding.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package parsley.internal.deepembedding.backend
77

88
import parsley.internal.deepembedding.ContOps, ContOps.{suspend, ContAdapter}
9+
import parsley.internal.deepembedding.frontend.LetMap
910
import parsley.internal.deepembedding.singletons.*
1011
import parsley.internal.machine.instructions
1112

@@ -47,7 +48,7 @@ private [backend] sealed abstract class BranchLike[A, B, C, D](finaliser: Option
4748
private [deepembedding] final class Branch[A, B, C](val b: StrictParsley[Either[A, B]], val p: StrictParsley[A => C], val q: StrictParsley[B => C])
4849
extends BranchLike[Either[A, B], A => C, B => C, C](Some(FlipApp)) {
4950
override def instr(label: Int): instructions.Instr = new instructions.Case(label)
50-
override def optimise: StrictParsley[C] = b match {
51+
override def optimise(implicit lets: LetMap): StrictParsley[C] = b match {
5152
case Pure(Left(x)) => <*>(p, new Pure(x)).optimise
5253
case Pure(Right(y)) => <*>(q, new Pure(y)).optimise
5354
case _ => (p, q) match {
@@ -63,7 +64,7 @@ private [deepembedding] final class Branch[A, B, C](val b: StrictParsley[Either[
6364
private [deepembedding] final class If[A](val b: StrictParsley[Boolean], val p: StrictParsley[A], val q: StrictParsley[A])
6465
extends BranchLike[Boolean, A, A, A](None) {
6566
override def instr(label: Int): instructions.Instr = new instructions.If(label)
66-
override def optimise: StrictParsley[A] = b match {
67+
override def optimise(implicit lets: LetMap): StrictParsley[A] = b match {
6768
case Pure(true) => p
6869
case Pure(false) => q
6970
case _ => this

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/SequenceEmbedding.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import parsley.XAssert.*
99

1010
import parsley.internal.collection.mutable.DoublyLinkedList
1111
import parsley.internal.deepembedding.ContOps, ContOps.{result, suspend, ContAdapter}
12-
import parsley.internal.deepembedding.frontend
12+
import parsley.internal.deepembedding.frontend, frontend.LetMap
1313
import parsley.internal.deepembedding.singletons.*
1414
import parsley.internal.machine.instructions
1515

@@ -19,7 +19,7 @@ import StrictParsley.InstrBuffer
1919
private [deepembedding] final class <*>[A, B](var left: StrictParsley[A => B], var right: StrictParsley[A]) extends StrictParsley[B] {
2020
def inlinable: Boolean = false
2121
// TODO: Refactor
22-
override def optimise: StrictParsley[B] = (left, right) match {
22+
override def optimise(implicit lets: LetMap): StrictParsley[B] = (left, right) match {
2323
// Fusion laws
2424
case (uf, Pure(x)) if (uf.isInstanceOf[Pure[?]] || uf.isInstanceOf[_ <*> _]) => uf match {
2525
// first position fusion
@@ -87,7 +87,7 @@ private [deepembedding] final class <*>[A, B](var left: StrictParsley[A => B], v
8787
}
8888

8989
private [deepembedding] final class >>=[A, B](val p: StrictParsley[A], private [>>=] val f: A => frontend.LazyParsley[B]) extends Unary[A, B] {
90-
override def optimise: StrictParsley[B] = p match {
90+
override def optimise(implicit lets: LetMap): StrictParsley[B] = p match {
9191
case z: MZero => z
9292
case _ => this
9393
}
@@ -138,7 +138,7 @@ private [deepembedding] final class Seq[A](private [backend] var before: DoublyL
138138

139139
// TODO: Get behaves much like pure except for shifting positions
140140
// TODO: can this be optimised to reduce repeated matching?
141-
override def optimise: StrictParsley[A] = this match {
141+
override def optimise(implicit lets: LetMap): StrictParsley[A] = this match {
142142
// Assume that this is eliminated first, so not other before or afters
143143
case (_: Pure[?] | _: Get[?] | Line | Col | Offset) **> u => u
144144
case (p: MZero) **> _ => p

parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import parsley.internal.deepembedding.frontend.LetMap
1818
import parsley.internal.machine.instructions, instructions.{Instr, Label}
1919

2020
import StrictParsley.*
21+
import org.typelevel.scalaccompat.annotation.unused
2122

2223
/** This is the root type of the parsley "backend": it represents a combinator tree
2324
* where the join-points in the tree (recursive or otherwise) have been factored into
@@ -76,7 +77,7 @@ private [deepembedding] trait StrictParsley[+A] {
7677
*
7778
* By default, this method just returns this parser unchanged.
7879
*/
79-
protected [deepembedding] def optimise: StrictParsley[A] = this
80+
protected [deepembedding] def optimise(implicit @unused lets: LetMap): StrictParsley[A] = this
8081

8182
/** Should this parser be inlined if it is shared?
8283
*
@@ -273,7 +274,7 @@ private [deepembedding] class CodeGenState(val numRefs: Int)(implicit letMap: Le
273274
label
274275
})
275276

276-
def getBody[A](sub: Let[A]): StrictParsley[A] = letMap.bodies(sub).asInstanceOf[StrictParsley[A]]
277+
def getBody[A](sub: Let[A]): StrictParsley[A] = letMap.findBody(sub).get.asInstanceOf[StrictParsley[A]]
277278

278279
/** Returns the next shared-parser that has been refered during code generation */
279280
def nextLet(): (Let[?], Boolean, Int) = queue.remove(0)

parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/LazyParsley.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ private [deepembedding] final class LetMap private (letGen: Map[LazyParsley[?],
281281
}
282282
}
283283

284+
def findBody(let: backend.Let[?]): Option[StrictParsley[?]] = bodyMap.get(let)
285+
284286
def bodies: Map[backend.Let[?], StrictParsley[?]] = bodyMap.toMap
285287

286288
// $COVERAGE-OFF$

0 commit comments

Comments
 (0)