Skip to content

Commit 9733df1

Browse files
committed
[MERGE #6143 @boingoing] Implement Numeric Separator
Merge pull request #6143 from boingoing:numericseparator Implement Numeric Separator Add support for numeric separator characters in numeric literals. These are just syntactic sugar which improve readability of numeric constants in source. They desugar completely out of the numbers when parsed. Numeric digits in the constant may have a single `'_'` character between them. The constant may not begin or end with a `'_'` character and there may not be multiple numeric separators in a row. All numeric constants are supported. Includes decimal, hex, octal and binary numeric constants. Legacy octal integer constants do not support numeric separators. Numeric values parsed from string functions (like ToNumber or parseInt) also do not support numeric separators. ```javascript 1234 === 1_2_3_4; // true 12.34e56 === 1_2.3_4e5_6; // true 0xff === 0xf_f; // true 0o17 === 0o1_7; // true 0b11 === 0b1_1; // true ``` Numeric Separator proposal is in stage 3 and implemented by JSC and V8. See proposal: https://github.com/tc39/proposal-numeric-separator Fixes: #6060
2 parents 1481d95 + 71adb7d commit 9733df1

File tree

10 files changed

+405
-33
lines changed

10 files changed

+405
-33
lines changed

bin/NativeTests/BigUIntTest.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace Js
2626
}
2727

2828
template <typename EncodedChar>
29-
double Js::NumberUtilities::StrToDbl(const EncodedChar *, const EncodedChar **, LikelyNumberType& , bool)
29+
double Js::NumberUtilities::StrToDbl(const EncodedChar *, const EncodedChar **, LikelyNumberType& , bool, bool)
3030
{
3131
Assert(false);
3232
return 0.0;// don't care

lib/Common/Common/NumberUtilities.cpp

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -405,17 +405,29 @@ using namespace Js;
405405
}
406406

407407
template<typename EncodedChar>
408-
double NumberUtilities::DblFromHex(const EncodedChar *psz, const EncodedChar **ppchLim)
408+
double NumberUtilities::DblFromHex(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled)
409409
{
410410
double dbl;
411411
uint uT;
412412
byte bExtra;
413413
int cbit;
414+
const EncodedChar* pszSave = psz;
414415

415416
// Skip leading zeros.
417+
LSkipZeroes:
416418
while (*psz == '0')
417419
psz++;
418420

421+
// If we stopped fast-scanning zeroes above because we ran into a numeric separator, skip that separator character if
422+
// the previous character was a '0' (meaning the separator was not the first character in the literal) and the following
423+
// character is a hex digit.
424+
int unused;
425+
if (*psz == '_' && isNumericSeparatorEnabled && pszSave < psz && psz[-1] == '0' && FHexDigit(psz[1], &unused))
426+
{
427+
psz++;
428+
goto LSkipZeroes;
429+
}
430+
419431
dbl = 0;
420432
Assert(Js::NumberUtilities::LuHiDbl(dbl) == 0);
421433
Assert(Js::NumberUtilities::LuLoDbl(dbl) == 0);
@@ -460,9 +472,17 @@ using namespace Js;
460472
if ((uT = (*psz - '0')) > 9)
461473
{
462474
if ((uT -= 'A' - '0') <= 5 || (uT -= 'a' - 'A') <= 5)
475+
{
463476
uT += 10;
477+
}
478+
else if (*psz == '_' && isNumericSeparatorEnabled && pszSave < psz && psz[-1] != '_' && FHexDigit(psz[1], &unused))
479+
{
480+
continue;
481+
}
464482
else
483+
{
465484
break;
485+
}
466486
}
467487

468488
if (cbit <= 17)
@@ -511,24 +531,40 @@ using namespace Js;
511531
}
512532

513533
template <typename EncodedChar>
514-
double NumberUtilities::DblFromBinary(const EncodedChar *psz, const EncodedChar **ppchLim)
534+
double NumberUtilities::DblFromBinary(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled)
515535
{
516536
double dbl = 0;
517537
Assert(Js::NumberUtilities::LuHiDbl(dbl) == 0);
518538
Assert(Js::NumberUtilities::LuLoDbl(dbl) == 0);
519539
uint uT;
520540
byte bExtra = 0;
521541
int cbit = 0;
542+
const EncodedChar* pszSave = psz;
543+
522544
// Skip leading zeros.
545+
LSkipZeroes:
523546
while (*psz == '0')
524547
psz++;
548+
525549
// Get the first digit.
526550
uT = *psz - '0';
527551
if (uT > 1)
528552
{
553+
// We can skip over this numeric separator character if:
554+
// - numeric separators are enabled
555+
// - we've walked past at least one zero character (ie: this isn't the first character in psz)
556+
// - the previous character was a zero
557+
// - the following character is a valid binary digit
558+
if (*psz == '_' && isNumericSeparatorEnabled && pszSave < psz && psz[-1] == '0' && static_cast<uint>(psz[1] - '0') <= 1)
559+
{
560+
psz++;
561+
goto LSkipZeroes;
562+
}
563+
529564
*ppchLim = psz;
530565
return dbl;
531566
}
567+
532568
//Now that leading zeros are skipped first bit should be one so lets
533569
//go ahead and count it and increment psz
534570
cbit = 1;
@@ -544,12 +580,14 @@ using namespace Js;
544580
// Why 52? 52 is the last explicit bit and 1 bit away from 53 (max bits of precision
545581
// for double precision floating point)
546582
const uint leftShiftValue = 52;
547-
for (; (uT = (*psz - '0')) <= 1; psz++)
583+
584+
LGetBinaryDigit:
585+
uT = *psz - '0';
586+
if (uT <= 1)
548587
{
549588
if (cbit <= rightShiftValue)
550589
{
551590
Js::NumberUtilities::LuHiDbl(dbl) |= (uint32)uT << (rightShiftValue - cbit);
552-
553591
}
554592
else if (cbit <= leftShiftValue)
555593
{
@@ -565,6 +603,16 @@ using namespace Js;
565603
bExtra |= 1;
566604
}
567605
cbit++;
606+
psz++;
607+
goto LGetBinaryDigit;
608+
}
609+
else if (*psz == '_')
610+
{
611+
if (isNumericSeparatorEnabled && cbit > 0 && psz[-1] != '_' && static_cast<uint>(psz[1] - '0') <= 1)
612+
{
613+
psz++;
614+
goto LGetBinaryDigit;
615+
}
568616
}
569617
// Set the lim.
570618
*ppchLim = psz;
@@ -593,17 +641,25 @@ using namespace Js;
593641
}
594642

595643
template <typename EncodedChar>
596-
double NumberUtilities::DblFromOctal(const EncodedChar *psz, const EncodedChar **ppchLim)
644+
double NumberUtilities::DblFromOctal(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled)
597645
{
598646
double dbl;
599647
uint uT;
600648
byte bExtra;
601649
int cbit;
650+
const EncodedChar* pszSave = psz;
602651

603652
// Skip leading zeros.
653+
LSkipZeroes:
604654
while (*psz == '0')
605655
psz++;
606656

657+
if (*psz == '_' && isNumericSeparatorEnabled && psz > pszSave && psz[-1] == '0' && static_cast<uint>(psz[1] - '0') <= 7)
658+
{
659+
psz++;
660+
goto LSkipZeroes;
661+
}
662+
607663
dbl = 0;
608664
Assert(Js::NumberUtilities::LuHiDbl(dbl) == 0);
609665
Assert(Js::NumberUtilities::LuLoDbl(dbl) == 0);
@@ -634,7 +690,9 @@ using namespace Js;
634690
}
635691
bExtra = 0;
636692

637-
for (; (uT = (*psz - '0')) <= 7; psz++)
693+
LGetOctalDigit:
694+
uT = *psz - '0';
695+
if (uT <= 7)
638696
{
639697
if (cbit <= 18)
640698
Js::NumberUtilities::LuHiDbl(dbl) |= (uint32)uT << (18 - cbit);
@@ -653,6 +711,16 @@ using namespace Js;
653711
else if (0 != uT)
654712
bExtra |= 1;
655713
cbit += 3;
714+
psz++;
715+
goto LGetOctalDigit;
716+
}
717+
else if (*psz == '_')
718+
{
719+
if (isNumericSeparatorEnabled && cbit > 0 && psz[-1] != '_' && static_cast<uint>(psz[1] - '0') <= 7)
720+
{
721+
psz++;
722+
goto LGetOctalDigit;
723+
}
656724
}
657725

658726
// Set the lim.
@@ -692,9 +760,9 @@ using namespace Js;
692760

693761
template double NumberUtilities::StrToDbl<char16>(const char16 * psz, const char16 **ppchLim, Js::ScriptContext *const scriptContext);
694762
template double NumberUtilities::StrToDbl<utf8char_t>(const utf8char_t * psz, const utf8char_t **ppchLim, Js::ScriptContext *const scriptContext);
695-
template double NumberUtilities::DblFromHex<char16>(const char16 *psz, const char16 **ppchLim);
696-
template double NumberUtilities::DblFromHex<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim);
697-
template double NumberUtilities::DblFromBinary<char16>(const char16 *psz, const char16 **ppchLim);
698-
template double NumberUtilities::DblFromBinary<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim);
699-
template double NumberUtilities::DblFromOctal<char16>(const char16 *psz, const char16 **ppchLim);
700-
template double NumberUtilities::DblFromOctal<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim);
763+
template double NumberUtilities::DblFromHex<char16>(const char16 *psz, const char16 **ppchLim, bool isNumericSeparatorEnabled);
764+
template double NumberUtilities::DblFromHex<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);
765+
template double NumberUtilities::DblFromBinary<char16>(const char16 *psz, const char16 **ppchLim, bool isNumericSeparatorEnabled);
766+
template double NumberUtilities::DblFromBinary<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);
767+
template double NumberUtilities::DblFromOctal<char16>(const char16 *psz, const char16 **ppchLim, bool isNumericSeparatorEnabled);
768+
template double NumberUtilities::DblFromOctal<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);

lib/Common/Common/NumberUtilities.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ namespace Js
227227

228228
// Implemented in lib\parser\common. Should move to lib\common
229229
template<typename EncodedChar>
230-
static double StrToDbl(const EncodedChar *psz, const EncodedChar **ppchLim, LikelyNumberType& likelyType, bool isESBigIntEnabled = false);
230+
static double StrToDbl(const EncodedChar *psz, const EncodedChar **ppchLim, LikelyNumberType& likelyType, bool isESBigIntEnabled = false, bool isNumericSeparatorEnabled = false);
231231

232232
static BOOL FDblToStr(double dbl, __out_ecount(nDstBufSize) char16 *psz, int nDstBufSize);
233233
static int FDblToStr(double dbl, NumberUtilities::FormatType ft, int nDigits, __out_ecount(cchDst) char16 *pchDst, int cchDst);
@@ -250,11 +250,11 @@ namespace Js
250250
static BOOL FDblIsInt32(double dbl, int32 *plw);
251251

252252
template<typename EncodedChar>
253-
static double DblFromHex(const EncodedChar *psz, const EncodedChar **ppchLim);
253+
static double DblFromHex(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled = false);
254254
template <typename EncodedChar>
255-
static double DblFromBinary(const EncodedChar *psz, const EncodedChar **ppchLim);
255+
static double DblFromBinary(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled = false);
256256
template<typename EncodedChar>
257-
static double DblFromOctal(const EncodedChar *psz, const EncodedChar **ppchLim);
257+
static double DblFromOctal(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled = false);
258258
template<typename EncodedChar>
259259
static double StrToDbl(const EncodedChar *psz, const EncodedChar **ppchLim, Js::ScriptContext *const scriptContext);
260260

0 commit comments

Comments
 (0)