我正在使用白盒宏注释来生成代码。我希望超越琐碎的quasiquote替换并根据额外信息决定宏:
我知道当调用宏扩展时,当前处理的文件仍无法用于反射。它仍然是AST形式。
但是,所有相关文件应该已经编译好了。因此,如果传递给宏定义的AST具有名为MyType
的类型,那么该类型应该已经在某处声明。令人遗憾的是,MyType
可能是import语句中引入的别名。
所以,我认为应该解决以下问题以制作丰富的宏:
而且,我不知道如何处理每项任务。你能建议一些出路吗?
相当长的代码示例,但仍然很少。编程宏非常详细
import scala.language.experimental.macros
import scala.reflect.macros.Context
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
import scala.collection.mutable.Buffer
trait Companion {
val ribbon : Array[Int]
}
trait Holder {
sealed trait AnyNode {
private[Holder] var _index : Int = 0
def index : Int = _index
//example use of companion
def next : AnyNode =
nodes( companion.ribbon(_index) )
}
class Node[T](default : T) extends AnyNode {
private var _value : T = default
def value : T = _value
def value_=(nv : T) = {
_value = nv
}
override def toString : String = s"Node( ${this._value} )"
}
def companion : Companion // override in macros
protected val nodes : Array[AnyNode] // override in macros
// accessors for generated subTrait
protected def placeNode(node : AnyNode, position : Int) {
node._index = position
nodes(position) = node
}
//debug output
def printNodes : String = "nodes: " + nodes.mkString("; ")
}
@compileTimeOnly("enable macro paradise to expand macro annotations")
class fillHolder extends StaticAnnotation {
def macroTransform(annottees: Any*) : Any = macro Implementation.fillHolder
}
class Implementation[C <: Context](val c : C) {
import c.universe._
lazy val typeNode = TypeName("Node")
lazy val typeAnyNode = TypeName("AnyNode")
lazy val typeCompanion = TypeName("Companion")
lazy val typeHolder = TypeName("Holder")
lazy val termCompanion = TermName("companion")
lazy val termNodes = TermName("nodes")
lazy val termRibbon = TermName("ribbon")
lazy val termPlaceNode = TermName("placeNode")
lazy val position = c.enclosingPosition
def error(msg : String) =
c.error(position, msg)
def info(msg : String) =
c.info(position, msg, true)
def warn(msg : String) =
c.warning(position, msg)
def escape = c.Expr[Any](EmptyTree)
def makeCompanion(name : TypeName) : ModuleDef = {
val tname = name.toTermName
q"object $tname extends $typeCompanion"
}
val extractHolder : PartialFunction[List[Tree], (ClassDef, ModuleDef)] = {
case (holder : ClassDef) :: Nil => ( holder, makeCompanion(holder.name) )
case (holder : ClassDef) :: (comp : ModuleDef) :: Nil => ( holder, comp )
}
def detectMethod(impl : Template, name : TermName) : Boolean = {
impl.body.exists {
case vd : ValDef => vd.name == name
case dd : DefDef => dd.name == name
case _ => false
}
}
def detectMethodConflict(impl : Template, name : TermName) : Boolean = {
val res = detectMethod(impl, name)
if (res)
error(s"name conflict, member $name is already defined")
! res
}
def requireParent(impl : Template, name : TypeName) : Boolean = {
if (! impl.parents.exists(Ident(name) equalsStructure _)) {
error(s"could not prove $name inheritance")
false
} else
true
}
def checkHolder(holder : Template) : Boolean = {
requireParent(holder, typeHolder) &
detectMethodConflict(holder, termCompanion) &
detectMethodConflict(holder, termNodes)
}
def checkCompanion(comp : Template) : Boolean = {
requireParent(comp, typeCompanion) &
detectMethodConflict(comp, termRibbon)
}
def detectNode(tree : Tree) : Boolean = tree match {
case Apply( Select( New(Ident(tp)), termNames.CONSTRUCTOR ), _ ) => tp == typeNode
case Apply( Select( New(AppliedTypeTree(Ident(tp), _)), termNames.CONSTRUCTOR ), _ ) => tp == typeNode
case _ => false
}
def unwindBlock(expr : Tree) : Tree = expr match {
case Block(_, last) => unwindBlock(last)
case other => other
}
def extractNode(tree : Tree) : Option[TermName] = tree match {
case ValDef(_, name, _, rhs) if detectNode(unwindBlock(rhs)) => Some(name)
case _ => None
}
def process(source : Array[Int]) : Unit = { //side effecting on the source
val len = source.length - 1
for (i <- 0 until len)
source(i) = source(i+1)
source(len) = 0
}
def substituteBody(classDef : ClassDef, body : Seq[Tree]) : ClassDef = {
val impl = classDef.impl
ClassDef( classDef.mods, classDef.name, classDef.tparams,
Template(impl.parents, impl.self, body.toList) )
}
def substituteBody(moduleDef : ModuleDef, body : Seq[Tree]) : ModuleDef = {
val impl = moduleDef.impl
ModuleDef( moduleDef.mods, moduleDef.name,
Template(impl.parents, impl.self, body.toList) )
}
def fillHolder(annottees : c.Expr[Any]*) : c.Expr[Any] = {
val (holder, comp) = extractHolder.lift( annottees.map(_.tree).toList ).getOrElse(return escape)
if (! (checkHolder(holder.impl) & checkCompanion(comp.impl)) )
return escape
val bodyHolder = holder.impl.body.to[Buffer]
val bodyComp = comp.impl.body.to[Buffer]
val nodes = holder.impl.body.flatMap(extractNode _)
val compName = comp.name
val ribbon = Range(0, nodes.length).toArray
process(ribbon)
bodyHolder += q"override def $termCompanion = $compName"
bodyHolder += q"override protected val $termNodes : Array[$typeAnyNode] = new Array(${nodes.length})"
bodyHolder ++= nodes.view.zipWithIndex.map{
case (name, index) => q"$termPlaceNode($name, $index)"
}
bodyComp += q"override val $termRibbon = $ribbon"
val genHolder = substituteBody(holder, bodyHolder)
val genComp = substituteBody(comp, bodyComp)
c.Expr[Any]( Block(genHolder :: genComp :: Nil, Literal(Constant(()))) )
}
}
object Implementation {
def impl(c : Context) : Implementation[c.type] = new Implementation[c.type](c)
def apply(c : Context) : Implementation[c.type] = new Implementation[c.type](c)
def fillHolder(c : Context)(annottees : c.Expr[Any]*) : c.Expr[Any] = impl(c).fillHolder(annottees: _*)
}
有时您想声明一个成员构成重要结构的类。每次实例化类时都会重现该结构。将此结构重定位到单例并将实例成员链接到它是很好的。可能有很多原因:计算结构的成本高,内存占用空间大,操作成本高。
在这个简化的示例中,关系是节点之间的链连接。它是伴侣对象的因素。所有节点都可以获得链中的下一个节点。为此,他们使用宏生成表将具体实例绑定到共享结构,对协同进行间接查询。
真实示例是javafx css property。要为窗口小部件定义css属性,您应该单独定义css元数据(最好是静态),作为窗口小部件类成员的css属性并将它们链接在一起。
在编译期间生成这样的单例非常方便。该程序将变得不那么冗长,并且可能代价高昂(假设Implementation.process
具有O(n ^ 3)复杂度)重新定位到编译时。
我转发词汇名称来确定课程。可以通过适当的导入轻松地重命名它们。 requireParent
和detectMethodConflict
会产生意想不到的结果。如果我可以访问语义对应物会好得多。但我不知道如何。
将继承与Holder
一起使用也很好,但我的宏实现无法处理节点定义在父节点之间分配的情况。我应该遍历一个类的所有值定义,包括在父类中定义的那些,并为它们构建通用结构。但我只能访问父母的名字,而不是它的反映。所以我无法提取他们的身体并查找它们。