我想用宏转换Scala XML文字。(不是带有XML的字符串文字,而是实际的XML文字)。据我所知,XML字面量实际上并没有内置到AST级别的语言中,而是在解析器中进行了解析。有趣的是,这确实有效:
case q"<specificTag></specificTag>" => ... // succeeds for specificTag with no
// attributes and children
但显然,这是完全无用的,因为不可能匹配任意xml的方式。比如
case q"<$prefix:$label ..$attrs>$children</$prefix:$label>" => ...
不能工作,因为我们必须在模式中绑定相同的变量两次。
打印出这样一个xml文字表达式的树实际上给出了不加糖的版本。例如,
new _root_.scala.xml.Elem(null,"specificTag",_root_.scala.xml.Null,$scope,false)
但是尝试匹配失败:
case q"new _root_.scala.xml.Elem(..$params)" => ... // never succeeds
我很困惑!我的问题是:是否有一种方法可以可靠地匹配scala宏中的任意xml文字?另外:为什么在准引号中支持常量xml,而不支持糖值呢?
xml被封装在块中,宏作为rename( <top><bottom>hello</bottom></top> )
调用。我注意到这是通过查看传入的树,而不是由准引号构造的。
当我之前看到你的问题时,我提出了这个问题;我不知道我的SO是不是这样;我试着把SS
放在聚光灯下。还有一个可能与此无关的SO问题。
class Normalizer(val c: Context) {
import c.universe._
def impl(e: c.Tree) = e match {
case Block(List(), Block(List(), x)) => x match {
case q"new scala.xml.Elem($prefix, $label, $attrs, $scope, $min, $t)" =>
Console println s"Childed tree is ${showRaw(e)}"
val b = t match {
case Typed(b, z) => c.untypecheck(b.duplicate)
case _ => EmptyTree
}
val Literal(Constant(tag: String)) = label
val x = c.eval(c.Expr[NodeBuffer](b))
//q"""<${tag.reverse}>..$x</${tag.reverse}>""" // SO
e
case q"new scala.xml.Elem($prefix, $label, $attrs, $scope, $min)" =>
Console println s"Childless tree is ${showRaw(e)}" ; e
case _ => Console println s"Tree is ${showRaw(e)}" ; e
}
case _ => Console println s"Nonblock is ${showRaw(e)}" ; e
}
}
不幸的是,准引号本身不支持xml文本的匹配,直到今天,唯一的方法是匹配一个糖树,如@som-snytt所示。但是很容易出错,这样的操作可能需要太多的AST节点,它们会破坏模式匹配器。
为了解决这个缺点,我们刚刚发布了scalamacros/xml的第一个里程碑,这个库扭转了这个问题:它允许您使用纯xml节点而不是AST的xml:
scala> val q"${elem: xml.Elem}" = q"<foo><bar/></foo>"
elem: scala.xml.Elem = <foo><bar/></foo>
这里我们使用解除将代码转换为值,然后我们可以将其作为xml处理。最后,在处理之后,您可能希望通过提升)将其转换回AST:
scala> q"$elem"
res4: org.scalamacros.xml.RuntimeLiftables.__universe.Tree =
new _root_.scala.xml.Elem(null, "foo", _root_.scala.xml.Null, $scope, false, ({
val $buf = new _root_.scala.xml.NodeBuffer();
$buf.$amp$plus(new _root_.scala.xml.Elem(null, "bar", _root_.scala.xml.Null, $scope, true));
$buf
}: _*))
如果您的原始AST的情况下,一些代码片段,他们将转换为特殊的Unquote
节点,其中包含这样的片段:
scala> val q"${elem: xml.Elem}" = q"<foo>{x + y}</foo>"
elem: scala.xml.Elem = <foo>{x.+(y)}</foo>
scala> val <foo>{Unquote(q"x + y")}</foo> = elem
// matches
通过投影过滤所有未引用节点也很容易:
scala> elem "#UNQUOTE"
res6: scala.xml.NodeSeq = NodeSeq({x.+(y)})
您可能还对使用此库的简单宏检查示例sbt项目感兴趣,或者更深入地查看我们的测试套件