Scala macro to print code?

不想你离开。 提交于 2019-12-18 05:01:06

问题


I want to do something like this:

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

Sample usage:

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

Now, for example, if you call fib(-20), I want it to throw an exception with a message like Assertion failed: -20 > 0 or Assertation failed: i > 0


回答1:


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.




回答2:


Have you consulted the docs at:

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)

Alternatively, folks have used source positions to glean snippets to print.




回答3:


If you are using Scala 2.11.x the best way to go is the showCode method. This method will correctly print an arbitrary Scala tree. For example:

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

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

In the previous versions of Scala you would have to use the method show which does not guarantee correctness:

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

The method showCode was designed with correctness in mind so it will not necessarily print beautiful code. If beauty is of importance for you can either contribute to the Scala Printers or you can write your own printer. Another interesting printer for Scala trees is the PrettyPrinter from Scala Refactoring.



来源:https://stackoverflow.com/questions/23261082/scala-macro-to-print-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!