Skip to content

Commit 22154e4

Browse files
committed
[base] Add Repeat.SplicePiece and typeclass instances using SplicePiece
This is intended to assist with splicing
1 parent 6a87c62 commit 22154e4

File tree

10 files changed

+368
-69
lines changed

10 files changed

+368
-69
lines changed

Base/src/main/scala-2/typeclass/VersionSpecificEithered.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ private[typeclass] trait VersionSpecificEithered extends LowPrioEithered {
77
implicit def unitUnit:Eithered[Unit, Unit, Unit] = symmetric[Unit]
88
implicit def unitGeneric[B, Z](implicit ev:Optionally[B, Z]):Eithered[Unit, B, Z] = Eithered(_ => ev.none, ev.some _)
99
implicit def genericUnit[A, Z](implicit ev:Optionally[A, Z]):Eithered[A, Unit, Z] = Eithered(ev.some _, _ => ev.none)
10+
11+
/**
12+
* @version 0.1.1
13+
*/
14+
trait Eithereds[Expr[+_]] {
15+
def splicePiece[A]: Eithered[Expr[A], Expr[Iterable[A]], Repeated.SplicePiece[Expr, A]]
16+
}
17+
/**
18+
* @version 0.1.1
19+
*/
20+
def forContext(c:Context):Eithereds[c.Expr] = {
21+
new Eithereds[c.Expr] {
22+
def splicePiece[A]: Eithered[c.Expr[A], c.Expr[Iterable[A]], Repeated.SplicePiece[c.Expr, A]] =
23+
Eithered(new Repeated.SplicePiece.One(_), new Repeated.SplicePiece.Many(_))
24+
}
25+
}
1026
}
1127

1228
private[typeclass] trait LowPrioEithered {

Base/src/main/scala-2/typeclass/VersionSpecificRepeated.scala

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package typeclass
33

44
import scala.collection.mutable.Builder
55
import scala.reflect.macros.blackbox.Context
6+
import Repeated.SplicePiece
67

78
private[typeclass]
89
trait VersionSpecificRepeated {
@@ -68,6 +69,125 @@ trait VersionSpecificRepeated {
6869
}
6970
new ConcatenateString()
7071
}
72+
73+
/**
74+
* Splice a sequence of `SplicePiece`s together using a Builder
75+
* @param newAccumulator an Expr creating a new Builder
76+
* @param ifZero the Expr to use when combining zero items together.
77+
* If None, `newAccumulator.result()` will be used.
78+
* If Some, should ideally be equivalent to but more efficient than the None expr.
79+
* @param ifOne the Expr to use when combining one scalar item together.
80+
* If None, `newAccumulator.+=(item).result()` will be used.
81+
* If Some, should ideally be equivalent to but more efficient than the None expr.
82+
* @version 0.1.1
83+
*/
84+
def forContextFromSplicesUsingBuilder[A, Z](c:Context)(
85+
newAccumulator: c.Expr[Builder[A, Z]],
86+
ifZero: Option[() => c.Expr[Z]],
87+
ifOne: Option[(c.Expr[A]) => c.Expr[Z]],
88+
)(implicit
89+
accumulatorType: c.universe.TypeTag[Builder[A, Z]],
90+
zType: c.universe.TypeTag[Z],
91+
): Repeated[SplicePiece[c.Expr, A], c.Expr[Z]] = {
92+
import c.universe.Tree
93+
import c.universe.Quasiquote
94+
95+
final class FromSplicesUsingBuilder extends Repeated[SplicePiece[c.Expr, A], c.Expr[Z]] {
96+
val accumulatorName = c.freshName(c.universe.TermName("accumulator$"))
97+
val accumulatorTypeTree = c.universe.TypeTree(accumulatorType.tpe)
98+
val accumulatorIdent = c.universe.Ident(accumulatorName)
99+
val accumulatorExpr = c.Expr(accumulatorIdent)(accumulatorType)
100+
101+
val accumulatorValDef = c.universe.ValDef(
102+
c.universe.NoMods,
103+
accumulatorName,
104+
accumulatorTypeTree,
105+
newAccumulator.tree
106+
)
107+
108+
sealed trait Acc
109+
final object AccZero extends Acc
110+
final class AccOne(val elem: c.Expr[A]) extends Acc
111+
final class AccMany extends Acc {
112+
val builder: Builder[Tree, c.Expr[Z]] = List.newBuilder.mapResult(stat =>
113+
c.Expr[Z](
114+
c.universe.Block(
115+
stat,
116+
q"$accumulatorIdent.result()"
117+
)
118+
)
119+
)
120+
}
121+
122+
def init():Acc = AccZero
123+
def append(acc:Acc, elem:SplicePiece[c.Expr, A]):Acc = acc match {
124+
case AccZero =>
125+
elem match {
126+
case _: SplicePiece.Zero[c.Expr] => AccZero
127+
case elemOne: SplicePiece.One[c.Expr, A] => new AccOne(elemOne.elem)
128+
case elemMany: SplicePiece.Many[c.Expr, A] => {
129+
val retval = new AccMany()
130+
retval.builder += accumulatorValDef
131+
retval.builder += q"$accumulatorIdent.++=(${elemMany.iter})"
132+
retval
133+
}
134+
}
135+
case accOne: AccOne => {
136+
elem match {
137+
case _: SplicePiece.Zero[c.Expr] => accOne
138+
case elemOne: SplicePiece.One[c.Expr, A] =>
139+
val retval = new AccMany()
140+
retval.builder += accumulatorValDef
141+
retval.builder += q"$accumulatorIdent.+=(${accOne.elem})"
142+
retval.builder += q"$accumulatorIdent.+=(${elemOne.elem})"
143+
retval
144+
case elemMany: SplicePiece.Many[c.Expr, A] =>
145+
val retval = new AccMany()
146+
retval.builder += accumulatorValDef
147+
retval.builder += q"$accumulatorIdent.+=(${accOne.elem})"
148+
retval.builder += q"$accumulatorIdent.++=(${elemMany.iter})"
149+
retval
150+
}
151+
}
152+
case accMany: AccMany => {
153+
elem match {
154+
case _: SplicePiece.Zero[c.Expr] =>
155+
// do nothing
156+
case elemOne: SplicePiece.One[c.Expr, A] =>
157+
accMany.builder += q"$accumulatorIdent.+=(${elemOne.elem})"
158+
case elemMany: SplicePiece.Many[c.Expr, A] =>
159+
accMany.builder += q"$accumulatorIdent.++=(${elemMany.iter})"
160+
}
161+
accMany
162+
}
163+
}
164+
165+
def result(acc:Acc):c.Expr[Z] = {
166+
acc match {
167+
case AccZero => ifZero.map(_.apply()).getOrElse(c.Expr[Z](q"$newAccumulator.result()"))
168+
case accOne: AccOne => ifOne.map(_.apply(accOne.elem)).getOrElse(c.Expr[Z](q"$newAccumulator.+=(${accOne.elem}).result()"))
169+
case accMany: AccMany => accMany.builder.result()
170+
}
171+
}
172+
173+
}
174+
new FromSplicesUsingBuilder()
175+
}
176+
177+
/**
178+
* Splice a sequence of `SplicePiece`s together into a `List`
179+
* @version 0.1.1
180+
*/
181+
def forContextFromSplicesToExprList[A](c: Context)(implicit
182+
accumulatorType: c.universe.TypeTag[Builder[A, List[A]]],
183+
zType: c.universe.TypeTag[List[A]],
184+
): Repeated[SplicePiece[c.Expr, A], c.Expr[List[A]]] = {
185+
forContextFromSplicesUsingBuilder[A, List[A]](c)(
186+
c.universe.reify(List.newBuilder),
187+
Option(() => c.universe.reify(List.empty)),
188+
Option((a: c.Expr[A]) => c.universe.reify(List(a.splice))),
189+
)
190+
}
71191
}
72192

73193
private[typeclass]

Base/src/main/scala-3/typeclass/VersionSpecificEithered.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ private[typeclass] trait VersionSpecificEithered extends LowPrioEithered {
77
given unitUnit:Eithered[Unit, Unit, Unit] = Eithered.generic
88
given unitAny[B, Z](using ev:Optionally[B, Z]):Eithered[Unit, B, Z] = Eithered(_ => ev.none, ev.some _)
99
given anyUnit[A, Z](using ev:Optionally[A, Z]):Eithered[A, Unit, Z] = Eithered(ev.some _, _ => ev.none)
10+
11+
def quotedSplicePiece[A]: Eithered[Expr[A], Expr[Iterable[A]], Repeated.SplicePiece[Expr, A]] =
12+
Eithered(new Repeated.SplicePiece.One(_), new Repeated.SplicePiece.Many(_))
1013
}
1114

1215
private[typeclass] trait LowPrioEithered {

Base/src/main/scala-3/typeclass/VersionSpecificRepeated.scala

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package typeclass
44
import scala.collection.mutable.Builder
55
import scala.collection.mutable.StringBuilder
66
import scala.quoted.*
7+
import Repeated.SplicePiece
78

89
private[typeclass]
910
trait VersionSpecificRepeated {
@@ -53,6 +54,99 @@ trait VersionSpecificRepeated {
5354
}
5455
new ConcatenateString()
5556
}
57+
58+
/**
59+
* Splice a sequence of `SplicePiece`s together using a Builder
60+
* @param newAccumulator an Expr creating a new Builder
61+
* @param ifZero the Expr to use when combining zero items together.
62+
* If None, `'{$newAccumulator.result()}` will be used.
63+
* If Some, should be equivalent to but more efficient than the None expr.
64+
* @param ifOne the Expr to use when combining one scalar item together.
65+
* If None, `'{$newAccumulator.addOne($item).result()}` will be used.
66+
* If Some, should be equivalent to but more efficient than the None expr.
67+
* @version 0.1.1
68+
*/
69+
def quotedFromSplicesUsingBuilder[A, Z](
70+
newAccumulator: Expr[Builder[A, Z]],
71+
ifZero: Option[() => Expr[Z]] = None,
72+
ifOne: Option[(Expr[A]) => Expr[Z]] = None,
73+
)(using Quotes, Type[A], Type[Z], Type[Builder[A, Z]],
74+
): Repeated[SplicePiece[Expr, A], Expr[Z]] = {
75+
final class FromSplicesUsingBuilder extends Repeated[SplicePiece[Expr, A], Expr[Z]] {
76+
sealed trait Acc
77+
object AccZero extends Acc
78+
final class AccOne(val elem: Expr[A]) extends Acc
79+
final class AccMany extends Acc {
80+
val builder: Builder[Expr[Builder[A, Z]] => Expr[_], Expr[Z]] = List.newBuilder.mapResult(parts =>
81+
'{
82+
val accumulator: Builder[A, Z] = ${newAccumulator}
83+
${Expr.block(parts.map(part => part('accumulator)), '{()})}
84+
accumulator.result()
85+
}
86+
)
87+
}
88+
89+
def init():Acc = AccZero
90+
def append(acc:Acc, elem:SplicePiece[Expr, A]):Acc = acc match {
91+
case AccZero =>
92+
elem match {
93+
case _: SplicePiece.Zero[Expr] => AccZero
94+
case elemOne: SplicePiece.One[Expr, A] => new AccOne(elemOne.elem)
95+
case elemMany: SplicePiece.Many[Expr, A] => {
96+
val retval = new AccMany()
97+
retval.builder += {(acc) => '{$acc.addAll(${elemMany.iter})}}
98+
retval
99+
}
100+
}
101+
case accOne: AccOne => {
102+
elem match {
103+
case _: SplicePiece.Zero[Expr] => accOne
104+
case elemOne: SplicePiece.One[Expr, A] =>
105+
val retval = new AccMany()
106+
retval.builder += {(acc) => '{$acc.addOne(${accOne.elem})}}
107+
retval.builder += {(acc) => '{$acc.addOne(${elemOne.elem})}}
108+
retval
109+
case elemMany: SplicePiece.Many[Expr, A] =>
110+
val retval = new AccMany()
111+
retval.builder += {(acc) => '{$acc.addOne(${accOne.elem})}}
112+
retval.builder += {(acc) => '{$acc.addAll(${elemMany.iter})}}
113+
retval
114+
}
115+
}
116+
case accMany: AccMany => {
117+
elem match {
118+
case _: SplicePiece.Zero[Expr] =>
119+
// do nothing
120+
case elemOne: SplicePiece.One[Expr, A] =>
121+
accMany.builder += {(acc) => '{$acc.addOne(${elemOne.elem})}}
122+
case elemMany: SplicePiece.Many[Expr, A] =>
123+
accMany.builder += {(acc) => '{$acc.addAll(${elemMany.iter})}}
124+
}
125+
accMany
126+
}
127+
}
128+
129+
def result(acc:Acc):Expr[Z] = {
130+
acc match {
131+
case AccZero => ifZero.map(_.apply()).getOrElse('{$newAccumulator.result()})
132+
case accOne: AccOne => ifOne.map(_.apply(accOne.elem)).getOrElse('{$newAccumulator.addOne(${accOne.elem}).result()})
133+
case accMany: AccMany => accMany.builder.result()
134+
}
135+
}
136+
}
137+
new FromSplicesUsingBuilder()
138+
}
139+
140+
/**
141+
* Splice a sequence of `SplicePiece`s together into a `List`
142+
* @since 0.1.1
143+
*/
144+
implicit def quotedFromSplicesToExprList[A](using Quotes, Type[A]): Repeated[SplicePiece[Expr, A], Expr[List[A]]] =
145+
quotedFromSplicesUsingBuilder[A, List[A]](
146+
'{ List.newBuilder },
147+
Option(() => '{ List.empty }),
148+
Option((a: Expr[A]) => '{ List($a) }),
149+
)
56150
}
57151

58152
private[typeclass]

Base/src/main/scala/typeclass/Repeat.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ object Repeated extends VersionSpecificRepeated with LowPrioRepeated {
144144
(acc:StringBuilder) => acc.toString,
145145
)
146146
}
147+
148+
/**
149+
* @version 0.1.1
150+
*/
151+
sealed trait SplicePiece[Expr[+_], +A]
152+
/**
153+
* @version 0.1.1
154+
*/
155+
object SplicePiece {
156+
final class Zero[Expr[+_]] extends SplicePiece[Expr, Nothing]
157+
final class One[Expr[+_], +A](val elem: Expr[A]) extends SplicePiece[Expr, A]
158+
final class Many[Expr[+_], +A](val iter: Expr[Iterable[A]]) extends SplicePiece[Expr, A]
159+
}
147160
}
148161

149162
private[typeclass] trait LowPrioRepeated {

Base/src/test/scala-3/typeclass/VersonSpecificRepeatedTest.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package name.rayrobdod.stringContextParserCombinator
22
package typeclass
33
package repeated
44

5+
import scala.collection.mutable.Builder
56
import scala.quoted.*
67
import munit.Location
78

@@ -26,3 +27,38 @@ final class QuotedConcatenateStringTest extends munit.FunSuite {
2627
assertParseSuccess(StringContext("ab", "cd", "ef"), "12", "34")("ab12cd34ef")
2728
}
2829
}
30+
31+
final class QuotedFromSplicesUsingBuilderTest extends munit.FunSuite {
32+
inline def assertParseSuccess[Z](
33+
newAcc: Builder[Int, Z],
34+
ifZeroCond: Boolean,
35+
ifZero: () => Z,
36+
ifOneCond: Boolean,
37+
ifOne: (Int) => Z,
38+
inline elems: (Int | Seq[Int]) *)(
39+
expecting: Z)(
40+
using loc:Location
41+
):Unit = ${
42+
QuotedFromSplicesUsingBuilderTestImpls.assertParseSuccessImpl(
43+
'this, 'newAcc, 'ifZeroCond, 'ifZero, 'ifOneCond, 'ifOne, 'elems, 'expecting, 'loc)
44+
}
45+
46+
test ("0 items with ifZero") {
47+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x)(-1)
48+
}
49+
test ("0 items without ifZero") {
50+
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x)(Nil)
51+
}
52+
test ("1 items with ifOne") {
53+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, 2)(2)
54+
}
55+
test ("1 items without ifOne") {
56+
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x, 2)(2 :: Nil)
57+
}
58+
test ("multiple individual items") {
59+
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x, 1,2,3)(1 :: 2 :: 3 :: Nil)
60+
}
61+
test ("Splice items") {
62+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, Seq(1,2))(1 :: 2 :: Nil)
63+
}
64+
}

Base/src/test/scala-3/typeclass/VersonSpecificRepeatedTestImpls.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package name.rayrobdod.stringContextParserCombinator
22
package typeclass
33
package repeated
44

5+
import scala.collection.mutable.Builder
56
import scala.quoted.*
67
import munit.Location
78
import Interpolator.{charWhere, ofType, end}
9+
import Repeated.SplicePiece
810

911
object QuotedConcatenateStringTestImpls {
1012
def assertParseSuccessImpl(
@@ -28,3 +30,38 @@ object QuotedConcatenateStringTestImpls {
2830
}
2931
}
3032
}
33+
34+
object QuotedFromSplicesUsingBuilderTestImpls {
35+
def assertParseSuccessImpl[Int, Z](
36+
self: Expr[munit.FunSuite],
37+
newAcc: Expr[Builder[Int,Z]],
38+
ifZeroCondExpr: Expr[Boolean],
39+
ifZero: Expr[() => Z],
40+
ifOneCondExpr: Expr[Boolean],
41+
ifOne: Expr[(Int) => Z],
42+
elems: Expr[Seq[Int | Seq[Int]]],
43+
expecting: Expr[Z],
44+
loc: Expr[Location])(
45+
using Quotes, Type[Int], Type[Z]
46+
):Expr[Unit] = {
47+
val Varargs(elems2) = elems: @unchecked
48+
val elems3: Seq[SplicePiece[Expr, Int]] = elems2.map:
49+
case '{ $x: Int } => SplicePiece.One[Expr, Int](x)
50+
case '{ $xs: Seq[Int] } => SplicePiece.Many[Expr, Int](xs)
51+
52+
val ifZeroCond = ifZeroCondExpr.valueOrAbort
53+
val ifOneCond = ifOneCondExpr.valueOrAbort
54+
55+
val ifZero2 = Option.when(ifZeroCond)({() => '{${ifZero}()}})
56+
val ifOne2 = Option.when(ifOneCond)({(a:Expr[Int]) => '{${ifOne}(${a})}})
57+
58+
val dut = Repeated.quotedFromSplicesUsingBuilder(newAcc, ifZero2, ifOne2)
59+
60+
val actual = dut.result(elems3.foldLeft(dut.init())((acc, elem) => dut.append(acc, elem)))
61+
62+
'{
63+
given Location = ${loc}
64+
$self.assertEquals($actual, $expecting)
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)