@@ -438,30 +438,67 @@ pp.readRegexp = function() {
438438// were read, the integer value otherwise. When `len` is given, this
439439// will return `null` unless the integer has exactly `len` digits.
440440
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 ) {
444452 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+
445462 if ( code >= 97 ) val = code - 97 + 10 // a
446463 else if ( code >= 65 ) val = code - 65 + 10 // A
447464 else if ( code >= 48 && code <= 57 ) val = code - 48 // 0-9
448465 else val = Infinity
449466 if ( val >= radix ) break
450- ++ this . pos
467+ lastCode = code
451468 total = total * radix + val
452469 }
470+
471+ if ( allowSeparators && lastCode === 95 ) this . raiseRecoverable ( this . pos - 1 , "Numeric separator is not allowed at the last of digits" )
453472 if ( this . pos === start || len != null && this . pos - start !== len ) return null
454473
455474 return total
456475}
457476
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+
458495pp . readRadixNumber = function ( radix ) {
459496 let start = this . pos
460497 this . pos += 2 // 0x
461498 let val = this . readInt ( radix )
462499 if ( val == null ) this . raise ( this . start + 2 , "Expected number in radix " + radix )
463500 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 ) )
465502 ++ this . pos
466503 } else if ( isIdentifierStart ( this . fullCharCodeAtPos ( ) ) ) this . raise ( this . pos , "Identifier directly after number" )
467504 return this . finishToken ( tt . num , val )
@@ -471,13 +508,12 @@ pp.readRadixNumber = function(radix) {
471508
472509pp . readNumber = function ( startsWithDot ) {
473510 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" )
475512 let octal = this . pos - start >= 2 && this . input . charCodeAt ( start ) === 48
476513 if ( octal && this . strict ) this . raise ( start , "Invalid number" )
477514 let next = this . input . charCodeAt ( this . pos )
478515 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 ) )
481517 ++ this . pos
482518 if ( isIdentifierStart ( this . fullCharCodeAtPos ( ) ) ) this . raise ( this . pos , "Identifier directly after number" )
483519 return this . finishToken ( tt . num , val )
@@ -495,8 +531,7 @@ pp.readNumber = function(startsWithDot) {
495531 }
496532 if ( isIdentifierStart ( this . fullCharCodeAtPos ( ) ) ) this . raise ( this . pos , "Identifier directly after number" )
497533
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 )
500535 return this . finishToken ( tt . num , val )
501536}
502537
0 commit comments