如何将Scala代码块转换为字符串?

3

我需要实现一个测试函数,用于检查“splain”插件的编译时错误信息。其中一部分需要将代码块转换为字符串,例如:

def convert(fn: => Unit): String

// for testing

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

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

你能用标准的Scala特性实现这个吗?非常感谢您的建议。

该函数将使复杂代码的编译时消息进行验证,该代码需要经常由IDE索引和重构。


哪个Scala版本?也许你可以通过宏实现一些东西。虽然我不确定你是否已经提供了足够的关于为什么需要这样做的上下文细节? - Gaël J
1个回答

3

是的,这是可能的。

李浩一的宏Text来自sourcecode

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 NoPositionInt.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 { (; }等都不会编译通过)。


非常感谢!如果我很幸运的话,它将出现在下一个版本的scalac测试代码中。 - tribbloid
它能运作,但有一个需要注意的地方:代码必须被解析并进行类型检查。否则,在lihaoyi的宏被调用之前会抛出一个错误。 - tribbloid
1
@tribbloid 在Scala中,所有的def宏都是在它们的参数被类型检查后展开的。 - Dmytro Mitin
@DmytroMitin,你知道一种方法可以将在convert方法外定义的变量替换为它们的值吗?例如:val i = 5; convert(10 + i) // 10 + 5 - Maxence Cramet
@MaxenceCramet 是的 https://stackoverflow.com/questions/63132189 https://dev59.com/ZmvXa4cB1Zd3GeqPM9Cq https://stackoverflow.com/questions/60806283 https://dev59.com/z7jna4cB1Zd3GeqP5ikz - Dmytro Mitin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接