【问题标题】:Matching XML Literals in Scala Macros在 Scala 宏中匹配 XML 文字
【发布时间】:2014-03-21 12:31:13
【问题描述】:

我想用宏转换 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 而不是 desugared 值?

【问题讨论】:

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


    【解决方案1】:

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

    我之前查看您的问题时已提交this issue;我不知道我的 SO 是不是这样;我尝试在 sbt 中碰撞 SSanother 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
        }
      }
    

    【讨论】:

    • 非常感谢您的回答!我今天没有时间尝试您的代码,但明天我会尝试给您一些反馈。
    • 好的,确实有效。不知道为什么我以前尝试过它时它不起作用。一定是犯了什么愚蠢的错误。非常感谢您的努力和提出问题!
    【解决方案2】:

    不幸的是,准引号本身不支持匹配 xml 文字,直到今天,唯一的方法是匹配去糖树,如 @som-snytt 所示。但是很容易出错,而且这样的操作可能需要太多的 AST 节点,以至于它们会blow up the pattern matcher

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

    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 案例中的某些代码 sn-ps 它们将被转换为包含此类 sn-ps 的特殊 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

    【讨论】:

    • 你们是 SHIELD 的 AST 代理人。我在上面回答时第一次看Unliftable,但我不明白第2点:“我们只为运行时世界定义了Unliftable,它不会在宏中可用。”
    • Liftable 路径依赖于 Universe。要使用它,您需要一个从中导入它的 Universe,当您为该 Universe 定义它时,它不适用于所有 Universe,仅适用于该 Universe。要对其进行抽象,您可以将 Universe 放入 trait 并为您感兴趣的每个 Universe(通常是运行时和宏)实例化 trait。例如:split into Liftables vs RuntimeLiftables vs MacroLiftables in scalaxml/macros.
    猜你喜欢
    • 2014-10-30
    • 1970-01-01
    • 2011-02-06
    • 2013-09-04
    • 1970-01-01
    • 2015-02-17
    • 1970-01-01
    • 2018-05-30
    • 2021-03-19
    相关资源
    最近更新 更多