Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export enum LuaLibFeature {
ObjectKeys = "ObjectKeys",
ObjectRest = "ObjectRest",
ObjectValues = "ObjectValues",
ParseFloat = "ParseFloat",
ParseInt = "ParseInt",
Set = "Set",
WeakMap = "WeakMap",
WeakSet = "WeakSet",
Expand Down
11 changes: 11 additions & 0 deletions src/lualib/ParseFloat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function __TS__ParseFloat(this: void, numberString: string): number {
// Check if string is infinity
const infinityMatch = string.match(numberString, "^%s*(-?Infinity)");
if (infinityMatch) {
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
return infinityMatch[0] === "-" ? -Infinity : Infinity;
}

const number = tonumber(string.match(numberString, "^%s*(-?%d+%.?%d*)"));
return number ?? NaN;
}
41 changes: 41 additions & 0 deletions src/lualib/ParseInt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const __TS__parseInt_base_pattern = "0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTvVwWxXyYzZ";

function __TS__ParseInt(this: void, numberString: string, base?: number): number {
// Check which base to use if none specified
if (base === undefined) {
base = 10;
const hexMatch = string.match(numberString, "^%s*-?0[xX]");
if (hexMatch) {
base = 16;
numberString = string.match(hexMatch, "-")
? "-" + numberString.substr(hexMatch.length)
: numberString.substr(hexMatch.length);
}
}

// Check if base is in bounds
if (base < 2 || base > 36) {
return NaN;
}

// Calculate string match pattern to use
const allowedDigits =
base <= 10
? __TS__parseInt_base_pattern.substring(0, base)
: __TS__parseInt_base_pattern.substr(0, 10 + 2 * (base - 10));
const pattern = `^%s*(-?[${allowedDigits}]*)`;

// Try to parse with Lua tonumber
const number = tonumber(string.match(numberString, pattern), base);

if (number === undefined) {
return NaN;
}

// Lua uses a different floor convention for negative numbers than JS
if (number >= 0) {
return math.floor(number);
} else {
return math.ceil(number);
}
}
3 changes: 3 additions & 0 deletions src/lualib/declarations/math.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ declare namespace math {
function atan(y: number, x?: number): number;

function atan2(y: number, x: number): number;

function ceil(x: number): number;
function floor(x: number): number;
}
1 change: 1 addition & 0 deletions src/lualib/declarations/string.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ declare namespace string {
): [string, number];
function sub(s: string, i: number, j?: number): string;
function format(formatstring: string, ...args: any[]): string;
function match(string: string, pattern: string): string;
}
4 changes: 4 additions & 0 deletions src/transformation/builtins/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@ export function transformGlobalCall(
node,
...numberParameters
);
case "parseFloat":
return transformLuaLibFunction(context, LuaLibFeature.ParseFloat, node, ...parameters);
case "parseInt":
return transformLuaLibFunction(context, LuaLibFeature.ParseInt, node, ...parameters);
}
}
54 changes: 54 additions & 0 deletions test/unit/builtins/numbers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,57 @@ test("number intersected method", () => {
test("numbers overflowing the float limit become math.huge", () => {
util.testExpression`1e309`.expectToMatchJsResult();
});

describe.each(["parseInt", "parseFloat"])("parse numbers with %s", parseFunction => {
const numberStrings = ["3", "3.0", "9", "42", "239810241", "-20391", "3.1415", "2.7182", "-34910.3"];

test.each(numberStrings)("parses (%s)", numberString => {
util.testExpression`${parseFunction}("${numberString}")`.expectToMatchJsResult();
});

test("empty string", () => {
util.testExpression`${parseFunction}("")`.expectToMatchJsResult();
});

test("invalid string", () => {
util.testExpression`${parseFunction}("bla")`.expectToMatchJsResult();
});

test.each(["1px", "2300m", "3,4", "452adkfl"])("trailing text (%s)", numberString => {
util.testExpression`${parseFunction}("${numberString}")`.expectToMatchJsResult();
});

test.each([" 3", " 4", " -231", " 1px"])("leading whitespace (%s)", numberString => {
util.testExpression`${parseFunction}("${numberString}")`.expectToMatchJsResult();
});
});

test.each(["Infinity", "-Infinity", " -Infinity"])("parseFloat handles Infinity", numberString => {
util.testExpression`parseFloat("${numberString}")`.expectToMatchJsResult();
});

test.each([
{ numberString: "36", base: 8 },
{ numberString: "-36", base: 8 },
{ numberString: "100010101101", base: 2 },
{ numberString: "-100010101101", base: 2 },
{ numberString: "3F", base: 16 },
])("parseInt with base (%p)", ({ numberString, base }) => {
util.testExpression`parseInt("${numberString}", ${base})`.expectToMatchJsResult();
});

test.each(["0x4A", "-0x42", "0X42", " 0x391", " -0x8F"])("parseInt detects hexadecimal", numberString => {
util.testExpression`parseInt("${numberString}")`.expectToMatchJsResult();
});

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

test.each([
{ numberString: "36px", base: 8 },
{ numberString: "10001010110231", base: 2 },
{ numberString: "3Fcolor", base: 16 },
])("parseInt with base and trailing text (%p)", ({ numberString, base }) => {
util.testExpression`parseInt("${numberString}", ${base})`.expectToMatchJsResult();
});