Skip to content

Instantly share code, notes, and snippets.

@dcsobral
Forked from paulp/gist:3022109
Created July 1, 2012 04:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcsobral/3026809 to your computer and use it in GitHub Desktop.
Save dcsobral/3026809 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 typed arguments.
val typedArgs = (args, tpes).zipped map ((arg, tpe) =>
Typed(arg, Ident(tpe.typeSymbol.name)))
// The call that got the ball rolling, now routed to Predef
val call = treeBuild.mkMethodCall(definitions.PredefModule, newTermName("printf"), Nil, format.tree :: typedArgs)
// Woodsy the Owl says "Type-safety first!"
c.Expr[Unit](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: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: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)
^
8 errors found
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment