Skip to content

Commit 60e5494

Browse files
committed
Accept underscore as numeric literal separator
The separator character is underscore as in Java. They may be mixed arbitrarily in a numeric literal, but only in the interior: the literal, and each part of double, must begin and end with a digit. Add spec for underscore numeric separator. This patch is a modification of a commit from som-snytt scala/scala@c359188
1 parent d87a89e commit 60e5494

File tree

4 files changed

+93
-19
lines changed

4 files changed

+93
-19
lines changed

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

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,15 @@ object Scanners {
6767

6868
/** Generate an error at the given offset */
6969
def error(msg: String, off: Offset = offset): Unit = {
70-
ctx.error(msg, source atSpan Span(off))
70+
errorButContinue(msg, off)
7171
token = ERROR
7272
errOffset = off
7373
}
7474

75+
def errorButContinue(msg: String, off: Offset = offset): Unit = {
76+
ctx.error(msg, source atSpan Span(off))
77+
}
78+
7579
/** signal an error where the input ended in the middle of a token */
7680
def incompleteInputError(msg: String): Unit = {
7781
ctx.incompleteInputError(msg, source atSpan Span(offset))
@@ -129,19 +133,22 @@ object Scanners {
129133
var i = 0
130134
val len = strVal.length
131135
while (i < len) {
132-
val d = digit2int(strVal charAt i, base)
133-
if (d < 0) {
134-
error("malformed integer number")
135-
return 0
136-
}
137-
if (value < 0 ||
138-
limit / (base / divider) < value ||
139-
limit - (d / divider) < value * (base / divider) &&
140-
!(negated && limit == value * base - 1 + d)) {
141-
error("integer number too large")
142-
return 0
136+
val c = strVal charAt i
137+
if (! isNumberSeparator(c)) {
138+
val d = digit2int(c, base)
139+
if (d < 0) {
140+
error(s"malformed integer number")
141+
return 0
142+
}
143+
if (value < 0 ||
144+
limit / (base / divider) < value ||
145+
limit - (d / divider) < value * (base / divider) &&
146+
!(negated && limit == value * base - 1 + d)) {
147+
error("integer number too large")
148+
return 0
149+
}
150+
value = value * base + d
143151
}
144-
value = value * base + d
145152
i += 1
146153
}
147154
if (negated) -value else value
@@ -153,10 +160,11 @@ object Scanners {
153160
/** Convert current strVal, base to double value
154161
*/
155162
def floatVal(negated: Boolean): Double = {
163+
val text = removeNumberSeparators(strVal)
156164
val limit: Double =
157165
if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue
158166
try {
159-
val value: Double = java.lang.Double.valueOf(strVal).doubleValue()
167+
val value: Double = java.lang.Double.valueOf(text).doubleValue()
160168
if (value > limit)
161169
error("floating point number too large")
162170
if (negated) -value else value
@@ -169,6 +177,17 @@ object Scanners {
169177

170178
def floatVal: Double = floatVal(false)
171179

180+
@inline def isNumberSeparator(c: Char): Boolean = c == '_'
181+
182+
@inline def removeNumberSeparators(s: String): String =
183+
if (s.indexOf('_') > 0) s.replaceAllLiterally("_", "") /*.replaceAll("'","")*/ else s
184+
185+
// disallow trailing numeric separator char, but continue lexing
186+
def checkNoTrailingSeparator(): Unit = {
187+
if (isNumberSeparator(litBuf.last))
188+
errorButContinue("trailing separator is not allowed", offset + litBuf.length - 1)
189+
}
190+
172191
}
173192

174193
class Scanner(source: SourceFile, override val startFrom: Offset = 0)(implicit ctx: Context) extends ScannerCommon(source)(ctx) {
@@ -911,27 +930,29 @@ object Scanners {
911930
*/
912931
protected def getFraction(): Unit = {
913932
token = DOUBLELIT
914-
while ('0' <= ch && ch <= '9') {
933+
while ('0' <= ch && ch <= '9' || isNumberSeparator(ch)) {
915934
putChar(ch)
916935
nextChar()
917936
}
937+
checkNoTrailingSeparator()
918938
if (ch == 'e' || ch == 'E') {
919939
val lookahead = lookaheadReader()
920940
lookahead.nextChar()
921941
if (lookahead.ch == '+' || lookahead.ch == '-') {
922942
lookahead.nextChar()
923943
}
924-
if ('0' <= lookahead.ch && lookahead.ch <= '9') {
944+
if ('0' <= lookahead.ch && lookahead.ch <= '9' || isNumberSeparator(ch)) {
925945
putChar(ch)
926946
nextChar()
927947
if (ch == '+' || ch == '-') {
928948
putChar(ch)
929949
nextChar()
930950
}
931-
while ('0' <= ch && ch <= '9') {
951+
while ('0' <= ch && ch <= '9' || isNumberSeparator(ch)) {
932952
putChar(ch)
933953
nextChar()
934954
}
955+
checkNoTrailingSeparator()
935956
}
936957
token = DOUBLELIT
937958
}
@@ -954,15 +975,18 @@ object Scanners {
954975
/** Read a number into strVal and set base
955976
*/
956977
protected def getNumber(): Unit = {
957-
while (digit2int(ch, base) >= 0) {
978+
while (isNumberSeparator(ch) || digit2int(ch, base) >= 0) {
958979
putChar(ch)
959980
nextChar()
960981
}
982+
checkNoTrailingSeparator()
961983
token = INTLIT
962984
if (base == 10 && ch == '.') {
963985
val lch = lookaheadChar()
964986
if ('0' <= lch && lch <= '9') {
965-
putChar('.'); nextChar(); getFraction()
987+
putChar('.')
988+
nextChar()
989+
getFraction()
966990
}
967991
} else (ch: @switch) match {
968992
case 'e' | 'E' | 'f' | 'F' | 'd' | 'D' =>
@@ -972,6 +996,9 @@ object Scanners {
972996
token = LONGLIT
973997
case _ =>
974998
}
999+
1000+
checkNoTrailingSeparator()
1001+
9751002
setStrVal()
9761003
}
9771004

tests/neg/t6124.check

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<273..273> in t6124.scala
2+
trailing separator is not allowed
3+
<235..235> in t6124.scala
4+
Invalid literal number
5+
<240..240> in t6124.scala
6+
trailing separator is not allowed
7+
<213..213> in t6124.scala
8+
trailing separator is not allowed
9+
<187..187> in t6124.scala
10+
trailing separator is not allowed
11+
<160..160> in t6124.scala
12+
trailing separator is not allowed
13+
<125..125> in t6124.scala
14+
Invalid literal number
15+
<101..101> in t6124.scala
16+
trailing separator is not allowed
17+
<67..67> in t6124.scala
18+
trailing separator is not allowed
19+
<33..33> in t6124.scala
20+
trailing separator is not allowed

tests/neg/t6124.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
trait T {
3+
def i: Int = 123_456_ // error
4+
def j: Long = 123_456_L * 1000 // error
5+
6+
def f = 3_14_E-2 // error
7+
def e = 3_14E-_2 // error
8+
def d = 3_14E-2_ // error
9+
10+
def p = 3.1_4_ // error
11+
def q = 3.1_4_d // error
12+
def r = 3.1_4_dd // error // error
13+
def s = 3_.1 // error
14+
15+
def z = 0
16+
}

tests/pos/t6124.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
trait T {
3+
def i: Int = 1_024
4+
def j: Long = 1_024L * 1_024
5+
//def k = 1'024
6+
7+
def f = 3_14e-2f
8+
def d = 3_14E-2_1
9+
10+
def z = 0
11+
}

0 commit comments

Comments
 (0)