Skip to content

Commit 8cfa1fb

Browse files
oderskynicolasstucki
authored andcommitted
Use $ for splices in syntax and parsing
Also, update the reference document.
1 parent 4141bd2 commit 8cfa1fb

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
@@ -493,9 +492,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
493492
case tree: Throw if expr eq tree.expr => tree
494493
case _ => finalize(tree, untpd.Throw(expr)(tree.source))
495494
}
496-
def Quote(tree: Tree)(expr: Tree)(implicit ctx: Context): TermTree = tree match {
497-
case tree: Quote if expr eq tree.expr => tree
498-
case _ => finalize(tree, untpd.Quote(expr)(tree.source))
495+
def Quote(tree: Tree)(t: Tree)(implicit ctx: Context): Tree = tree match {
496+
case tree: Quote if t eq tree.t => tree
497+
case _ => finalize(tree, untpd.Quote(t)(tree.source))
498+
}
499+
def Splice(tree: Tree)(expr: Tree)(implicit ctx: Context): Tree = tree match {
500+
case tree: Splice if expr eq tree.expr => tree
501+
case _ => finalize(tree, untpd.Splice(expr)(tree.source))
502+
}
503+
def TypSplice(tree: Tree)(expr: Tree)(implicit ctx: Context): Tree = tree match {
504+
case tree: TypSplice if expr eq tree.expr => tree
505+
case _ => finalize(tree, untpd.TypSplice(expr)(tree.source))
499506
}
500507
def DoWhile(tree: Tree)(body: Tree, cond: Tree)(implicit ctx: Context): TermTree = tree match {
501508
case tree: DoWhile if (body eq tree.body) && (cond eq tree.cond) => tree
@@ -557,8 +564,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
557564
cpy.Tuple(tree)(transform(trees))
558565
case Throw(expr) =>
559566
cpy.Throw(tree)(transform(expr))
560-
case Quote(expr) =>
561-
cpy.Quote(tree)(transform(expr))
567+
case Quote(t) =>
568+
cpy.Quote(tree)(transform(t))
569+
case Splice(expr) =>
570+
cpy.Splice(tree)(transform(expr))
571+
case TypSplice(expr) =>
572+
cpy.TypSplice(tree)(transform(expr))
562573
case DoWhile(body, cond) =>
563574
cpy.DoWhile(tree)(transform(body), transform(cond))
564575
case ForYield(enums, expr) =>
@@ -606,7 +617,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
606617
this(x, trees)
607618
case Throw(expr) =>
608619
this(x, expr)
609-
case Quote(expr) =>
620+
case Quote(t) =>
621+
this(x, t)
622+
case Splice(expr) =>
623+
this(x, expr)
624+
case TypSplice(expr) =>
610625
this(x, expr)
611626
case DoWhile(body, cond) =>
612627
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
@@ -918,15 +918,33 @@ object Parsers {
918918
})
919919
else t
920920

921+
/** 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 block() finally in.inQuote = saved
927+
}
928+
}
929+
930+
/** SimpleEpxr ::= ‘$’ (id | ‘{’ Block ‘}’)
931+
* SimpleType ::= ‘$’ (id | ‘{’ Block ‘}’)
932+
*/
933+
def splice(isType: Boolean): Tree =
934+
atSpan(in.skipToken()) {
935+
val expr = if (isIdent) termIdent() else stagedBlock(isQuote = false)
936+
if (isType) TypSplice(expr) else Splice(expr)
937+
}
938+
921939
/** SimpleType ::= SimpleType TypeArgs
922940
* | SimpleType `#' id
923941
* | StableId
924-
* | ['~'] StableId
925942
* | Path `.' type
926943
* | `(' ArgTypes `)'
927944
* | `_' TypeBounds
928945
* | Refinement
929946
* | Literal
947+
* | ‘$’ (id | ‘{’ Block ‘}’)
930948
*/
931949
def simpleType(): Tree = simpleTypeRest {
932950
if (in.token == LPAREN)
@@ -940,8 +958,8 @@ object Parsers {
940958
val start = in.skipToken()
941959
typeBounds().withSpan(Span(start, in.lastOffset, start))
942960
}
943-
else if (isIdent(nme.raw.TILDE) && in.lookaheadIn(BitSet(IDENTIFIER, BACKQUOTED_IDENT)))
944-
atSpan(in.offset) { PrefixOp(typeIdent(), path(thisOK = true)) }
961+
else if (in.token == SPLICE)
962+
splice(isType = true)
945963
else path(thisOK = false, handleSingletonType) match {
946964
case r @ SingletonTypeTree(_) => r
947965
case r => convertToTypeId(r)
@@ -1402,9 +1420,9 @@ object Parsers {
14021420

14031421
/** SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody)
14041422
* | BlockExpr
1405-
* | ‘'{’ BlockExprContents ‘}’
1406-
* | ‘'(’ ExprsInParens ‘)
1407-
* | ‘'[’ Type ‘]’
1423+
* | ‘'’ (id | ‘{’ Block ‘}’)
1424+
* | ‘'’ ‘[’ Type ‘]
1425+
* | ‘$’ (id | ‘{’ Block ‘}’)
14081426
* | SimpleExpr1 [`_']
14091427
* SimpleExpr1 ::= literal
14101428
* | xmlLiteral
@@ -1433,15 +1451,21 @@ object Parsers {
14331451
case LBRACE =>
14341452
canApply = false
14351453
blockExpr()
1436-
case QPAREN =>
1437-
in.token = LPAREN
1438-
atSpan(in.offset)(Quote(simpleExpr()))
1439-
case QBRACE =>
1440-
in.token = LBRACE
1441-
atSpan(in.offset)(Quote(simpleExpr()))
1442-
case QBRACKET =>
1443-
in.token = LBRACKET
1444-
atSpan(in.offset)(Quote(inBrackets(typ())))
1454+
case QUOTE =>
1455+
atSpan(in.skipToken()) {
1456+
Quote {
1457+
if (in.token == LBRACKET) {
1458+
val saved = in.inQuote
1459+
in.inQuote = true
1460+
inBrackets {
1461+
try typ() finally in.inQuote = saved
1462+
}
1463+
}
1464+
else stagedBlock(isQuote = true)
1465+
}
1466+
}
1467+
case SPLICE =>
1468+
splice(isType = false)
14451469
case NEW =>
14461470
canApply = false
14471471
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) ~ "`"
@@ -572,7 +577,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
572577
keywordStr("try ") ~ toText(expr) ~ " " ~ keywordStr("catch") ~ " {" ~ toText(handler) ~ "}" ~ optText(finalizer)(keywordStr(" finally ") ~ _)
573578
}
574579
case Quote(tree) =>
575-
if (tree.isType) keywordStr("'[") ~ toTextGlobal(tree) ~ keywordStr("]") else keywordStr("'{") ~ toTextGlobal(tree) ~ keywordStr("}")
580+
if (tree.isType) keywordStr("'[") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("]")
581+
else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
582+
case Splice(tree) =>
583+
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
576584
case Thicket(trees) =>
577585
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
578586
case _ =>

0 commit comments

Comments
 (0)