Skip to content

Instantly share code, notes, and snippets.

@travisbrown
Created March 11, 2013 19:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save travisbrown/5136824 to your computer and use it in GitHub Desktop.
Save travisbrown/5136824 to your computer and use it in GitHub Desktop.
String interpolation with macro-supported access to positions.
sealed trait Piece
case class Place(p: String) extends Piece
case class Name(n: String) extends Piece
case class LocatedPieces(located: Seq[(String, Piece, (Int, Int))])
object S2 extends ReflectionUtils {
import scala.language.experimental.macros
import scala.language.reflectiveCalls
import scala.reflect.macros.Context
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._
val ac = companionApplier(c.universe)
val parts = c.prefix.tree match {
case Apply(_, List(Apply(_, parts))) => parts zip pieces map {
case (part, piece) =>
val line = c.literal(piece.tree.pos.line).tree
val column = c.literal(piece.tree.pos.column).tree
ac[(_, _, _)](part, piece.tree, ac[(_, _)](line, column))
}
}
c.Expr(ac[LocatedPieces](ac[Seq[_]](parts: _*)))
}
}
trait ReflectionUtils {
import scala.reflect.api.Universe
def companionApplier(u: Universe) = new {
def apply[A: u.TypeTag](xs: u.Tree*): u.Tree = u.Apply(
u.Select(u.Ident(u.typeOf[A].typeSymbol.companionSymbol), "apply"),
xs.toList
)
}
}
@travisbrown
Copy link
Author

Note that it's possible to make this very slightly more concise with reify and splice:

def s2Impl(c: Context)(pieces: c.Expr[Piece]*): c.Expr[LocatedPieces] = {
  import c.universe._

  val ac = companionApplier(c.universe)

  val parts = c.prefix.tree match {
    case Apply(_, List(Apply(_, parts))) => parts zip pieces map {
      case (part, piece) => reify((
        c.Expr[String](part).splice,
        piece.splice,
        (
          c.literal(piece.tree.pos.line).splice,
          c.literal(piece.tree.pos.column).splice
        )
      )).tree
    }
  }

  val ps = c.Expr(ac[Seq[_]](parts: _*))
  reify(LocatedPieces(ps.splice))
}

I personally don't prefer this style—it makes it much easier to run into errors at (macro use site—i.e., after you've already compiled your macros) compile time. I definitely "have troubles tracking free term variables", to quote the error message I'm always seeing.

reify is also just weird magic. I don't like the fact that this compiles and works:

val ps = c.Expr(ac[Seq[_]](parts: _*))
reify(LocatedPieces(ps.splice))

But this does not:

reify(LocatedPieces(c.Expr(ac[Seq[_]](parts: _*)).splice))

I have no idea why this doesn't work—it may be a bug.

@travisbrown
Copy link
Author

Also note that this is in reply to a Stack Overflow question by Eric Torreborre.

@travisbrown
Copy link
Author

And a usage example:

scala> val x = Name("Ben Baxter")
x: Name = Name(Ben Baxter)

scala> val y = Name("Pea Soup Limited")
y: Name = Name(Pea Soup Limited)

scala> val z = Place("Ingleby Barwick, England")
z: Place = Place(Ingleby Barwick, England)

scala> import S2._
import S2._

scala> s2"$x, director of $y, a smoke-machine supplier in $z..."
res0: LocatedPieces = ...

scala> res0.located foreach println
(,Name(Ben Baxter),(13,21))
(, director of ,Name(Pea Soup Limited),(13,37))
(, a smoke-machine supplier in ,Place(Ingleby Barwick, England),(13,69))

@kiritsuku
Copy link

Could you explain why the code snippet with the inlined c.Expr fails to compile? I don't get it.

@travisbrown
Copy link
Author

Actually good point—I'm looking back at it and I don't know why it doesn't work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment