Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/lualib/Number.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { __TS__Match } from "./Match";

export function __TS__Number(this: void, value: unknown): number {
const valueType = type(value);
if (valueType === "number") {
Expand All @@ -11,6 +13,16 @@ export function __TS__Number(this: void, value: unknown): number {
const [stringWithoutSpaces] = string.gsub(value as string, "%s", "");
if (stringWithoutSpaces === "") return 0;

// Handle 0b/0B (binary) and 0o/0O (octal) literal prefixes
const [sign, prefix, digits] = __TS__Match(stringWithoutSpaces, "^(-?)0([bBoO])(.+)");
if (prefix !== undefined) {
const base = prefix === "b" || prefix === "B" ? 2 : 8;
const result = tonumber(digits, base);
if (result !== undefined) {
return sign === "-" ? -result : result;
}
}

return NaN;
} else if (valueType === "boolean") {
return value ? 1 : 0;
Expand Down
4 changes: 3 additions & 1 deletion src/lualib/ParseFloat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export function __TS__ParseFloat(this: void, numberString: string): number {
return infinityMatch[0] === "-" ? -Infinity : Infinity;
}

const number = tonumber(__TS__Match(numberString, "^%s*(-?%d+%.?%d*)")[0]);
// Try with scientific notation first, fall back to basic decimal
const [numberMatch] = __TS__Match(numberString, "^%s*(-?%d+%.?%d*[eE][+-]?%d+)");
const number = tonumber(numberMatch ?? __TS__Match(numberString, "^%s*(-?%d+%.?%d*)")[0]);
return number ?? NaN;
}
8 changes: 8 additions & 0 deletions src/lualib/ParseInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export function __TS__ParseInt(this: void, numberString: string, base?: number):
? "-" + numberString.substring(hexMatch.length)
: numberString.substring(hexMatch.length);
}
} else if (base === 16) {
// Strip 0x/0X prefix when radix is explicitly 16 (per ECMA-262 21.1.2.13 step 11)
const [hexMatch] = __TS__Match(numberString, "^%s*-?0[xX]");
if (hexMatch !== undefined) {
numberString = __TS__Match(hexMatch, "-")[0]
? "-" + numberString.substring(hexMatch.length)
: numberString.substring(hexMatch.length);
}
}

// Check if base is in bounds
Expand Down
16 changes: 16 additions & 0 deletions test/unit/builtins/numbers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const stringCases = ["-1", "0", "1", "1.5", "Infinity", "-Infinity"];
const restCases = [true, false, "", " ", "\t", "\n", "foo", {}];
const cases = [...numberCases, ...stringCases, ...restCases];

// Number() with ES6 numeric literal strings
test.each(["0o17", "0O17", "0b101", "0B101", "0x1A", "0X1a"])("Number(%p) parses literal prefix", value => {
util.testExpressionTemplate`Number(${value})`.expectToMatchJsResult();
});

describe("Number", () => {
test.each(cases)("constructor(%p)", value => {
util.testExpressionTemplate`Number(${value})`.expectToMatchJsResult();
Expand Down Expand Up @@ -143,6 +148,13 @@ test.each(["Infinity", "-Infinity", " -Infinity"])("parseFloat handles Infinit
util.testExpression`parseFloat("${numberString}")`.expectToMatchJsResult();
});

test.each(["1.5e2", "1e3", "-2.5E4", "1.5e-2", "1e+3", "1.5e2px"])(
"parseFloat handles scientific notation (%s)",
numberString => {
util.testExpression`parseFloat("${numberString}")`.expectToMatchJsResult();
}
);

test.each([
{ numberString: "36", base: 8 },
{ numberString: "-36", base: 8 },
Expand All @@ -157,6 +169,10 @@ test.each(["0x4A", "-0x42", "0X42", " 0x391", " -0x8F"])("parseInt detects h
util.testExpression`parseInt("${numberString}")`.expectToMatchJsResult();
});

test.each(["0xFF", "-0xFF", "0X1A", " 0xff"])("parseInt with 0x prefix and explicit base 16 (%s)", numberString => {
util.testExpression`parseInt("${numberString}", 16)`.expectToMatchJsResult();
});

test.each([1, 37, -100])("parseInt with invalid base (%p)", base => {
util.testExpression`parseInt("11111", ${base})`.expectToMatchJsResult();
});
Expand Down
Loading