2013-03-10 9 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 
    ??? 

私の質問は以下のとおりです。私はすべてのStringContext.parts文字列を取得するために、マクロ内部StringContextオブジェクトにアクセスするにはどうすればよい

  1. 各ピースの位置を取得するにはどうすればよいですか?

  2. 上記のcreateLocatedPiecesメソッドを呼び出して、結果を確認してマクロ呼び出しの結果を得るにはどうすればよいですか?

+0

私はさまざまなAPIを試しましたが、まだ完全なソリューションを組み立てることができませんでした。アドバイスや一般的な方向性が役立ちます。そして、完全な解決策は私の永遠の感謝を得るでしょう:-) – Eric

+0

答えがあるかどうかわかりませんが、あなたの質問は私にこの記事を思い出させました:http://hootenannylas.blogspot.com.au/2013/02/syntax -checking-in-scala-string.html –

+0

私はすでにそれを読んでいます。しかし、私はTonyを知っています。私はその間に答えが得られなければ、今週のScalaSydの間に私に助けを求めるでしょう:-)。 – Eric

答えて

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)) 

私はと思っていませんでしたそれはすべてのものをtogeth得るために非常に多くの時間がかかるそれは楽しい時でした。私はコードがあなたの要求事項を遵守することを願っています(参照

トラヴィス・ブラウンに感謝を:あなたは物事が働いているかの特定の詳細情報が必要な場合は、他の質問と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)。私は今朝この実装を開始しましたが、(あなたのように)完全な「位置」を返さないため、投稿しませんでした。 –

+0

あなたの努力のおかげで!私は、あなたのソリューションを私の正確なユースケースに適応させようとしていますが、生のテキストを抽出する方法、全体の位置とパッケージ方法についてはここに必要な情報がすべてあるようです。トラヴィス、私はまた、見た目にいくつかのポジションでいくつかの興味深いアンダースコアを持っているあなたの要点を見ていきます。 – Eric

+1

@TravisBrown:ありがとうございます。あなたのソリューションは素晴らしいです。私は知っていた*このすべてのASTの建設のたわごとを抽象化する方法がなければならないが、私は解決策を思い付いていない。パラメータのリストと同じように、複数のパラメータとタプルについて考えるのはすごくクールです。私の2回目の試行は、これまでのところこれまでに短かった – sschaef

関連する問題