如何获取StringContext和表达式位置

如何获取StringContext和表达式位置

本文介绍了字符串插值和宏:如何获取StringContext和表达式位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时删除!!

我正在尝试使用宏实现自定义字符串插值方法,并且需要有关使用API​​的一些指导.

I'm trying to implement a custom string interpolation method with a macro and I need some guidance on using the API.

这就是我想要做的:

/** expected
  * LocatedPieces(List(("\nHello ", Place("world"), Position()),
                       ("\nHow are you, ", Name("Eric"), Position(...)))
  */
val locatedPieces: LocatedPieces =
  s2"""
    Hello $place

    How are you, $name
    """

val place: Piece = Place("world")
val name: Piece = Name("Eric")

trait Piece
case class Place(p: String) extends Piece
case class Name(n: String) extends Piece

/** sequence of each interpolated Piece object with:
  * the preceding text and its location
  */
case class LocatedPieces(located: Seq[(String, Piece, Position)])

implicit class s2pieces(sc: StringContext) {
  def s2(parts: Piece*) = macro s2Impl
}

def impl(c: Context)(pieces: c.Expr[Piece]*): c.Expr[LocatedPieces] = {
  // I want to build a LocatedPieces object with the positions for all
  // the pieces + the pieces + the (sc: StringContext).parts
  // with the method createLocatedPieces below
  // ???
}

def createLocatedPieces(parts: Seq[String], pieces: Seq[Piece], positions: Seq[Position]):
  LocatedPieces =
  // zip the text parts, pieces and positions together to create a LocatedPieces object
  ???

我的问题是:

  1. 如何获取宏中的StringContext对象以获取所有StringContext.parts字符串?

  1. How do I access the StringContext object inside the macro in order to get all the StringContext.parts strings?

我该如何把握每件作品的位置?

How can I grab the positions of a each piece?

如何调用上面的createLocatedPieces方法并调整结果以获取宏调用的结果?

How can I call the createLocatedPieces method above and reify the result to get the result of the macro call?

推荐答案

经过几个小时的努力,我发现了一个可运行的解决方案:

I found a runnable solution after some hours of hard work:

object Macros {

  import scala.reflect.macros.Context
  import language.experimental.macros

  sealed trait Piece
  case class Place(str: String) extends Piece
  case class Name(str: String) extends Piece
  case class Pos(column: Int, line: Int)
  case class LocatedPieces(located: List[(String, Piece, Pos)])

  implicit class s2pieces(sc: StringContext) {
    def s2(pieces: Piece*) = macro s2impl
  }

  // pieces contain all the Piece instances passed inside of the string interpolation
  def s2impl(c: Context)(pieces: c.Expr[Piece]*): c.Expr[LocatedPieces] = {
    import c.universe.{ Name => _, _ }

    c.prefix.tree match {
      // access data of string interpolation
      case Apply(_, List(Apply(_, rawParts))) =>

        // helper methods
        def typeIdent[A : TypeTag] =
          Ident(typeTag[A].tpe.typeSymbol)

        def companionIdent[A : TypeTag] =
          Ident(typeTag[A].tpe.typeSymbol.companionSymbol)

        def identFromString(tpt: String) =
          Ident(c.mirror.staticModule(tpt))

        // We need to translate the data calculated inside of the macro to an AST
        // in order to write it back to the compiler.
        def toAST(any: Any) =
          Literal(Constant(any))

        def toPosAST(column: Tree, line: Tree) =
          Apply(
            Select(companionIdent[Pos], newTermName("apply")),
            List(column, line))

        def toTupleAST(t1: Tree, t2: Tree, t3: Tree) =
          Apply(
            TypeApply(
              Select(identFromString("scala.Tuple3"), newTermName("apply")),
              List(typeIdent[String], typeIdent[Piece], typeIdent[Pos])),
            List(t1, t2, t3))

        def toLocatedPiecesAST(located: Tree) =
          Apply(
            Select(companionIdent[LocatedPieces], newTermName("apply")),
            List(located))

        def toListAST(xs: List[Tree]) =
          Apply(
            TypeApply(
              Select(identFromString("scala.collection.immutable.List"), newTermName("apply")),
              List(AppliedTypeTree(
                typeIdent[Tuple3[String, Piece, Pos]],
                List(typeIdent[String], typeIdent[Piece], typeIdent[Pos])))),
            xs)

        // `parts` contain the strings a string interpolation is built of
        val parts = rawParts map { case Literal(Constant(const: String)) => const }
        // translate compiler positions to a data structure that can live outside of the compiler
        val positions = pieces.toList map (_.tree.pos) map (p => Pos(p.column, p.line))
        // discard last element of parts, `transpose` does not work otherwise
        // trim parts to discard unnecessary white space
        val data = List(parts.init map (_.trim), pieces.toList, positions).transpose
        // create an AST containing a List[(String, Piece, Pos)]
        val tupleAST = data map { case List(part: String, piece: c.Expr[_], Pos(column, line)) =>
          toTupleAST(toAST(part), piece.tree, toPosAST(toAST(column), toAST(line)))
        }
        // create an AST of `LocatedPieces`
        val locatedPiecesAST = toLocatedPiecesAST(toListAST(tupleAST))
        c.Expr(locatedPiecesAST)

      case _ =>
        c.abort(c.enclosingPosition, "invalid")
    }
  }
}

用法:

object StringContextTest {
  val place: Piece = Place("world")
  val name: Piece = Name("Eric")
  val pieces = s2"""
    Hello $place
    How are you, $name?
  """
  pieces.located foreach println
}

结果:

(Hello,Place(world),Pos(12,9))
(How are you,,Name(Eric),Pos(19,10))

我没想到花那么多时间将所有东西放在一起,但这是一段愉快的时光.我希望代码符合您的要求.如果您需要有关特定事物工作方式的更多信息,请查看SO上的其他问题及其答案:

I didn't thought that it can take so many time to get all things together, but it was a nice time of fun. I hope the code conforms your requirements. If you need more information on how specific things are working then look at other questions and their answers on SO:

  • Information on how ASTs are constructed
  • How to work with TypeTag
  • How to use reify in the REPL to get information about the AST

非常感谢Travis Brown(请参阅评论),我得到了一个更短的编译解决方案:

Many thanks to Travis Brown (see comments), I got a far shorter solution to compile:

object Macros {

  import scala.reflect.macros.Context
  import language.experimental.macros

  sealed trait Piece
  case class Place(str: String) extends Piece
  case class Name(str: String) extends Piece
  case class Pos(column: Int, line: Int)
  case class LocatedPieces(located: Seq[(String, Piece, Pos)])

  implicit class s2pieces(sc: StringContext) {
    def s2(pieces: Piece*) = macro s2impl
  }

  def s2impl(c: Context)(pieces: c.Expr[Piece]*): c.Expr[LocatedPieces] = {
    import c.universe.{ Name => _, _ }

    def toAST[A : TypeTag](xs: Tree*): Tree =
      Apply(
        Select(Ident(typeOf[A].typeSymbol.companionSymbol), newTermName("apply")),
        xs.toList)

    val parts = c.prefix.tree match {
      case Apply(_, List(Apply(_, rawParts))) =>
        rawParts zip (pieces map (_.tree)) map {
          case (Literal(Constant(rawPart: String)), piece) =>
            val line = c.literal(piece.pos.line).tree
            val column = c.literal(piece.pos.column).tree
            val part = c.literal(rawPart.trim).tree
            toAST[(_, _, _)](part, piece, toAST[Pos](line, column))
      }
    }
    c.Expr(toAST[LocatedPieces](toAST[Seq[_]](parts: _*)))
  }
}

它对详细的AST构造进行了抽象,其逻辑略有不同,但几乎相同.如果您在理解代码的工作方式上遇到困难,请首先尝试了解第一个解决方案.它的作用更加明确.

It abstracts over the verbose AST construction and its logic is a little different but nearly the same. If you have difficulties in understanding how the code works, first try to understand the first solution. It is more explicit in what it does.

这篇关于字符串插值和宏:如何获取StringContext和表达式位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

1403页,肝出来的..

09-06 22:25