Макрос Scala для печати кода?

Я хочу сделать что-то вроде этого:

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 > 0 или Assertation failed: i > 0.


person pathikrit    schedule 24.04.2014    source источник


Ответы (3)


Чувак, разве макрос assert не является одним из основных вариантов использования, который ты реализуешь, чтобы научиться использовать макросы?

Ну, я тоже так подумал.

Под «подбором фрагментов» в моем другом ответе я имел в виду, что делает spec2 в своем макросе s2.

Или вы можете сделать произвольное представление, как в моем варианте обдирал ожидания.

Я подумал, что наберу ваш пример в REPL в пару строк. В конце концов, вы просто пытаетесь напечатать фрагмент из источника, который соответствует дереву, представляющему ваше условное выражение.

Что может быть проще?

Конечно, под -Yrangepos проще, но мы можем постулировать позиции.

Я готов поделиться тем, как далеко я продвинулся, прежде чем потерял интерес.

Люди (например, paulp, который является vox paulpuli) хотят, чтобы деревья имели вложения, представляющие «источник, который я набрал на своей клавиатуре», потому что, вы знаете, может быть, я хочу, чтобы это было для сообщения или чтобы выяснить, что пользователь пытался сделать. выполнить.

Похоже, предикат 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. Запас на ошибку.

person som-snytt    schedule 01.05.2014
comment
Коды макросов Scala настолько нечитаемы/отладки... Не могу дождаться, когда это выйдет: scalamacros.org/paperstalks/ - person pathikrit; 01.05.2014

Вы ознакомились с документами по адресу:

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)

В качестве альтернативы люди использовали исходные позиции, чтобы подобрать фрагменты для печати.

person som-snytt    schedule 24.04.2014

Если вы используете 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.

person vjovanov    schedule 01.05.2014