Skip to content

Commit d95d056

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents 5846d72 + 7b6580b commit d95d056

File tree

10 files changed

+339
-22
lines changed

10 files changed

+339
-22
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
}

src/transformation/visitors/binary-expression/compound.ts

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
3-
import { cast } from "../../../utils";
3+
import { cast, assertNever } from "../../../utils";
44
import { TransformationContext } from "../../context";
55
import { createImmediatelyInvokedFunctionExpression } from "../../utils/lua-ast";
66
import { isArrayType, isExpressionWithEvaluationEffect } from "../../utils/typescript";
@@ -46,9 +46,12 @@ type CompoundAssignmentToken =
4646
| ts.SyntaxKind.AsteriskAsteriskToken
4747
| ts.SyntaxKind.LessThanLessThanToken
4848
| ts.SyntaxKind.GreaterThanGreaterThanToken
49-
| ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
49+
| ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken
50+
| ts.SyntaxKind.BarBarToken
51+
| ts.SyntaxKind.AmpersandAmpersandToken
52+
| ts.SyntaxKind.QuestionQuestionToken;
5053

51-
const compoundToAssignmentTokens: Partial<Record<ts.CompoundAssignmentOperator, CompoundAssignmentToken>> = {
54+
const compoundToAssignmentTokens: Record<ts.CompoundAssignmentOperator, CompoundAssignmentToken> = {
5255
[ts.SyntaxKind.BarEqualsToken]: ts.SyntaxKind.BarToken,
5356
[ts.SyntaxKind.PlusEqualsToken]: ts.SyntaxKind.PlusToken,
5457
[ts.SyntaxKind.CaretEqualsToken]: ts.SyntaxKind.CaretToken,
@@ -61,14 +64,16 @@ const compoundToAssignmentTokens: Partial<Record<ts.CompoundAssignmentOperator,
6164
[ts.SyntaxKind.LessThanLessThanEqualsToken]: ts.SyntaxKind.LessThanLessThanToken,
6265
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken]: ts.SyntaxKind.GreaterThanGreaterThanToken,
6366
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken]: ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken,
67+
[ts.SyntaxKind.BarBarEqualsToken]: ts.SyntaxKind.BarBarToken,
68+
[ts.SyntaxKind.AmpersandAmpersandEqualsToken]: ts.SyntaxKind.AmpersandAmpersandToken,
69+
[ts.SyntaxKind.QuestionQuestionEqualsToken]: ts.SyntaxKind.QuestionQuestionToken,
6470
};
6571

6672
export const isCompoundAssignmentToken = (token: ts.BinaryOperator): token is ts.CompoundAssignmentOperator =>
6773
token in compoundToAssignmentTokens;
6874

69-
export const unwrapCompoundAssignmentToken = (
70-
token: ts.CompoundAssignmentOperator
71-
): CompoundAssignmentToken | undefined => compoundToAssignmentTokens[token];
75+
export const unwrapCompoundAssignmentToken = (token: ts.CompoundAssignmentOperator): CompoundAssignmentToken =>
76+
compoundToAssignmentTokens[token];
7277

7378
export function transformCompoundAssignmentExpression(
7479
context: TransformationContext,
@@ -139,6 +144,15 @@ export function transformCompoundAssignmentExpression(
139144
const operatorExpression = transformBinaryOperation(context, left, right, operator, expression);
140145
const tmpDeclaration = lua.createVariableDeclarationStatement(tmpIdentifier, operatorExpression);
141146
const assignStatements = transformAssignment(context, lhs, tmpIdentifier);
147+
148+
if (isSetterSkippingCompoundAssignmentOperator(operator)) {
149+
return createImmediatelyInvokedFunctionExpression(
150+
[tmpDeclaration, ...transformSetterSkippingCompoundAssignment(context, tmpIdentifier, operator, rhs)],
151+
tmpIdentifier,
152+
expression
153+
);
154+
}
155+
142156
return createImmediatelyInvokedFunctionExpression(
143157
[tmpDeclaration, ...assignStatements],
144158
tmpIdentifier,
@@ -175,13 +189,74 @@ export function transformCompoundAssignmentStatement(
175189
[context.transformExpression(objExpression), context.transformExpression(indexExpression)]
176190
);
177191
const accessExpression = lua.createTableIndexExpression(obj, index);
192+
193+
if (isSetterSkippingCompoundAssignmentOperator(operator)) {
194+
return [
195+
objAndIndexDeclaration,
196+
...transformSetterSkippingCompoundAssignment(context, accessExpression, operator, rhs, node),
197+
];
198+
}
199+
178200
const operatorExpression = transformBinaryOperation(context, accessExpression, right, operator, node);
179201
const assignStatement = lua.createAssignmentStatement(accessExpression, operatorExpression);
180202
return [objAndIndexDeclaration, assignStatement];
181203
} else {
204+
if (isSetterSkippingCompoundAssignmentOperator(operator)) {
205+
const luaLhs = context.transformExpression(lhs) as lua.AssignmentLeftHandSideExpression;
206+
return transformSetterSkippingCompoundAssignment(context, luaLhs, operator, rhs, node);
207+
}
208+
182209
// Simple statements
183210
// ${left} = ${left} ${replacementOperator} ${right}
184211
const operatorExpression = transformBinaryOperation(context, left, right, operator, node);
185212
return transformAssignment(context, lhs, operatorExpression);
186213
}
187214
}
215+
216+
/* These setter-skipping operators will not execute the setter if result does not change.
217+
* x.y ||= z does NOT call the x.y setter if x.y is already true.
218+
* x.y &&= z does NOT call the x.y setter if x.y is already false.
219+
* x.y ??= z does NOT call the x.y setter if x.y is already not nullish.
220+
*/
221+
type SetterSkippingCompoundAssignmentOperator = ts.LogicalOperator | ts.SyntaxKind.QuestionQuestionToken;
222+
223+
function isSetterSkippingCompoundAssignmentOperator(
224+
operator: ts.BinaryOperator
225+
): operator is SetterSkippingCompoundAssignmentOperator {
226+
return (
227+
operator === ts.SyntaxKind.AmpersandAmpersandToken ||
228+
operator === ts.SyntaxKind.BarBarToken ||
229+
operator === ts.SyntaxKind.QuestionQuestionToken
230+
);
231+
}
232+
233+
function transformSetterSkippingCompoundAssignment(
234+
context: TransformationContext,
235+
lhs: lua.AssignmentLeftHandSideExpression,
236+
operator: SetterSkippingCompoundAssignmentOperator,
237+
rhs: ts.Expression,
238+
node?: ts.Node
239+
): lua.Statement[] {
240+
// These assignments have the form 'if x then y = z', figure out what condition x is first.
241+
let condition: lua.Expression;
242+
243+
if (operator === ts.SyntaxKind.AmpersandAmpersandToken) {
244+
condition = lhs;
245+
} else if (operator === ts.SyntaxKind.BarBarToken) {
246+
condition = lua.createUnaryExpression(lhs, lua.SyntaxKind.NotOperator);
247+
} else if (operator === ts.SyntaxKind.QuestionQuestionToken) {
248+
condition = lua.createBinaryExpression(lhs, lua.createNilLiteral(), lua.SyntaxKind.EqualityOperator);
249+
} else {
250+
assertNever(operator);
251+
}
252+
253+
// if condition then lhs = rhs end
254+
return [
255+
lua.createIfStatement(
256+
condition,
257+
lua.createBlock([lua.createAssignmentStatement(lhs, context.transformExpression(rhs))]),
258+
undefined,
259+
node
260+
),
261+
];
262+
}

src/transformation/visitors/binary-expression/index.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
33
import { FunctionVisitor, TransformationContext } from "../../context";
44
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
5-
import { extensionInvalidInstanceOf, luaTableInvalidInstanceOf, unsupportedNodeKind } from "../../utils/diagnostics";
5+
import { extensionInvalidInstanceOf, luaTableInvalidInstanceOf } from "../../utils/diagnostics";
66
import { createImmediatelyInvokedFunctionExpression, wrapInToStringForConcat } from "../../utils/lua-ast";
77
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
88
import { isStandardLibraryType, isStringType, typeCanSatisfy } from "../../utils/typescript";
@@ -15,6 +15,7 @@ import {
1515
transformCompoundAssignmentStatement,
1616
unwrapCompoundAssignmentToken,
1717
} from "./compound";
18+
import { assert } from "../../../utils";
1819

1920
type SimpleOperator =
2021
| ts.AdditiveOperatorOrHigher
@@ -45,13 +46,18 @@ export function transformBinaryOperation(
4546
context: TransformationContext,
4647
left: lua.Expression,
4748
right: lua.Expression,
48-
operator: BitOperator | SimpleOperator,
49+
operator: BitOperator | SimpleOperator | ts.SyntaxKind.QuestionQuestionToken,
4950
node: ts.Node
5051
): lua.Expression {
5152
if (isBitOperator(operator)) {
5253
return transformBinaryBitOperation(context, node, left, right, operator);
5354
}
5455

56+
if (operator === ts.SyntaxKind.QuestionQuestionToken) {
57+
assert(ts.isBinaryExpression(node));
58+
return transformNullishCoalescingExpression(context, node);
59+
}
60+
5561
let luaOperator = simpleOperatorsToLua[operator];
5662

5763
// Check if we need to use string concat operator
@@ -78,11 +84,6 @@ export const transformBinaryExpression: FunctionVisitor<ts.BinaryExpression> = (
7884

7985
if (isCompoundAssignmentToken(operator)) {
8086
const token = unwrapCompoundAssignmentToken(operator);
81-
if (!token) {
82-
context.diagnostics.push(unsupportedNodeKind(node, operator));
83-
return lua.createNilLiteral(node);
84-
}
85-
8687
return transformCompoundAssignmentExpression(context, node, node.left, node.right, token, false);
8788
}
8889

@@ -131,10 +132,6 @@ export const transformBinaryExpression: FunctionVisitor<ts.BinaryExpression> = (
131132
);
132133
}
133134

134-
case ts.SyntaxKind.QuestionQuestionToken: {
135-
return transformNullishCoalescingExpression(context, node);
136-
}
137-
138135
default:
139136
return transformBinaryOperation(
140137
context,
@@ -157,11 +154,6 @@ export function transformBinaryExpressionStatement(
157154
if (isCompoundAssignmentToken(operator)) {
158155
// +=, -=, etc...
159156
const token = unwrapCompoundAssignmentToken(operator);
160-
if (!token) {
161-
context.diagnostics.push(unsupportedNodeKind(node, operator));
162-
return;
163-
}
164-
165157
return transformCompoundAssignmentStatement(context, expression, expression.left, expression.right, token);
166158
} else if (operator === ts.SyntaxKind.EqualsToken) {
167159
return transformAssignmentStatement(context, expression as ts.AssignmentExpression<ts.EqualsToken>);

0 commit comments

Comments
 (0)