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.
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
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 –
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