@@ -1338,47 +1338,9 @@ object Scanners {
13381338 }
13391339 }
13401340 else if (isInterpolated && ch == '$' ) {
1341- // Handle interpolation
1342- def getInterpolatedIdentRest (hasSupplement : Boolean ): Unit =
1343- @ tailrec def loopRest (): Unit =
1344- if ch != SU && isUnicodeIdentifierPart(ch) then
1345- putChar(ch) ; nextRawChar()
1346- loopRest()
1347- else if atSupplementary(ch, isUnicodeIdentifierPart) then
1348- putChar(ch) ; nextRawChar()
1349- putChar(ch) ; nextRawChar()
1350- loopRest()
1351- else
1352- finishNamedToken(IDENTIFIER , target = next)
1353- end loopRest
1354- setStrVal()
1355- token = STRINGPART
1356- next.lastOffset = charOffset - 1
1357- next.offset = charOffset - 1
1358- putChar(ch) ; nextRawChar()
1359- if hasSupplement then
1360- putChar(ch) ; nextRawChar()
1361- loopRest()
1362- end getInterpolatedIdentRest
1363-
1364- nextRawChar()
1365- if (ch == '$' || ch == '\' ' ) {
1366- putChar(ch)
1367- nextRawChar()
1341+ if (handleStringInterpolation('\' ' )) {
13681342 getDedentedStringPartWithDelimiter(quoteCount, isInterpolated)
13691343 }
1370- else if (ch == '{' ) {
1371- setStrVal()
1372- token = STRINGPART
1373- }
1374- else if isUnicodeIdentifierStart(ch) || ch == '_' then
1375- getInterpolatedIdentRest(hasSupplement = false )
1376- else if atSupplementary(ch, isUnicodeIdentifierStart) then
1377- getInterpolatedIdentRest(hasSupplement = true )
1378- else
1379- error(" invalid string interpolation: `$$`, `$'`, `$`ident or `$`BlockExpr expected" .toMessage, off = charOffset - 2 )
1380- putChar('$' )
1381- getDedentedStringPartWithDelimiter(quoteCount, isInterpolated)
13821344 }
13831345 else {
13841346 val isUnclosedLiteral = ! isUnicodeEscape && ch == SU
@@ -1391,6 +1353,57 @@ object Scanners {
13911353 }
13921354 end getDedentedStringPartWithDelimiter
13931355
1356+ /** Handle `$` in string interpolations. This is shared between regular strings and dedented strings.
1357+ * @param escapeChar The character that can be escaped with `$` (either `"` or `'`)
1358+ * @return true if the caller should continue parsing the rest of the string, false otherwise
1359+ */
1360+ private def handleStringInterpolation (escapeChar : Char ): Boolean = {
1361+ def getInterpolatedIdentRest (hasSupplement : Boolean ): Unit =
1362+ @ tailrec def loopRest (): Unit =
1363+ if ch != SU && isUnicodeIdentifierPart(ch) then
1364+ putChar(ch) ; nextRawChar()
1365+ loopRest()
1366+ else if atSupplementary(ch, isUnicodeIdentifierPart) then
1367+ putChar(ch) ; nextRawChar()
1368+ putChar(ch) ; nextRawChar()
1369+ loopRest()
1370+ else
1371+ finishNamedToken(IDENTIFIER , target = next)
1372+ end loopRest
1373+ setStrVal()
1374+ token = STRINGPART
1375+ next.lastOffset = charOffset - 1
1376+ next.offset = charOffset - 1
1377+ putChar(ch) ; nextRawChar()
1378+ if hasSupplement then
1379+ putChar(ch) ; nextRawChar()
1380+ loopRest()
1381+ end getInterpolatedIdentRest
1382+
1383+ nextRawChar()
1384+ if (ch == '$' || ch == escapeChar) {
1385+ putChar(ch)
1386+ nextRawChar()
1387+ true // continue parsing
1388+ }
1389+ else if (ch == '{' ) {
1390+ setStrVal()
1391+ token = STRINGPART
1392+ false // don't continue, we're done with this string part
1393+ }
1394+ else if isUnicodeIdentifierStart(ch) || ch == '_' then
1395+ getInterpolatedIdentRest(hasSupplement = false )
1396+ false // don't continue, identifier rest handles it
1397+ else if atSupplementary(ch, isUnicodeIdentifierStart) then
1398+ getInterpolatedIdentRest(hasSupplement = true )
1399+ false // don't continue, identifier rest handles it
1400+ else
1401+ val escapeDesc = if escapeChar == '"' then " `$\"\" `, " else " `$'`, "
1402+ error(s " invalid string interpolation: ` $$$$ `, $escapeDesc` $$ `ident or ` $$ `BlockExpr expected " .toMessage, off = charOffset - 2 )
1403+ putChar('$' )
1404+ true // continue parsing after error
1405+ }
1406+
13941407 private def getRawStringLit (): Unit =
13951408 if (ch == '\" ' ) {
13961409 nextRawChar()
@@ -1435,46 +1448,9 @@ object Scanners {
14351448 getStringPart(multiLine)
14361449 }
14371450 else if (ch == '$' ) {
1438- def getInterpolatedIdentRest (hasSupplement : Boolean ): Unit =
1439- @ tailrec def loopRest (): Unit =
1440- if ch != SU && isUnicodeIdentifierPart(ch) then
1441- putChar(ch) ; nextRawChar()
1442- loopRest()
1443- else if atSupplementary(ch, isUnicodeIdentifierPart) then
1444- putChar(ch) ; nextRawChar()
1445- putChar(ch) ; nextRawChar()
1446- loopRest()
1447- else
1448- finishNamedToken(IDENTIFIER , target = next)
1449- end loopRest
1450- setStrVal()
1451- token = STRINGPART
1452- next.lastOffset = charOffset - 1
1453- next.offset = charOffset - 1
1454- putChar(ch) ; nextRawChar()
1455- if hasSupplement then
1456- putChar(ch) ; nextRawChar()
1457- loopRest()
1458- end getInterpolatedIdentRest
1459-
1460- nextRawChar()
1461- if (ch == '$' || ch == '"' ) {
1462- putChar(ch)
1463- nextRawChar()
1451+ if (handleStringInterpolation('"' )) {
14641452 getStringPart(multiLine)
14651453 }
1466- else if (ch == '{' ) {
1467- setStrVal()
1468- token = STRINGPART
1469- }
1470- else if isUnicodeIdentifierStart(ch) || ch == '_' then
1471- getInterpolatedIdentRest(hasSupplement = false )
1472- else if atSupplementary(ch, isUnicodeIdentifierStart) then
1473- getInterpolatedIdentRest(hasSupplement = true )
1474- else
1475- error(" invalid string interpolation: `$$`, `$\" `, `$`ident or `$`BlockExpr expected" .toMessage, off = charOffset - 2 )
1476- putChar('$' )
1477- getStringPart(multiLine)
14781454 }
14791455 else {
14801456 val isUnclosedLiteral = ! isUnicodeEscape && (ch == SU || (! multiLine && (ch == CR || ch == LF )))
0 commit comments