在Scala宏中匹配XML文字

时间:2014-03-21 12:31:14

标签: scala scala-macros scala-quasiquotes scala-xml

我想用宏转换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文字表达式的树实际上给出了desugared版本。例如。

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?另外:为什么它们在常量xml的quasiquotes中支持,而不是在desugared值中支持?

2 个答案:

答案 0 :(得分:2)

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

当我先前看过你的问题时,我已提出this issue;我不知道我的SO是否是那个;我尝试在sbt中碰撞SS。那些可能不相关的another SO issue

  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
    }
  }

答案 1 :(得分:2)

不幸的是,quasiquotes本身不支持xml文字的匹配,直到今天,唯一的方法是在@som-snytt所示的desugared树上进行匹配。但它很容易弄错,这样的操作可能需要这么多的AST节点,他们会blow up the pattern matcher

为了解决这个弱点,我们刚刚发布了scalamacros/xml的第一个里程碑,这是一个解决这个问题的库:它不是使用XML的XML,而是让你使用纯XML节点:< / p>

scala> val q"${elem: xml.Elem}" = q"<foo><bar/></foo>"
elem: scala.xml.Elem = <foo><bar/></foo>

这里我们使用unlifting将代码转换为值,而不是将其作为xml处理。在处理结束后,您可能希望通过lifting)将其转换回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)})

您可能还有兴趣使用使用此库的简单宏查看example sbt project或深入了解我们的test suite