Skip to content

Instantly share code, notes, and snippets.

@paulp
Created June 30, 2012 03:46
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save paulp/3022109 to your computer and use it in GitHub Desktop.
Save paulp/3022109 to your computer and use it in GitHub Desktop.
// a.scala
// Fri Jun 29 16:15:08 PDT 2012
import scala.reflect.makro.Context
import collection.mutable.ListBuffer
import collection.mutable.Stack
import language.experimental.macros
object Macros {
val conversionChars = "%BbCcdEefGgHhinosuXx"
val flagChars = "-#+ 0,("
val optIndex = """(\d+[$])?"""
val optWidth = """([0-9]+)?"""
val optFlags = """([%s]+)?""".format(flagChars)
val optPrecision = """(\.[0-9]+)?"""
// Regex accepts everything in last spot so we can issue errors about invalid chars
val regex = "%" + optIndex + optWidth + optFlags + optPrecision + "."
// macro definition is a normal function with anything you fancy in its signature
// its body, though, is nothing more that a reference to an implementation
def printf(format: String, params: Any*): Unit = macro printf_impl
// macro implementation must correspond to macro definitions that use it
// required signature is quite involved, but don't be scared
// if the compiler is unhappy, it will print the signature it wants in the error message
def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = {
import c.universe._
val Literal(Constant(fmt: String)) = format.tree
// Inhibiting the generation of a switch, else we enjoy a delicious
// java.lang.VerifyError: Bad lookupswitch instruction in method Macros$Fmt$1.tpe()Lscala/reflect/api/Types$TypeApi; at offset 6
def specifierType(ch: Char): Type = ch match {
case 'b' | 'B' if true /* guard = !switch */ => typeOf[Boolean]
case 'c' | 'C' => typeOf[Char]
case 'd' | 'u' | 'i' | 'o' | 'x' | 'X' | 'c' => typeOf[Int]
case 'f' | 'e' | 'E' | 'g' | 'G' => typeOf[Double]
case 'h' | 'H' => typeOf[Object]
case 's' => typeOf[Any]
case '%' | 'n' => NoType // no corresponding value
}
val args = params.toList map (_.tree)
val specs = (regex.r findAllIn fmt).toList
val (valid, invalid) = specs partition (conversionChars contains _.last)
val tpes = valid map (s => specifierType(s.last)) filterNot (_ eq NoType)
if (invalid.nonEmpty)
c.error(c.enclosingPosition, "Unknown conversion in format string: " + invalid.head)
else if (args.size != tpes.size)
c.error(c.enclosingPosition, "%s arguments for format string, expected: %s".format(
if (args.size < tpes.size) "not enough" else "too many", tpes.mkString(", ")))
// Zip up the actual arguments with the types implied by the spec chars
// and create vals to induce any latent type errors.
val validation = (args, tpes).zipped map ((arg, tpe) =>
ValDef(Modifiers(), newTermName(c.fresh("eval$")), TypeTree(tpe), arg))
// The call that got the ball rolling, now routed to Predef
val call = treeBuild.mkMethodCall(definitions.PredefModule, newTermName("printf"), Nil, format.tree :: args)
// Woodsy the Owl says "Type-safety first!"
c.Expr[Unit](Block(validation :+ call: _*))
}
}
// pos
object Test extends App {
import Macros._
val ms1 = 1d/3
val ms2 = false
val ms3 = 54
val who = "bippy"
val what = "dingus"
printf("Times were %.3f, %s, %d, and %%%% and %s did %H\n", ms1, ms2, ms3, who, what)
}
// neg
object Test extends App {
import Macros._
val ms1 = 1d/3
val ms2 = false
val ms3 = 54
val who = "bippy"
val what = "dingus"
// fail, too few
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", ms1, ms2, ms3, who)
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", ms1, ms3, who, what)
// fail, too many
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", ms1, ms2, ms3, who, what, what)
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", "", ms1, ms2, ms3, who, what)
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", "", ms1, ms2, ms3, who, Array(what))
// fail, types
printf("Times were %.3f, %.3f, %d, and %%%% and %s did %s\n", who, what, ms1, ms2, ms3)
printf("Times were %.3f, %.3f, %d, and %%%% and %s did %s\n", ms1, ms2, ms1, who, what)
// fail, specifier
printf("Times were %.3f, %.3f, %D, and %%%% and %s did %s\n", ms1, ms2, ms3, who, what)
}
neg.scala:11: error: not enough arguments for format string, expected: Double, Boolean, Int, Any, Object
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", ms1, ms2, ms3, who)
^
neg.scala:12: error: not enough arguments for format string, expected: Double, Boolean, Int, Any, Object
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", ms1, ms3, who, what)
^
neg.scala:15: error: too many arguments for format string, expected: Double, Boolean, Int, Any, Object
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", ms1, ms2, ms3, who, what, what)
^
neg.scala:16: error: too many arguments for format string, expected: Double, Boolean, Int, Any, Object
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", "", ms1, ms2, ms3, who, what)
^
neg.scala:17: error: too many arguments for format string, expected: Double, Boolean, Int, Any, Object
printf("Times were %.3f, %b, %d, and %%%% and %s did %H\n", "", ms1, ms2, ms3, who, Array(what))
^
neg.scala:20: error: type mismatch;
found : String
required: Double
printf("Times were %.3f, %.3f, %d, and %%%% and %s did %s\n", who, what, ms1, ms2, ms3)
^
neg.scala:20: error: type mismatch;
found : String
required: Double
printf("Times were %.3f, %.3f, %d, and %%%% and %s did %s\n", who, what, ms1, ms2, ms3)
^
neg.scala:20: error: type mismatch;
found : Double
required: Int
printf("Times were %.3f, %.3f, %d, and %%%% and %s did %s\n", who, what, ms1, ms2, ms3)
^
neg.scala:21: error: type mismatch;
found : Boolean
required: Double
printf("Times were %.3f, %.3f, %d, and %%%% and %s did %s\n", ms1, ms2, ms1, who, what)
^
neg.scala:21: error: type mismatch;
found : Double
required: Int
printf("Times were %.3f, %.3f, %d, and %%%% and %s did %s\n", ms1, ms2, ms1, who, what)
^
neg.scala:24: error: Unknown conversion in format string: %D
printf("Times were %.3f, %.3f, %D, and %%%% and %s did %s\n", ms1, ms2, ms3, who, what)
^
11 errors found
@martin-g
Copy link

martin-g commented Jul 1, 2012

case 'd' | 'u' | 'i' | 'o' | 'x' | 'X' | 'c' => typeOf[Int]

'c' is used in the previous case, for Char. Is it really needed in the one for Int ? I guess it is a copy/paste error.

@paulp
Copy link
Author

paulp commented Jul 1, 2012

It's a code-evolution error, I started out accepting an Int. It was a good error though, as it exposed a VerifyError in the backend. (The compiler should have errored out at compile time with 'unreachable code.')

https://issues.scala-lang.org/browse/SI-6011

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