Skip to content

Commit fc98c63

Browse files
committed
Implement numeric separators for octal integer literals
1 parent 257c57e commit fc98c63

File tree

4 files changed

+78
-6
lines changed

4 files changed

+78
-6
lines changed

lib/Common/Common/NumberUtilities.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,17 +642,25 @@ using namespace Js;
642642
}
643643

644644
template <typename EncodedChar>
645-
double NumberUtilities::DblFromOctal(const EncodedChar *psz, const EncodedChar **ppchLim)
645+
double NumberUtilities::DblFromOctal(const EncodedChar *psz, const EncodedChar **ppchLim, bool isNumericSeparatorEnabled)
646646
{
647647
double dbl;
648648
uint uT;
649649
byte bExtra;
650650
int cbit;
651+
const EncodedChar* pszSave = psz;
651652

652653
// Skip leading zeros.
654+
LSkipZeroes:
653655
while (*psz == '0')
654656
psz++;
655657

658+
if (*psz == '_' && isNumericSeparatorEnabled && psz > pszSave && psz[-1] == '0' && static_cast<uint>(psz[1] - '0') <= 7)
659+
{
660+
psz++;
661+
goto LSkipZeroes;
662+
}
663+
656664
dbl = 0;
657665
Assert(Js::NumberUtilities::LuHiDbl(dbl) == 0);
658666
Assert(Js::NumberUtilities::LuLoDbl(dbl) == 0);
@@ -683,7 +691,9 @@ using namespace Js;
683691
}
684692
bExtra = 0;
685693

686-
for (; (uT = (*psz - '0')) <= 7; psz++)
694+
LGetOctalDigit:
695+
uT = *psz - '0';
696+
if (uT <= 7)
687697
{
688698
if (cbit <= 18)
689699
Js::NumberUtilities::LuHiDbl(dbl) |= (uint32)uT << (18 - cbit);
@@ -702,6 +712,16 @@ using namespace Js;
702712
else if (0 != uT)
703713
bExtra |= 1;
704714
cbit += 3;
715+
psz++;
716+
goto LGetOctalDigit;
717+
}
718+
else if (*psz == '_')
719+
{
720+
if (isNumericSeparatorEnabled && cbit > 0 && psz[-1] != '_' && static_cast<uint>(psz[1] - '0') <= 7)
721+
{
722+
psz++;
723+
goto LGetOctalDigit;
724+
}
705725
}
706726

707727
// Set the lim.
@@ -745,5 +765,5 @@ using namespace Js;
745765
template double NumberUtilities::DblFromHex<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);
746766
template double NumberUtilities::DblFromBinary<char16>(const char16 *psz, const char16 **ppchLim, bool isNumericSeparatorEnabled);
747767
template double NumberUtilities::DblFromBinary<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);
748-
template double NumberUtilities::DblFromOctal<char16>(const char16 *psz, const char16 **ppchLim);
749-
template double NumberUtilities::DblFromOctal<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim);
768+
template double NumberUtilities::DblFromOctal<char16>(const char16 *psz, const char16 **ppchLim, bool isNumericSeparatorEnabled);
769+
template double NumberUtilities::DblFromOctal<utf8char_t>(const utf8char_t *psz, const utf8char_t **ppchLim, bool isNumericSeparatorEnabled);

lib/Common/Common/NumberUtilities.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ namespace Js
254254
template <typename EncodedChar>
255255
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

lib/Parser/Scan.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ typename Scanner<EncodingPolicy>::EncodedCharPtr Scanner<EncodingPolicy>::FScanN
641641
case 'o':
642642
case 'O':
643643
// Octal
644-
*pdbl = Js::NumberUtilities::DblFromOctal(p + 2, &pchT);
644+
*pdbl = Js::NumberUtilities::DblFromOctal(p + 2, &pchT, m_scriptContext->GetConfig()->IsESNumericSeparatorEnabled());
645645
baseSpecifierCheck();
646646
goto LIdCheck;
647647

test/Number/NumericSeparator.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,58 @@ var tests = [
170170
assert.areEqual(0, parseFloat('0x1_fe'), "0 === parseFloat('0x1_fe')");
171171
}
172172
},
173+
{
174+
name: "Octal literal support",
175+
body: function () {
176+
assert.areEqual(0o00, 0o0_0, "0o00 === 0o0_0");
177+
assert.areEqual(0o11, 0o1_1, "0o11 === 0o1_1");
178+
assert.areEqual(0o10, 0o1_0, "0o10 === 0o1_0");
179+
assert.areEqual(0o01, 0o0_1, "0o01 === 0o0_1");
180+
assert.areEqual(0o0001, 0o000_1, "0o0001 === 0o000_1");
181+
assert.areEqual(0o0000, 0o000_0, "0o0000 === 0o000_0");
182+
assert.areEqual(0o000011110000, 0o0000_1111_0000, "0o000011110000 === 0o0000_1111_0000");
183+
assert.areEqual(0o000011110000, 0o0_0_0_0_1_111_00_00, "0o000011110000 === 0o0_0_0_0_1_111_00_00");
184+
}
185+
},
186+
{
187+
name: "Octal literal bad syntax",
188+
body: function () {
189+
assert.throws(()=>eval('0o_'), SyntaxError, "'_' cannot immediately follow 0o");
190+
assert.throws(()=>eval('0o__'), SyntaxError, "'_' cannot immediately follow 0o");
191+
assert.throws(()=>eval('0o_1'), SyntaxError, "Octal literal may not begin with numeric separator");
192+
assert.throws(()=>eval('0o_0'), SyntaxError, "Octal literal may not begin with numeric separator");
193+
assert.throws(()=>eval('0o__1'), SyntaxError, "Octal literal may not begin with numeric separator");
194+
assert.throws(()=>eval('0o__0'), SyntaxError, "Octal literal may not begin with numeric separator");
195+
assert.throws(()=>eval('0o1_'), SyntaxError, "Octal literal may not end with numeric separator");
196+
assert.throws(()=>eval('0o0_'), SyntaxError, "Octal literal may not end with numeric separator");
197+
assert.throws(()=>eval('0o1__'), SyntaxError, "Octal literal may not end with numeric separator");
198+
assert.throws(()=>eval('0o0__'), SyntaxError, "Octal literal may not end with numeric separator");
199+
assert.throws(()=>eval('0o1__1'), SyntaxError, "Multiple numeric separator characters may not follow each other");
200+
assert.throws(()=>eval('0o0__0'), SyntaxError, "Multiple numeric separator characters may not follow each other");
201+
assert.throws(()=>eval('0o000__1'), SyntaxError, "Multiple numeric separator characters may not follow each other");
202+
assert.throws(()=>eval('0o000_a'), SyntaxError, "After initial zeroes, a numeric separator followed by an invalid character");
203+
}
204+
},
205+
{
206+
name: "Strings parsed as number do not support numeric separators for octal literals",
207+
body: function () {
208+
assert.areEqual(NaN, Number('0o0_1'), "NaN === Number('0o0_1')");
209+
assert.areEqual(NaN, Number('0o1_0'), "NaN === Number('0o1_0')");
210+
assert.areEqual(NaN, Number('0o0_0'), "NaN === Number('0o0_0')");
211+
assert.areEqual(NaN, Number('0o1_1'), "NaN === Number('0o1_1')");
212+
213+
assert.areEqual(0, parseInt('0b1_0'), "0 === parseInt('0b1_0')");
214+
215+
assert.areEqual(0, parseFloat('0b1_0'), "0 === parseFloat('0b1_0')");
216+
}
217+
},
218+
{
219+
name: "Legacy octal numeric literals do not support numeric separators",
220+
body: function () {
221+
assert.throws(()=>eval('0_1'), SyntaxError, "'_' cannot immediately follow 0 in legacy octal numeric literal");
222+
assert.throws(()=>eval('07_7'), SyntaxError, "Legacy octal numeric literals do not support numeric separator");
223+
}
224+
},
173225
];
174226

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

0 commit comments

Comments
 (0)