Created
June 30, 2012 03:46
-
-
Save paulp/3022109 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
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.')
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.