diff options
author | Paul Phillips <paulp@improving.org> | 2013-05-10 11:25:39 -0700 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2013-05-10 11:25:39 -0700 |
commit | ece84b704e20c76efab15d82b565544ec350c950 (patch) | |
tree | 950baf597c312b393fce6e5dcbbdd93714f72fb2 /src/compiler | |
parent | 34d28aaaa8abd0ef9f9b29ec201b59d0d1e335db (diff) | |
parent | 25f49cb392846052025d745e2403b681793ce647 (diff) | |
download | scala-ece84b704e20c76efab15d82b565544ec350c950.tar.gz scala-ece84b704e20c76efab15d82b565544ec350c950.tar.bz2 scala-ece84b704e20c76efab15d82b565544ec350c950.zip |
Merge pull request #2461 from scalamacros/ticket/7325
Some fixes for string interpolation
Diffstat (limited to 'src/compiler')
3 files changed, 81 insertions, 58 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index b05902a6ef..b8791c15dc 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1107,32 +1107,33 @@ self => * | symbol * | null * }}} - * @note The returned tree does not yet have a position */ - def literal(isNegated: Boolean = false, inPattern: Boolean = false): Tree = { - def finish(value: Any): Tree = { - val t = Literal(Constant(value)) - in.nextToken() - t - } - if (in.token == SYMBOLLIT) - Apply(scalaDot(nme.Symbol), List(finish(in.strVal))) - else if (in.token == INTERPOLATIONID) - interpolatedString(inPattern = inPattern) - else finish(in.token match { - case CHARLIT => in.charVal - case INTLIT => in.intVal(isNegated).toInt - case LONGLIT => in.intVal(isNegated) - case FLOATLIT => in.floatVal(isNegated).toFloat - case DOUBLELIT => in.floatVal(isNegated) - case STRINGLIT | STRINGPART => in.strVal.intern() - case TRUE => true - case FALSE => false - case NULL => null - case _ => - syntaxErrorOrIncomplete("illegal literal", true) - null - }) + def literal(isNegated: Boolean = false, inPattern: Boolean = false, start: Int = in.offset): Tree = { + atPos(start) { + def finish(value: Any): Tree = { + val t = Literal(Constant(value)) + in.nextToken() + t + } + if (in.token == SYMBOLLIT) + Apply(scalaDot(nme.Symbol), List(finish(in.strVal))) + else if (in.token == INTERPOLATIONID) + interpolatedString(inPattern = inPattern) + else finish(in.token match { + case CHARLIT => in.charVal + case INTLIT => in.intVal(isNegated).toInt + case LONGLIT => in.intVal(isNegated) + case FLOATLIT => in.floatVal(isNegated).toFloat + case DOUBLELIT => in.floatVal(isNegated) + case STRINGLIT | STRINGPART => in.strVal.intern() + case TRUE => true + case FALSE => false + case NULL => null + case _ => + syntaxErrorOrIncomplete("illegal literal", true) + null + }) + } } private def stringOp(t: Tree, op: TermName) = { @@ -1509,7 +1510,7 @@ self => atPos(in.offset) { val name = nme.toUnaryName(rawIdent()) if (name == nme.UNARY_- && isNumericLit) - simpleExprRest(atPos(in.offset)(literal(isNegated = true)), canApply = true) + simpleExprRest(literal(isNegated = true), canApply = true) else Select(stripParens(simpleExpr()), name) } @@ -1534,7 +1535,7 @@ self => def simpleExpr(): Tree = { var canApply = true val t = - if (isLiteral) atPos(in.offset)(literal()) + if (isLiteral) literal() else in.token match { case XMLSTART => xmlLiteral() @@ -1923,7 +1924,7 @@ self => case INTLIT | LONGLIT | FLOATLIT | DOUBLELIT => t match { case Ident(nme.MINUS) => - return atPos(start) { literal(isNegated = true, inPattern = true) } + return literal(isNegated = true, inPattern = true, start = start) case _ => } case _ => @@ -1941,7 +1942,7 @@ self => atPos(start, start) { Ident(nme.WILDCARD) } case CHARLIT | INTLIT | LONGLIT | FLOATLIT | DOUBLELIT | STRINGLIT | INTERPOLATIONID | SYMBOLLIT | TRUE | FALSE | NULL => - atPos(start) { literal(inPattern = true) } + literal(inPattern = true) case LPAREN => atPos(start)(makeParens(noSeq.patterns())) case XMLSTART => diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index c05906c740..1aa50be83a 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -425,6 +425,7 @@ trait Scanners extends ScannersCommon { if (ch == '\"') { nextRawChar() if (ch == '\"') { + offset += 3 nextRawChar() getStringPart(multiLine = true) sepRegions = STRINGPART :: sepRegions // indicate string part @@ -434,6 +435,7 @@ trait Scanners extends ScannersCommon { strVal = "" } } else { + offset += 1 getStringPart(multiLine = false) sepRegions = STRINGLIT :: sepRegions // indicate single line string part } diff --git a/src/compiler/scala/tools/reflect/MacroImplementations.scala b/src/compiler/scala/tools/reflect/MacroImplementations.scala index ae13cc561f..f4f385f8b3 100644 --- a/src/compiler/scala/tools/reflect/MacroImplementations.scala +++ b/src/compiler/scala/tools/reflect/MacroImplementations.scala @@ -4,6 +4,7 @@ import scala.reflect.macros.{ReificationException, UnexpectedReificationExceptio 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 @@ -26,16 +27,12 @@ abstract class MacroImplementations { c.abort(args(parts.length-1).pos, "too many arguments for interpolated string") } - val stringParts = parts map { - case Literal(Constant(s: String)) => s; - case _ => throw new IllegalArgumentException("argument parts must be a list of string literals") - } - val pi = stringParts.iterator + val pi = parts.iterator val bldr = new java.lang.StringBuilder val evals = ListBuffer[ValDef]() val ids = ListBuffer[Ident]() - val argsStack = Stack(args : _*) + val argStack = Stack(args : _*) def defval(value: Tree, tpe: Type): Unit = { val freshName = newTermName(c.fresh("arg$")) @@ -82,50 +79,73 @@ abstract class MacroImplementations { } def copyString(first: Boolean): Unit = { - val str = StringContext.treatEscapes(pi.next()) + 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 - var start = 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 = argsStack.pop - if (strIsEmpty || (str charAt 0) != '%') { - bldr append "%s" - defval(arg, AnyTpe) - } else { + 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 + 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) { + if (pos < strLen) { conversionType(str charAt pos, arg) match { case Some(tpe) => defval(arg, tpe) - case None => c.error(arg.pos, "illegal conversion character") + case None => illegalConversionCharacter(pos) } } else { - // TODO: place error message on conversion string - c.error(arg.pos, "wrong conversion string") + wrongConversionString(pos - 1) } + idx = 1 + } else { + bldr append "%s" + defval(arg, AnyTpe) } - idx = 1 } + + // 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) { - val len = str.length - while (idx < len) { - def notPercentN = str(idx) != '%' || (idx + 1 < len && str(idx + 1) != 'n') - if (str(idx) == '%' && notPercentN) { - bldr append (str substring (start, idx)) append "%%" - start = idx + 1 + 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 substring (start, idx)) + bldr append (str take idx) } } |