Scala宏打印代码?

时间:2014-04-24 06:10:25

标签: scala scala-2.10 scala-macros scala-2.11 scala-macro-paradise

我想做这样的事情:

def assuming[A](condition: => Boolean)(f: => A): A = {
  require(condition, /* print source-code of condition */)
  f
}

样本用法:

def fib(n: Int) = n match { // yes, yes, I know this is not efficient
  case 0 => 0 
  case 1 => 1
  case i => assuming(i > 0) { fib(i-1) + fib(i-2) }
}

现在,例如,如果您致电fib(-20),我希望它通过Assertion failed: -20 > 0Assertation failed: i > 0

等消息抛出异常

3 个答案:

答案 0 :(得分:5)

您是否咨询了以下文档:

http://www.scala-lang.org/api/2.11.0/scala-reflect/#scala.reflect.api.Printers

scala> show(q"-1 < 0")
res6: String = -1.$less(0)

scala> showCode(q"-1 < 0")
res7: String = (-1).<(0)

或者,人们使用源位置来收集片段进行打印。

答案 1 :(得分:5)

Dude,不是断言宏是您实现学习如何使用宏的基本用例之一吗?

嗯,这也是我的想法。

通过&#34; glean snippets&#34;在我的另一个答案中,我指的是specs2的作用in its s2 macro

或者,您可以执行任意表示,如in my variant rip-off of expecty

我以为我会把你的例子输入REPL,有几行。毕竟,您只是尝试从与代表您的条件的树相对应的来源打印代码段。

什么可以更容易?

当然,-Yrangepos下更容易,但我们可以假设职位。

我愿意分享我失去兴趣之前的距离。

人们(例如,paulp,谁是vox paulpuli)希望树有附件代表&#34;我在键盘输入的来源&#34;,因为,你知道,也许我想要它用于消息或者找出用户想要完成的任务。

谓词p看起来没有范围位置。所以另一个想法是我们知道宏应用程序的开始,这是第二个参数列表的填充,因此在源中向后工作,匹配第一个参数列表的结束列表是可行的。

请注意,showCode并非有用,因为对于像10 < 5这样的条件,它会显示false,整齐地折叠。

object X {
  import reflect.macros.blackbox.Context
  def impl[A: c.WeakTypeTag](c: Context)(p: c.Expr[Boolean])(body: c.Expr[A]) = {
    import c.universe._
    def treeLine(t: Tree): String = lineAt(t.pos)
    def lineAt(pos: Position): String = if (pos.isRange) pos.lineContent.drop(pos.column - 1).take(pos.end - pos.start + 1) else "???"
    val msg =
      if (p.tree.pos.isRange) {  // oh, joy
        treeLine(p.tree)
      } else {
        /*
        Console println s"content ${p.tree.pos.lineContent}"
        Console println s"column ${p.tree.pos.column}"  // alas, that's the column of the point of the top of the tree, e.g., < in "a < b".
        val len = body.tree.pos.start - p.tree.pos.start
        p.tree.pos.lineContent drop (p.tree.pos.column - 1) take len
        */
        // OK, I get it: positions are a big mystery. Make woo-woo ghost noises.
        // What we do know is the start of the apply, which must have a close paren or brace in front of it to match:
        // apply(condition)(body)
        showCode(p.tree)
      }
    q"require($p, $msg) ; $body"
  }
  def x[A](p: Boolean)(body: =>A): A = macro X.impl[A]
}

我突然想到这样一个笨拙的位置:

object X {
  import reflect.macros.blackbox.Context
  def impl(c: Context)(p: c.Expr[Boolean]) = {
    import c.universe._
    def lineAt(pos: Position): String = if (pos.isRange) pos.lineContent.drop(pos.column - 1).take(pos.end - pos.start + 1) else "???" 
    val msg = lineAt(c.macroApplication.pos)  // oh, joy
    q"require($p, $msg) ; new { def apply[A](body: =>A): A = body }"
  }
  def x(p: Boolean): { def apply[A](body: =>A): A } = macro X.impl
}

关于使用率x(10 < 5)(println("hi"))的关闭:requirement failed: (10 < 5)(p。保证金误差。

答案 2 :(得分:1)

如果您使用的是Scala 2.11.x,最好的方法是使用showCode方法。此方法将正确打印任意Scala树。例如:

scala> import reflect.runtime.universe._
import reflect.runtime.universe._

showCode(q"3.14 < 42")
res1: String = 3.14.<(42)

在以前版本的Scala中,您必须使用不保证正确性的方法show

scala> show(q"3.14 < 42")
res2: String = 3.14.$less(42)

方法showCode的设计考虑了正确性,因此不一定会打印漂亮的代码。如果美丽对您很重要,您可以为Scala Printers做出贡献,也可以编写自己的打印机。另一个有趣的Scala树打印机是来自Scala Refactoring的PrettyPrinter