summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2011-10-10 13:53:09 +0000
committerMartin Odersky <odersky@gmail.com>2011-10-10 13:53:09 +0000
commitd08296f0639e9d97281dc0cdfe88dcf207941995 (patch)
tree91da5d6d176166de797e857d8486d49e0d0e3e73
parent4e86106b5b0637aa88de2e3d5a8751d918e4c069 (diff)
downloadscala-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.
-rw-r--r--src/compiler/scala/reflect/internal/StdNames.scala2
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala31
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Scanners.scala102
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Tokens.scala6
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)