Skip to content

Commit 028e3a1

Browse files
committed
fix Number(), parseFloat, parseInt not handling some string formats
1 parent 5176fcd commit 028e3a1

File tree

4 files changed

+39
-1
lines changed

4 files changed

+39
-1
lines changed

src/lualib/Number.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { __TS__Match } from "./Match";
2+
13
export function __TS__Number(this: void, value: unknown): number {
24
const valueType = type(value);
35
if (valueType === "number") {
@@ -11,6 +13,16 @@ export function __TS__Number(this: void, value: unknown): number {
1113
const [stringWithoutSpaces] = string.gsub(value as string, "%s", "");
1214
if (stringWithoutSpaces === "") return 0;
1315

16+
// Handle 0b/0B (binary) and 0o/0O (octal) literal prefixes
17+
const [sign, prefix, digits] = __TS__Match(stringWithoutSpaces, "^(-?)0([bBoO])(.+)");
18+
if (prefix !== undefined) {
19+
const base = prefix === "b" || prefix === "B" ? 2 : 8;
20+
const result = tonumber(digits, base);
21+
if (result !== undefined) {
22+
return sign === "-" ? -result : result;
23+
}
24+
}
25+
1426
return NaN;
1527
} else if (valueType === "boolean") {
1628
return value ? 1 : 0;

src/lualib/ParseFloat.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export function __TS__ParseFloat(this: void, numberString: string): number {
88
return infinityMatch[0] === "-" ? -Infinity : Infinity;
99
}
1010

11-
const number = tonumber(__TS__Match(numberString, "^%s*(-?%d+%.?%d*)")[0]);
11+
// Try with scientific notation first, fall back to basic decimal
12+
const [numberMatch] = __TS__Match(numberString, "^%s*(-?%d+%.?%d*[eE][+-]?%d+)");
13+
const number = tonumber(numberMatch ?? __TS__Match(numberString, "^%s*(-?%d+%.?%d*)")[0]);
1214
return number ?? NaN;
1315
}

src/lualib/ParseInt.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ export function __TS__ParseInt(this: void, numberString: string, base?: number):
1313
? "-" + numberString.substring(hexMatch.length)
1414
: numberString.substring(hexMatch.length);
1515
}
16+
} else if (base === 16) {
17+
// Strip 0x/0X prefix when radix is explicitly 16 (per ECMA-262 21.1.2.13 step 11)
18+
const [hexMatch] = __TS__Match(numberString, "^%s*-?0[xX]");
19+
if (hexMatch !== undefined) {
20+
numberString = __TS__Match(hexMatch, "-")[0]
21+
? "-" + numberString.substring(hexMatch.length)
22+
: numberString.substring(hexMatch.length);
23+
}
1624
}
1725

1826
// Check if base is in bounds

test/unit/builtins/numbers.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ const stringCases = ["-1", "0", "1", "1.5", "Infinity", "-Infinity"];
3434
const restCases = [true, false, "", " ", "\t", "\n", "foo", {}];
3535
const cases = [...numberCases, ...stringCases, ...restCases];
3636

37+
// Number() with ES6 numeric literal strings
38+
test.each(["0o17", "0O17", "0b101", "0B101", "0x1A", "0X1a"])("Number(%p) parses literal prefix", value => {
39+
util.testExpressionTemplate`Number(${value})`.expectToMatchJsResult();
40+
});
41+
3742
describe("Number", () => {
3843
test.each(cases)("constructor(%p)", value => {
3944
util.testExpressionTemplate`Number(${value})`.expectToMatchJsResult();
@@ -143,6 +148,13 @@ test.each(["Infinity", "-Infinity", " -Infinity"])("parseFloat handles Infinit
143148
util.testExpression`parseFloat("${numberString}")`.expectToMatchJsResult();
144149
});
145150

151+
test.each(["1.5e2", "1e3", "-2.5E4", "1.5e-2", "1e+3", "1.5e2px"])(
152+
"parseFloat handles scientific notation (%s)",
153+
numberString => {
154+
util.testExpression`parseFloat("${numberString}")`.expectToMatchJsResult();
155+
}
156+
);
157+
146158
test.each([
147159
{ numberString: "36", base: 8 },
148160
{ numberString: "-36", base: 8 },
@@ -157,6 +169,10 @@ test.each(["0x4A", "-0x42", "0X42", " 0x391", " -0x8F"])("parseInt detects h
157169
util.testExpression`parseInt("${numberString}")`.expectToMatchJsResult();
158170
});
159171

172+
test.each(["0xFF", "-0xFF", "0X1A", " 0xff"])("parseInt with 0x prefix and explicit base 16 (%s)", numberString => {
173+
util.testExpression`parseInt("${numberString}", 16)`.expectToMatchJsResult();
174+
});
175+
160176
test.each([1, 37, -100])("parseInt with invalid base (%p)", base => {
161177
util.testExpression`parseInt("11111", ${base})`.expectToMatchJsResult();
162178
});

0 commit comments

Comments
 (0)