Skip to content

Commit 7b6580b

Browse files
authored
Implement parseInt and parseFloat (#920)
* Alias parseInt parseFloat to tonumber * Implemented ParseFloat and ParseInt as lualib functions * PR comments
1 parent 849f630 commit 7b6580b

File tree

7 files changed

+116
-0
lines changed

7 files changed

+116
-0
lines changed

src/LuaLib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export enum LuaLibFeature {
4949
ObjectKeys = "ObjectKeys",
5050
ObjectRest = "ObjectRest",
5151
ObjectValues = "ObjectValues",
52+
ParseFloat = "ParseFloat",
53+
ParseInt = "ParseInt",
5254
Set = "Set",
5355
WeakMap = "WeakMap",
5456
WeakSet = "WeakSet",

src/lualib/ParseFloat.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function __TS__ParseFloat(this: void, numberString: string): number {
2+
// Check if string is infinity
3+
const infinityMatch = string.match(numberString, "^%s*(-?Infinity)");
4+
if (infinityMatch) {
5+
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
6+
return infinityMatch[0] === "-" ? -Infinity : Infinity;
7+
}
8+
9+
const number = tonumber(string.match(numberString, "^%s*(-?%d+%.?%d*)"));
10+
return number ?? NaN;
11+
}

src/lualib/ParseInt.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const __TS__parseInt_base_pattern = "0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTvVwWxXyYzZ";
2+
3+
function __TS__ParseInt(this: void, numberString: string, base?: number): number {
4+
// Check which base to use if none specified
5+
if (base === undefined) {
6+
base = 10;
7+
const hexMatch = string.match(numberString, "^%s*-?0[xX]");
8+
if (hexMatch) {
9+
base = 16;
10+
numberString = string.match(hexMatch, "-")
11+
? "-" + numberString.substr(hexMatch.length)
12+
: numberString.substr(hexMatch.length);
13+
}
14+
}
15+
16+
// Check if base is in bounds
17+
if (base < 2 || base > 36) {
18+
return NaN;
19+
}
20+
21+
// Calculate string match pattern to use
22+
const allowedDigits =
23+
base <= 10
24+
? __TS__parseInt_base_pattern.substring(0, base)
25+
: __TS__parseInt_base_pattern.substr(0, 10 + 2 * (base - 10));
26+
const pattern = `^%s*(-?[${allowedDigits}]*)`;
27+
28+
// Try to parse with Lua tonumber
29+
const number = tonumber(string.match(numberString, pattern), base);
30+
31+
if (number === undefined) {
32+
return NaN;
33+
}
34+
35+
// Lua uses a different floor convention for negative numbers than JS
36+
if (number >= 0) {
37+
return math.floor(number);
38+
} else {
39+
return math.ceil(number);
40+
}
41+
}

src/lualib/declarations/math.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ declare namespace math {
88
function atan(y: number, x?: number): number;
99

1010
function atan2(y: number, x: number): number;
11+
12+
function ceil(x: number): number;
13+
function floor(x: number): number;
1114
}

src/lualib/declarations/string.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ declare namespace string {
1313
): [string, number];
1414
function sub(s: string, i: number, j?: number): string;
1515
function format(formatstring: string, ...args: any[]): string;
16+
function match(string: string, pattern: string): string;
1617
}

src/transformation/builtins/global.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,9 @@ export function transformGlobalCall(
3030
node,
3131
...numberParameters
3232
);
33+
case "parseFloat":
34+
return transformLuaLibFunction(context, LuaLibFeature.ParseFloat, node, ...parameters);
35+
case "parseInt":
36+
return transformLuaLibFunction(context, LuaLibFeature.ParseInt, node, ...parameters);
3337
}
3438
}

test/unit/builtins/numbers.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,57 @@ test("number intersected method", () => {
7878
test("numbers overflowing the float limit become math.huge", () => {
7979
util.testExpression`1e309`.expectToMatchJsResult();
8080
});
81+
82+
describe.each(["parseInt", "parseFloat"])("parse numbers with %s", parseFunction => {
83+
const numberStrings = ["3", "3.0", "9", "42", "239810241", "-20391", "3.1415", "2.7182", "-34910.3"];
84+
85+
test.each(numberStrings)("parses (%s)", numberString => {
86+
util.testExpression`${parseFunction}("${numberString}")`.expectToMatchJsResult();
87+
});
88+
89+
test("empty string", () => {
90+
util.testExpression`${parseFunction}("")`.expectToMatchJsResult();
91+
});
92+
93+
test("invalid string", () => {
94+
util.testExpression`${parseFunction}("bla")`.expectToMatchJsResult();
95+
});
96+
97+
test.each(["1px", "2300m", "3,4", "452adkfl"])("trailing text (%s)", numberString => {
98+
util.testExpression`${parseFunction}("${numberString}")`.expectToMatchJsResult();
99+
});
100+
101+
test.each([" 3", " 4", " -231", " 1px"])("leading whitespace (%s)", numberString => {
102+
util.testExpression`${parseFunction}("${numberString}")`.expectToMatchJsResult();
103+
});
104+
});
105+
106+
test.each(["Infinity", "-Infinity", " -Infinity"])("parseFloat handles Infinity", numberString => {
107+
util.testExpression`parseFloat("${numberString}")`.expectToMatchJsResult();
108+
});
109+
110+
test.each([
111+
{ numberString: "36", base: 8 },
112+
{ numberString: "-36", base: 8 },
113+
{ numberString: "100010101101", base: 2 },
114+
{ numberString: "-100010101101", base: 2 },
115+
{ numberString: "3F", base: 16 },
116+
])("parseInt with base (%p)", ({ numberString, base }) => {
117+
util.testExpression`parseInt("${numberString}", ${base})`.expectToMatchJsResult();
118+
});
119+
120+
test.each(["0x4A", "-0x42", "0X42", " 0x391", " -0x8F"])("parseInt detects hexadecimal", numberString => {
121+
util.testExpression`parseInt("${numberString}")`.expectToMatchJsResult();
122+
});
123+
124+
test.each([1, 37, -100])("parseInt with invalid base (%p)", base => {
125+
util.testExpression`parseInt("11111", ${base})`.expectToMatchJsResult();
126+
});
127+
128+
test.each([
129+
{ numberString: "36px", base: 8 },
130+
{ numberString: "10001010110231", base: 2 },
131+
{ numberString: "3Fcolor", base: 16 },
132+
])("parseInt with base and trailing text (%p)", ({ numberString, base }) => {
133+
util.testExpression`parseInt("${numberString}", ${base})`.expectToMatchJsResult();
134+
});

0 commit comments

Comments
 (0)