Как преобразовать блок кода scala в строку?

#scala #metaprogramming #scala-macros

Вопрос:

Мне нужно реализовать функцию тестирования, которая проверяет информацию об ошибках во время компиляции для плагина «splain», часть этой функции должна преобразовать кодовый блок в строку, например:

 def convert(fn: => Unit): String

// for testing

val code = convert {
  object I extends Seq {}
}

assert(code == "object I extends Seq {}")
 

Возможно ли это с помощью стандартных функций scala? Большое спасибо за ваш совет.

Эта функция позволит проверять сообщения во время компиляции сложного кода, который часто нуждается в индексировании и рефакторинге в среде IDE

Комментарии:

1. Какая версия Scala? Может быть, вы сможете чего-то добиться с помощью макросов. Хотя я не уверен, что вы достаточно подробно рассказали о контексте того, почему вам нужно это делать ?

Ответ №1:

Да, это возможно.

Макрос Ли Хаоя Text из исходного кода

 def text[T: c.WeakTypeTag](c: Compat.Context)(v: c.Expr[T]): c.Expr[sourcecode.Text[T]] = {
  import c.universe._
  val fileContent = new String(v.tree.pos.source.content)
  val start = v.tree.collect {
    case treeVal => treeVal.pos match {
      case NoPosition ⇒ Int.MaxValue
      case p ⇒ p.startOrPoint
    }
  }.min
  val g = c.asInstanceOf[reflect.macros.runtime.Context].global
  val parser = g.newUnitParser(fileContent.drop(start))
  parser.expr()
  val end = parser.in.lastOffset
  val txt = fileContent.slice(start, start   end)
  val tree = q"""${c.prefix}(${v.tree}, $txt)"""
  c.Expr[sourcecode.Text[T]](tree)
}
 

делает почти то, что ты хочешь:

 def convert[A](fn: => Text[A]): String = fn.source

convert(10   20  
  30
)

//10   20  
//  30
 

К несчастью,

если у вас несколько операторов в {} блоке, sourcecode.Text будет сохранен только исходный код для последнего возвращаемого выражения.

И так { object I extends Seq {} } как на самом деле { object I extends Seq {}; () } макрос в этом случае работать не будет.

Итак, давайте напишем наш собственный простой макрос

 import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def convert(fn: => Any): String = macro convertImpl

def convertImpl(c: blackbox.Context)(fn: c.Tree): c.Tree = {
  import c.universe._

  val pos = fn.pos
  val res = new String(pos.source.content).slice(pos.start, pos.end)

  Literal(Constant(res))
}
 

Использование:

 trait Seq

convert {
  val i: Int = 1
  object I extends Seq {}
  10   20   30
  convert(1)
}

//{
//    val i: Int = 1
//    object I extends Seq {}
//    10   20   30
//    convert(1)
//  }
 

Обратите внимание , что аргументы макросов def проверяются по типу перед расширением макросов (так convert { val i: Int = "a" } , convert { object I extends XXX } без определения XXX и convert { (; } т. Д. не будет компилироваться).

Комментарии:

1. Большое спасибо! Если мне повезет, это будет в тестовом коде следующей версии scalac

2. Это работает, но с оговоркой: код должен быть проанализирован и проверен на тип. Или ошибка будет выдана до того, как макрос lihaoyi будет вызван

3. @tribbloid В Scala все макросы def расширяются после проверки типов их аргументов.