Scala宏:将树转换/解析为名称

时间:2013-08-27 14:39:30

标签: scala macros scala-macros

这是一个简化的例子,但问题仍然存在。

我想使用宏(基于scala的伪代码)实现这一点:

(a: Int) => {
  val z = "toShort"
  a.z
}

如果我知道它,我会得到类似的东西:

Function(
  List(
    ValDef(
      Modifiers(Flag.PARAM),
      newTermName("a"),
      Ident(scala.Int),
      EmptyTree
    )
  ),
  Block(
    List(
      ValDef(
        Modifiers(),
        newTermName("z"),
        TypeTree(),
        Literal(Constant("toShort"))
      )
    ),
    Apply(
      Select(
        Ident(newTermName("a")),
        newTermName("toShort")
      ),
      List()
    )
  )
)

我不知道如何访问某个值,然后将其用作TermName。

我尝试用newTermName("toShort")替换newTermName(c.Expr[String](Select(Ident(newTermName("z")))).splice),但编译器似乎不喜欢:

  宏扩展期间的

异常:   java.lang.UnsupportedOperationException:你正在调用的函数没有被&gt;拼接。编译器。   这意味着涉及跨阶段评估,需要明确调用。   如果您确定这不是疏忽,请将scala-compiler.jar添加到类路径中,   导入scala.tools.reflect.Eval并改为呼叫<your expr>.eval

我也按照编译器的建议尝试'eval':newTermName(c.eval(c.Expr[String](...))但是都没有用。

我怎样才能将像Select(Ident(newTermName("z")))这样的树(可以访问本地值的值)转换为一个名称一个字符串,该字符串可以用作{{{{ 1}?有可能吗?

更新

这里真正的问题是gist

提前致谢,

1 个答案:

答案 0 :(得分:3)

我很难理解你想要实现的目标,以及为什么你到处都在使用树。树木真的很低级,难以使用,很棘手,而且很难理解代码的作用。 Quasiquotes(http://docs.scala-lang.org/overviews/macros/quasiquotes.html)是确实可行的方法,你可以在scala 2.10.x生产版上使用它们,这要归功于宏天堂插件(http://docs.scala-lang.org/overviews/macros/paradise.html)。您可以简单地编写q"(a: Int) => {val z = "toShort"; a.z}"并直接获取刚刚输入的树表达式。

要回答您的问题,第一点是要记住在编译时评估宏。因此,它们无法生成依赖于运行时值的代码。这就是编译器抱怨你splice的原因。但是,如果传递的值可以在编译时计算,通常是文字,那么您可以使用eval在宏代码中获取其值。如scaladoc所示,Eval确实遇到了一个bug。它应该只在无类型树上调用。因此,在s: c.Expr[String]表达式上调用eval的方式是val s2 = c.eval(c.Expr[String](c.resetAllAttrs(c.tree.duplicate))),它会为您提供String,然后您可以在代码中正常使用,例如q"(a: Int) => a.${newTermName(s2)}"

要把它们放在一起,让我们假设您创建一个宏,它将从一个对象和一个String字段输出一个字符串值。它会给出像

这样的东西
def attr[A](a: A, field: String): String = macro attrImpl[A]

def attrImpl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A], field: c.Expr[String]) = {
  import c.universe._

  val s = c.eval(c.Expr[String](c.resetAllAttrs(field.tree.duplicate)))
  c.Expr[String](q"a.${newTermName(s)}")

}

REPL会话测试:

scala> object a { val field1 = "field1"; val field2 = "field2" }
defined module a

scala> attr(a, "field1")
res0: String = field1

scala> attr(a, "field2")
res1: String = field2

要理解编译时和运行时之间的区别,你可以在REPL中思考以下结果; - )

scala> val s = "field1"; attr(a, s)
error: exception during macro expansion: 
scala.tools.reflect.ToolBoxError: reflective compilation has failed: 

$iw is not an enclosing class
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:311)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:244)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:408)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:411)
    at scala.reflect.macros.runtime.Evals$class.eval(Evals.scala:16)
    at scala.reflect.macros.runtime.Context.eval(Context.scala:6)
    at .attrImpl(<console>:14)


scala> val s = "field1"
s: String = field1

scala> attr(a, s)
res3: String = field1

希望它有所帮助;))