Skip to content

Commit ca8ddab

Browse files
committed
Use $ for splices in syntax and parsing
Also, update the reference document.
1 parent d101462 commit ca8ddab

File tree

13 files changed

+341
-298
lines changed

13 files changed

+341
-298
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,11 +1313,15 @@ object desugar {
13131313
val desugared = tree match {
13141314
case SymbolLit(str) =>
13151315
Literal(Constant(scala.Symbol(str)))
1316-
case Quote(expr) =>
1317-
if (expr.isType)
1318-
TypeApply(ref(defn.QuotedType_applyR), List(expr))
1316+
case Quote(t) =>
1317+
if (t.isType)
1318+
TypeApply(ref(defn.QuotedType_applyR), List(t))
13191319
else
1320-
Apply(ref(defn.QuotedExpr_applyR), expr)
1320+
Apply(ref(defn.QuotedExpr_applyR), t)
1321+
case Splice(expr) =>
1322+
Select(expr, nme.UNARY_PREFIX ++ nme.raw.TILDE)
1323+
case TypSplice(expr) =>
1324+
Select(expr, tpnme.UNARY_PREFIX ++ nme.raw.TILDE)
13211325
case InterpolatedString(id, segments) =>
13221326
val strs = segments map {
13231327
case ts: Thicket => ts.trees.head

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
1616

1717
abstract class OpTree(implicit @constructorOnly src: SourceFile) extends Tree {
1818
def op: Ident
19-
override def isTerm: Boolean = op.name.isTermName
20-
override def isType: Boolean = op.name.isTypeName
19+
override def isTerm: Boolean = op.isTerm
20+
override def isType: Boolean = op.isType
2121
}
2222

2323
/** A typed subtree of an untyped tree needs to be wrapped in a TypedSplice
@@ -84,10 +84,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
8484

8585
case class InfixOp(left: Tree, op: Ident, right: Tree)(implicit @constructorOnly src: SourceFile) extends OpTree
8686
case class PostfixOp(od: Tree, op: Ident)(implicit @constructorOnly src: SourceFile) extends OpTree
87-
case class PrefixOp(op: Ident, od: Tree)(implicit @constructorOnly src: SourceFile) extends OpTree {
88-
override def isType: Boolean = op.isType
89-
override def isTerm: Boolean = op.isTerm
90-
}
87+
case class PrefixOp(op: Ident, od: Tree)(implicit @constructorOnly src: SourceFile) extends OpTree
9188
case class Parens(t: Tree)(implicit @constructorOnly src: SourceFile) extends ProxyTree {
9289
def forwardTo: Tree = t
9390
}
@@ -96,7 +93,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
9693
override def isType: Boolean = !isTerm
9794
}
9895
case class Throw(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
99-
case class Quote(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
96+
case class Quote(t: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
97+
case class Splice(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
98+
case class TypSplice(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree
10099
case class DoWhile(body: Tree, cond: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
101100
case class ForYield(enums: List[Tree], expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
102101
case class ForDo(enums: List[Tree], body: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
@@ -492,9 +491,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
492491
case tree: Throw if expr eq tree.expr => tree
493492
case _ => finalize(tree, untpd.Throw(expr)(tree.source))
494493
}
495-
def Quote(tree: Tree)(expr: Tree)(implicit ctx: Context): TermTree = tree match {
496-
case tree: Quote if expr eq tree.expr => tree
497-
case _ => finalize(tree, untpd.Quote(expr)(tree.source))
494+
def Quote(tree: Tree)(t: Tree)(implicit ctx: Context): Tree = tree match {
495+
case tree: Quote if t eq tree.t => tree
496+
case _ => finalize(tree, untpd.Quote(t)(tree.source))
497+
}
498+
def Splice(tree: Tree)(expr: Tree)(implicit ctx: Context): Tree = tree match {
499+
case tree: Splice if expr eq tree.expr => tree
500+
case _ => finalize(tree, untpd.Splice(expr)(tree.source))
501+
}
502+
def TypSplice(tree: Tree)(expr: Tree)(implicit ctx: Context): Tree = tree match {
503+
case tree: TypSplice if expr eq tree.expr => tree
504+
case _ => finalize(tree, untpd.TypSplice(expr)(tree.source))
498505
}
499506
def DoWhile(tree: Tree)(body: Tree, cond: Tree)(implicit ctx: Context): TermTree = tree match {
500507
case tree: DoWhile if (body eq tree.body) && (cond eq tree.cond) => tree
@@ -556,8 +563,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
556563
cpy.Tuple(tree)(transform(trees))
557564
case Throw(expr) =>
558565
cpy.Throw(tree)(transform(expr))
559-
case Quote(expr) =>
560-
cpy.Quote(tree)(transform(expr))
566+
case Quote(t) =>
567+
cpy.Quote(tree)(transform(t))
568+
case Splice(expr) =>
569+
cpy.Splice(tree)(transform(expr))
570+
case TypSplice(expr) =>
571+
cpy.TypSplice(tree)(transform(expr))
561572
case DoWhile(body, cond) =>
562573
cpy.DoWhile(tree)(transform(body), transform(cond))
563574
case ForYield(enums, expr) =>
@@ -605,7 +616,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
605616
this(x, trees)
606617
case Throw(expr) =>
607618
this(x, expr)
608-
case Quote(expr) =>
619+
case Quote(t) =>
620+
this(x, t)
621+
case Splice(expr) =>
622+
this(x, expr)
623+
case TypSplice(expr) =>
609624
this(x, expr)
610625
case DoWhile(body, cond) =>
611626
this(this(x, body), cond)

compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ object PickledQuotes {
2626
def pickleQuote(tree: Tree)(implicit ctx: Context): scala.runtime.quoted.Unpickler.Pickled = {
2727
if (ctx.reporter.hasErrors) Nil
2828
else {
29-
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'(~x)` which should be optimized to `x`
29+
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
3030
val pickled = pickle(tree)
3131
TastyString.pickle(pickled)
3232
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ abstract class CharArrayReader { self =>
122122
/** A new reader that takes off at the current character position */
123123
def lookaheadReader(): CharArrayLookaheadReader = new CharArrayLookaheadReader
124124

125+
def lookaheadChar(): Char = lookaheadReader().getc()
126+
125127
class CharArrayLookaheadReader extends CharArrayReader {
126128
val buf: Array[Char] = self.buf
127129
charOffset = self.charOffset

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

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -910,15 +910,33 @@ object Parsers {
910910
if (in.token == AT) annotTypeRest(atSpan(startOffset(t)) { Annotated(t, annot()) })
911911
else t
912912

913+
/** The block in a quote or splice */
914+
def stagedBlock(isQuote: Boolean) = {
915+
val saved = in.inQuote
916+
in.inQuote = isQuote
917+
inDefScopeBraces {
918+
try block() finally in.inQuote = saved
919+
}
920+
}
921+
922+
/** SimpleEpxr ::= ‘$’ (id | ‘{’ Block ‘}’)
923+
* SimpleType ::= ‘$’ (id | ‘{’ Block ‘}’)
924+
*/
925+
def splice(isType: Boolean): Tree =
926+
atSpan(in.skipToken()) {
927+
val expr = if (isIdent) termIdent() else stagedBlock(isQuote = false)
928+
if (isType) TypSplice(expr) else Splice(expr)
929+
}
930+
913931
/** SimpleType ::= SimpleType TypeArgs
914932
* | SimpleType `#' id
915933
* | StableId
916-
* | ['~'] StableId
917934
* | Path `.' type
918935
* | `(' ArgTypes `)'
919936
* | `_' TypeBounds
920937
* | Refinement
921938
* | Literal
939+
* | ‘$’ (id | ‘{’ Block ‘}’)
922940
*/
923941
def simpleType(): Tree = simpleTypeRest {
924942
if (in.token == LPAREN)
@@ -932,8 +950,8 @@ object Parsers {
932950
val start = in.skipToken()
933951
typeBounds().withSpan(Span(start, in.lastOffset, start))
934952
}
935-
else if (isIdent(nme.raw.TILDE) && in.lookaheadIn(BitSet(IDENTIFIER, BACKQUOTED_IDENT)))
936-
atSpan(in.offset) { PrefixOp(typeIdent(), path(thisOK = true)) }
953+
else if (in.token == SPLICE)
954+
splice(isType = true)
937955
else path(thisOK = false, handleSingletonType) match {
938956
case r @ SingletonTypeTree(_) => r
939957
case r => convertToTypeId(r)
@@ -1430,9 +1448,9 @@ object Parsers {
14301448

14311449
/** SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody)
14321450
* | BlockExpr
1433-
* | ‘'{’ BlockExprContents ‘}’
1434-
* | ‘'(’ ExprsInParens ‘)
1435-
* | ‘'[’ Type ‘]’
1451+
* | ‘'’ (id | ‘{’ Block ‘}’)
1452+
* | ‘'’ ‘[’ Type ‘]
1453+
* | ‘$’ (id | ‘{’ Block ‘}’)
14361454
* | SimpleExpr1 [`_']
14371455
* SimpleExpr1 ::= literal
14381456
* | xmlLiteral
@@ -1461,15 +1479,21 @@ object Parsers {
14611479
case LBRACE =>
14621480
canApply = false
14631481
blockExpr()
1464-
case QPAREN =>
1465-
in.token = LPAREN
1466-
atSpan(in.offset)(Quote(simpleExpr()))
1467-
case QBRACE =>
1468-
in.token = LBRACE
1469-
atSpan(in.offset)(Quote(simpleExpr()))
1470-
case QBRACKET =>
1471-
in.token = LBRACKET
1472-
atSpan(in.offset)(Quote(inBrackets(typ())))
1482+
case QUOTE =>
1483+
atSpan(in.skipToken()) {
1484+
Quote {
1485+
if (in.token == LBRACKET) {
1486+
val saved = in.inQuote
1487+
in.inQuote = true
1488+
inBrackets {
1489+
try typ() finally in.inQuote = saved
1490+
}
1491+
}
1492+
else stagedBlock(isQuote = true)
1493+
}
1494+
}
1495+
case SPLICE =>
1496+
splice(isType = false)
14731497
case NEW =>
14741498
canApply = false
14751499
newExpr()

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

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

187+
var inQuote = false
188+
187189
private[this] def addComment(comment: Comment): Unit = {
188190
val lookahead = lookaheadReader()
189191
def nextPos: Int = (lookahead.getc(): @switch) match {
@@ -415,7 +417,7 @@ object Scanners {
415417
'K' | 'L' | 'M' | 'N' | 'O' |
416418
'P' | 'Q' | 'R' | 'S' | 'T' |
417419
'U' | 'V' | 'W' | 'X' | 'Y' |
418-
'Z' | '$' | '_' |
420+
'Z' | '_' |
419421
'a' | 'b' | 'c' | 'd' | 'e' |
420422
'f' | 'g' | 'h' | 'i' | 'j' |
421423
'k' | 'l' | 'm' | 'n' | 'o' |
@@ -424,9 +426,15 @@ object Scanners {
424426
'z' =>
425427
putChar(ch)
426428
nextChar()
427-
getIdentRest()
428-
if (ch == '"' && token == IDENTIFIER)
429-
token = INTERPOLATIONID
429+
finishIdent()
430+
case '$' =>
431+
putChar(ch)
432+
nextChar()
433+
if (inQuote) {
434+
token = SPLICE
435+
litBuf.clear()
436+
}
437+
else finishIdent()
430438
case '<' => // is XMLSTART?
431439
def fetchLT() = {
432440
val last = if (charOffset >= 2) buf(charOffset - 2) else ' '
@@ -523,19 +531,13 @@ object Scanners {
523531
charLitOr { getIdentRest(); SYMBOLLIT }
524532
else if (isOperatorPart(ch) && (ch != '\\'))
525533
charLitOr { getOperatorRest(); SYMBOLLIT }
526-
else if (ch == '(' || ch == '{' || ch == '[') {
527-
val tok = quote(ch)
528-
charLitOr(tok)
529-
}
530-
else {
531-
getLitChar()
532-
if (ch == '\'') {
533-
nextChar()
534-
token = CHARLIT
535-
setStrVal()
536-
} else {
537-
error("unclosed character literal")
538-
}
534+
else ch match {
535+
case '{' | '[' | ' ' | '\t' if lookaheadChar() != '\'' =>
536+
token = QUOTE
537+
case _ =>
538+
getLitChar()
539+
if (ch == '\'') finishCharLit()
540+
else error("unclosed character literal")
539541
}
540542
}
541543
fetchSingleQuote()
@@ -716,6 +718,11 @@ object Scanners {
716718
}
717719
}
718720

721+
def finishIdent(): Unit = {
722+
getIdentRest()
723+
if (ch == '"' && token == IDENTIFIER) token = INTERPOLATIONID
724+
}
725+
719726
private def getOperatorRest(): Unit = (ch: @switch) match {
720727
case '~' | '!' | '@' | '#' | '%' |
721728
'^' | '*' | '+' | '-' | '<' |
@@ -965,9 +972,8 @@ object Scanners {
965972
}
966973
token = INTLIT
967974
if (base == 10 && ch == '.') {
968-
val lookahead = lookaheadReader()
969-
lookahead.nextChar()
970-
if ('0' <= lookahead.ch && lookahead.ch <= '9') {
975+
val lch = lookaheadChar()
976+
if ('0' <= lch && lch <= '9') {
971977
putChar('.'); nextChar(); getFraction()
972978
}
973979
} else (ch: @switch) match {
@@ -981,30 +987,26 @@ object Scanners {
981987
setStrVal()
982988
}
983989

990+
private def finishCharLit(): Unit = {
991+
nextChar()
992+
token = CHARLIT
993+
setStrVal()
994+
}
995+
984996
/** Parse character literal if current character is followed by \',
985997
* or follow with given op and return a symbol literal token
986998
*/
987999
def charLitOr(op: => Token): Unit = {
9881000
putChar(ch)
9891001
nextChar()
990-
if (ch == '\'') {
991-
nextChar()
992-
token = CHARLIT
993-
setStrVal()
994-
} else {
1002+
if (ch == '\'') finishCharLit()
1003+
else {
9951004
token = op
9961005
strVal = if (name != null) name.toString else null
9971006
litBuf.clear()
9981007
}
9991008
}
10001009

1001-
/** The opening quote bracket token corresponding to `c` */
1002-
def quote(c: Char): Token = c match {
1003-
case '(' => QPAREN
1004-
case '{' => QBRACE
1005-
case '[' => QBRACKET
1006-
}
1007-
10081010
override def toString: String =
10091011
showTokenDetailed(token) + {
10101012
if ((identifierTokens contains token) || (literalTokens contains token)) " " + name

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,14 @@ 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 QPAREN = 85; enter(QPAREN, "'(")
197-
final val QBRACE = 86; enter(QBRACE, "'{")
198-
final val QBRACKET = 87; enter(QBRACKET, "'[")
196+
final val SPLICE = 85; enter(SPLICE, "$")
197+
final val QUOTE = 86; enter(QUOTE, "'")
199198

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

203202
final val alphaKeywords: TokenSet = tokenRange(IF, GIVEN)
204-
final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND)
205-
final val symbolicTokens: TokenSet = tokenRange(COMMA, VIEWBOUND)
203+
final val symbolicKeywords: TokenSet = tokenRange(USCORE, SPLICE)
206204
final val keywords: TokenSet = alphaKeywords | symbolicKeywords
207205

208206
final val allTokens: TokenSet = tokenRange(minToken, maxToken)
@@ -214,10 +212,10 @@ object Tokens extends TokensCommon {
214212
USCORE, NULL, THIS, SUPER, TRUE, FALSE, RETURN, XMLSTART)
215213

216214
final val canStartExpressionTokens: TokenSet = atomicExprTokens | BitSet(
217-
LBRACE, LPAREN, QBRACE, QPAREN, QBRACKET, IF, DO, WHILE, FOR, NEW, TRY, THROW)
215+
LBRACE, LPAREN, QUOTE, SPLICE, IF, DO, WHILE, FOR, NEW, TRY, THROW)
218216

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

222220
final val canStartBindingTokens: TokenSet = identifierTokens | BitSet(USCORE, LPAREN)
223221

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
302302
case _ => toTextGlobal(arg)
303303
}
304304

305+
def dropBlock(tree: Tree): Tree = tree match {
306+
case Block(Nil, expr) => expr
307+
case _ => tree
308+
}
309+
305310
tree match {
306311
case id: Trees.BackquotedIdent[_] if !homogenizedView =>
307312
"`" ~ toText(id.name) ~ "`"
@@ -571,7 +576,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
571576
keywordStr("try ") ~ toText(expr) ~ " " ~ keywordStr("catch") ~ " {" ~ toText(handler) ~ "}" ~ optText(finalizer)(keywordStr(" finally ") ~ _)
572577
}
573578
case Quote(tree) =>
574-
if (tree.isType) keywordStr("'[") ~ toTextGlobal(tree) ~ keywordStr("]") else keywordStr("'{") ~ toTextGlobal(tree) ~ keywordStr("}")
579+
if (tree.isType) keywordStr("'[") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("]")
580+
else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
581+
case Splice(tree) =>
582+
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
575583
case Thicket(trees) =>
576584
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
577585
case _ =>

0 commit comments

Comments
 (0)