6

我想做这样的事情:

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

4

3 回答 3

5

Dude, isn't an assert macro one of the basic use cases you implement to learn how to use macros?

Well, that's what I thought, too.

By "glean snippets" in my other answer I meant what specs2 does in its s2 macro.

Or, you can do an arbitrary representation, as in my variant rip-off of expecty.

I thought I'd type your example into REPL, in a couple of lines. After all, you're just trying to print the snippet from the source that corresponds to the tree representing your conditional.

What could be easier?

Of course, it's easier under -Yrangepos, but we can postulate positions.

I'm willing to share how far I got before I lost interest.

People (e.g., paulp, who is the vox paulpuli) want trees to have attachments representing "the source I typed on my keyboard", because, you know, maybe I want it for a message or to figure out what the user was trying to accomplish.

It looks like the predicate p doesn't have a range position. So the other idea is that we know the start of the macro application, which is the paren of the second param list, so working backward through the source, matching the closing paren of the first param list, is doable.

Note that showCode isn't helpful because for a conditional like 10 < 5 it shows false, neatly folded.

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]
}

It just occurred to me to get the rangy position this way:

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
}

That's close on usage x(10 < 5)(println("hi")): requirement failed: (10 < 5)(p. Margin for error.

于 2014-05-01T11:09:06.630 回答
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)

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

于 2014-04-24T06:45:25.740 回答
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 树的另一个有趣的打印机是PrettyPrinter来自 Scala Refactoring。

于 2014-05-01T18:23:45.823 回答