Skip to content

Commit

Permalink
Merge branch 'concatenateExprString'
Browse files Browse the repository at this point in the history
  • Loading branch information
rayrobdod committed Mar 2, 2024
2 parents 5a8880f + 4e3b25a commit db5a527
Show file tree
Hide file tree
Showing 11 changed files with 396 additions and 170 deletions.
66 changes: 66 additions & 0 deletions Base/src/main/scala-2/typeclass/VersionSpecificRepeated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,72 @@ package typeclass
import scala.collection.mutable.Builder
import scala.reflect.macros.blackbox.Context

private[typeclass]
trait VersionSpecificRepeated {
/**
* Creates an Expr[String] consisting of the concatenation of the component Expr[String]s
* @since 0.1.1
*/
def forContextConcatenateString(c:Context):Repeated[c.Expr[String], c.Expr[String]] = {
import c.universe.Tree
import c.universe.Quasiquote
val ttString0 = c.universe.typeTag[String]
final class ConcatenateString extends Repeated[c.Expr[String], c.Expr[String]] {
val accumulatorName = c.freshName(c.universe.TermName("accumulator$"))
val accumulatorTypeTree = c.universe.TypeTree(
c.universe.rootMirror.staticClass("scala.collection.mutable.StringBuilder").asType.toTypeConstructor
)
val accumulatorIdent = c.universe.Ident(accumulatorName)
val accumulatorValDef = c.universe.ValDef(
c.universe.NoMods,
accumulatorName,
accumulatorTypeTree,
q"new $accumulatorTypeTree()",
)

implicit val ttString: c.TypeTag[String] = ttString0

sealed trait Acc
final object AccZero extends Acc
final class AccOne(val elem: c.Expr[String]) extends Acc
final class AccMany extends Acc {
val builder: Builder[Tree, c.Expr[String]] = List.newBuilder.mapResult(stat =>
c.Expr[String](
c.universe.Block(
stat,
q"$accumulatorIdent.toString"
)
)
)
}

def init():Acc = AccZero
def append(acc:Acc, elem:c.Expr[String]):Acc = acc match {
case AccZero => new AccOne(elem)
case accOne: AccOne => {
val retval = new AccMany()
retval.builder += accumulatorValDef
retval.builder += q"$accumulatorIdent.append(${accOne.elem})"
retval.builder += q"$accumulatorIdent.append($elem)"
retval
}
case accMany: AccMany => {
accMany.builder += q"$accumulatorIdent.append($elem)"
accMany
}
}
def result(acc:Acc):c.Expr[String] = {
acc match {
case AccZero => c.Expr[String](c.universe.Literal(c.universe.Constant("")))
case accOne: AccOne => accOne.elem
case accMany: AccMany => accMany.builder.result()
}
}
}
new ConcatenateString()
}
}

private[typeclass]
trait VersionSpecificContraRepeated {
trait ContraRepeateds[Expr[_], Type[_]] {
Expand Down
52 changes: 52 additions & 0 deletions Base/src/main/scala-3/typeclass/VersionSpecificRepeated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,60 @@ package name.rayrobdod.stringContextParserCombinator
package typeclass

import scala.collection.mutable.Builder
import scala.collection.mutable.StringBuilder
import scala.quoted.*

private[typeclass]
trait VersionSpecificRepeated {
/**
* Creates an Expr[String] consisting of the concatenation of the component Expr[String]s
* @since 0.1.1
*/
def quotedConcatenateString(using Quotes):Repeated[Expr[String], Expr[String]] = {
final class ConcatenateString extends Repeated[Expr[String], Expr[String]] {
sealed trait Acc
object AccZero extends Acc
final class AccOne(val elem: Expr[String]) extends Acc
final class AccMany extends Acc {
val builder: Builder[Expr[String], Expr[String]] = List.newBuilder.mapResult(stat =>
val accumulator:Expr[StringBuilder] = '{new scala.collection.mutable.StringBuilder}
import quotes.reflect.*
val retval = ValDef.let(Symbol.spliceOwner, "builder$", accumulator.asTerm): accumulatorRef =>
val accumulatorRefExpr = accumulatorRef.asExprOf[StringBuilder]
Block(
stat.map(addend => '{$accumulatorRefExpr.append($addend)}.asTerm),
Apply(Select.unique(accumulatorRef, "result"), Nil),
)
retval.asExprOf[String]
)
}

def init():Acc = AccZero
def append(acc:Acc, elem:Expr[String]):Acc = acc match {
case AccZero => new AccOne(elem)
case accOne: AccOne => {
val retval = new AccMany()
retval.builder += accOne.elem
retval.builder += elem
retval
}
case accMany: AccMany => {
accMany.builder += elem
accMany
}
}
def result(acc:Acc):Expr[String] = {
acc match {
case AccZero => Expr[String]("")
case accOne: AccOne => accOne.elem
case accMany: AccMany => accMany.builder.result()
}
}
}
new ConcatenateString()
}
}

private[typeclass]
trait VersionSpecificContraRepeated {
given quotedUnit(using Quotes):ContraRepeated[Expr, Unit, Unit] = BiRepeated.quotedUnit
Expand Down
28 changes: 27 additions & 1 deletion Base/src/main/scala/typeclass/Repeat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,21 @@ trait BiRepeated[Expr[_], A, Z]
with ContraRepeated[Expr, A, Z]

/** Predefined implicit implementations of Repeated */
object Repeated extends LowPrioRepeated {
object Repeated extends VersionSpecificRepeated with LowPrioRepeated {
private def apply[A, Acc, Z](
initFn: () => Acc,
appendFn: (Acc, A) => Acc,
resultFn: Acc => Z,
): Repeated[A, Z] = {
type Acc2 = Acc
new Repeated[A, Z] {
type Acc = Acc2
def init():Acc = initFn()
def append(acc:Acc, elem:A):Acc = appendFn(acc, elem)
def result(acc:Acc):Z = resultFn(acc)
}
}

/**
* Repeated units results in a unit
*/
Expand Down Expand Up @@ -124,6 +138,18 @@ object Repeated extends LowPrioRepeated {
}
new RepeatedCodepoint()
}

/**
* Creates a String consisting of the concatenation of the component strings
* @since 0.1.1
*/
def idConcatenateString:Repeated[String, String] = {
Repeated.apply(
() => new StringBuilder,
(acc:StringBuilder, elem:String) => acc ++= elem,
(acc:StringBuilder) => acc.toString,
)
}
}

private[typeclass] trait LowPrioRepeated {
Expand Down
28 changes: 28 additions & 0 deletions Base/src/test/scala-3/typeclass/VersonSpecificRepeatedTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package name.rayrobdod.stringContextParserCombinator
package typeclass
package repeated

import scala.quoted.*
import munit.Location

final class QuotedConcatenateStringTest extends munit.FunSuite {
inline def assertParseSuccess(
inline sc: StringContext,
inline args: Any*)(
expecting:String)(
using loc:Location
):Unit = ${
QuotedConcatenateStringTestImpls.assertParseSuccessImpl(
'this, 'sc, 'args, 'expecting, 'loc)
}

test ("0") {
assertParseSuccess(StringContext(""))("")
}
test ("1") {
assertParseSuccess(StringContext("ab"))("ab")
}
test ("many") {
assertParseSuccess(StringContext("ab", "cd", "ef"), "12", "34")("ab12cd34ef")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package name.rayrobdod.stringContextParserCombinator
package typeclass
package repeated

import scala.quoted.*
import munit.Location
import Interpolator.{charWhere, ofType, end}

object QuotedConcatenateStringTestImpls {
def assertParseSuccessImpl(
self: Expr[munit.FunSuite],
sc: Expr[StringContext],
args: Expr[Seq[Any]],
expecting: Expr[String],
loc: Expr[Location])(
using Quotes
):Expr[Unit] = {

val dut = (charWhere(_ => true).repeat(1).mapToExpr orElse ofType[String])
.repeat()(using Repeated.quotedConcatenateString)
.andThen(end)

val actual = dut.interpolate(sc, args)

'{
given Location = ${loc}
$self.assertEquals($actual, $expecting)
}
}
}
21 changes: 21 additions & 0 deletions Base/src/test/scala/typeclass/RepeatedTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package name.rayrobdod.stringContextParserCombinator
package typeclass
package repeated

import Interpolator.idInterpolators.{charWhere, ofType, end}

final class IdConcatenateString extends BaseInterpolatorSuite {
val dut = (charWhere(_ => true).repeat(1) orElse ofType[String])
.repeat()(using Repeated.idConcatenateString)
.andThen(end)

test ("0") {
assertParseSuccess(dut, ("" :: Nil, Nil), "")
}
test ("1") {
assertParseSuccess(dut, ("ab" :: Nil, Nil), "ab")
}
test ("many") {
assertParseSuccess(dut, ("ab" :: "cd" :: "ef" :: Nil, "12" :: "34" :: Nil), "ab12cd34ef")
}
}
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
* `<+>` on Interpolator only as a specialization of `orElse` when the desired result is a discriminated union
* `</>` on Interpolator only as a specialization of `orElse` when the second argument is a pure parser
* `<::>` on Interpolator only as a specialization of `andThen` which prepends the left side to a list on the right side
* Add built-in but explicit Repeated instances for concatenating strings
* `idConcatenateString` for id context
* `forContextConcatenateString` for scala-2 macro context
* `quotedConcatenateString` for scala-3 quoted context

## [0.1.0] 2024-02-01
Initial tagged version
42 changes: 1 addition & 41 deletions JsonParser/src/main/scala-2/MacroImpl.scala
Original file line number Diff line number Diff line change
@@ -1,50 +1,11 @@
package name.rayrobdod.stringContextParserCombinatorExample.json

import scala.collection.immutable.Seq
import scala.math.BigDecimal
import scala.reflect.macros.whitebox.Context
import org.json4s._
import name.rayrobdod.stringContextParserCombinator._

final class MacroImpl(val c:Context {type PrefixType = JsonStringContext}) {
/**
* Creates an Expr that represents the concatenation of the component Exprs
*/
private[this] def concatenateStrings(strings:List[c.Expr[String]]):c.Expr[String] = {
strings match {
case Seq() => c.Expr[String](c.universe.Literal(c.universe.Constant("")))
case Seq(x) => x
case _ => {
val accumulatorName = c.universe.TermName("accumulator$")
val accumulatorType = c.universe.typeTag[scala.collection.mutable.StringBuilder]
val accumulatorTypeTree = c.universe.TypeTree(accumulatorType.tpe)
val accumulatorExpr = c.Expr(c.universe.Ident(accumulatorName))(accumulatorType)
val stats = scala.collection.mutable.Buffer[c.universe.Tree](
c.universe.ValDef(
c.universe.NoMods,
accumulatorName,
accumulatorTypeTree,
c.universe.Apply(
c.universe.Select(
c.universe.New(accumulatorTypeTree),
c.universe.termNames.CONSTRUCTOR
),
List()
)
)
)
strings.foreach(x => stats += c.universe.reify(accumulatorExpr.splice.append(x.splice)).tree)

c.Expr[String](
c.universe.Block(
stats.toList,
c.universe.reify(accumulatorExpr.splice.toString).tree
)
)
}
}
}

private[this] def assembleCollection[A](parts:List[Either[c.Expr[A], c.Expr[List[A]]]])(implicit builderType: c.universe.TypeTag[scala.collection.mutable.Builder[A, List[A]]]):c.Expr[List[A]] = {
val builderName = c.freshName(c.universe.TermName("builder"))
val builderTypeTree = c.universe.TypeTree(builderType.tpe)
Expand Down Expand Up @@ -196,8 +157,7 @@ final class MacroImpl(val c:Context {type PrefixType = JsonStringContext}) {
val content:Parser[c.Expr[String]] = paired(
(jCharsLifted <|> jCharsImmediate)
.toInterpolator
.repeat[c.Expr[Any], List[c.Expr[String]]](strategy = RepeatStrategy.Possessive)
.map(strs => concatenateStrings(strs))
.repeat(strategy = RepeatStrategy.Possessive)(typeclass.Repeated.forContextConcatenateString(c))
,
(jCharsImmediate).toExtractor
)
Expand Down
16 changes: 1 addition & 15 deletions JsonParser/src/main/scala-3/MacroImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@ import org.json4s._
import name.rayrobdod.stringContextParserCombinator._

object MacroImpl {
/**
* Creates an Expr that represents the concatenation of the component Exprs
*/
private def concatenateStrings(strings:Seq[Expr[String]])(using Quotes):Expr[String] = {
strings match {
case Seq() => '{ "" }
case Seq(x) => x
case _ => '{
${strings.foldLeft('{new _root_.java.lang.StringBuilder()})({(builder, part) => '{$builder.append($part)}})}.toString
}
}
}

private def assembleCollection[A]
(parts:List[Either[Expr[A], Expr[List[A]]]])
(using Type[A], Quotes)
Expand Down Expand Up @@ -169,8 +156,7 @@ object MacroImpl {
val content:Parser[Expr[String]] = paired(
(jCharsLifted <|> jCharsImmediate)
.toInterpolator
.repeat(strategy = RepeatStrategy.Possessive)
.map(strs => concatenateStrings(strs))
.repeat(strategy = RepeatStrategy.Possessive)(using typeclass.Repeated.quotedConcatenateString)
,
(jCharsImmediate).toExtractor
)
Expand Down
Loading

0 comments on commit db5a527

Please sign in to comment.