diff --git a/dist/TSHelper.js b/dist/TSHelper.js index 4f519703a..1438d99bd 100644 --- a/dist/TSHelper.js +++ b/dist/TSHelper.js @@ -28,6 +28,9 @@ var TSHelper = /** @class */ (function () { } return "unknown"; }; + TSHelper.containsStatement = function (statements, kind) { + return statements.some(function (statement) { return statement.kind === kind; }); + }; TSHelper.isFileModule = function (sourceFile) { if (sourceFile) { // Vanilla ts flags files as external module if they have an import or diff --git a/dist/Transpiler.js b/dist/Transpiler.js index f673dadc2..4a91d7ed6 100644 --- a/dist/Transpiler.js +++ b/dist/Transpiler.js @@ -221,7 +221,7 @@ var LuaTranspiler = /** @class */ (function () { }; LuaTranspiler.prototype.transpileBreak = function () { if (this.transpilingSwitch) { - return this.indent + ("goto switchDone" + this.genVarCounter + "\n"); + return ''; } else { return this.indent + "break\n"; @@ -312,33 +312,54 @@ var LuaTranspiler = /** @class */ (function () { var expression = this.transpileExpression(node.expression, true); var clauses = node.caseBlock.clauses; var result = this.indent + "-------Switch statement start-------\n"; + var jumpTableName = "____switch" + this.genVarCounter; + this.genVarCounter++; + result += this.indent + ("local " + jumpTableName + " = {\n"); + this.pushIndent(); // If statement to go to right entry label clauses.forEach(function (clause, index) { if (ts.isCaseClause(clause)) { - var keyword = index == 0 ? "if" : "elseif"; - var condition = _this.transpileExpression(clause.expression, true); - result += _this.indent + (keyword + " " + expression + "==" + condition + " then\n"); + result += _this.indent + "-- case:\n"; + result += _this.indent + ("[" + _this.transpileExpression(clause.expression, true) + "] = function(self)\n"); } - else { - // Default - result += _this.indent + "else\n"; + if (ts.isDefaultClause(clause)) { + result += _this.indent + "-- default:\n"; + result += _this.indent + ("[\"____default" + _this.genVarCounter + "\"] = function(self)\n"); } _this.pushIndent(); - // Labels for fallthrough - result += _this.indent + ("::switchCase" + (_this.genVarCounter + index) + "::\n"); _this.transpilingSwitch = true; clause.statements.forEach(function (statement) { result += _this.transpileNode(statement); }); _this.transpilingSwitch = false; - // If this goto is reached, fall through to the next case - if (index < clauses.length - 1) { - result += _this.indent + ("goto switchCase" + (_this.genVarCounter + index + 1) + "\n"); + var i = index + 1; + if (i < clauses.length && !TSHelper_1.TSHelper.containsStatement(clause.statements, ts.SyntaxKind.BreakStatement)) { + var nextClause = clauses[i]; + while (i < clauses.length + && ts.isCaseClause(nextClause) + && nextClause.statements.length === 0) { + i++; + nextClause = clauses[i]; + } + if (i !== index && nextClause) { + if (ts.isCaseClause(nextClause)) { + result += _this.indent + ("self[" + _this.transpileExpression(nextClause.expression, true) + "]()\n"); + } + else { + result += _this.indent + ("self[\"____default" + _this.genVarCounter + "\"]()\n"); + } + } + } + else { + result += _this.indent + "-- break;\n"; } _this.popIndent(); + result += _this.indent + "end,\n"; }); - result += this.indent + "end\n"; - result += this.indent + ("::switchDone" + this.genVarCounter + "::\n"); + this.popIndent(); + result += this.indent + "}\n"; + result += this.indent + ("if " + jumpTableName + "[" + expression + "] then " + jumpTableName + "[" + expression + "](" + jumpTableName + ")\n"); + result += this.indent + ("elseif " + jumpTableName + "[\"____default" + this.genVarCounter + "\"] then " + jumpTableName + "[\"____default" + this.genVarCounter + "\"](" + jumpTableName + ") end\n"); result += this.indent + "--------Switch statement end--------\n"; //Increment counter for next switch statement this.genVarCounter += clauses.length; diff --git a/src/Compiler.ts b/src/Compiler.ts index 8670bd57c..573e2fa27 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -34,7 +34,7 @@ function compile(fileNames: string[], options: ts.CompilerOptions, projectRoot: } program.getSourceFiles().forEach(sourceFile => { - if (!sourceFile.isDeclarationFile) { + if (!sourceFile.isDeclarationFile) { // Print AST for debugging //printAST(sourceFile, 0); @@ -43,7 +43,7 @@ function compile(fileNames: string[], options: ts.CompilerOptions, projectRoot: const addHeader = options.noHeader === true ? false : true; let lua = LuaTranspiler.transpileSourceFile(sourceFile, checker, addHeader); let outPath = sourceFile.fileName.substring(0, sourceFile.fileName.lastIndexOf(".")) + ".lua"; - + if (options.outDir) { var extension = options.outDir; if (extension[extension.length - 1] != "/") extension = extension + "/"; @@ -83,7 +83,7 @@ function printAST(node: ts.Node, indent: number) { const filename = process.argv[2].split("\\").join("/"); const filepath = filename.substring(0, filename.lastIndexOf("/")); let configPath = ts.findConfigFile(filepath, ts.sys.fileExists); - + if (configPath) { configPath = configPath.split("\\").join("/"); const projectRoot = configPath.substring(0, configPath.lastIndexOf("/")); diff --git a/src/TSHelper.ts b/src/TSHelper.ts index ee60ab4e7..1712a8d2d 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -29,6 +29,10 @@ export class TSHelper { return "unknown"; } + static containsStatement(statements: ts.NodeArray, kind: ts.SyntaxKind): boolean { + return statements.some(statement => statement.kind === kind); + } + static isFileModule(sourceFile: ts.SourceFile) { if (sourceFile) { // Vanilla ts flags files as external module if they have an import or diff --git a/src/Transpiler.ts b/src/Transpiler.ts index a92d788e0..18795a61a 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -232,7 +232,7 @@ export class LuaTranspiler { transpileBreak(): string { if (this.transpilingSwitch) { - return this.indent + `goto switchDone${this.genVarCounter}\n`; + return ''; } else { return this.indent + "break\n"; } @@ -347,37 +347,61 @@ export class LuaTranspiler { let result = this.indent + "-------Switch statement start-------\n"; + let jumpTableName = "____switch" + this.genVarCounter; + this.genVarCounter++; + + result += this.indent + `local ${jumpTableName} = {\n`; + + this.pushIndent(); + // If statement to go to right entry label clauses.forEach((clause, index) => { if (ts.isCaseClause(clause)) { - let keyword = index == 0 ? "if" : "elseif"; - let condition = this.transpileExpression(clause.expression, true); - result += this.indent + `${keyword} ${expression}==${condition} then\n`; - } else { - // Default - result += this.indent + `else\n`; + result += this.indent + `-- case:\n`; + result += this.indent + `[${this.transpileExpression(clause.expression, true)}] = function(self)\n`; + } + if (ts.isDefaultClause(clause)) { + result += this.indent + `-- default:\n`; + result += this.indent + `["____default${this.genVarCounter}"] = function(self)\n`; } - this.pushIndent(); - // Labels for fallthrough - result += this.indent + `::switchCase${this.genVarCounter + index}::\n`; - this.transpilingSwitch = true; clause.statements.forEach(statement => { result += this.transpileNode(statement); }); this.transpilingSwitch = false; - // If this goto is reached, fall through to the next case - if (index < clauses.length - 1) { - result += this.indent + `goto switchCase${this.genVarCounter + index + 1}\n`; + let i = index + 1; + if (i < clauses.length && !tsEx.containsStatement(clause.statements, ts.SyntaxKind.BreakStatement)) { + let nextClause = clauses[i]; + while(i < clauses.length + && ts.isCaseClause(nextClause) + && nextClause.statements.length === 0 + ) { + i++; + nextClause = clauses[i]; + } + + if (i !== index && nextClause) { + if (ts.isCaseClause(nextClause)) { + result += this.indent + `self[${this.transpileExpression(nextClause.expression, true)}]()\n`; + } else { + result += this.indent + `self["____default${this.genVarCounter}"]()\n`; + } + } + } else { + result += this.indent + `-- break;\n`; } this.popIndent(); + + result += this.indent + `end,\n`; }); - result += this.indent + "end\n"; - result += this.indent + `::switchDone${this.genVarCounter}::\n`; + this.popIndent(); + result += this.indent + "}\n"; + result += this.indent + `if ${jumpTableName}[${expression}] then ${jumpTableName}[${expression}](${jumpTableName})\n`; + result += this.indent + `elseif ${jumpTableName}["____default${this.genVarCounter}"] then ${jumpTableName}["____default${this.genVarCounter}"](${jumpTableName}) end\n`; result += this.indent + "--------Switch statement end--------\n"; //Increment counter for next switch statement diff --git a/test/integration/lua/conditionals.spec.ts b/test/integration/lua/conditionals.spec.ts new file mode 100644 index 000000000..f0081bc17 --- /dev/null +++ b/test/integration/lua/conditionals.spec.ts @@ -0,0 +1,219 @@ +import { Expect, Test, TestCase } from "alsatian"; +import * as util from "../../src/util" + +export class LuaConditionalsTests { + + @TestCase(0, 0) + @TestCase(1, 1) + @Test("if") + public if(inp: number, expected: number) { + // Transpile + let lua = util.transpileString( + `let input = ${inp} + if (input === 0) { + return 0; + } + return 1;` + , util.dummyTypes.Number + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase(0, 0) + @TestCase(1, 1) + @Test("ifelse") + public ifelse(inp: number, expected: number) { + // Transpile + let lua = util.transpileString( + `let input = ${inp} + if (input === 0) { + return 0; + } else { + return 1; + }` + , util.dummyTypes.Number + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase(0, 0) + @TestCase(1, 1) + @TestCase(2, 2) + @TestCase(3, 3) + @Test("ifelseif") + public ifelseif(inp: number, expected: number) { + // Transpile + let lua = util.transpileString( + `let input = ${inp} + if (input === 0) { + return 0; + } else if (input === 1){ + return 1; + } else if (input === 2){ + return 2; + } + return 3;` + , util.dummyTypes.Number + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase(0, 0) + @TestCase(1, 1) + @TestCase(2, 2) + @TestCase(3, 3) + @Test("ifelseifelse") + public ifelseifelse(inp: number, expected: number) { + // Transpile + let lua = util.transpileString( + `let input = ${inp} + if (input === 0) { + return 0; + } else if (input === 1){ + return 1; + } else if (input === 2){ + return 2; + } else { + return 3; + }` + , util.dummyTypes.Number + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase(0, 0) + @TestCase(1, 1) + @TestCase(2, 2) + @TestCase(3, -1) + @Test("switch") + public switch(inp: number, expected: number) { + // Transpile + let 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;` + , util.dummyTypes.Number + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase(0, 0) + @TestCase(1, 1) + @TestCase(2, 2) + @TestCase(3, -2) + @Test("switchdefault") + public switchdefault(inp: number, expected: number) { + // Transpile + let lua = util.transpileString( + `let result = -1; + + switch (${inp}) { + case 0: + result = 0; + break; + case 1: + result = 1; + break; + case 2: + result = 2; + break; + default: + result = -2; + break; + } + return result;` + , util.dummyTypes.Number + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase(0, 1) + @TestCase(0, 1) + @TestCase(2, 4) + @TestCase(3, 4) + @TestCase(4, 4) + @TestCase(5, 15) + @TestCase(7, -2) + @Test("switchfallthrough") + public switchfallthrough(inp: number, expected: number) { + /// Transpile + let lua = util.transpileString( + `let result = -1; + + switch (${inp}) { + case 0: + result = 0; + case 1: + result = 1; + break; + case 2: + result = 2; + case 3: + case 4: + result = 4; + break; + case 5: + result = 5; + case 6: + result += 10; + break; + case 7: + result = 7; + default: + result = -2; + break; + } + return result;` + , util.dummyTypes.Number + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + +} diff --git a/test/integration/lua/modules.spec.ts b/test/integration/lua/modules.spec.ts index ff7dbcd7b..c54f39b02 100644 --- a/test/integration/lua/modules.spec.ts +++ b/test/integration/lua/modules.spec.ts @@ -132,7 +132,7 @@ export class LuaModuleTests { let lua = util.transpileString(inp, util.dummyTypes.Object); // Assert - // Dont test for correct indention this allows easier tes case definition + // Dont test for correct indention this allows easier test case definition Expect(dedent(lua)).toBe(dedent(expected)); } } diff --git a/test/src/util.ts b/test/src/util.ts index 751d81676..4ade2f44e 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -5,8 +5,10 @@ const LuaVM = require("lua.vm.js"); const fs = require("fs"); export namespace dummyTypes { + export const None = {}; export const Array = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Array" } }; - export const Object = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Object" } } + export const Object = { flags: ts.TypeFlags.Object, symbol: { escapedName: "Object" } }; + export const Number = { flags: ts.TypeFlags.Number, symbol: { escapedName: "Number" } }; } export function transpileString(str: string, dummyType: any): string { @@ -16,7 +18,7 @@ export function transpileString(str: string, dummyType: any): string { return result.trim(); } -export function executeLua(lua: string, withLib = true): string { +export function executeLua(lua: string, withLib = true): any { if (withLib) { lua = minimalTestLib + lua }