Skip to content

Commit 7cb97ad

Browse files
authored
Basic Luau target (#1608)
* Initial Luau implementation * Add support for luau specific transformation fixtures Also fixed not providing Luau to the `expectEachVersionExceptJit` function. It seems to work properly in the test suite but I need to make sure that this is intended or if the function should be renamed to something else (like `expectOnlyPUC`) and exclude Luau as well. * Refactor target specific fixtures Now stored in separate folders. Future proof and doesn't feel gross. Also disabled Luau in expectEachVersionExceptJit. Ideally the function would be renamed but the change would be too large here (not necessary either). * Add remaining Luau test suite cases Luau supports both math.atan & math.atan2 so having a certain result isn't important. Same goes for table.unpack and unpack. Had to pass an empty function for the continue case where luau wouldn't make a label at all since it has a proper continue statement. * Fix eslint issue
1 parent 6dfe252 commit 7cb97ad

File tree

16 files changed

+135
-20
lines changed

16 files changed

+135
-20
lines changed

src/CompilerOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export enum LuaTarget {
6262
Lua53 = "5.3",
6363
Lua54 = "5.4",
6464
LuaJIT = "JIT",
65+
Luau = "Luau",
6566
}
6667

6768
export enum BuildMode {

src/LuaAST.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum SyntaxKind {
2525
LabelStatement,
2626
ReturnStatement,
2727
BreakStatement,
28+
ContinueStatement, // Luau only.
2829
ExpressionStatement,
2930

3031
// Expression
@@ -45,6 +46,7 @@ export enum SyntaxKind {
4546
Identifier,
4647
TableIndexExpression,
4748
ParenthesizedExpression,
49+
ConditionalExpression, // Luau only
4850

4951
// Operators
5052

@@ -488,6 +490,18 @@ export function createBreakStatement(tsOriginal?: ts.Node): BreakStatement {
488490
return createNode(SyntaxKind.BreakStatement, tsOriginal) as BreakStatement;
489491
}
490492

493+
export interface ContinueStatement extends Statement {
494+
kind: SyntaxKind.ContinueStatement;
495+
}
496+
497+
export function isContinueStatement(node: Node): node is ContinueStatement {
498+
return node.kind === SyntaxKind.ContinueStatement;
499+
}
500+
501+
export function createContinueStatement(tsOriginal?: ts.Node): ContinueStatement {
502+
return createNode(SyntaxKind.ContinueStatement, tsOriginal) as ContinueStatement;
503+
}
504+
491505
export interface ExpressionStatement extends Statement {
492506
kind: SyntaxKind.ExpressionStatement;
493507
expression: Expression;
@@ -861,3 +875,26 @@ export function createParenthesizedExpression(expression: Expression, tsOriginal
861875
parenthesizedExpression.expression = expression;
862876
return parenthesizedExpression;
863877
}
878+
879+
export type ConditionalExpression = Expression & {
880+
condition: Expression;
881+
whenTrue: Expression;
882+
whenFalse: Expression;
883+
};
884+
885+
export function isConditionalExpression(node: Node): node is ConditionalExpression {
886+
return node.kind === SyntaxKind.ConditionalExpression;
887+
}
888+
889+
export function createConditionalExpression(
890+
condition: Expression,
891+
whenTrue: Expression,
892+
whenFalse: Expression,
893+
tsOriginal?: ts.Node
894+
): ConditionalExpression {
895+
const conditionalExpression = createNode(SyntaxKind.ConditionalExpression, tsOriginal) as ConditionalExpression;
896+
conditionalExpression.condition = condition;
897+
conditionalExpression.whenTrue = whenTrue;
898+
conditionalExpression.whenFalse = whenFalse;
899+
return conditionalExpression;
900+
}

src/LuaPrinter.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ export class LuaPrinter {
396396
return this.printReturnStatement(statement as lua.ReturnStatement);
397397
case lua.SyntaxKind.BreakStatement:
398398
return this.printBreakStatement(statement as lua.BreakStatement);
399+
case lua.SyntaxKind.ContinueStatement:
400+
return this.printContinueStatement(statement as lua.ContinueStatement);
399401
case lua.SyntaxKind.ExpressionStatement:
400402
return this.printExpressionStatement(statement as lua.ExpressionStatement);
401403
default:
@@ -574,6 +576,10 @@ export class LuaPrinter {
574576
return this.createSourceNode(statement, this.indent("break"));
575577
}
576578

579+
public printContinueStatement(statement: lua.ContinueStatement): SourceNode {
580+
return this.createSourceNode(statement, this.indent("continue"));
581+
}
582+
577583
public printExpressionStatement(statement: lua.ExpressionStatement): SourceNode {
578584
return this.createSourceNode(statement, [this.indent(), this.printExpression(statement.expression)]);
579585
}
@@ -614,6 +620,8 @@ export class LuaPrinter {
614620
return this.printTableIndexExpression(expression as lua.TableIndexExpression);
615621
case lua.SyntaxKind.ParenthesizedExpression:
616622
return this.printParenthesizedExpression(expression as lua.ParenthesizedExpression);
623+
case lua.SyntaxKind.ConditionalExpression:
624+
return this.printConditionalExpression(expression as lua.ConditionalExpression);
617625
default:
618626
throw new Error(`Tried to print unknown statement kind: ${lua.SyntaxKind[expression.kind]}`);
619627
}
@@ -828,6 +836,17 @@ export class LuaPrinter {
828836
return this.createSourceNode(expression, ["(", this.printExpression(expression.expression), ")"]);
829837
}
830838

839+
public printConditionalExpression(expression: lua.ConditionalExpression): SourceNode {
840+
return this.createSourceNode(expression, [
841+
"if ",
842+
this.printExpression(expression.condition),
843+
" then ",
844+
this.printExpression(expression.whenTrue),
845+
" else ",
846+
this.printExpression(expression.whenFalse),
847+
]);
848+
}
849+
831850
public printOperator(kind: lua.Operator): SourceNode {
832851
return new SourceNode(null, null, this.relativeSourcePath, LuaPrinter.operatorMap[kind]);
833852
}

src/transformation/utils/scope.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface FunctionDefinitionInfo {
2525
export enum LoopContinued {
2626
WithGoto,
2727
WithRepeatBreak,
28+
WithContinue,
2829
}
2930

3031
export interface Scope {

src/transformation/visitors/break-continue.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@ export const transformBreakStatement: FunctionVisitor<ts.BreakStatement> = (brea
1111

1212
export const transformContinueStatement: FunctionVisitor<ts.ContinueStatement> = (statement, context) => {
1313
const scope = findScope(context, ScopeType.Loop);
14-
const continuedWith =
15-
context.luaTarget === LuaTarget.Universal ||
16-
context.luaTarget === LuaTarget.Lua50 ||
17-
context.luaTarget === LuaTarget.Lua51
18-
? LoopContinued.WithRepeatBreak
19-
: LoopContinued.WithGoto;
14+
15+
const continuedWith = {
16+
[LuaTarget.Universal]: LoopContinued.WithRepeatBreak,
17+
[LuaTarget.Lua50]: LoopContinued.WithRepeatBreak,
18+
[LuaTarget.Lua51]: LoopContinued.WithRepeatBreak,
19+
[LuaTarget.Lua52]: LoopContinued.WithGoto,
20+
[LuaTarget.Lua53]: LoopContinued.WithGoto,
21+
[LuaTarget.Lua54]: LoopContinued.WithGoto,
22+
[LuaTarget.LuaJIT]: LoopContinued.WithGoto,
23+
[LuaTarget.Luau]: LoopContinued.WithContinue,
24+
}[context.luaTarget];
2025

2126
if (scope) {
2227
scope.loopContinued = continuedWith;
@@ -28,6 +33,9 @@ export const transformContinueStatement: FunctionVisitor<ts.ContinueStatement> =
2833
case LoopContinued.WithGoto:
2934
return lua.createGotoStatement(label, statement);
3035

36+
case LoopContinued.WithContinue:
37+
return lua.createContinueStatement(statement);
38+
3139
case LoopContinued.WithRepeatBreak:
3240
return [
3341
lua.createAssignmentStatement(lua.createIdentifier(label), lua.createBooleanLiteral(true), statement),

src/transformation/visitors/conditional.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { performHoisting, ScopeType } from "../utils/scope";
66
import { transformBlockOrStatement } from "./block";
77
import { canBeFalsy } from "../utils/typescript";
88
import { truthyOnlyConditionalValue } from "../utils/diagnostics";
9+
import { LuaTarget } from "../../CompilerOptions";
910

1011
function transformProtectedConditionalExpression(
1112
context: TransformationContext,
@@ -38,6 +39,16 @@ function transformProtectedConditionalExpression(
3839
}
3940

4041
export const transformConditionalExpression: FunctionVisitor<ts.ConditionalExpression> = (expression, context) => {
42+
if (context.luaTarget === LuaTarget.Luau) {
43+
// Luau's ternary operator doesn't have these issues
44+
return lua.createConditionalExpression(
45+
context.transformExpression(expression.condition),
46+
context.transformExpression(expression.whenTrue),
47+
context.transformExpression(expression.whenFalse),
48+
expression
49+
);
50+
}
51+
4152
// Check if we need to add diagnostic about Lua truthiness
4253
checkOnlyTruthyCondition(expression.condition, context);
4354

src/transformation/visitors/loops/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function transformLoopBody(
2121

2222
switch (scope.loopContinued) {
2323
case undefined:
24+
case LoopContinued.WithContinue:
2425
return body;
2526

2627
case LoopContinued.WithGoto:

test/cli/parse.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ describe("tsconfig", () => {
248248
["luaTarget", "5.3", { luaTarget: tstl.LuaTarget.Lua53 }],
249249
["luaTarget", "5.4", { luaTarget: tstl.LuaTarget.Lua54 }],
250250
["luaTarget", "jit", { luaTarget: tstl.LuaTarget.LuaJIT }],
251+
["luaTarget", "luau", { luaTarget: tstl.LuaTarget.Luau }],
251252

252253
["luaBundle", "foo", { luaBundle: "foo" }],
253254
["luaBundleEntry", "bar", { luaBundleEntry: "bar" }],

test/translation/__snapshots__/transformation.spec.ts.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`Luau Transformation (luauSpecificTransformations) 1`] = `
4+
"t = if true then "is true" else "is false"
5+
while false do
6+
continue
7+
end
8+
repeat
9+
do
10+
continue
11+
end
12+
until not false"
13+
`;
14+
315
exports[`Transformation (blockScopeVariables) 1`] = `
416
"do
517
local a = 1

test/translation/transformation.spec.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@ import { annotationDeprecated } from "../../src/transformation/utils/diagnostics
55
import { couldNotResolveRequire } from "../../src/transpilation/diagnostics";
66
import * as util from "../util";
77

8-
const fixturesPath = path.join(__dirname, "./transformation");
9-
const fixtures = fs
10-
.readdirSync(fixturesPath)
11-
.filter(f => path.extname(f) === ".ts")
12-
.sort()
13-
.map(f => [path.parse(f).name, fs.readFileSync(path.join(fixturesPath, f), "utf8")]);
8+
const targetSpecs: Array<[string, tstl.LuaTarget | undefined, string]> = [
9+
["", undefined, "./transformation"],
10+
["Luau ", tstl.LuaTarget.Luau, "./transformation/luau"],
11+
];
1412

15-
test.each(fixtures)("Transformation (%s)", (_name, content) => {
16-
util.testModule(content)
17-
.setOptions({ luaLibImport: tstl.LuaLibImportKind.Require })
18-
.ignoreDiagnostics([annotationDeprecated.code, couldNotResolveRequire.code])
19-
.disableSemanticCheck()
20-
.expectLuaToMatchSnapshot();
21-
});
13+
for (const [name, luaTarget, targetDir] of targetSpecs) {
14+
const fixturesPath = path.join(__dirname, targetDir);
15+
const fixtures = fs
16+
.readdirSync(fixturesPath)
17+
.filter(f => path.extname(f) === ".ts")
18+
.sort()
19+
.map(f => [path.parse(f).name, fs.readFileSync(path.join(fixturesPath, f), "utf8")]);
20+
21+
test.each(fixtures)(`${name}Transformation (%s)`, (_name, content) => {
22+
util.testModule(content)
23+
.setOptions({
24+
luaLibImport: tstl.LuaLibImportKind.Require,
25+
luaTarget,
26+
})
27+
.ignoreDiagnostics([annotationDeprecated.code, couldNotResolveRequire.code])
28+
.disableSemanticCheck()
29+
.expectLuaToMatchSnapshot();
30+
});
31+
}

0 commit comments

Comments
 (0)