如何获取传递给Scala宏的参数的运行时值? [英] How to get the runtime value of parameter passed to a Scala macro?

查看:97
本文介绍了如何获取传递给Scala宏的参数的运行时值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的表面上看似很简单的宏问题,但我碰了好几个小时都没碰运气.也许有更多经验的人可以提供帮助.

I have an ostensibly simple macro problem that I’ve been banging my head against for a few hours, with no luck. Perhaps someone with more experience can help.

我有以下宏:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

object MacroObject {
  def run(s: String): Unit =
    macro runImpl

  def runImpl(c: Context)(s: c.Tree): c.Tree = {
    import c.universe._
    println(s)    // <-- I need the macro to know the value of s at compile time
    q"()"
  }
}

问题是这样的:我希望宏知道传递给它的值s —不是s的AST,而是s本身的值.具体来说,我希望它具有以下行为:

The problem is this: I’d like the macro to know the value s that is passed to it — not an AST of s, but the value of s itself. Specifically, I’d like it to have this behavior:

def runTheMacro(str: String): Unit = MacroObject.run(str)

final val HardCodedString1 = "Hello, world!"
runTheMacro(HardCodedString1)    // the macro should print "Hello, world!"
                                 // to the console during macro expansion

final val HardCodedString2 = "So long!"
runTheMacro(HardCodedString2)    // the macro should print "So long!"
                                 // to the console during macro expansion

保证,唯一要传递给runTheMacro的字符串是硬编码的常量值(即在编译时已知).

It is guaranteed that the only strings that will be passed to runTheMacro are hard-coded constant values (i.e., known at compile-time).

这有可能吗?怎么做到的?

Is this possible, and how does one do this?

-

还有以下限制:

  1. 它必须是黑盒宏.
  2. 宏签名必须使用c.Tree,而不是c.Expr[_](旧版代码;不能更改该部分)
  3. 如果需要,我的宏中确实有一个toolbox:
  1. It must be a blackbox macro.
  2. The macro signature must use c.Trees, not c.Expr[_]s (legacy code; can’t change that part)
  3. I do have a toolbox within the macro at my disposal if needed:

import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
private val toolbox = currentMirror.mkToolBox()

/** Evaluate the given code fragment at compile time. */
private def eval[A](code: String): A = {
  import scala.reflect.runtime.{universe => u}
  val uTree: u.Tree = toolbox.parse(code)
  toolbox.eval(uTree).asInstanceOf[A]
}

推荐答案

您的eval是运行时反射的eval,编译时宏的eval将是c.eval.

Your eval is runtime reflection's eval, compile-time macro's eval would be c.eval.

"Hello, world!" in

final val HardCodedString1 = "Hello, world!"
runTheMacro(HardCodedString1) 

HardCodedString1的运行时值.

您无法在编译时访问运行时值.

You can't have access to runtime value at compile time.

在编译时,字符串HardCodedString1的树只是对val树的右侧一无所知.

At compile time the tree of string HardCodedString1 just doesn't know anything about right hand side of the val tree.

Scala:Context.eval引用中可以进行哪些编码?

如果您确实需要在程序树中使用运行时值,则必须将其编译推迟到运行时

If you really need to use a runtime value inside the tree of your program you have to postpone its compilation till runtime

import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox

object MacroObject {
  val toolbox = currentMirror.mkToolBox()

  def run(s: String): Unit = {
    toolbox.eval(q"""
      println($s)
      ()
    """)
  }
}

runTheMacro(HardCodedString1)//Hello, world!
runTheMacro(HardCodedString2)//So long!

或者,在编译时,您可以以某种方式找到封闭类的树,并在其中寻找val树,并取其右手

Alternatively at compile time you can somehow find the tree of enclosing class and look inside it for the val tree and take its right hand side

def runImpl(c: blackbox.Context)(s: c.Tree): c.Tree = {
  import c.universe._

  var rhs: Tree = null

  val traverser = new Traverser {
    override def traverse(tree: Tree): Unit = {
      tree match {
        case q"$mods val $tname: $tpt = $expr" if tname == TermName("HardCodedString1") =>
          rhs = expr
        case _ => ()
      }
      super.traverse(tree)
    }
  }

  traverser.traverse(c.enclosingClass) // deprecated

  val rhsStr =
    if (rhs != null) c.eval[String](c.Expr(c.untypecheck(rhs.duplicate)))
    else c.abort(c.enclosingPosition, "no val HardCodedString1 defined")

  println(rhsStr)

  q"()"
}

runTheMacro(HardCodedString1)//Warning:scalac: Hello, world!

或针对所有此类变量

def runImpl(c: blackbox.Context)(s: c.Tree): c.Tree = {
  import c.universe._

  val sEvaluated =
    try {
      c.eval[String](c.Expr(c.untypecheck(s.duplicate)))
    } catch {
      case e: IllegalArgumentException if e.getMessage.startsWith("Could not find proxy") =>
        s match {
          case q"$sName" =>
            var rhs: Tree = null

            val traverser = new Traverser {
              override def traverse(tree: Tree): Unit = {
                tree match {
                  case q"$mods val $tname: $tpt = $expr" if tname == sName =>
                    rhs = expr
                  case _ => ()
                }
                super.traverse(tree)
              }
            }

            traverser.traverse(c.enclosingClass)

            if (rhs != null) c.eval[String](c.Expr(c.untypecheck(rhs.duplicate)))
            else c.abort(c.enclosingPosition, s"no val $sName defined")

          case _ => c.abort(c.enclosingPosition, s"unsupported tree $s")
        }

    }

  println(sEvaluated)

  q"()"
}

MacroObject.run(HardCodedString1) //Warning:scalac: Hello, world!
MacroObject.run(HardCodedString2) //Warning:scalac: So long!

runTheMacro在这种情况下将不起作用:Error: no val str defined. 要使其正常工作,您也可以使其成为一个宏

runTheMacro will not work in this case: Error: no val str defined. To make it work you can make it a macro too

def runTheMacro(str: String): Unit = macro runTheMacroImpl

def runTheMacroImpl(c: blackbox.Context)(str: c.Tree): c.Tree = {
  import c.universe._
  q"MacroObject.run($str)"
}

runTheMacro(HardCodedString1) //Warning:scalac: Hello, world!
runTheMacro(HardCodedString2) //Warning:scalac: So long!

这篇关于如何获取传递给Scala宏的参数的运行时值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆