interpolacion cadenas scala macros scala-2.10 string-interpolation

scala - interpolacion - Interpolación de cadenas y macro: cómo obtener las ubicaciones StringContext y expresión



java interpolacion string (1)

Encontré una solución ejecutable después de algunas horas de trabajo duro:

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

Uso:

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 }

Resultado:

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

No pensé que tomarse tanto tiempo para juntar todas las cosas, pero fue un buen momento de diversión. Espero que el código cumpla con tus requisitos. Si necesita más información sobre cómo funcionan las cosas específicas, mire otras preguntas y sus respuestas en SO:

Muchas gracias a Travis Brown (ver comentarios), obtuve una solución mucho más corta para compilar:

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

Resume sobre la construcción detallada de AST y su lógica es un poco diferente pero casi igual. Si tiene dificultades para entender cómo funciona el código, primero intente comprender la primera solución. Es más explícito en lo que hace.

Estoy intentando implementar un método de interpolación de cadenas personalizado con una macro y necesito alguna guía sobre el uso de la API.

Esto es lo que quiero hacer:

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

Mis preguntas son:

  1. ¿Cómo StringContext objeto StringContext dentro de la macro para obtener todas las cadenas StringContext.parts ?

  2. ¿Cómo puedo tomar las posiciones de cada pieza?

  3. ¿Cómo puedo llamar al método createLocatedPieces anterior y reificar el resultado para obtener el resultado de la macro llamada?