2013-03-10 24 views
13

Tôi đang cố triển khai phương thức nội suy chuỗi tùy chỉnh bằng macro và tôi cần một số hướng dẫn về cách sử dụng API.Nội suy chuỗi và macro: cách lấy vị trí StringContext và biểu thức

Dưới đây là những gì tôi muốn làm:

/** 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 
    ??? 

Câu hỏi của tôi là:

  1. Làm thế nào để truy cập vào các đối tượng StringContext bên trong macro để có được tất cả các StringContext.parts chuỗi?

  2. Tôi có thể lấy vị trí của từng phần bằng cách nào?

  3. Làm cách nào tôi có thể gọi phương thức createLocatedPieces ở trên và sửa lại kết quả để nhận kết quả cuộc gọi macro?

+0

Tôi đã thử các phần khác nhau của API nhưng tôi chưa thể lắp ráp giải pháp đầy đủ. Bất kỳ lời khuyên hoặc hướng chung nào sẽ giúp ích cho bạn. Và một giải pháp hoàn chỉnh sẽ nhận được lòng biết ơn vĩnh cửu của tôi :-) – Eric

+0

Tôi không chắc chắn nếu nó có câu trả lời, nhưng câu hỏi của bạn nhắc nhở tôi về bài đăng này: http://hootenannylas.blogspot.com.au/2013/02/syntax -checking-in-scala-string.html –

+0

Tôi đã đọc nó và trường hợp sử dụng của tôi hơi hơn một chút. Nhưng tôi biết Tony và tôi sẽ yêu cầu anh ta giúp tôi trong ScalaSyd tiếp theo trong tuần này nếu tôi không nhận được câu trả lời trong thời gian chờ đợi :-). – Eric

Trả lời

10

Tôi tìm thấy một giải pháp Runnable sau vài giờ làm việc vất vả:

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

Cách sử dụng:

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 
} 

Kết quả:

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

Tôi không nghĩ rằng có thể mất rất nhiều thời gian để có được mọi thứ er, nhưng đó là một thời gian vui vẻ. Tôi hy vọng mã phù hợp với yêu cầu của bạn. Nếu bạn cần thêm thông tin về cách điều cụ thể đang làm việc sau đó nhìn vào câu hỏi khác và câu trả lời của họ trên SO:

Nhiều nhờ Travis Brown (xem nhận xét), tôi nhận được giải pháp ngắn hơn để biên dịch:

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: _*))) 
    } 
} 

Nó tóm tắt qua việc xây dựng AST tiết và logic của nó hơi khác một chút nhưng gần như giống nhau. Nếu bạn gặp khó khăn trong việc hiểu mã hoạt động như thế nào, trước tiên hãy cố gắng hiểu giải pháp đầu tiên. Nó rõ ràng hơn trong những gì nó làm.

+3

+1 và tôi không muốn ăn cắp sấm của bạn, nhưng có thể thực hiện điều này [_much_ chính xác hơn] (https://gist.github.com/travisbrown/5136824). Tôi bắt đầu thực hiện điều này sáng nay nhưng không đăng nó bởi vì (như của bạn) nó không trả lại một 'Vị trí 'đầy đủ. –

+0

Cảm ơn sự nỗ lực của bạn! Tôi sẽ điều chỉnh giải pháp của bạn cho trường hợp sử dụng chính xác của tôi nhưng có vẻ như tôi có tất cả thông tin cần thiết ở đây về cách trích xuất văn bản thô, vị trí và gói toàn bộ. Travis, tôi cũng sẽ xem xét ý kiến ​​của bạn, cái nhìn đầu tiên, có một số dấu gạch dưới hấp dẫn ở một số vị trí. – Eric

+1

@TravisBrown: Rất cảm ơn, giải pháp của bạn thật tuyệt vời. Tôi * biết * phải có một cách để trừu tượng hóa tất cả các công trình xây dựng AST này, nhưng tôi đã không đưa ra giải pháp. Suy nghĩ về nhiều tham số và bộ dữ liệu giống như một danh sách các tham số là cực kỳ thú vị. Lần thử thứ hai của tôi ngắn hơn rất nhiều. – sschaef

Các vấn đề liên quan