【问题标题】:Macro to access source code text at runtime在运行时访问源代码文本的宏
【发布时间】:2013-04-12 13:16:59
【问题描述】:

是否已经有或是否有一个 Scala 宏可以让我访问源代码的文本?例如我想写这样的代码:

val list = List(1, 2, 3)
val (text, sum) = (list.sum).withSource{(source, sum) => (source, sum)}
// would return ("list.sum", 6)
(list.sum).withSource{(source, sum) => println(s"$source: $sum"}
// prints list.sum: 6

【问题讨论】:

标签: scala macros


【解决方案1】:

你真的想要源代码还是Tree就够了?

对于Tree,您可以像这样使用prefix of Context

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

implicit class WithSourceHelper[T](source: T) {
  def withSource[R](f: (String, T) => R): R = macro withSourceImpl[T, R]
}

def withSourceImpl[T, R](c: Context)(f: c.Expr[(String, T) => R]): c.Expr[R] = {
  import c.universe.{reify, Apply}

  val source = c.prefix.tree match {
    case Apply(_, List(s)) => s
    case _ => c.abort(c.enclosingPosition, "can't find source")
  }

  reify{ f.splice.apply(c.literal(source.toString).splice, c.Expr[T](source).splice) }
}

用法:

scala> val (x, y) = (1, 2)
x: Int = 1
y: Int = 2

scala> {x + y}.withSource{ (s, r) => s"$s = $r" }
res15: String = x.+(y) = 3

scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)

scala> val (text, sum) = (list.sum).withSource{(source, sum) => (source, sum)}
text: String = list.sum[Int](math.this.Numeric.IntIsIntegral)
sum: Int = 6

scala> (list.sum).withSource{(source, sum) => println(s"$source: $sum")}
$line38.$read.$iw.$iw.$iw.list.sum[Int](math.this.Numeric.IntIsIntegral): 6

【讨论】:

  • 最好有出处,因为这是我写的。但是树的顶层可能已经足够了。
  • 似乎不可能删除implicit关键字,而是创建一个隐含的WithSourceHelper子类,它有一个引用withSource的方法...你知道是否有限制?我希望将宏的内容保持在最低限度,并能够提供withSource 的包装器。例如我有这个想法:implicit class LogValue[T](t: T) extends WithSourceHelper(t) { def logvalue = withSource{(s, t) => { println(s); t } }
【解决方案2】:

我无法直接重用withSource 来打印源和值并返回值。 withSource 宏不能从同一个对象本身使用(所以我不能只在该文件中添加我稍微修改过的 withSource 版本)并且我不能从 WithSourceHelper 的子类调用 withSource,从而限制了重用通过继承。

如果有人感兴趣,这里是对 Senia 答案的补充,只需将值与源一起记录并返回值,以便可以进行其余的计算。

def logValueImpl[T](c: Context): c.Expr[T] = {
  import c.universe._
  val source = c.prefix.tree match {
    case Apply(_, List(s)) => s
    case _ => c.abort(c.enclosingPosition, "can't find source")
  }
  val freshName = newTermName(c.fresh("logValue$"))
  val valDef = ValDef(Modifiers(), freshName, TypeTree(source.tpe), source)
  val ident = Ident(freshName)
  val print = reify{
    println(c.literal(show(source)).splice + ": " + c.Expr[T](ident).splice) }
  c.Expr[T](Block(List(valDef, print.tree), ident))
}

然后我将其定义为def p = macro Debug.logValueImpl[T] 上的隐式转换。然后我可以这样使用:

List(1, 2, 3).reverse.p.head 
// prints: immutable.this.List.apply[Int](1, 2, 3).reverse: List(3, 2, 1)

有趣的是我可以应用两次:

List(1, 2, 3).reverse.p.p

它会告诉我logValueImpl 宏做了什么:

{
  val logValue$7: List[Int] = immutable.this.List.apply[Int](1, 2, 3).reverse;
  Predef.println("immutable.this.List.apply[Int](1, 2, 3).reverse: ".+(logValue$7));
  logValue$7
}

它似乎也适用于其他宏:

f"float ${1.3f}%3.2f; str ${"foo".reverse}%s%n".p`
//prints:
{
  val arg$1: Float = 1.3;
  val arg$2: Any = scala.this.Predef.augmentString("foo").reverse;
  scala.this.Predef.augmentString("float %3.2f; str %s%%n").format(arg$1, arg$2)
}: float 1.30; str oof%n

更有趣的是,如果我使用showRaw 而不是show,我什至可以看到扩展宏的树,这可能有助于弄清楚如何编写其他宏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-31
    • 1970-01-01
    • 1970-01-01
    • 2018-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多