Skip to content

Commit e7324e7

Browse files
committed
Implement generic number literals
Example: ``` val x: BigInt = 111111100000022222222222 ```
1 parent 12e5dc7 commit e7324e7

File tree

13 files changed

+354
-149
lines changed

13 files changed

+354
-149
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
110110
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
111111
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
112112
case class Export(impliedOnly: Boolean, expr: Tree, selectors: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
113+
case class Number(digits: String, kind: NumberKind)(implicit @constructorOnly src: SourceFile) extends TermTree
114+
115+
enum NumberKind {
116+
case Whole(radix: Int)
117+
case Decimal
118+
case Floating
119+
}
113120

114121
/** Short-lived usage in typer, does not need copy/transform/fold infrastructure */
115122
case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree
@@ -566,6 +573,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
566573
case tree: Export if (impliedOnly == tree.impliedOnly) && (expr eq tree.expr) && (selectors eq tree.selectors) => tree
567574
case _ => finalize(tree, untpd.Export(impliedOnly, expr, selectors)(tree.source))
568575
}
576+
def Number(tree: Tree)(digits: String, kind: NumberKind)(implicit ctx: Context): Tree = tree match {
577+
case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree
578+
case _ => finalize(tree, untpd.Number(digits, kind))
579+
}
569580
def TypedSplice(tree: Tree)(splice: tpd.Tree)(implicit ctx: Context): ProxyTree = tree match {
570581
case tree: TypedSplice if splice `eq` tree.splice => tree
571582
case _ => finalize(tree, untpd.TypedSplice(splice)(ctx))
@@ -622,7 +633,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
622633
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
623634
case Export(impliedOnly, expr, selectors) =>
624635
cpy.Export(tree)(impliedOnly, transform(expr), selectors)
625-
case TypedSplice(_) =>
636+
case Number(_, _) | TypedSplice(_) =>
626637
tree
627638
case _ =>
628639
super.transformMoreCases(tree)
@@ -679,6 +690,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
679690
this(this(this(x, pats), tpt), rhs)
680691
case Export(_, expr, _) =>
681692
this(x, expr)
693+
case Number(_, _) =>
694+
x
682695
case TypedSplice(splice) =>
683696
this(x, splice)
684697
case _ =>

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,11 @@ class Definitions {
697697
@threadUnsafe lazy val StatsModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("dotty.tools.dotc.util.Stats"))
698698
@threadUnsafe lazy val Stats_doRecord: SymbolPerRun = perRunSym(StatsModule.requiredMethodRef("doRecord"))
699699

700+
@threadUnsafe lazy val FromDigitsClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits"))
701+
@threadUnsafe lazy val FromDigits_WithRadixClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.WithRadix"))
702+
@threadUnsafe lazy val FromDigits_DecimalClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.Decimal"))
703+
@threadUnsafe lazy val FromDigits_FloatingClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.Floating"))
704+
700705
@threadUnsafe lazy val XMLTopScopeModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("scala.xml.TopScope"))
701706

702707
@threadUnsafe lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple")

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ object StdNames {
440440
val flagsFromBits : N = "flagsFromBits"
441441
val flatMap: N = "flatMap"
442442
val foreach: N = "foreach"
443+
val fromDigits: N = "fromDigits"
443444
val fromProduct: N = "fromProduct"
444445
val genericArrayOps: N = "genericArrayOps"
445446
val genericClass: N = "genericClass"

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

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -510,20 +510,13 @@ object JavaScanners {
510510
// Errors -----------------------------------------------------------------
511511

512512
override def toString(): String = token match {
513-
case IDENTIFIER =>
514-
"id(" + name + ")"
515-
case CHARLIT =>
516-
"char(" + intVal + ")"
517-
case INTLIT =>
518-
"int(" + intVal + ")"
519-
case LONGLIT =>
520-
"long(" + intVal + ")"
521-
case FLOATLIT =>
522-
"float(" + floatVal + ")"
523-
case DOUBLELIT =>
524-
"double(" + doubleVal + ")"
525-
case STRINGLIT =>
526-
"string(" + name + ")"
513+
case IDENTIFIER => s"id($name)"
514+
case CHARLIT => s"char($strVal)"
515+
case INTLIT => s"int($strVal, $base)"
516+
case LONGLIT => s"long($strVal, $base)"
517+
case FLOATLIT => s"float($strVal)"
518+
case DOUBLELIT => s"double($strVal)"
519+
case STRINGLIT => s"string($strVal)"
527520
case SEMI =>
528521
";"
529522
case COMMA =>

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

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -732,24 +732,37 @@ object Parsers {
732732
* @param negOffset The offset of a preceding `-' sign, if any.
733733
* If the literal is not negated, negOffset = in.offset.
734734
*/
735-
def literal(negOffset: Int = in.offset, inPattern: Boolean = false, inStringInterpolation: Boolean = false): Tree = {
736-
737-
def literalOf(token: Token): Literal = {
735+
def literal(negOffset: Int = in.offset, inPattern: Boolean = false, inType: Boolean = false, inStringInterpolation: Boolean = false): Tree = {
736+
def literalOf(token: Token): Tree = {
738737
val isNegated = negOffset < in.offset
739-
val value = token match {
740-
case CHARLIT => in.charVal
741-
case INTLIT => in.intVal(isNegated).toInt
742-
case LONGLIT => in.intVal(isNegated)
743-
case FLOATLIT => in.floatVal(isNegated).toFloat
744-
case DOUBLELIT => in.doubleVal(isNegated)
745-
case STRINGLIT | STRINGPART => in.strVal
746-
case TRUE => true
747-
case FALSE => false
748-
case NULL => null
749-
case _ =>
750-
syntaxErrorOrIncomplete(IllegalLiteral())
751-
null
752-
}
738+
def digits0 = in.removeNumberSeparators(in.strVal)
739+
def digits = if (isNegated) "-" + digits0 else digits0
740+
if (!inType)
741+
token match {
742+
case INTLIT => return Number(digits, NumberKind.Whole(in.base))
743+
case DECILIT => return Number(digits, NumberKind.Decimal)
744+
case EXPOLIT => return Number(digits, NumberKind.Floating)
745+
case _ =>
746+
}
747+
import scala.util.FromDigits._
748+
val value =
749+
try token match {
750+
case INTLIT => intFromDigits(digits, in.base)
751+
case LONGLIT => longFromDigits(digits, in.base)
752+
case FLOATLIT => floatFromDigits(digits)
753+
case DOUBLELIT | DECILIT | EXPOLIT => doubleFromDigits(digits)
754+
case CHARLIT => in.strVal.head
755+
case STRINGLIT | STRINGPART => in.strVal
756+
case TRUE => true
757+
case FALSE => false
758+
case NULL => null
759+
case _ =>
760+
syntaxErrorOrIncomplete(IllegalLiteral())
761+
null
762+
}
763+
catch {
764+
case ex: FromDigitsException => syntaxErrorOrIncomplete(ex.getMessage)
765+
}
753766
Literal(Constant(value))
754767
}
755768

@@ -862,6 +875,7 @@ object Parsers {
862875
}
863876

864877
/* ------------- TYPES ------------------------------------------------------ */
878+
865879
/** Same as [[typ]], but if this results in a wildcard it emits a syntax error and
866880
* returns a tree for type `Any` instead.
867881
*/
@@ -1075,11 +1089,11 @@ object Parsers {
10751089
}
10761090
else if (in.token == LBRACE)
10771091
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement()) }
1078-
else if (isSimpleLiteral) { SingletonTypeTree(literal()) }
1092+
else if (isSimpleLiteral) { SingletonTypeTree(literal(inType = true)) }
10791093
else if (isIdent(nme.raw.MINUS) && in.lookaheadIn(numericLitTokens)) {
10801094
val start = in.offset
10811095
in.nextToken()
1082-
SingletonTypeTree(literal(negOffset = start))
1096+
SingletonTypeTree(literal(negOffset = start, inType = true))
10831097
}
10841098
else if (in.token == USCORE) {
10851099
if (ctx.settings.strict.value) {

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

Lines changed: 8 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -115,98 +115,10 @@ object Scanners {
115115
def setStrVal(): Unit =
116116
strVal = flushBuf(litBuf)
117117

118-
/** Convert current strVal to char value
119-
*/
120-
def charVal: Char = if (strVal.length > 0) strVal.charAt(0) else 0
121-
122-
/** Convert current strVal, base to long value
123-
* This is tricky because of max negative value.
124-
*/
125-
def intVal(negated: Boolean): Long = {
126-
if (token == CHARLIT && !negated) {
127-
charVal
128-
} else {
129-
var value: Long = 0
130-
val divider = if (base == 10) 1 else 2
131-
val limit: Long =
132-
if (token == LONGLIT) Long.MaxValue else Int.MaxValue
133-
var i = 0
134-
val len = strVal.length
135-
while (i < len) {
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
151-
}
152-
i += 1
153-
}
154-
if (negated) -value else value
155-
}
156-
}
157-
158-
def intVal: Long = intVal(false)
159-
160-
private val zeroFloat = raw"[0.]+(?:[eE][+-]?[0-9]+)?[fFdD]?".r
161-
162-
/** Convert current strVal, base to double value
163-
*/
164-
def floatVal(negated: Boolean): Float = {
165-
assert(token == FLOATLIT)
166-
val text = removeNumberSeparators(strVal)
167-
try {
168-
val value: Float = java.lang.Float.valueOf(text).floatValue()
169-
if (value > Float.MaxValue)
170-
errorButContinue("floating point number too large")
171-
172-
if (value == 0.0f && !zeroFloat.pattern.matcher(text).matches)
173-
errorButContinue("floating point number too small")
174-
if (negated) -value else value
175-
} catch {
176-
case _: NumberFormatException =>
177-
error("malformed floating point number")
178-
0.0f
179-
}
180-
}
181-
182-
def floatVal: Float = floatVal(false)
183-
184-
/** Convert current strVal, base to double value
185-
*/
186-
def doubleVal(negated: Boolean): Double = {
187-
assert(token == DOUBLELIT)
188-
val text = removeNumberSeparators(strVal)
189-
try {
190-
val value: Double = java.lang.Double.valueOf(text).doubleValue()
191-
if (value > Double.MaxValue)
192-
errorButContinue("double precision floating point number too large")
193-
194-
if (value == 0.0d && !zeroFloat.pattern.matcher(text).matches)
195-
errorButContinue("double precision floating point number too small")
196-
if (negated) -value else value
197-
} catch {
198-
case _: NumberFormatException =>
199-
error("malformed floating point number")
200-
0.0
201-
}
202-
}
203-
204-
def doubleVal: Double = doubleVal(false)
205-
206118
@inline def isNumberSeparator(c: Char): Boolean = c == '_'
207119

208120
@inline def removeNumberSeparators(s: String): String =
209-
if (s.indexOf('_') > 0) s.replaceAllLiterally("_", "") /*.replaceAll("'","")*/ else s
121+
if (s.indexOf('_') > 0) s.replaceAllLiterally("_", "") else s
210122

211123
// disallow trailing numeric separator char, but continue lexing
212124
def checkNoTrailingSeparator(): Unit = {
@@ -967,7 +879,7 @@ object Scanners {
967879
* if one is present.
968880
*/
969881
protected def getFraction(): Unit = {
970-
token = DOUBLELIT
882+
token = DECILIT
971883
while ('0' <= ch && ch <= '9' || isNumberSeparator(ch)) {
972884
putChar(ch)
973885
nextChar()
@@ -992,7 +904,7 @@ object Scanners {
992904
}
993905
checkNoTrailingSeparator()
994906
}
995-
token = DOUBLELIT
907+
token = EXPOLIT
996908
}
997909
if (ch == 'd' || ch == 'D') {
998910
putChar(ch)
@@ -1068,11 +980,11 @@ object Scanners {
1068980

1069981
def show: String = token match {
1070982
case IDENTIFIER | BACKQUOTED_IDENT => s"id($name)"
1071-
case CHARLIT => s"char($intVal)"
1072-
case INTLIT => s"int($intVal)"
1073-
case LONGLIT => s"long($intVal)"
1074-
case FLOATLIT => s"float($floatVal)"
1075-
case DOUBLELIT => s"double($doubleVal)"
983+
case CHARLIT => s"char($strVal)"
984+
case INTLIT => s"int($strVal, $base)"
985+
case LONGLIT => s"long($strVal, $base)"
986+
case FLOATLIT => s"float($strVal)"
987+
case DOUBLELIT => s"double($strVal)"
1076988
case STRINGLIT => s"string($strVal)"
1077989
case STRINGPART => s"stringpart($strVal)"
1078990
case INTERPOLATIONID => s"interpolationid($name)"
@@ -1083,8 +995,6 @@ object Scanners {
1083995
case _ => showToken(token)
1084996
}
1085997

1086-
// (does not seem to be needed) def flush = { charOffset = offset; nextChar(); this }
1087-
1088998
/* Resume normal scanning after XML */
1089999
def resume(lastToken: Token): Unit = {
10901000
token = lastToken

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,19 @@ abstract class TokensCommon {
3737
/** literals */
3838
final val CHARLIT = 3; enter(CHARLIT, "character literal")
3939
final val INTLIT = 4; enter(INTLIT, "integer literal")
40-
final val LONGLIT = 5; enter(LONGLIT, "long literal")
41-
final val FLOATLIT = 6; enter(FLOATLIT, "float literal")
42-
final val DOUBLELIT = 7; enter(DOUBLELIT, "double literal")
43-
final val STRINGLIT = 8; enter(STRINGLIT, "string literal")
44-
final val STRINGPART = 9; enter(STRINGPART, "string literal", "string literal part")
45-
//final val INTERPOLATIONID = 10; enter(INTERPOLATIONID, "string interpolator")
46-
//final val QUOTEID = 11; enter(QUOTEID, "quoted identifier") // TODO: deprecate
40+
final val DECILIT = 5; enter(DECILIT, "number literal") // with decimal point
41+
final val EXPOLIT = 6; enter(EXPOLIT, "number literal with exponent")
42+
final val LONGLIT = 7; enter(LONGLIT, "long literal")
43+
final val FLOATLIT = 8; enter(FLOATLIT, "float literal")
44+
final val DOUBLELIT = 9; enter(DOUBLELIT, "double literal")
45+
final val STRINGLIT = 10; enter(STRINGLIT, "string literal")
46+
final val STRINGPART = 11; enter(STRINGPART, "string literal", "string literal part")
47+
//final val INTERPOLATIONID = 12; enter(INTERPOLATIONID, "string interpolator")
48+
//final val QUOTEID = 13; enter(QUOTEID, "quoted identifier") // TODO: deprecate
4749

4850
/** identifiers */
49-
final val IDENTIFIER = 12; enter(IDENTIFIER, "identifier")
50-
//final val BACKQUOTED_IDENT = 13; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident")
51+
final val IDENTIFIER = 14; enter(IDENTIFIER, "identifier")
52+
//final val BACKQUOTED_IDENT = 15; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident")
5153

5254
/** alphabetic keywords */
5355
final val IF = 20; enter(IF, "if")
@@ -148,10 +150,10 @@ object Tokens extends TokensCommon {
148150
final val minToken = EMPTY
149151
final def maxToken: Int = XMLSTART
150152

151-
final val INTERPOLATIONID = 10; enter(INTERPOLATIONID, "string interpolator")
152-
final val QUOTEID = 11; enter(QUOTEID, "quoted identifier") // TODO: deprecate
153+
final val INTERPOLATIONID = 12; enter(INTERPOLATIONID, "string interpolator")
154+
final val QUOTEID = 13; enter(QUOTEID, "quoted identifier") // TODO: deprecate
153155

154-
final val BACKQUOTED_IDENT = 13; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident")
156+
final val BACKQUOTED_IDENT = 15; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident")
155157

156158
final val identifierTokens: TokenSet = BitSet(IDENTIFIER, BACKQUOTED_IDENT)
157159

@@ -251,7 +253,7 @@ object Tokens extends TokensCommon {
251253
final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(
252254
TYPE, RPAREN, RBRACE, RBRACKET)
253255

254-
final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT)
256+
final val numericLitTokens: TokenSet = BitSet(INTLIT, DECILIT, EXPOLIT, LONGLIT, FLOATLIT, DOUBLELIT)
255257

256258
final val scala3keywords = BitSet(ENUM, ERASED, GIVEN, IMPLIED)
257259

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
606606
changePrec(GlobalPrec) {
607607
keywordStr("try ") ~ toText(expr) ~ " " ~ keywordStr("catch") ~ " {" ~ toText(handler) ~ "}" ~ optText(finalizer)(keywordStr(" finally ") ~ _)
608608
}
609+
case Number(digits, kind) =>
610+
digits
609611
case Quote(tree) =>
610612
if (tree.isType) keywordStr("'[") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("]")
611613
else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")

0 commit comments

Comments
 (0)