Skip to content

Commit 257c57e

Browse files
committed
Implement numeric separators for hex integer literals
1 parent d4fec4e commit 257c57e

File tree

4 files changed

+69
-7
lines changed

4 files changed

+69
-7
lines changed

lib/Common/Common/NumberUtilities.cpp

Lines changed: 23 additions & 3 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)
@@ -721,8 +741,8 @@ using namespace Js;
721741

722742
template double NumberUtilities::StrToDbl<char16>(const char16 * psz, const char16 **ppchLim, Js::ScriptContext *const scriptContext);
723743
template double NumberUtilities::StrToDbl<utf8char_t>(const utf8char_t * psz, const utf8char_t **ppchLim, Js::ScriptContext *const scriptContext);
724-
template double NumberUtilities::DblFromHex<char16>(const char16 *psz, const char16 **ppchLim);
725-
template double NumberUtilities::DblFromHex<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim);
744+
template double NumberUtilities::DblFromHex<char16>(const char16 *psz, const char16 **ppchLim, bool isNumericSeparatorEnabled);
745+
template double NumberUtilities::DblFromHex<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);
726746
template double NumberUtilities::DblFromBinary<char16>(const char16 *psz, const char16 **ppchLim, bool isNumericSeparatorEnabled);
727747
template double NumberUtilities::DblFromBinary<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);
728748
template double NumberUtilities::DblFromOctal<char16>(const char16 *psz, const char16 **ppchLim);

lib/Common/Common/NumberUtilities.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ 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>
255255
static double DblFromBinary(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled = false);
256256
template<typename EncodedChar>

lib/Parser/Scan.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ typename Scanner<EncodingPolicy>::EncodedCharPtr Scanner<EncodingPolicy>::FScanN
635635
case 'x':
636636
case 'X':
637637
// Hex
638-
*pdbl = Js::NumberUtilities::DblFromHex(p + 2, &pchT);
638+
*pdbl = Js::NumberUtilities::DblFromHex(p + 2, &pchT, m_scriptContext->GetConfig()->IsESNumericSeparatorEnabled());
639639
baseSpecifierCheck();
640640
goto LIdCheck;
641641
case 'o':

test/Number/NumericSeparator.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
77

88
var tests = [
99
{
10-
name: "Basic decimal literal support",
10+
name: "Decimal literal support",
1111
body: function () {
1212
assert.areEqual(1234, 1_234, "1234 === 1_234");
1313
assert.areEqual(1234, 1_2_3_4, "1234 === 1_2_3_4");
@@ -85,7 +85,7 @@ var tests = [
8585
}
8686
},
8787
{
88-
name: "Basic binary literal support",
88+
name: "Binary literal support",
8989
body: function () {
9090
assert.areEqual(0b00, 0b0_0, "0b00 === 0b0_0");
9191
assert.areEqual(0b11, 0b1_1, "0b11 === 0b1_1");
@@ -94,6 +94,7 @@ var tests = [
9494
assert.areEqual(0b0001, 0b000_1, "0b0001 === 0b000_1");
9595
assert.areEqual(0b0000, 0b000_0, "0b0000 === 0b000_0");
9696
assert.areEqual(0b000011110000, 0b0000_1111_0000, "0b000011110000 === 0b0000_1111_0000");
97+
assert.areEqual(0b000011110000, 0b0_0_0_0_1_111_00_00, "0b000011110000 === 0b0_0_0_0_1_111_00_00");
9798
}
9899
},
99100
{
@@ -128,6 +129,47 @@ var tests = [
128129
assert.areEqual(0, parseFloat('0b1_0'), "0 === parseFloat('0b1_0')");
129130
}
130131
},
132+
{
133+
name: "Hex numeric literal support",
134+
body: function () {
135+
assert.areEqual(0x00, 0x0_0, "0x00 === 0x0_0");
136+
assert.areEqual(0x1f, 0x1_f, "0x1f === 0x1_f");
137+
assert.areEqual(0x000000Ae1, 0x00_0_000_A_e1, "0x000000Ae1 === 0x00_0_000_A_e1");
138+
assert.areEqual(0xaabbccdd, 0xaa_bb_cc_dd, "0xaabbccdd === 0xaa_bb_cc_dd");
139+
}
140+
},
141+
{
142+
name: "Hex numeric literal bad syntax",
143+
body: function () {
144+
assert.throws(()=>eval('0x_'), SyntaxError, "'_' cannot immediately follow 0x");
145+
assert.throws(()=>eval('0x__'), SyntaxError, "'_' cannot immediately follow 0x");
146+
assert.throws(()=>eval('0x_1'), SyntaxError, "Hex literal may not begin with numeric separator");
147+
assert.throws(()=>eval('0x_0'), SyntaxError, "Hex literal may not begin with numeric separator");
148+
assert.throws(()=>eval('0x__1'), SyntaxError, "Hex literal may not begin with numeric separator");
149+
assert.throws(()=>eval('0x__0'), SyntaxError, "Hex literal may not begin with numeric separator");
150+
assert.throws(()=>eval('0x1_'), SyntaxError, "Hex literal may not end with numeric separator");
151+
assert.throws(()=>eval('0x0_'), SyntaxError, "Hex literal may not end with numeric separator");
152+
assert.throws(()=>eval('0x1__'), SyntaxError, "Hex literal may not end with numeric separator");
153+
assert.throws(()=>eval('0x0__'), SyntaxError, "Hex literal may not end with numeric separator");
154+
assert.throws(()=>eval('0x1__1'), SyntaxError, "Multiple numeric separator characters may not follow each other");
155+
assert.throws(()=>eval('0x0__0'), SyntaxError, "Multiple numeric separator characters may not follow each other");
156+
assert.throws(()=>eval('0x000__1'), SyntaxError, "Multiple numeric separator characters may not follow each other");
157+
assert.throws(()=>eval('0x000_q'), SyntaxError, "After initial zeroes, a numeric separator followed by an invalid character");
158+
}
159+
},
160+
{
161+
name: "Strings parsed as number do not support numeric separators for hex literals",
162+
body: function () {
163+
assert.areEqual(NaN, Number('0x0_ff'), "NaN === Number('0x0_ff')");
164+
assert.areEqual(NaN, Number('0xF_F'), "NaN === Number('0xF_F')");
165+
166+
assert.areEqual(0, parseInt('0x0_ff'), "0 === parseInt('0x0_ff')");
167+
assert.areEqual(15, parseInt('0xf_00f'), "15 === parseInt('0xf_00f')");
168+
169+
assert.areEqual(0, parseFloat('0x0_ff'), "0 === parseFloat('0x0_ff')");
170+
assert.areEqual(0, parseFloat('0x1_fe'), "0 === parseFloat('0x1_fe')");
171+
}
172+
},
131173
];
132174

133175
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

0 commit comments

Comments
 (0)