@@ -438,30 +438,67 @@ pp.readRegexp = function() {
438
438
// were read, the integer value otherwise. When `len` is given, this
439
439
// will return `null` unless the integer has exactly `len` digits.
440
440
441
- pp . readInt = function ( radix , len ) {
442
- let start = this . pos , total = 0
443
- for ( let i = 0 , e = len == null ? Infinity : len ; i < e ; ++ i ) {
441
+ pp . readInt = function ( radix , len , maybeLegacyOctalNumericLiteral ) {
442
+ // `len` is used for character escape sequences. In that case, disallow separators.
443
+ const allowSeparators = this . options . ecmaVersion >= 12 && len === undefined
444
+
445
+ // `maybeLegacyOctalNumericLiteral` is true if it doesn't have prefix (0x,0o,0b)
446
+ // and isn't fraction part nor exponent part. In that case, if the first digit
447
+ // is zero then disallow separators.
448
+ const isLegacyOctalNumericLiteral = maybeLegacyOctalNumericLiteral && this . input . charCodeAt ( this . pos ) === 48
449
+
450
+ let start = this . pos , total = 0 , lastCode = 0
451
+ for ( let i = 0 , e = len == null ? Infinity : len ; i < e ; ++ i , ++ this . pos ) {
444
452
let code = this . input . charCodeAt ( this . pos ) , val
453
+
454
+ if ( allowSeparators && code === 95 ) {
455
+ if ( isLegacyOctalNumericLiteral ) this . raiseRecoverable ( this . pos , "Numeric separator is not allowed in legacy octal numeric literals" )
456
+ if ( lastCode === 95 ) this . raiseRecoverable ( this . pos , "Numeric separator must be exactly one underscore" )
457
+ if ( i === 0 ) this . raiseRecoverable ( this . pos , "Numeric separator is not allowed at the first of digits" )
458
+ lastCode = code
459
+ continue
460
+ }
461
+
445
462
if ( code >= 97 ) val = code - 97 + 10 // a
446
463
else if ( code >= 65 ) val = code - 65 + 10 // A
447
464
else if ( code >= 48 && code <= 57 ) val = code - 48 // 0-9
448
465
else val = Infinity
449
466
if ( val >= radix ) break
450
- ++ this . pos
467
+ lastCode = code
451
468
total = total * radix + val
452
469
}
470
+
471
+ if ( allowSeparators && lastCode === 95 ) this . raiseRecoverable ( this . pos - 1 , "Numeric separator is not allowed at the last of digits" )
453
472
if ( this . pos === start || len != null && this . pos - start !== len ) return null
454
473
455
474
return total
456
475
}
457
476
477
+ function stringToNumber ( str , isLegacyOctalNumericLiteral ) {
478
+ if ( isLegacyOctalNumericLiteral ) {
479
+ return parseInt ( str , 8 )
480
+ }
481
+
482
+ // `parseFloat(value)` stops parsing at the first numeric separator then returns a wrong value.
483
+ return parseFloat ( str . replace ( / _ / g, "" ) )
484
+ }
485
+
486
+ function stringToBigInt ( str ) {
487
+ if ( typeof BigInt !== "function" ) {
488
+ return null
489
+ }
490
+
491
+ // `BigInt(value)` throws syntax error if the string contains numeric separators.
492
+ return BigInt ( str . replace ( / _ / g, "" ) )
493
+ }
494
+
458
495
pp . readRadixNumber = function ( radix ) {
459
496
let start = this . pos
460
497
this . pos += 2 // 0x
461
498
let val = this . readInt ( radix )
462
499
if ( val == null ) this . raise ( this . start + 2 , "Expected number in radix " + radix )
463
500
if ( this . options . ecmaVersion >= 11 && this . input . charCodeAt ( this . pos ) === 110 ) {
464
- val = typeof BigInt !== "undefined" ? BigInt ( this . input . slice ( start , this . pos ) ) : null
501
+ val = stringToBigInt ( this . input . slice ( start , this . pos ) )
465
502
++ this . pos
466
503
} else if ( isIdentifierStart ( this . fullCharCodeAtPos ( ) ) ) this . raise ( this . pos , "Identifier directly after number" )
467
504
return this . finishToken ( tt . num , val )
@@ -471,13 +508,12 @@ pp.readRadixNumber = function(radix) {
471
508
472
509
pp . readNumber = function ( startsWithDot ) {
473
510
let start = this . pos
474
- if ( ! startsWithDot && this . readInt ( 10 ) === null ) this . raise ( start , "Invalid number" )
511
+ if ( ! startsWithDot && this . readInt ( 10 , undefined , true ) === null ) this . raise ( start , "Invalid number" )
475
512
let octal = this . pos - start >= 2 && this . input . charCodeAt ( start ) === 48
476
513
if ( octal && this . strict ) this . raise ( start , "Invalid number" )
477
514
let next = this . input . charCodeAt ( this . pos )
478
515
if ( ! octal && ! startsWithDot && this . options . ecmaVersion >= 11 && next === 110 ) {
479
- let str = this . input . slice ( start , this . pos )
480
- let val = typeof BigInt !== "undefined" ? BigInt ( str ) : null
516
+ let val = stringToBigInt ( this . input . slice ( start , this . pos ) )
481
517
++ this . pos
482
518
if ( isIdentifierStart ( this . fullCharCodeAtPos ( ) ) ) this . raise ( this . pos , "Identifier directly after number" )
483
519
return this . finishToken ( tt . num , val )
@@ -495,8 +531,7 @@ pp.readNumber = function(startsWithDot) {
495
531
}
496
532
if ( isIdentifierStart ( this . fullCharCodeAtPos ( ) ) ) this . raise ( this . pos , "Identifier directly after number" )
497
533
498
- let str = this . input . slice ( start , this . pos )
499
- let val = octal ? parseInt ( str , 8 ) : parseFloat ( str )
534
+ let val = stringToNumber ( this . input . slice ( start , this . pos ) , octal )
500
535
return this . finishToken ( tt . num , val )
501
536
}
502
537
0 commit comments