scala.tools.reflect.MacroImplementations.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the Scala Programming Language
package scala.tools.reflect
import scala.reflect.macros.{ReificationException, UnexpectedReificationException}
import scala.reflect.macros.runtime.Context
import scala.collection.mutable.ListBuffer
import scala.collection.mutable.Stack
import scala.reflect.internal.util.OffsetPosition
abstract class MacroImplementations {
val c: Context
import c.universe._
import definitions._
def macro_StringInterpolation_f(parts: List[Tree], args: List[Tree], origApplyPos: c.universe.Position): Tree = {
// the parts all have the same position information (as the expression is generated by the compiler)
// the args have correct position information
// the following conditions can only be violated if invoked directly
if (parts.length != args.length + 1) {
if(parts.length == 0)
c.abort(c.prefix.tree.pos, "too few parts")
else if(args.length + 1 < parts.length)
c.abort(if(args.length==0) c.enclosingPosition else args.last.pos,
"too few arguments for interpolated string")
else
c.abort(args(parts.length-1).pos,
"too many arguments for interpolated string")
}
val pi = parts.iterator
val bldr = new java.lang.StringBuilder
val evals = ListBuffer[ValDef]()
val ids = ListBuffer[Ident]()
val argStack = Stack(args : _*)
def defval(value: Tree, tpe: Type): Unit = {
val freshName = newTermName(c.fresh("arg$"))
evals += ValDef(Modifiers(), freshName, TypeTree(tpe) setPos value.pos.focus, value) setPos value.pos
ids += Ident(freshName)
}
def isFlag(ch: Char): Boolean = {
ch match {
case '-' | '#' | '+' | ' ' | '0' | ',' | '(' => true
case _ => false
}
}
def checkType(arg: Tree, variants: Type*): Option[Type] = {
variants.find(arg.tpe <:< _).orElse(
variants.find(c.inferImplicitView(arg, arg.tpe, _) != EmptyTree).orElse(
Some(variants(0))
)
)
}
val stdContextTags = new { val tc: c.type = c } with StdContextTags
import stdContextTags._
def conversionType(ch: Char, arg: Tree): Option[Type] = {
ch match {
case 'b' | 'B' =>
if(arg.tpe <:< NullTpe) Some(NullTpe) else Some(BooleanTpe)
case 'h' | 'H' =>
Some(AnyTpe)
case 's' | 'S' =>
Some(AnyTpe)
case 'c' | 'C' =>
checkType(arg, CharTpe, ByteTpe, ShortTpe, IntTpe)
case 'd' | 'o' | 'x' | 'X' =>
checkType(arg, IntTpe, LongTpe, ByteTpe, ShortTpe, tagOfBigInt.tpe)
case 'e' | 'E' | 'g' | 'G' | 'f' | 'a' | 'A' =>
checkType(arg, DoubleTpe, FloatTpe, tagOfBigDecimal.tpe)
case 't' | 'T' =>
checkType(arg, LongTpe, tagOfCalendar.tpe, tagOfDate.tpe)
case _ => None
}
}
def copyString(first: Boolean): Unit = {
val strTree = pi.next()
val rawStr = strTree match {
case Literal(Constant(str: String)) => str
case _ => throw new IllegalArgumentException("internal error: argument parts must be a list of string literals")
}
val str = StringContext.treatEscapes(rawStr)
val strLen = str.length
val strIsEmpty = strLen == 0
def charAtIndexIs(idx: Int, ch: Char) = idx < strLen && str(idx) == ch
def isPercent(idx: Int) = charAtIndexIs(idx, '%')
def isConversion(idx: Int) = isPercent(idx) && !charAtIndexIs(idx + 1, 'n') && !charAtIndexIs(idx + 1, '%')
var idx = 0
def errorAtIndex(idx: Int, msg: String) = c.error(new OffsetPosition(strTree.pos.source, strTree.pos.point + idx), msg)
def wrongConversionString(idx: Int) = errorAtIndex(idx, "wrong conversion string")
def illegalConversionCharacter(idx: Int) = errorAtIndex(idx, "illegal conversion character")
def nonEscapedPercent(idx: Int) = errorAtIndex(idx, "percent signs not directly following splicees must be escaped")
// STEP 1: handle argument conversion
// 1) "...${smth}" => okay, equivalent to "...${smth}%s"
// 2) "...${smth}blahblah" => okay, equivalent to "...${smth}%sblahblah"
// 3) "...${smth}%" => error
// 4) "...${smth}%n" => okay, equivalent to "...${smth}%s%n"
// 5) "...${smth}%%" => okay, equivalent to "...${smth}%s%%"
// 6) "...${smth}[%legalJavaConversion]" => okay, according to http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html
// 7) "...${smth}[%illegalJavaConversion]" => error
if (!first) {
val arg = argStack.pop
if (isConversion(0)) {
// PRE str is not empty and str(0) == '%'
// argument index parameter is not allowed, thus parse
// [flags][width][.precision]conversion
var pos = 1
while (pos < strLen && isFlag(str charAt pos)) pos += 1
while (pos < strLen && Character.isDigit(str charAt pos)) pos += 1
if (pos < strLen && str.charAt(pos) == '.') {
pos += 1
while (pos < strLen && Character.isDigit(str charAt pos)) pos += 1
}
if (pos < strLen) {
conversionType(str charAt pos, arg) match {
case Some(tpe) => defval(arg, tpe)
case None => illegalConversionCharacter(pos)
}
} else {
wrongConversionString(pos - 1)
}
idx = 1
} else {
bldr append "%s"
defval(arg, AnyTpe)
}
}
// STEP 2: handle the rest of the text
// 1) %n tokens are left as is
// 2) %% tokens are left as is
// 3) other usages of percents are reported as errors
if (!strIsEmpty) {
while (idx < strLen) {
if (isPercent(idx)) {
if (isConversion(idx)) nonEscapedPercent(idx)
else idx += 1 // skip n and % in %n and %%
}
idx += 1
}
bldr append (str take idx)
}
}
copyString(first = true)
while (pi.hasNext) {
copyString(first = false)
}
val fstring = bldr.toString
// val expr = c.reify(fstring.format((ids.map(id => Expr(id).eval)) : _*))
// https://issues.scala-lang.org/browse/SI-5824, therefore
val expr =
Apply(
Select(
Literal(Constant(fstring)),
newTermName("format")),
List(ids: _* )
);
Block(evals.toList, atPos(origApplyPos.focus)(expr)) setPos origApplyPos.makeTransparent
}
}