diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 5596ee6b5..7a69a2b59 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -54,10 +54,6 @@ export class TSHelper { return result; } - public static containsStatement(statements: ts.NodeArray, kind: ts.SyntaxKind): boolean { - return statements.some(statement => statement.kind === kind); - } - public static getExtendedType(node: ts.ClassLikeDeclarationBase, checker: ts.TypeChecker): ts.Type | undefined { if (node && node.heritageClauses) { for (const clause of node.heritageClauses) { diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 5ba9c4ef8..3dc736649 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -57,6 +57,17 @@ interface ExportInfo { dummy: boolean; } +export enum ScopeType { + Function, + Switch, + Loop, +} + +interface SpecialScope { + type: ScopeType; + id: number; +} + export abstract class LuaTranspiler { // Lua key words for this Lua target // https://www.lua.org/manual/5.0/manual.html#2.1 @@ -70,13 +81,12 @@ export abstract class LuaTranspiler { public checker: ts.TypeChecker; public options: CompilerOptions; public genVarCounter: number; - public transpilingSwitch: number; public namespace: string[]; public importCount: number; public isModule: boolean; public isStrict: boolean; public sourceFile: ts.SourceFile; - public loopStack: number[]; + public scopeStack: SpecialScope[]; public classStack: string[]; public exportStack: ExportInfo[][]; @@ -89,7 +99,6 @@ export abstract class LuaTranspiler { this.checker = checker; this.options = options; this.genVarCounter = 0; - this.transpilingSwitch = 0; this.namespace = []; this.importCount = 0; this.sourceFile = sourceFile; @@ -97,7 +106,7 @@ export abstract class LuaTranspiler { this.isStrict = options.alwaysStrict || (options.strict && options.alwaysStrict !== false) || (this.isModule && options.target && options.target >= ts.ScriptTarget.ES2015); - this.loopStack = []; + this.scopeStack = []; this.classStack = []; this.exportStack = []; this.luaLibFeatureSet = new Set(); @@ -128,6 +137,19 @@ export abstract class LuaTranspiler { this.exportStack[this.exportStack.length - 1].push({name: nameIn, node: nodeIn, dummy: dummyIn}); } + public peekSpecialScope(): SpecialScope { + return this.scopeStack[this.scopeStack.length - 1]; + } + + public pushSpecialScope(scopeType: ScopeType): void { + this.scopeStack.push({ type: scopeType, id: this.genVarCounter }); + this.genVarCounter++; + } + + public popSpecialScope(): SpecialScope { + return this.scopeStack.pop(); + } + public makeExport(name: string, node: ts.Node, dummy?: boolean): string { let result: string = ""; if (node && @@ -327,7 +349,7 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.SwitchStatement: return this.transpileSwitch(node as ts.SwitchStatement); case ts.SyntaxKind.BreakStatement: - return this.transpileBreak(); + return this.transpileBreak(node as ts.BreakStatement); case ts.SyntaxKind.TryStatement: return this.transpileTry(node as ts.TryStatement); case ts.SyntaxKind.ThrowStatement: @@ -459,12 +481,8 @@ export abstract class LuaTranspiler { return result; } - public transpileBreak(): string { - if (this.transpilingSwitch > 0) { - return ""; - } else { - return this.indent + "break\n"; - } + public transpileBreak(node: ts.BreakStatement): string { + return this.indent + "break\n"; } public transpileContinue(node: ts.ContinueStatement): string { @@ -506,8 +524,6 @@ export abstract class LuaTranspiler { | ts.ForOfStatement | ts.ForInStatement ): string { - this.loopStack.push(this.genVarCounter); - this.genVarCounter++; let result = this.indent + "do\n"; this.pushIndent(); result += this.transpileStatement(node.statement); @@ -1880,6 +1896,7 @@ export abstract class LuaTranspiler { body: ts.Block, spreadIdentifier: string = "" ): string { + this.pushSpecialScope(ScopeType.Function); let result = ""; // Add default parameters @@ -1892,6 +1909,7 @@ export abstract class LuaTranspiler { } result += this.transpileBlock(body); + this.popSpecialScope(); return result; } diff --git a/src/targets/Transpiler.52.ts b/src/targets/Transpiler.52.ts index 0fcba461f..e3cc8a3ff 100644 --- a/src/targets/Transpiler.52.ts +++ b/src/targets/Transpiler.52.ts @@ -1,5 +1,5 @@ import { TSTLErrors } from "../Errors"; -import { TSHelper as tsHelper } from "../TSHelper"; +import { ScopeType } from "../Transpiler"; import { LuaTranspiler51 } from "./Transpiler.51"; import * as ts from "typescript"; @@ -13,20 +13,26 @@ export class LuaTranspiler52 extends LuaTranspiler51 { | ts.ForOfStatement | ts.ForInStatement ): string { - this.loopStack.push(this.genVarCounter); - this.genVarCounter++; - let result = this.indent + "do\n"; - this.pushIndent(); - result += this.transpileStatement(node.statement); - this.popIndent(); - result += this.indent + "end\n"; - result += this.indent + `::__continue${this.loopStack.pop()}::\n`; + this.pushSpecialScope(ScopeType.Loop); + let result = super.transpileLoopBody(node); + result += this.indent + `::__continue${this.popSpecialScope().id}::\n`; return result; } /** @override */ public transpileContinue(node: ts.ContinueStatement): string { - return this.indent + `goto __continue${this.loopStack[this.loopStack.length - 1]}\n`; + return this.indent + `goto __continue${this.peekSpecialScope().id}\n`; + } + + /** @override */ + public transpileBreak(node: ts.BreakStatement): string { + const topScope = this.peekSpecialScope(); + + if (topScope.type === ScopeType.Switch) { + return this.indent + `goto ____switch${topScope.id}_end\n`; + } else { + return super.transpileBreak(node); + } } /** @override */ @@ -67,7 +73,7 @@ export class LuaTranspiler52 extends LuaTranspiler51 { let result = this.indent + "-------Switch statement start-------\n"; const switchVarName = "____switch" + this.genVarCounter; - this.genVarCounter++; + this.pushSpecialScope(ScopeType.Switch); result += this.indent + `local ${switchVarName} = ${expression}\n`; @@ -101,13 +107,11 @@ export class LuaTranspiler52 extends LuaTranspiler51 { result += "\n"; const transpileClauseBody = (clause: ts.CaseOrDefaultClause) => { - this.transpilingSwitch++; result += this.indent + "do\n"; this.pushIndent(); result += this.transpileBlock(ts.createBlock(clause.statements)); this.popIndent(); result += this.indent + "end\n"; - this.transpilingSwitch--; }; clauses.forEach((clause, index) => { @@ -115,10 +119,6 @@ export class LuaTranspiler52 extends LuaTranspiler51 { result += this.indent + `::${switchVarName}_case_${index}::\n`; transpileClauseBody(clause); - - if (tsHelper.containsStatement(clause.statements, ts.SyntaxKind.BreakStatement)) { - result += this.indent + `goto ${switchVarName}_end\n`; - } } else if (ts.isDefaultClause(clause)) { result += this.indent + `::${switchVarName}_default::\n`; @@ -129,6 +129,8 @@ export class LuaTranspiler52 extends LuaTranspiler51 { result += this.indent + `::${switchVarName}_end::\n`; result += this.indent + "-------Switch statement end-------\n"; + this.popSpecialScope(); + return result; } diff --git a/test/unit/conditionals.spec.ts b/test/unit/conditionals.spec.ts index e48fd962f..12b6d14d7 100644 --- a/test/unit/conditionals.spec.ts +++ b/test/unit/conditionals.spec.ts @@ -313,6 +313,117 @@ export class LuaConditionalsTests { Expect(result).toBe(expected); } + @TestCase(0, 0) + @TestCase(1, 1) + @TestCase(2, 2) + @TestCase(3, -1) + @Test("switchWithBrackets") + public switchWithBrackets(inp: number, expected: number): void { + // Transpile + const lua = util.transpileString( + `let result = -1; + + switch (${inp}) { + case 0: { + result = 0; + break; + } + case 1: { + result = 1; + break; + } + case 2: { + result = 2; + break; + } + } + return result;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + + @TestCase(0, 0) + @TestCase(1, 1) + @TestCase(2, 2) + @TestCase(3, -1) + @Test("switchWithBracketsBreakInConditional") + public switchWithBracketsBreakInConditional(inp: number, expected: number): void { + // Transpile + const lua = util.transpileString( + `let result = -1; + + switch (${inp}) { + case 0: { + result = 0; + break; + } + case 1: { + result = 1; + + if (result == 1) break; + } + case 2: { + result = 2; + break; + } + } + return result;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase(0, 4) + @TestCase(1, 0) + @TestCase(2, 2) + @TestCase(3, -1) + @Test("switchWithBracketsBreakInInternalLoop") + public switchWithBracketsBreakInInternalLoop(inp: number, expected: number): void { + // Transpile + const lua = util.transpileString( + `let result = -1; + + switch (${inp}) { + case 0: { + result = 0; + + for (let i = 0; i < 5; i++) { + result++; + + if (i >= 2) { + break; + } + } + } + case 1: { + result++; + break; + } + case 2: { + result = 2; + break; + } + } + return result;` + ); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + @Test("If dead code after return") public ifDeadCodeAfterReturn(): void { const result = util.transpileAndExecute(