8

我想用宏转换 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 litarals?另外:为什么它们在 quasiquotes 中支持常量 xml 而不是脱糖值?

4

2 回答 2

2

xml 包装在块中,宏调用为rename( <top><bottom>hello</bottom></top> ). 我注意到通过查看传入的树,而不是 quasiquotes 构造的内容。

我之前在查看您的问题时提出了这个问题;我不知道我的 SO 是不是这样;我试着SS在 sbt. 还有另一个可能不相关的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
    }
  }
于 2014-04-02T07:46:38.547 回答
2

不幸的是,quasiquotes 本身并不支持 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>

在这里,我们使用unlifting将代码转换为值,然后我们可以将其作为 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 项目,或者更深入地了解我们的测试套件

于 2014-04-09T21:13:03.650 回答