diff options
author | Martin Odersky <odersky@gmail.com> | 2011-10-10 13:53:09 +0000 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2011-10-10 13:53:09 +0000 |
commit | d08296f0639e9d97281dc0cdfe88dcf207941995 (patch) | |
tree | 91da5d6d176166de797e857d8486d49e0d0e3e73 | |
parent | 4e86106b5b0637aa88de2e3d5a8751d918e4c069 (diff) | |
download | scala-d08296f0639e9d97281dc0cdfe88dcf207941995.tar.gz scala-d08296f0639e9d97281dc0cdfe88dcf207941995.tar.bz2 scala-d08296f0639e9d97281dc0cdfe88dcf207941995.zip |
String interpolation added as experimental feat...
String interpolation added as experimental feature. Review by extempore.
4 files changed, 116 insertions, 25 deletions
diff --git a/src/compiler/scala/reflect/internal/StdNames.scala b/src/compiler/scala/reflect/internal/StdNames.scala index 9e40b11fc8..86c32ec3e6 100644 --- a/src/compiler/scala/reflect/internal/StdNames.scala +++ b/src/compiler/scala/reflect/internal/StdNames.scala @@ -198,6 +198,7 @@ trait StdNames extends /*reflect.generic.StdNames with*/ NameManglers { self: Sy val TYPE_ : NameType = "TYPE" val add_ : NameType = "add" val anyValClass: NameType = "anyValClass" + val append: NameType = "append" val apply: NameType = "apply" val arrayValue: NameType = "arrayValue" val arraycopy: NameType = "arraycopy" @@ -226,6 +227,7 @@ trait StdNames extends /*reflect.generic.StdNames with*/ NameManglers { self: Sy val find_ : NameType = "find" val flatMap: NameType = "flatMap" val foreach: NameType = "foreach" + val formatted: NameType = "formatted" val genericArrayOps: NameType = "genericArrayOps" val get: NameType = "get" val hasNext: NameType = "hasNext" diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 8955537345..787dde541d 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -9,7 +9,7 @@ package scala.tools.nsc package ast.parser -import scala.collection.mutable.ListBuffer +import scala.collection.mutable.{ListBuffer, StringBuilder} import util.{ SourceFile, OffsetPosition, FreshNameCreator } import scala.reflect.internal.{ ModifierFlags => Flags } import Tokens._ @@ -616,8 +616,8 @@ self => def isLiteralToken(token: Int) = token match { case CHARLIT | INTLIT | LONGLIT | FLOATLIT | DOUBLELIT | - STRINGLIT | SYMBOLLIT | TRUE | FALSE | NULL => true - case _ => false + STRINGLIT | STRINGPART | SYMBOLLIT | TRUE | FALSE | NULL => true + case _ => false } def isLiteral = isLiteralToken(in.token) @@ -1109,6 +1109,8 @@ self => } if (in.token == SYMBOLLIT) Apply(scalaDot(nme.Symbol), List(finish(in.strVal))) + else if (in.token == STRINGPART) + interpolatedString() else finish(in.token match { case CHARLIT => in.charVal case INTLIT => in.intVal(isNegated).toInt @@ -1125,6 +1127,27 @@ self => }) } + private def stringOp(t: Tree, op: TermName) = { + val str = in.strVal + in.nextToken() + if (str.isEmpty) t + else atPos(t.pos.startOrPoint) { + Apply(Select(t, op), List(Literal(Constant(str)))) + } + } + + private def interpolatedString(): Tree = { + var t = atPos(o2p(in.offset))(New(TypeTree(definitions.StringBuilderClass.tpe), List(List()))) + while (in.token == STRINGPART) { + t = stringOp(t, nme.append) + var e = expr() + if (in.token == STRINGFMT) e = stringOp(e, nme.formatted) + t = atPos(t.pos.startOrPoint)(Apply(Select(t, nme.append), List(e))) + } + if (in.token == STRINGLIT) t = stringOp(t, nme.append) + atPos(t.pos)(Select(t, nme.toString_)) + } + /* ------------- NEW LINES ------------------------------------------------- */ def newLineOpt() { @@ -1812,7 +1835,7 @@ self => in.nextToken() atPos(start, start) { Ident(nme.WILDCARD) } case CHARLIT | INTLIT | LONGLIT | FLOATLIT | DOUBLELIT | - STRINGLIT | SYMBOLLIT | TRUE | FALSE | NULL => + STRINGLIT | STRINGPART | SYMBOLLIT | TRUE | FALSE | NULL => atPos(start) { literal(false) } case LPAREN => atPos(start)(makeParens(noSeq.patterns())) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index a5d0f98f64..89384b8801 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -152,6 +152,13 @@ trait Scanners extends ScannersCommon { val prev : TokenData = new TokenData0 /** a stack of tokens which indicates whether line-ends can be statement separators + * also used for keeping track of nesting levels. + * We keep track of the closing symbol of a region. This can be + * RPAREN if region starts with '(' + * RBRACKET if region starts with '[' + * RBRACE if region starts with '{' + * ARROW if region starts with `case' + * STRINGFMT if region is a string interpolation expression starting with '\{' */ var sepRegions: List[Int] = List() @@ -179,10 +186,12 @@ trait Scanners extends ScannersCommon { sepRegions = RBRACE :: sepRegions case CASE => sepRegions = ARROW :: sepRegions + case STRINGPART => + sepRegions = STRINGFMT :: sepRegions case RBRACE => sepRegions = sepRegions dropWhile (_ != RBRACE) if (!sepRegions.isEmpty) sepRegions = sepRegions.tail - case RBRACKET | RPAREN | ARROW => + case RBRACKET | RPAREN | ARROW | STRINGFMT => if (!sepRegions.isEmpty && sepRegions.head == lastToken) sepRegions = sepRegions.tail case _ => @@ -349,11 +358,8 @@ trait Scanners extends ScannersCommon { token = STRINGLIT strVal = "" } - } else if (getStringLit('\"')) { - setStrVal() - token = STRINGLIT } else { - syntaxError("unclosed string literal") + getStringPart() } case '\'' => nextChar() @@ -379,7 +385,9 @@ trait Scanners extends ScannersCommon { token = DOT } case ';' => - nextChar(); token = SEMI + nextChar() + if (inStringInterpolation) getFormatString() + else token = SEMI case ',' => nextChar(); token = COMMA case '(' => @@ -389,7 +397,16 @@ trait Scanners extends ScannersCommon { case ')' => nextChar(); token = RPAREN case '}' => - nextChar(); token = RBRACE + if (token == STRINGFMT) { + nextChar() + getStringPart() + } else if (inStringInterpolation) { + strVal = ""; + token = STRINGFMT + } else { + nextChar(); + token = RBRACE + } case '[' => nextChar(); token = LBRACKET case ']' => @@ -475,6 +492,11 @@ trait Scanners extends ScannersCommon { } } + /** Are we directly in a string interpolation expression? + */ + private def inStringInterpolation = + sepRegions.nonEmpty && sepRegions.head == STRINGFMT + /** Can token start a statement? */ def inFirstOfStat(token: Int) = token match { case EOF | CATCH | ELSE | EXTENDS | FINALLY | FORSOME | MATCH | WITH | YIELD | @@ -499,7 +521,9 @@ trait Scanners extends ScannersCommon { private def getBackquotedIdent() { nextChar() - if (getStringLit('`')) { + getLitChars('`') + if (ch == '`') { + nextChar() finishNamed() if (name.length == 0) syntaxError("empty quoted identifier") token = BACKQUOTED_IDENT @@ -571,12 +595,30 @@ trait Scanners extends ScannersCommon { } } - private def getStringLit(delimiter: Char): Boolean = { - while (ch != delimiter && (isUnicodeEscape || ch != CR && ch != LF && ch != SU)) { - getLitChar() + def getFormatString() = { + getLitChars('}', '"', ' ', '\t') + if (ch == '}') { + setStrVal() + if (!strVal.isEmpty) strVal = "%" + strVal + token = STRINGFMT + } else { + syntaxError("unclosed format string") + } + } + + def getStringPart() = { + while (ch != '"' && (ch != CR && ch != LF && ch != SU || isUnicodeEscape) && maybeGetLitChar()) {} + if (ch == '"') { + setStrVal() + nextChar() + token = STRINGLIT + } else if (ch == '{' && settings.Xexperimental.value) { + setStrVal() + nextChar() + token = STRINGPART + } else { + syntaxError("unclosed string literal") } - if (ch == delimiter) { nextChar(); true } - else false } private def getMultiLineStringLit() { @@ -613,8 +655,10 @@ trait Scanners extends ScannersCommon { // Literals ----------------------------------------------------------------- /** read next character in character or string literal: - */ - protected def getLitChar() = + * if character sequence is a \{ escape, do not copy it into the string and return false. + * otherwise return true. + */ + protected def maybeGetLitChar(): Boolean = { if (ch == '\\') { nextChar() if ('0' <= ch && ch <= '7') { @@ -640,9 +684,8 @@ trait Scanners extends ScannersCommon { case '\"' => putChar('\"') case '\'' => putChar('\'') case '\\' => putChar('\\') - case _ => - syntaxError(charOffset - 1, "invalid escape character") - putChar(ch) + case '{' => return false + case _ => invalidEscape() } nextChar() } @@ -650,6 +693,22 @@ trait Scanners extends ScannersCommon { putChar(ch) nextChar() } + true + } + + protected def invalidEscape(): Unit = { + syntaxError(charOffset - 1, "invalid escape character") + putChar(ch) + } + + protected def getLitChar(): Unit = + if (!maybeGetLitChar()) invalidEscape() + + private def getLitChars(delimiters: Char*) { + while (!(delimiters contains ch) && (ch != CR && ch != LF && ch != SU || isUnicodeEscape)) { + getLitChar() + } + } /** read fractional part and exponent of floating point number * if one is present. @@ -875,6 +934,10 @@ trait Scanners extends ScannersCommon { "double(" + floatVal + ")" case STRINGLIT => "string(" + strVal + ")" + case STRINGPART => + "stringpart(" + strVal + ")" + case STRINGFMT => + "stringfmt(" + strVal + ")" case SEMI => ";" case NEWLINE => @@ -990,7 +1053,8 @@ trait Scanners extends ScannersCommon { case LONGLIT => "long literal" case FLOATLIT => "float literal" case DOUBLELIT => "double literal" - case STRINGLIT => "string literal" + case STRINGLIT | STRINGPART => "string literal" + case STRINGFMT => "format string" case SYMBOLLIT => "symbol literal" case LPAREN => "'('" case RPAREN => "')'" diff --git a/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala b/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala index 70f98aa71b..07970bb36e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala @@ -45,7 +45,9 @@ abstract class Tokens { } object Tokens extends Tokens { - final val SYMBOLLIT = 7 + final val STRINGPART = 7 + final val SYMBOLLIT = 8 + final val STRINGFMT = 9 def isLiteral(code: Int) = code >= CHARLIT && code <= SYMBOLLIT @@ -57,7 +59,7 @@ object Tokens extends Tokens { @switch def canBeginExpression(code: Int) = code match { case IDENTIFIER|BACKQUOTED_IDENT|USCORE => true - case LBRACE|LPAREN|LBRACKET|COMMENT|STRINGLIT => true + case LBRACE|LPAREN|LBRACKET|COMMENT => true case IF|DO|WHILE|FOR|NEW|TRY|THROW => true case NULL|THIS|TRUE|FALSE => true case code => isLiteral(code) |