3

Unfortunately, the most intuitive way,

val world = "Earth"
val tree = q"""println("Hello $world")"""

results in

Error:(16, 36) Don't know how to unquote here
val tree = q"""println("Hello $world")"""
                      ^

because $ within quasiquotes expects a tree.

val world = "Earth"
val tree = q"""println(${c.literal(s"Hello $world")})"""

works, but is very ugly AND I get an Intellij warning that the c.literal is deprecated and I should use quasiquotes, instead.

So ... how do I do this?

UPDATE

In response to flavian's comment:

import scala.language.experimental.macros
import scala.reflect.macros._

object TestMacros {

  def doTest() = macro impl

  def impl(c: blackbox.Context)(): c.Expr[Unit] = {
    import c.universe._ //access to AST classes
    /*
    val world = "Earth"
    val tree = q"""println(${c.literal(s"Hello $world")})"""
    */

    val world = TermName("Earth")
    val tree = q"""println("Hello $world")"""

    tree match {
      case q"""println("Hello Earth")""" => println("succeeded")
      case _ => c.abort(c.enclosingPosition, s"huh? was: $tree")
    }

    c.Expr(tree) //wrap tree and tag with its type
  }
}

gives

Error:(18, 40) Don't know how to unquote here
    val tree = q"""println("Hello $world")"""
                                   ^
flavian
  • 28,161
  • 11
  • 65
  • 105
User1291
  • 7,664
  • 8
  • 51
  • 108

2 Answers2

7

You need a TermName or something that's a compiler primitive.

The real problem is that you are mixing interpolators, without realising. The interpolator in hello world is really a string interpolator, not a quasiquote one which is good at unquoting trees as you suggest.

This is one way to go about it:

import c.universe._

val world = TermName("Earth")
val tree = q"""println("Hello" + ${world.decodedName.toString})"""
User1291
  • 7,664
  • 8
  • 51
  • 108
flavian
  • 28,161
  • 11
  • 65
  • 105
  • could you elaborate on "the interpolator in hello world is really a string interpolator, not a quasiquote one"? – User1291 Oct 26 '16 at 11:27
  • also, neither of the two ways you suggest work. in the first, the compiler will complain that ``world`` is not known to him, in the second, he will complain that he doesn't know how to unquote that. – User1291 Oct 26 '16 at 12:12
  • I did test before answering, you're doing something and Lee wrong possibly. What version of scala are you using? – flavian Oct 26 '16 at 12:56
  • ``Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77)`` – User1291 Oct 26 '16 at 13:10
  • @User1291 Again I have compiled everything before placing it here, can you copy paste your entire snippet? – flavian Oct 26 '16 at 13:11
  • @User1291 You're example still puts the `+` in the wrong place. You are placing `$world` in between the `""`(double quotes) that delimit the string passed as an argument to `println`, where as what you want is to put the `""` only around the constant literal `"Hello"`, otherwise the quasiquotes get confused. A string interpolator would know how to handle stuff like: `s"my name is $name"`, but obviously what you are interpolating are ASTs not strings, that's what I meant earlier. I've tried this locally and it works. – flavian Oct 26 '16 at 13:28
  • But if I do that and try to *call* the macro, then I get ``Error:(9, 11) not found: value Earth`` because instead of ``"Hello" + "Earth"`` we got ``"Hello" + Earth`` – User1291 Oct 26 '16 at 13:42
  • @User1291 Sorry you need `${world.decodedName.toString}` – flavian Oct 26 '16 at 13:42
  • That did it. Thank you. ``val msg = TermName(s"Hello $world")`` and then ``val tree = q"""println(${msg.decodedName.toString})"""`` even passes the test. Want to update your answer? – User1291 Oct 26 '16 at 13:49
  • Am I missing something else? Shouldn't the first code snippet (the one you haven't edited) cause any program that uses the macro to fail compilation if no ``world`` variable can be found? – User1291 Oct 26 '16 at 14:15
  • @User1291 To be fair I haven't ran the macro, only compiled it, so if you've tested that's probably true. – flavian Oct 26 '16 at 14:17
0

I just started learning Macro-Fu.

For those also exploring Scala 2 Macros / Scalameta Quasiquotes, seems to me the simplest approach is the following (using SBT 1.5.5; inline explanations):

scala> import scala.language.experimental.macros
     | import scala.reflect.macros.blackbox
     |
     | object UnquoteString {
     |
     |   def helloWorld(): Unit = macro Impl.helloWorld
     |
     |   object Impl {
     |     def helloWorld(c: blackbox.Context)(): c.Expr[Unit] = {
     |       import c.universe._
     |
     |       val world = "Earth" // or any other value here...
     |
     |       val msg = s"Hello $world" // build the entire string with it here...
     |
     |       implicitly[Liftable[String]] // as we can lift 'whole' string values with this Liftable[_]...
     |
     |       val tree = q"""println($msg)""" // quasi-unquote the entire string here...
     |
     |       c.Expr[Unit](Block(Nil, tree))
     |     }
     |   }
     | }
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object UnquoteString

scala> UnquoteString.helloWorld()
Hello Earth

scala>

Also the following change would also work

      val tree = q"""println("Hello, " + $world)""" // quasi-unquote the string to append here...
Darren Bishop
  • 2,379
  • 23
  • 20