Skip to content

Commit 5536a54

Browse files
committed
Change use of $ for splices in syntax and parsing
- '$' is no longer a keyword. - 'x is treated as a quote instead of a symbol literal inside splices - 'x and $x are single tokens (of kind QUOTEID and IDENTIFIER) This reverts part of commit 8cfa1fb.
1 parent 2ca5297 commit 5536a54

File tree

4 files changed

+83
-70
lines changed

4 files changed

+83
-70
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+67-38
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import util.Spans._
2121
import Constants._
2222
import ScriptParsers._
2323
import Decorators._
24+
import scala.tasty.util.Chars.isIdentifierStart
2425
import scala.annotation.{tailrec, switch}
2526
import rewrites.Rewrites.patch
2627

@@ -48,6 +49,13 @@ object Parsers {
4849
val Class, Type, TypeParam, Def: Value = Value
4950
}
5051

52+
type StageKind = Int
53+
object StageKind {
54+
val None = 0
55+
val Quoted = 1
56+
val Spliced = 2
57+
}
58+
5159
private implicit class AddDeco(val buf: ListBuffer[Tree]) extends AnyVal {
5260
def +++=(x: Tree) = x match {
5361
case x: Thicket => buf ++= x.trees
@@ -199,6 +207,16 @@ object Parsers {
199207
def isStatSep: Boolean =
200208
in.token == NEWLINE || in.token == NEWLINES || in.token == SEMI
201209

210+
/** A '$' identifier is treated as a splice if followed by a `{`.
211+
* A longer identifier starting with `$` is treated as a splice/id combination
212+
* in a quoted block '{...'
213+
*/
214+
def isSplice: Boolean =
215+
in.token == IDENTIFIER && in.name(0) == '$' && {
216+
if (in.name.length == 1) in.lookaheadIn(BitSet(LBRACE))
217+
else (staged & StageKind.Quoted) != 0
218+
}
219+
202220
/* ------------- ERROR HANDLING ------------------------------------------- */
203221

204222
/** The offset of the last time when a statement on a new line was definitely
@@ -354,6 +372,14 @@ object Parsers {
354372
finally inEnum = saved
355373
}
356374

375+
private[this] var staged = StageKind.None
376+
def withinStaged[T](kind: StageKind)(op: => T): T = {
377+
val saved = staged
378+
staged |= kind
379+
try op
380+
finally staged = saved
381+
}
382+
357383
def migrationWarningOrError(msg: String, offset: Int = in.offset): Unit =
358384
if (in.isScala2Mode)
359385
ctx.migrationWarning(msg, source.atSpan(Span(offset)))
@@ -690,14 +716,18 @@ object Parsers {
690716
}
691717
val isNegated = negOffset < in.offset
692718
atSpan(negOffset) {
693-
if (in.token == SYMBOLLIT) {
694-
migrationWarningOrError(em"""symbol literal '${in.name} is no longer supported,
695-
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead.""")
696-
if (in.isScala2Mode) {
697-
patch(source, Span(in.offset, in.offset + 1), "Symbol(\"")
698-
patch(source, Span(in.charOffset - 1), "\")")
719+
if (in.token == QUOTEID) {
720+
if ((staged & StageKind.Spliced) != 0 && isIdentifierStart(in.name(1)))
721+
Quote(atSpan(in.offset + 1)(Ident(in.name.drop(1))))
722+
else {
723+
migrationWarningOrError(em"""symbol literal '${in.name} is no longer supported,
724+
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead.""")
725+
if (in.isScala2Mode) {
726+
patch(source, Span(in.offset, in.offset + 1), "Symbol(\"")
727+
patch(source, Span(in.charOffset - 1), "\")")
728+
}
729+
atSpan(in.skipToken()) { SymbolLit(in.strVal) }
699730
}
700-
atSpan(in.skipToken()) { SymbolLit(in.strVal) }
701731
}
702732
else if (in.token == INTERPOLATIONID) interpolatedString(inPattern)
703733
else finish(in.token match {
@@ -919,26 +949,27 @@ object Parsers {
919949
else t
920950

921951
/** The block in a quote or splice */
922-
def stagedBlock(isQuote: Boolean) = {
923-
val saved = in.inQuote
924-
in.inQuote = isQuote
925-
inDefScopeBraces {
926-
try
927-
block() match {
928-
case t @ Block(Nil, expr) =>
929-
if (expr.isEmpty) t else expr
930-
case t => t
931-
}
932-
finally in.inQuote = saved
952+
def stagedBlock() =
953+
inDefScopeBraces(block()) match {
954+
case t @ Block(Nil, expr) if !expr.isEmpty => expr
955+
case t => t
933956
}
934-
}
935957

936-
/** SimpleEpxr ::= ‘$’ (id | ‘{’ Block ‘}’)
937-
* SimpleType ::= ‘$’ (id | ‘{’ Block ‘}’)
958+
/** SimpleEpxr ::= spliceId | ‘$’ ‘{’ Block ‘}’)
959+
* SimpleType ::= spliceId | ‘$’ ‘{’ Block ‘}’)
938960
*/
939961
def splice(isType: Boolean): Tree =
940-
atSpan(in.skipToken()) {
941-
val expr = if (isIdent) termIdent() else stagedBlock(isQuote = false)
962+
atSpan(in.offset) {
963+
val expr =
964+
if (in.name.length == 1) {
965+
in.nextToken()
966+
withinStaged(StageKind.Spliced)(stagedBlock())
967+
}
968+
else atSpan(in.offset + 1) {
969+
val id = Ident(in.name.drop(1))
970+
in.nextToken()
971+
id
972+
}
942973
if (isType) TypSplice(expr) else Splice(expr)
943974
}
944975

@@ -950,7 +981,7 @@ object Parsers {
950981
* | `_' TypeBounds
951982
* | Refinement
952983
* | Literal
953-
* | ‘$’ (id | ‘{’ Block ‘}’)
984+
* | ‘$’ ‘{’ Block ‘}’
954985
*/
955986
def simpleType(): Tree = simpleTypeRest {
956987
if (in.token == LPAREN)
@@ -964,7 +995,7 @@ object Parsers {
964995
val start = in.skipToken()
965996
typeBounds().withSpan(Span(start, in.lastOffset, start))
966997
}
967-
else if (in.token == SPLICE)
998+
else if (isSplice)
968999
splice(isType = true)
9691000
else path(thisOK = false, handleSingletonType) match {
9701001
case r @ SingletonTypeTree(_) => r
@@ -1426,9 +1457,10 @@ object Parsers {
14261457

14271458
/** SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody)
14281459
* | BlockExpr
1429-
* | ‘'’ (id | ‘{’ Block ‘}’)
1460+
* | ‘'’ ‘{’ Block ‘}’
14301461
* | ‘'’ ‘[’ Type ‘]’
1431-
* | ‘$’ (id | ‘{’ Block ‘}’)
1462+
* | ‘$’ ‘{’ Block ‘}’
1463+
* | quoteId
14321464
* | SimpleExpr1 [`_']
14331465
* SimpleExpr1 ::= literal
14341466
* | xmlLiteral
@@ -1443,7 +1475,10 @@ object Parsers {
14431475
val t = in.token match {
14441476
case XMLSTART =>
14451477
xmlLiteral()
1446-
case IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER =>
1478+
case IDENTIFIER =>
1479+
if (isSplice) splice(isType = false)
1480+
else path(thisOK = true)
1481+
case BACKQUOTED_IDENT | THIS | SUPER =>
14471482
path(thisOK = true)
14481483
case USCORE =>
14491484
val start = in.skipToken()
@@ -1459,19 +1494,13 @@ object Parsers {
14591494
blockExpr()
14601495
case QUOTE =>
14611496
atSpan(in.skipToken()) {
1462-
Quote {
1463-
if (in.token == LBRACKET) {
1464-
val saved = in.inQuote
1465-
in.inQuote = true
1466-
inBrackets {
1467-
try typ() finally in.inQuote = saved
1468-
}
1497+
withinStaged(StageKind.Quoted) {
1498+
Quote {
1499+
if (in.token == LBRACKET) inBrackets(typ())
1500+
else stagedBlock()
14691501
}
1470-
else stagedBlock(isQuote = true)
14711502
}
14721503
}
1473-
case SPLICE =>
1474-
splice(isType = false)
14751504
case NEW =>
14761505
canApply = false
14771506
newExpr()

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

+6-19
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,6 @@ object Scanners {
184184
/** Return a list of all the comment positions */
185185
def commentSpans: List[Span] = commentPosBuf.toList
186186

187-
var inQuote = false
188-
189187
private[this] def addComment(comment: Comment): Unit = {
190188
val lookahead = lookaheadReader()
191189
def nextPos: Int = (lookahead.getc(): @switch) match {
@@ -417,7 +415,7 @@ object Scanners {
417415
'K' | 'L' | 'M' | 'N' | 'O' |
418416
'P' | 'Q' | 'R' | 'S' | 'T' |
419417
'U' | 'V' | 'W' | 'X' | 'Y' |
420-
'Z' | '_' |
418+
'Z' | '$' | '_' |
421419
'a' | 'b' | 'c' | 'd' | 'e' |
422420
'f' | 'g' | 'h' | 'i' | 'j' |
423421
'k' | 'l' | 'm' | 'n' | 'o' |
@@ -426,15 +424,9 @@ object Scanners {
426424
'z' =>
427425
putChar(ch)
428426
nextChar()
429-
finishIdent()
430-
case '$' =>
431-
putChar(ch)
432-
nextChar()
433-
if (inQuote) {
434-
token = SPLICE
435-
litBuf.clear()
436-
}
437-
else finishIdent()
427+
getIdentRest()
428+
if (ch == '"' && token == IDENTIFIER)
429+
token = INTERPOLATIONID
438430
case '<' => // is XMLSTART?
439431
def fetchLT() = {
440432
val last = if (charOffset >= 2) buf(charOffset - 2) else ' '
@@ -528,9 +520,9 @@ object Scanners {
528520
def fetchSingleQuote() = {
529521
nextChar()
530522
if (isIdentifierStart(ch))
531-
charLitOr { getIdentRest(); SYMBOLLIT }
523+
charLitOr { getIdentRest(); QUOTEID }
532524
else if (isOperatorPart(ch) && (ch != '\\'))
533-
charLitOr { getOperatorRest(); SYMBOLLIT }
525+
charLitOr { getOperatorRest(); QUOTEID }
534526
else ch match {
535527
case '{' | '[' | ' ' | '\t' if lookaheadChar() != '\'' =>
536528
token = QUOTE
@@ -718,11 +710,6 @@ object Scanners {
718710
}
719711
}
720712

721-
def finishIdent(): Unit = {
722-
getIdentRest()
723-
if (ch == '"' && token == IDENTIFIER) token = INTERPOLATIONID
724-
}
725-
726713
private def getOperatorRest(): Unit = (ch: @switch) match {
727714
case '~' | '!' | '@' | '#' | '%' |
728715
'^' | '*' | '+' | '-' | '<' |

compiler/src/dotty/tools/dotc/parsing/Tokens.scala

+9-9
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ abstract class TokensCommon {
4343
final val STRINGLIT = 8; enter(STRINGLIT, "string literal")
4444
final val STRINGPART = 9; enter(STRINGPART, "string literal", "string literal part")
4545
//final val INTERPOLATIONID = 10; enter(INTERPOLATIONID, "string interpolator")
46-
//final val SYMBOLLIT = 11; enter(SYMBOLLIT, "symbol literal") // TODO: deprecate
46+
//final val QUOTEID = 11; enter(QUOTEID, "quoted identifier") // TODO: deprecate
4747

4848
/** identifiers */
4949
final val IDENTIFIER = 12; enter(IDENTIFIER, "identifier")
@@ -149,7 +149,7 @@ object Tokens extends TokensCommon {
149149
final def maxToken: Int = XMLSTART
150150

151151
final val INTERPOLATIONID = 10; enter(INTERPOLATIONID, "string interpolator")
152-
final val SYMBOLLIT = 11; enter(SYMBOLLIT, "symbol literal") // TODO: deprecate
152+
final val QUOTEID = 11; enter(QUOTEID, "quoted identifier") // TODO: deprecate
153153

154154
final val BACKQUOTED_IDENT = 13; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident")
155155

@@ -193,29 +193,29 @@ object Tokens extends TokensCommon {
193193
final val SUPERTYPE = 81; enter(SUPERTYPE, ">:")
194194
final val HASH = 82; enter(HASH, "#")
195195
final val VIEWBOUND = 84; enter(VIEWBOUND, "<%") // TODO: deprecate
196-
final val SPLICE = 85; enter(SPLICE, "$")
197-
final val QUOTE = 86; enter(QUOTE, "'")
196+
final val QUOTE = 85; enter(QUOTE, "'")
198197

199198
/** XML mode */
200199
final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
201200

202201
final val alphaKeywords: TokenSet = tokenRange(IF, GIVEN)
203-
final val symbolicKeywords: TokenSet = tokenRange(USCORE, SPLICE)
202+
final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND)
204203
final val keywords: TokenSet = alphaKeywords | symbolicKeywords
205204

206205
final val allTokens: TokenSet = tokenRange(minToken, maxToken)
207206

208-
final val simpleLiteralTokens: TokenSet = tokenRange(CHARLIT, STRINGLIT) | BitSet(TRUE, FALSE, SYMBOLLIT)
207+
final val simpleLiteralTokens: TokenSet =
208+
tokenRange(CHARLIT, STRINGLIT) | BitSet(TRUE, FALSE, QUOTEID) // TODO: drop QUOTEID when symbol literals are gone
209209
final val literalTokens: TokenSet = simpleLiteralTokens | BitSet(INTERPOLATIONID, NULL)
210210

211211
final val atomicExprTokens: TokenSet = literalTokens | identifierTokens | BitSet(
212-
USCORE, NULL, THIS, SUPER, TRUE, FALSE, RETURN, XMLSTART)
212+
USCORE, NULL, THIS, SUPER, TRUE, FALSE, RETURN, QUOTEID, XMLSTART)
213213

214214
final val canStartExpressionTokens: TokenSet = atomicExprTokens | BitSet(
215-
LBRACE, LPAREN, QUOTE, SPLICE, IF, DO, WHILE, FOR, NEW, TRY, THROW)
215+
LBRACE, LPAREN, QUOTE, IF, DO, WHILE, FOR, NEW, TRY, THROW)
216216

217217
final val canStartTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet(
218-
THIS, SUPER, USCORE, LPAREN, AT, SPLICE)
218+
THIS, SUPER, USCORE, LPAREN, AT)
219219

220220
final val canStartBindingTokens: TokenSet = identifierTokens | BitSet(USCORE, LPAREN)
221221

docs/docs/internals/syntax.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ id ::= plainid
4444
| ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’
4545
| INT // interpolation id, only for quasi-quotes
4646
idrest ::= {letter | digit} [‘_’ op]
47+
quoteId ::= ‘'’ alphaid
4748
4849
integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’]
4950
decimalNumeral ::= ‘0’ | nonZeroDigit {digit}
@@ -78,8 +79,6 @@ escape ::= ‘$$’
7879
stringFormat ::= {printableChar \ (‘"’ | ‘}’ | ‘ ’ | ‘\t’ | ‘\n’)}
7980
8081
symbolLiteral ::= ‘'’ plainid // until 2.13
81-
quoteId ::= ‘'’ plainid // from 3.1
82-
spliceId ::= '$' plainid
8382
8483
comment ::= ‘/*’ “any sequence of characters; nested comments are allowed” ‘*/’
8584
| ‘//’ “any sequence of characters up to end of line”
@@ -160,7 +159,6 @@ SimpleType ::= SimpleType TypeArgs
160159
| ‘_’ SubtypeBounds
161160
| Refinement RefinedTypeTree(EmptyTree, refinement)
162161
| SimpleLiteral SingletonTypeTree(l)
163-
| spliceId // only inside quotes
164162
| ‘$’ ‘{’ Block ‘}’
165163
ArgTypes ::= Type {‘,’ Type}
166164
| NamedTypeArg {‘,’ NamedTypeArg}
@@ -214,7 +212,6 @@ SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody)
214212
| ‘'’ ‘{’ Block ‘}’
215213
| ‘'’ ‘[’ Type ‘]’
216214
| ‘$’ ‘{’ Block ‘}’
217-
| spliceId // only inside quotes
218215
| quoteId // only inside splices
219216
| SimpleExpr1 [‘_’] PostfixOp(expr, _)
220217
SimpleExpr1 ::= Literal

0 commit comments

Comments
 (0)