Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite and adapt to Scala 3 features #51

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ lazy val root = project

scalaVersion := dottyVersion,
scalacOptions ++= Seq(
"-deprecation"
"-deprecation", "-feature", "-language:implicitConversions"
),
libraryDependencies ++= Seq(
"org.scala-lang.modules" % "scala-parser-combinators_2.13" % "1.1.2",
Expand Down
104 changes: 48 additions & 56 deletions src/main/scala/dotty/xml/interpolator/internal/Expand.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package dotty.xml.interpolator
package internal

import scala.language.implicitConversions
import scala.quoted._

import dotty.xml.interpolator.internal.Tree._
import Tree.*

object Expand {
object Expand:

def apply(nodes: Seq[Node])(implicit ctx: XmlContext, q: Quotes): Expr[scala.xml.Node | scala.xml.NodeBuffer] = {
if (nodes.size == 1) expandNode(nodes.head).asInstanceOf[Expr[scala.xml.Node]]
def apply(nodes: Seq[Node])(using XmlContext, Quotes): Expr[scala.xml.Node | scala.xml.NodeBuffer] =
if nodes.size == 1 then expandNode(nodes.head).asInstanceOf[Expr[scala.xml.Node]]
else expandNodes(nodes)
}

private def expandNode(node: Node)(implicit ctx: XmlContext, q: Quotes): Expr[Any] = {
node match {
private def expandNode(node: Node)(using XmlContext, Quotes): Expr[Any] =
node match
case group: Group => expandGroup(group)
case elem: Elem => expandElem(elem)
case text: Text => expandText(text)
Expand All @@ -24,33 +22,32 @@ object Expand {
case procInstr: ProcInstr => expandProcInstr(procInstr)
case entityRef: EntityRef => expandEntityRef(entityRef)
case unparsed: Unparsed => expandUnparsed(unparsed)
}
}
end expandNode

private def expandNodes(nodes: Seq[Node])(implicit ctx: XmlContext, q: Quotes): Expr[scala.xml.NodeBuffer] = {
nodes.foldLeft('{ new _root_.scala.xml.NodeBuffer() })((expr, node) => '{ $expr &+ ${expandNode(node)} } )
}
private def expandNodes(nodes: Seq[Node])(using XmlContext, Quotes): Expr[scala.xml.NodeBuffer] =
nodes.foldLeft('{ scala.xml.NodeBuffer() }): (expr, node) =>
'{ $expr &+ ${ expandNode(node) } }

private def expandGroup(group: Group)(implicit ctx: XmlContext, q: Quotes): Expr[scala.xml.Group] =
'{ new _root_.scala.xml.Group(${expandNodes(group.nodes)}) }
private def expandGroup(group: Group)(using XmlContext, Quotes): Expr[scala.xml.Group] =
'{ scala.xml.Group(${ expandNodes(group.nodes) }) }

private def expandElem(elem: Elem)(implicit ctx: XmlContext, q: Quotes): Expr[scala.xml.Elem] = {
private def expandElem(elem: Elem)(using ctx: XmlContext, q: Quotes): Expr[scala.xml.Elem] =
val (namespaces, attributes) = elem.attributes.partition(_.isNamespace)
val prefix = if (elem.prefix.nonEmpty) Expr(elem.prefix) else '{ null: String }
val prefix = if elem.prefix.nonEmpty then Expr(elem.prefix) else '{ null }
val label = Expr(elem.label)
val attributes1 = expandAttributes(attributes)
val scope = expandNamespaces(namespaces)
val empty = Expr(elem.end.isEmpty)
val child = expandNodes(elem.children)(new XmlContext(ctx.args, scope), q)
if (elem.children.isEmpty)
'{ new _root_.scala.xml.Elem($prefix, $label, $attributes1, $scope, $empty) }
val child = expandNodes(elem.children)(using new XmlContext(ctx.args, scope), q)
if elem.children.isEmpty then
'{ new scala.xml.Elem($prefix, $label, $attributes1, $scope, $empty) }
else
'{ new _root_.scala.xml.Elem($prefix, $label, $attributes1, $scope, $empty, _root_.scala.xml.NodeSeq.seqToNodeSeq($child): _*) }
}
'{ new scala.xml.Elem($prefix, $label, $attributes1, $scope, $empty, scala.xml.NodeSeq.seqToNodeSeq($child)*) }
end expandElem

private def expandAttributes(attributes: Seq[Attribute])(implicit ctx: XmlContext, q: Quotes): Expr[scala.xml.MetaData] = {
private def expandAttributes(attributes: Seq[Attribute])(using XmlContext, Quotes): Expr[scala.xml.MetaData] =
import quotes.reflect._
attributes.foldRight('{ _root_.scala.xml.Null }: Expr[scala.xml.MetaData])((attribute, rest) => {
attributes.foldRight('{ _root_.scala.xml.Null }: Expr[scala.xml.MetaData]): (attribute, rest) =>
val value = attribute.value match {
case Seq(v) => expandNode(v)
case vs => expandNodes(vs)
Expand All @@ -71,57 +68,52 @@ object Expand {
*/

val term = value.asTerm
if (term.tpe <:< TypeRepr.of[String]) {
if term.tpe <:< TypeRepr.of[String] then
val value = term.asExprOf[String]
if (attribute.prefix.isEmpty) '{ new _root_.scala.xml.UnprefixedAttribute(${Expr(attribute.key)}, $value, $rest) }
else '{ new _root_.scala.xml.PrefixedAttribute(${Expr(attribute.prefix)}, ${Expr(attribute.key)}, $value, $rest) }
} else if (term.tpe <:< TypeRepr.of[collection.Seq[scala.xml.Node]]) {
if attribute.prefix.isEmpty then '{ scala.xml.UnprefixedAttribute(${ Expr(attribute.key) }, $value, $rest) }
else '{ scala.xml.PrefixedAttribute(${ Expr(attribute.prefix) }, ${ Expr(attribute.key) }, $value, $rest) }
else if term.tpe <:< TypeRepr.of[collection.Seq[scala.xml.Node]] then
val value = term.asExprOf[collection.Seq[scala.xml.Node]]
if (attribute.prefix.isEmpty) '{ new _root_.scala.xml.UnprefixedAttribute(${Expr(attribute.key)}, $value, $rest) }
else '{ new _root_.scala.xml.PrefixedAttribute(${Expr(attribute.prefix)}, ${Expr(attribute.key)}, $value, $rest) }
} else {
if attribute.prefix.isEmpty then '{ scala.xml.UnprefixedAttribute(${ Expr(attribute.key) }, $value, $rest) }
else '{ scala.xml.PrefixedAttribute(${ Expr(attribute.prefix) }, ${ Expr(attribute.key) }, $value, $rest) }
else
val value = term.asExprOf[Option[collection.Seq[scala.xml.Node]]]
if (attribute.prefix.isEmpty) '{ new _root_.scala.xml.UnprefixedAttribute(${Expr(attribute.key)}, $value, $rest) }
else '{ new _root_.scala.xml.PrefixedAttribute(${Expr(attribute.prefix)}, ${Expr(attribute.key)}, $value, $rest) }
}
})
}
if attribute.prefix.isEmpty then '{ scala.xml.UnprefixedAttribute(${ Expr(attribute.key) }, $value, $rest) }
else '{ scala.xml.PrefixedAttribute(${ Expr(attribute.prefix) }, ${ Expr(attribute.key) }, $value, $rest) }
end expandAttributes

private def expandNamespaces(namespaces: Seq[Attribute])(implicit ctx: XmlContext, q: Quotes): Expr[scala.xml.NamespaceBinding] = {
private def expandNamespaces(namespaces: Seq[Attribute])(using XmlContext, Quotes): Expr[Scope] =
import quotes.reflect._
namespaces.foldLeft(ctx.scope)((rest, namespace) => {
val prefix = if (namespace.prefix.nonEmpty) Expr(namespace.key) else '{ null: String }
val uri = (namespace.value.head: @unchecked) match {
namespaces.foldLeft(ctx.scope): (rest, namespace) =>
val prefix = if namespace.prefix.nonEmpty then Expr(namespace.key) else '{ null }
val uri = (namespace.value.head: @unchecked) match
case Text(text) => Expr(text)
case Placeholder(id) =>
val call = '{ ${ctx.args(id)}(using _root_.scala.xml.TopScope) }
val call = '{ ${ctx.args(id)}(using scala.xml.TopScope) }
Expr.betaReduce(call).asExprOf[String]
}
'{ new _root_.scala.xml.NamespaceBinding($prefix, $uri, $rest) }
})
}
'{ scala.xml.NamespaceBinding($prefix, $uri, $rest) }
end expandNamespaces

private def expandText(text: Text)(using Quotes): Expr[scala.xml.Text] =
'{ new _root_.scala.xml.Text(${Expr(text.text)}) }
'{ scala.xml.Text(${ Expr(text.text) }) }

private def expandComment(comment: Comment)(using Quotes): Expr[scala.xml.Comment] =
'{ new _root_.scala.xml.Comment(${Expr(comment.text)}) }
'{ scala.xml.Comment(${ Expr(comment.text) }) }

private def expandPlaceholder(placeholder: Placeholder)(implicit ctx: XmlContext, q: Quotes): Expr[Any] = {
private def expandPlaceholder(placeholder: Placeholder)(using XmlContext, Quotes): Expr[Any] =
val arg = ctx.args(placeholder.id)
val scope = ctx.scope
Expr.betaReduce('{ $arg(using $scope) })
}
Expr.betaReduce('{ $arg(using ${ ctx.scope }) })

private def expandPCData(pcdata: PCData)(using Quotes): Expr[scala.xml.PCData] =
'{ new _root_.scala.xml.PCData(${Expr(pcdata.data)}) }
'{ scala.xml.PCData(${ Expr(pcdata.data) }) }

private def expandProcInstr(instr: ProcInstr)(using Quotes): Expr[scala.xml.ProcInstr] =
'{ new _root_.scala.xml.ProcInstr(${Expr(instr.target)}, ${Expr(instr.proctext)}) }
'{ scala.xml.ProcInstr(${ Expr(instr.target) }, ${ Expr(instr.proctext) }) }

private def expandEntityRef(ref: EntityRef)(using Quotes): Expr[scala.xml.EntityRef] =
'{ new _root_.scala.xml.EntityRef(${Expr(ref.name)}) }
'{ scala.xml.EntityRef(${ Expr(ref.name) }) }

private def expandUnparsed(unparsed: Unparsed)(using Quotes): Expr[scala.xml.Unparsed] =
'{ new _root_.scala.xml.Unparsed(${Expr(unparsed.data)}) }
}
'{ scala.xml.Unparsed(${ Expr(unparsed.data) }) }

end Expand
5 changes: 2 additions & 3 deletions src/main/scala/dotty/xml/interpolator/internal/Hole.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package dotty.xml.interpolator
package internal

object Hole {
object Hole:
val HoleStart = 0xE000.toChar.toString
val HoleChar = 0xE001.toChar.toString
def encode(i: Int) = HoleStart + HoleChar * i
}
def encode(i: Int) = HoleStart + HoleChar * i
96 changes: 48 additions & 48 deletions src/main/scala/dotty/xml/interpolator/internal/Macro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,43 @@ import scala.quoted._
import scala.collection.mutable.ArrayBuffer
import scala.language.implicitConversions

object Macro {
object Macro:

def impl(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Scope ?=> Any]], scope: Expr[Scope])(using qctx: Quotes): Expr[scala.xml.Node | scala.xml.NodeBuffer] = {
((strCtxExpr, argsExpr): @unchecked) match {
/** ??? */
def impl(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Scope ?=> Any]], scope: Expr[Scope])
(using Quotes): Expr[scala.xml.Node | scala.xml.NodeBuffer] =

(strCtxExpr, argsExpr) match
case ('{ StringContext(${Varargs(parts)}: _*) }, Varargs(args)) =>

val (xmlStr, offsets) = encode(parts)
implicit val ctx: XmlContext = new XmlContext(args, scope)
implicit val reporter: Reporter = new Reporter {
import quotes.reflect._

def error(msg: String, idx: Int): Unit = {
val (part, offset) = Reporter.from(idx, offsets, parts)
val pos = part.asTerm.pos
val (srcF, start) = (pos.sourceFile, pos.start)
report.error(msg, Position(srcF, start + offset, start + offset + 1))
}
given XmlContext = new XmlContext(args, scope)
given Reporter = new Reporter {
import quotes.reflect.*

def error(msg: String, idx: Int): Unit = {
val (part, offset) = Reporter.from(idx, offsets, parts)
val pos = part.asTerm.pos
val (srcF, start) = (pos.sourceFile, pos.start)
report.error(msg, Position(srcF, start + offset, start + offset + 1))
}

def error(msg: String, expr: Expr[Any]): Unit =
report.error(msg, expr)
}

def error(msg: String, expr: Expr[Any]): Unit = {
report.error(msg, expr)
}
}
implCore(xmlStr)
}
}
end impl

def implErrors(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Scope ?=> Any]], scope: Expr[Scope])(using qctx: Quotes): Expr[List[(Int, String)]] = {
((strCtxExpr, argsExpr): @unchecked) match {
def implErrors(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Scope ?=> Any]], scope: Expr[Scope])
(using Quotes): Expr[List[(Int, String)]] =
(strCtxExpr, argsExpr) match
case ('{ StringContext(${Varargs(parts)}: _*) }, Varargs(args)) =>
val errors = List.newBuilder[Expr[(Int, String)]]
val (xmlStr, offsets) = encode(parts)
implicit val ctx: XmlContext = new XmlContext(args, scope)
implicit val reporter: Reporter = new Reporter {
given XmlContext = new XmlContext(args, scope)
given Reporter = new Reporter {
import quotes.reflect._

def error(msg: String, idx: Int): Unit = {
Expand All @@ -53,48 +58,43 @@ object Macro {
}
implCore(xmlStr)
Expr.ofList(errors.result())
}
}
end implErrors

private def implCore(xmlStr: String)(using XmlContext, Reporter, Quotes): Expr[scala.xml.Node | scala.xml.NodeBuffer] = {
private def implCore(xmlStr: String)(using XmlContext, Reporter, Quotes): Expr[scala.xml.Node | scala.xml.NodeBuffer] =

import Parse.{apply => parse}
import Transform.{apply => transform}
import Validate.{apply => validate}
import TypeCheck.{apply => typecheck}
import Expand.{apply => expand}
import Parse.apply as parse
import Transform.apply as transform
import Validate.apply as validate
import TypeCheck.apply as typecheck
import Expand.apply as expand

val interpolate = (
parse
andThen transform
andThen validate
andThen typecheck
andThen expand
)
val interpolate =
parse andThen
transform andThen
validate andThen
typecheck andThen
expand

interpolate(xmlStr)
}
end implCore

private def encode(parts: Seq[Expr[String]])(using Quotes): (String, Array[Int]) = {
private def encode(parts: Seq[Expr[String]])(using Quotes): (String, Array[Int]) =
val sb = new StringBuilder()
val bf = ArrayBuffer.empty[Int]

def appendPart(part: Expr[String]) = {
def appendPart(part: Expr[String]) =
bf += sb.length
sb ++= part.valueOrAbort
bf += sb.length
}

def appendHole(index: Int) = {
sb ++= Hole.encode(index)
}
def appendHole(index: Int) = sb ++= Hole.encode(index)

for ((part, index) <- parts.init.zipWithIndex) {
for (part, index) <- parts.init.zipWithIndex do
appendPart(part)
appendHole(index)
}
appendPart(parts.last)

(sb.toString, bf.toArray)
}
}
end encode

end Macro
10 changes: 4 additions & 6 deletions src/main/scala/dotty/xml/interpolator/internal/Reporter.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package dotty.xml.interpolator
package internal

import scala.quoted._
import scala.quoted.*

trait Reporter {
trait Reporter:
def error(msg: String, idx: Int): Unit
def error(msg: String, expr: Expr[Any]): Unit
}

object Reporter {
object Reporter:
def from(idx: Int, offsets: Array[Int], parts: Seq[Expr[String]]): (Expr[String], Int) = {
val index = offsets.lastIndexWhere(idx >= _)
val isWithinHoleOrAtTheEnd = index % 2 != 0
Expand All @@ -17,5 +16,4 @@ object Reporter {
case false => (index / 2, idx - offsets(index))
}
(parts(partIndex), offset)
}
}
}
17 changes: 9 additions & 8 deletions src/main/scala/dotty/xml/interpolator/internal/Transform.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package dotty.xml.interpolator
package internal

import dotty.xml.interpolator.internal.Tree._
import Tree.*

object Transform {
def apply(nodes: Seq[Node]): Seq[Node] = {
nodes.map {
object Transform:

def apply(nodes: Seq[Node]): Seq[Node] =
nodes.map:
case elem : Elem =>
val children = apply(elem.children)
if (elem.name == "xml:group" && !elem.end.isEmpty) Group(elem.children).setPos(elem.pos)
if elem.name == "xml:group" && !elem.end.isEmpty then Group(elem.children).setPos(elem.pos)
else elem.copy(children = children).setPos(elem.pos)
case node => node
}
}
}
end apply

end Transform
Loading
Loading