diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 214e8aaf0..0d28ebba2 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -61,6 +61,7 @@ export class LuaTranspiler { public importCount: number; public isModule: boolean; public sourceFile: ts.SourceFile; + public loopStack: number[]; constructor(checker: ts.TypeChecker, options: ts.CompilerOptions, sourceFile: ts.SourceFile) { this.indent = ""; @@ -72,6 +73,7 @@ export class LuaTranspiler { this.importCount = 0; this.sourceFile = sourceFile; this.isModule = tsHelper.isFileModule(sourceFile); + this.loopStack = []; } public pushIndent(): void { @@ -194,8 +196,7 @@ export class LuaTranspiler { case ts.SyntaxKind.ThrowStatement: return this.transpileThrow(node as ts.ThrowStatement); case ts.SyntaxKind.ContinueStatement: - // Disallow continue - throw new TranspileError("Continue is not supported in Lua", node); + return this.transpileContinue(); case ts.SyntaxKind.TypeAliasDeclaration: case ts.SyntaxKind.InterfaceDeclaration: case ts.SyntaxKind.EndOfFileToken: @@ -317,6 +318,10 @@ export class LuaTranspiler { } } + public transpileContinue(): string { + return this.indent + `goto __continue${this.loopStack[this.loopStack.length - 1]}\n`; + } + public transpileIf(node: ts.IfStatement): string { const condition = this.transpileExpression(node.expression); @@ -335,12 +340,30 @@ export class LuaTranspiler { return result + this.indent + "end\n"; } + public transpileLoopBody( + node: ts.WhileStatement + | ts.DoStatement + | ts.ForStatement + | 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`; + return result; + } + public transpileWhile(node: ts.WhileStatement): string { const condition = this.transpileExpression(node.expression); let result = this.indent + `while ${condition} do\n`; this.pushIndent(); - result += this.transpileStatement(node.statement); + result += this.transpileLoopBody(node); this.popIndent(); return result + this.indent + "end\n"; } @@ -349,7 +372,7 @@ export class LuaTranspiler { let result = this.indent + `repeat\n`; this.pushIndent(); - result += this.transpileStatement(node.statement); + result += this.transpileLoopBody(node); this.popIndent(); // Negate the expression because we translate from do-while to repeat-until (repeat-while-not) @@ -368,7 +391,7 @@ export class LuaTranspiler { // Add body this.pushIndent(); - result += this.transpileStatement(node.statement); + result += this.transpileLoopBody(node); result += this.indent + this.transpileExpression(node.incrementor) + "\n"; this.popIndent(); @@ -394,7 +417,7 @@ export class LuaTranspiler { // For body this.pushIndent(); - result += this.transpileStatement(node.statement); + result += this.transpileLoopBody(node); this.popIndent(); return result + this.indent + "end\n"; @@ -417,7 +440,7 @@ export class LuaTranspiler { // For body this.pushIndent(); - result += this.transpileStatement(node.statement); + result += this.transpileLoopBody(node); this.popIndent(); return result + this.indent + "end\n"; diff --git a/test/translation/lua/continue.lua b/test/translation/lua/continue.lua new file mode 100644 index 000000000..0c0f62812 --- /dev/null +++ b/test/translation/lua/continue.lua @@ -0,0 +1,10 @@ +local i = 0 +while(i<10) do + do + if i<5 then + goto __continue0 + end + end + ::__continue0:: + i=i+1 +end diff --git a/test/translation/lua/continueConcurrent.lua b/test/translation/lua/continueConcurrent.lua new file mode 100644 index 000000000..a41b64ada --- /dev/null +++ b/test/translation/lua/continueConcurrent.lua @@ -0,0 +1,13 @@ +local i = 0 +while(i<10) do + do + if i<5 then + goto __continue0 + end + if i==7 then + goto __continue0 + end + end + ::__continue0:: + i=i+1 +end diff --git a/test/translation/lua/continueNested.lua b/test/translation/lua/continueNested.lua new file mode 100644 index 000000000..fb9d08c89 --- /dev/null +++ b/test/translation/lua/continueNested.lua @@ -0,0 +1,20 @@ +local i = 0 +while(i<5) do + do + if (i%2)==0 then + goto __continue0 + end + local j = 0 + while(j<2) do + do + if j==1 then + goto __continue1 + end + end + ::__continue1:: + j=j+1 + end + end + ::__continue0:: + i=i+1 +end diff --git a/test/translation/lua/continueNestedConcurrent.lua b/test/translation/lua/continueNestedConcurrent.lua new file mode 100644 index 000000000..65c146739 --- /dev/null +++ b/test/translation/lua/continueNestedConcurrent.lua @@ -0,0 +1,23 @@ +local i = 0 +while(i<5) do + do + if (i%2)==0 then + goto __continue0 + end + local j = 0 + while(j<2) do + do + if j==1 then + goto __continue1 + end + end + ::__continue1:: + j=j+1 + end + if i==4 then + goto __continue0 + end + end + ::__continue0:: + i=i+1 +end diff --git a/test/translation/lua/do.lua b/test/translation/lua/do.lua new file mode 100644 index 000000000..87b49f201 --- /dev/null +++ b/test/translation/lua/do.lua @@ -0,0 +1,8 @@ +local e = 10 + +repeat + do + e=e-1 + end + ::__continue0:: +until not (e>0) diff --git a/test/translation/lua/for.lua b/test/translation/lua/for.lua new file mode 100644 index 000000000..0d9b20db4 --- /dev/null +++ b/test/translation/lua/for.lua @@ -0,0 +1,7 @@ +local i = 1 +while(i<=100) do + do + end + ::__continue0:: + i=i+1 +end diff --git a/test/translation/lua/forIn.lua b/test/translation/lua/forIn.lua new file mode 100644 index 000000000..9d1bd7bad --- /dev/null +++ b/test/translation/lua/forIn.lua @@ -0,0 +1,5 @@ +for i, _ in pairs({a = 1,b = 2,c = 3,d = 4}) do + do + end + ::__continue0:: +end diff --git a/test/translation/lua/forOf.lua b/test/translation/lua/forOf.lua new file mode 100644 index 000000000..79c0a9fed --- /dev/null +++ b/test/translation/lua/forOf.lua @@ -0,0 +1,5 @@ +for _, i in ipairs({1,2,3,4,5,6,7,8,9,10}) do + do + end + ::__continue0:: +end diff --git a/test/translation/lua/tupleArrayUses.lua b/test/translation/lua/tupleArrayUses.lua index 396b53995..eb5076124 100644 --- a/test/translation/lua/tupleArrayUses.lua +++ b/test/translation/lua/tupleArrayUses.lua @@ -1,4 +1,7 @@ for _, value in ipairs(tuple) do + do + end + ::__continue0:: end TS_forEach(tuple, function(v) end diff --git a/test/translation/lua/while.lua b/test/translation/lua/while.lua new file mode 100644 index 000000000..c9fb9ba85 --- /dev/null +++ b/test/translation/lua/while.lua @@ -0,0 +1,8 @@ +local d = 10 + +while d>0 do + do + d=d-1 + end + ::__continue0:: +end diff --git a/test/translation/ts/continue.ts b/test/translation/ts/continue.ts new file mode 100644 index 000000000..64830718d --- /dev/null +++ b/test/translation/ts/continue.ts @@ -0,0 +1,5 @@ +for (let i = 0; i < 10; i++) { + if (i < 5) { + continue; + } +} diff --git a/test/translation/ts/continueConcurrent.ts b/test/translation/ts/continueConcurrent.ts new file mode 100644 index 000000000..ef1b20c68 --- /dev/null +++ b/test/translation/ts/continueConcurrent.ts @@ -0,0 +1,9 @@ +for (let i = 0; i < 10; i++) { + if (i < 5) { + continue; + } + + if (i === 7) { + continue; + } +} diff --git a/test/translation/ts/continueNested.ts b/test/translation/ts/continueNested.ts new file mode 100644 index 000000000..563b7d0f1 --- /dev/null +++ b/test/translation/ts/continueNested.ts @@ -0,0 +1,11 @@ +for (let i = 0; i < 5; i++) { + if (i % 2 === 0) { + continue; + } + + for (let j = 0; j < 2; j++) { + if (j === 1) { + continue; + } + } +} diff --git a/test/translation/ts/continueNestedConcurrent.ts b/test/translation/ts/continueNestedConcurrent.ts new file mode 100644 index 000000000..fe0bd0966 --- /dev/null +++ b/test/translation/ts/continueNestedConcurrent.ts @@ -0,0 +1,15 @@ +for (let i = 0; i < 5; i++) { + if (i % 2 === 0) { + continue; + } + + for (let j = 0; j < 2; j++) { + if (j === 1) { + continue; + } + } + + if (i === 4) { + continue; + } +} diff --git a/test/translation/ts/do.ts b/test/translation/ts/do.ts new file mode 100644 index 000000000..62b1c0a65 --- /dev/null +++ b/test/translation/ts/do.ts @@ -0,0 +1,4 @@ +let e = 10 +do { + e--; +} while (e > 0) diff --git a/test/translation/ts/for.ts b/test/translation/ts/for.ts new file mode 100644 index 000000000..02d8156e2 --- /dev/null +++ b/test/translation/ts/for.ts @@ -0,0 +1,2 @@ +for (let i = 1; i <= 100; i++) { +} diff --git a/test/translation/ts/forIn.ts b/test/translation/ts/forIn.ts new file mode 100644 index 000000000..55e50cce7 --- /dev/null +++ b/test/translation/ts/forIn.ts @@ -0,0 +1,7 @@ +for (let i in { + a: 1, + b: 2, + c: 3, + d: 4 +}) { +} diff --git a/test/translation/ts/forOf.ts b/test/translation/ts/forOf.ts new file mode 100644 index 000000000..c017d5c0c --- /dev/null +++ b/test/translation/ts/forOf.ts @@ -0,0 +1,2 @@ +for (let i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) { +} diff --git a/test/translation/ts/while.ts b/test/translation/ts/while.ts new file mode 100644 index 000000000..6101bd4f2 --- /dev/null +++ b/test/translation/ts/while.ts @@ -0,0 +1,4 @@ +let d = 10; +while (d > 0) { + d--; +} diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index f3db44381..30aaa9941 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -5,19 +5,6 @@ const deepEqual = require('deep-equal') export class LuaLoopTests { - @Test("continue") - public continue(inp: number[], expected: number[]) { - // Transpile & Assert - Expect(() => { - let lua = util.transpileString( - `while (i < arrTest.length) { - continue; - }` - ); - }).toThrowError(Error, "Continue is not supported in Lua") - } - - @TestCase([0, 1, 2, 3], [1, 2, 3, 4]) @Test("while") public while(inp: number[], expected: number[]) { @@ -39,6 +26,74 @@ export class LuaLoopTests { Expect(result).toBe(JSON.stringify(expected)); } + @TestCase([0, 1, 2, 3, 4], [0, 1, 2, 1, 4]) + @Test("while with continue") + public whileWithContinue(inp: number[], expected: number[]) { + // Transpile + let lua = util.transpileString( + `let arrTest = ${JSON.stringify(inp)}; + let i = 0; + while (i < arrTest.length) { + if (i % 2 == 0) { + i++; + continue; + } + let j = 2; + while (j > 0) { + if (j == 2) { + j-- + continue; + } + arrTest[i] = j; + j--; + } + + i++; + } + return JSONStringify(arrTest);` + ); + + // Executre + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(JSON.stringify(expected)); + } + + @TestCase([0, 1, 2, 3, 4], [0, 1, 2, 1, 4]) + @Test("dowhile with continue") + public dowhileWithContinue(inp: number[], expected: number[]) { + // Transpile + let lua = util.transpileString( + `let arrTest = ${JSON.stringify(inp)}; + let i = 0; + do { + if (i % 2 == 0) { + i++; + continue; + } + let j = 2; + do { + if (j == 2) { + j-- + continue; + } + arrTest[i] = j; + j--; + } while (j > 0) + + i++; + } while (i < arrTest.length) + return JSONStringify(arrTest);` + ); + + // Executre + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(JSON.stringify(expected)); + } + @TestCase([0, 1, 2, 3], [1, 2, 3, 4]) @Test("for") public for(inp: number[], expected: number[]) { @@ -58,6 +113,35 @@ export class LuaLoopTests { Expect(result).toBe(JSON.stringify(expected)); } + @TestCase([0, 1, 2, 3, 4], [0, 0, 2, 0, 4]) + @Test("for with continue") + public forWithContinue(inp: number[], expected: number[]) { + // Transpile + let lua = util.transpileString( + `let arrTest = ${JSON.stringify(inp)}; + for (let i = 0; i < arrTest.length; i++) { + if (i % 2 == 0) { + continue; + } + + for (let j = 0; j < 2; j++) { + if (j == 1) { + continue; + } + arrTest[i] = j; + } + } + return JSONStringify(arrTest); + ` + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(JSON.stringify(expected)); + } + @TestCase([0, 1, 2, 3], [1, 2, 3, 4]) @Test("forMirror") public forMirror(inp: number[], expected: number[]) { @@ -156,6 +240,30 @@ export class LuaLoopTests { }).toThrowError(Error, "Iterating over arrays with 'for in' is not allowed."); } + @TestCase({a: 0, b: 1, c: 2, d: 3, e: 4}, {a: 0, b: 0, c: 2, d: 0, e: 4}) + @Test("forin with continue") + public forinWithContinue(inp: number[], expected: number[]) { + // Transpile + let lua = util.transpileString( + `let obj = ${JSON.stringify(inp)}; + for (let i in obj) { + if (obj[i] % 2 == 0) { + continue; + } + + obj[i] = 0; + } + return JSONStringify(obj); + ` + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(JSON.stringify(expected)); + } + @TestCase([0, 1, 2], [1, 2, 3]) @Test("forof") public forof(inp: any, expected: any) { @@ -176,4 +284,35 @@ export class LuaLoopTests { Expect(result).toBe(JSON.stringify(expected)); } + @TestCase([0, 1, 2, 3, 4], [0, 0, 2, 0, 4]) + @Test("forof with continue") + public forofWithContinue(inp: number[], expected: number[]) { + // Transpile + let lua = util.transpileString( + `let testArr = ${JSON.stringify(inp)}; + let a = 0; + for (let i of testArr) { + if (i % 2 == 0) { + a++; + continue; + } + + for (let j of [0, 1]) { + if (j == 1) { + continue; + } + testArr[a] = j; + } + a++; + } + return JSONStringify(testArr);` + ); + + // Execute + let result = util.executeLua(lua); + + // Assert + Expect(result).toBe(JSON.stringify(expected)); + } + } diff --git a/test/unit/while.spec.ts b/test/unit/while.spec.ts deleted file mode 100644 index 29f9e3295..000000000 --- a/test/unit/while.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Expect, Test, TestCase, FocusTest } from "alsatian"; -import * as util from "../src/util"; - -export class WhileTests { - - @Test("While loop") - public defaultWhile() { - const input = `declare var a: number; - while (a < 10) { - a++; - }`; - - const expected = `while a<10 do - a=a+1 - end`; - - util.expectCodeEqual(util.transpileString(input), expected); - } - - @Test("Do While") - public doWhile() { - const input = `declare var a: number; - do { - a++; - } while (a < 10);`; - - const expected = `repeat - a=a+1 - until not (a<10)`; - - util.expectCodeEqual(util.transpileString(input), expected); - } -}