2013-03-10 65 views
13

我想实现一个自定义的字符串插值方法与宏,我需要一些使用API​​的指导。字符串插值和宏:如何获取StringContext和表达式位置

这是我想做的事:

/** 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字符串?

  2. 我怎样才能抓住每件的位置?

  3. 如何调用上面的createLocatedPieces方法并将结果通知给宏调用的结果?

+0

我已经尝试过各种API,但我还没有能够组装完整的解决方案。任何建议或大方向都会有所帮助。而一个完整的解决方案将得到我永恒的感激之情:-) – Eric 2013-03-10 23:29:39

+0

我不确定它是否有答案,但你的问题提醒我这个帖子:http://hootenannylas.blogspot.com.au/2013/02/syntax -checking-in-scala-string.html – 2013-03-11 02:38:25

+0

我已经阅读过它,并且我的用例稍微多一点。但是我知道Tony和我会请他在本周的下一个ScalaSyd期间帮助我,如果在此期间我没有得到答案:-)。 – Eric 2013-03-11 03:25:21

回答

10

我发现了一个可运行的解决方案的努力工作几个小时后:

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的更多信息:

非常感谢特拉维斯·布朗(见评论),我得到了一个更短的解决方案来编译:

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结构,其逻辑有点不同,但几乎相同。如果您在理解代码的工作方式时遇到困难,请首先尝试理解第一个解决方案。它在做什么时更明确。

+3

+1,我并不是想要窃取你的霹雳,但可以这样做[更简洁](https://gist.github.com/travisbrown/5136824)。我今天早上开始这个实现,但没有发布它,因为(像你的)它不会返回一个完整的'Position'。 – 2013-03-11 19:17:09

+0

感谢您的辛勤工作!我将根据我的确切用例调整您的解决方案,但看起来我有关于如何提取原始文本,职位和整体打包的所有必要信息。特拉维斯,我也会看看你的要点,乍看之下,在某些位置有一些有趣的下划线。 – Eric 2013-03-11 20:23:35

+1

@TravisBrown:非常感谢,您的解决方案非常棒。我*知道*必须有一种方法来抽象所有这些AST施工垃圾,但我没有提出解决方案。考虑多参数和元组就像参数列表一样,真是太酷了。我的第二次尝试现在比现在短得多。 – sschaef 2013-03-11 20:45:22

相关问题