diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 9fc558448..0df3f10ed 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1817,6 +1817,14 @@ export class LuaTransformer { ); } + if (!ts.isCallLikeExpression(expression)) { + // Assign expression statements to dummy to make sure they're legal lua + return tstl.createVariableDeclarationStatement( + tstl.createAnnonymousIdentifier(), + this.transformExpression(expression) + ); + } + return tstl.createExpressionStatement(this.transformExpression(expression)); } @@ -3481,7 +3489,10 @@ export class LuaTransformer { const logCall1 = tstl.createCallExpression(log1, params); const e = tstl.createNumericLiteral(expressionName === "log10" ? Math.LN10 : Math.LN2); const div = tstl.createBinaryExpression(logCall1, e, tstl.SyntaxKind.DivisionOperator); - return tstl.createParenthesizedExpression(div, node); + return ts.isExpressionStatement(node.parent) + // if used as a stand-alone statement, needs to be a call expression to be valid lua + ? this.createImmediatelyInvokedFunctionExpression([], div, node) + : tstl.createParenthesizedExpression(div, node); } // math.log(1 + x) @@ -4235,7 +4246,8 @@ export class LuaTransformer { { const body = statements ? statements.slice(0) : []; body.push(tstl.createReturnStatement(Array.isArray(result) ? result : [result])); - const iife = tstl.createFunctionExpression(tstl.createBlock(body)); + const flags = statements.length === 0 ? tstl.FunctionExpressionFlags.Inline : tstl.FunctionExpressionFlags.None; + const iife = tstl.createFunctionExpression(tstl.createBlock(body), undefined, undefined, undefined, flags); return tstl.createCallExpression(tstl.createParenthesizedExpression(iife), [], tsOriginal); } diff --git a/test/unit/compiler/configuration/options.spec.ts b/test/unit/compiler/configuration/options.spec.ts index 47bf9bedf..a26532c7c 100644 --- a/test/unit/compiler/configuration/options.spec.ts +++ b/test/unit/compiler/configuration/options.spec.ts @@ -5,7 +5,7 @@ test.each([LuaTarget.LuaJIT, "jit", "JiT"])("Options luaTarget case-insensitive const options = { luaTarget: target as LuaTarget }; const result = util.transpileString("~a", options); - expect(result).toBe("bit.bnot(a);"); + expect(result).toBe("local ____ = bit.bnot(a);"); }); test.each([LuaLibImportKind.None, "none", "NoNe"])( diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 072accf9b..f2f820328 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -8,9 +8,9 @@ test.each([ { input: "++i", lua: "i = i + 1;" }, { input: "i--", lua: "i = i - 1;" }, { input: "--i", lua: "i = i - 1;" }, - { input: "!a", lua: "not a;" }, - { input: "-a", lua: "-a;" }, - { input: "+a", lua: "a;" }, + { input: "!a", lua: "local ____ = not a;" }, + { input: "-a", lua: "local ____ = -a;" }, + { input: "+a", lua: "local ____ = a;" }, { input: "let a = delete tbl['test']", lua: "local a = (function()\n tbl.test = nil;\n return true;\nend)();", @@ -106,18 +106,18 @@ test.each([ }); test.each([ - { input: "~a", lua: "bit.bnot(a);" }, - { input: "a&b", lua: "bit.band(a, b);" }, + { input: "~a", lua: "local ____ = bit.bnot(a);" }, + { input: "a&b", lua: "local ____ = bit.band(a, b);" }, { input: "a&=b", lua: "a = bit.band(a, b);" }, - { input: "a|b", lua: "bit.bor(a, b);" }, + { input: "a|b", lua: "local ____ = bit.bor(a, b);" }, { input: "a|=b", lua: "a = bit.bor(a, b);" }, - { input: "a^b", lua: "bit.bxor(a, b);" }, + { input: "a^b", lua: "local ____ = bit.bxor(a, b);" }, { input: "a^=b", lua: "a = bit.bxor(a, b);" }, - { input: "a<>b", lua: "bit.arshift(a, b);" }, + { input: "a>>b", lua: "local ____ = bit.arshift(a, b);" }, { input: "a>>=b", lua: "a = bit.arshift(a, b);" }, - { input: "a>>>b", lua: "bit.rshift(a, b);" }, + { input: "a>>>b", lua: "local ____ = bit.rshift(a, b);" }, { input: "a>>>=b", lua: "a = bit.rshift(a, b);" }, ])("Bitop [JIT] (%p)", ({ input, lua }) => { const options = { luaTarget: LuaTarget.LuaJIT, luaLibImport: LuaLibImportKind.None }; @@ -125,18 +125,18 @@ test.each([ }); test.each([ - { input: "~a", lua: "bit32.bnot(a);" }, - { input: "a&b", lua: "bit32.band(a, b);" }, + { input: "~a", lua: "local ____ = bit32.bnot(a);" }, + { input: "a&b", lua: "local ____ = bit32.band(a, b);" }, { input: "a&=b", lua: "a = bit32.band(a, b);" }, - { input: "a|b", lua: "bit32.bor(a, b);" }, + { input: "a|b", lua: "local ____ = bit32.bor(a, b);" }, { input: "a|=b", lua: "a = bit32.bor(a, b);" }, - { input: "a^b", lua: "bit32.bxor(a, b);" }, + { input: "a^b", lua: "local ____ = bit32.bxor(a, b);" }, { input: "a^=b", lua: "a = bit32.bxor(a, b);" }, - { input: "a<>b", lua: "bit32.arshift(a, b);" }, + { input: "a>>b", lua: "local ____ = bit32.arshift(a, b);" }, { input: "a>>=b", lua: "a = bit32.arshift(a, b);" }, - { input: "a>>>b", lua: "bit32.rshift(a, b);" }, + { input: "a>>>b", lua: "local ____ = bit32.rshift(a, b);" }, { input: "a>>>=b", lua: "a = bit32.rshift(a, b);" }, ])("Bitop [5.2] (%p)", ({ input, lua }) => { const options = { luaTarget: LuaTarget.Lua52, luaLibImport: LuaLibImportKind.None }; @@ -144,16 +144,16 @@ test.each([ }); test.each([ - { input: "~a", lua: "~a;" }, - { input: "a&b", lua: "a & b;" }, + { input: "~a", lua: "local ____ = ~a;" }, + { input: "a&b", lua: "local ____ = a & b;" }, { input: "a&=b", lua: "a = a & b;" }, - { input: "a|b", lua: "a | b;" }, + { input: "a|b", lua: "local ____ = a | b;" }, { input: "a|=b", lua: "a = a | b;" }, - { input: "a^b", lua: "a ~ b;" }, + { input: "a^b", lua: "local ____ = a ~ b;" }, { input: "a^=b", lua: "a = a ~ b;" }, - { input: "a<>>b", lua: "a >> b;" }, + { input: "a>>>b", lua: "local ____ = a >> b;" }, { input: "a>>>=b", lua: "a = a >> b;" }, ])("Bitop [5.3] (%p)", ({ input, lua }) => { const options = { luaTarget: LuaTarget.Lua53, luaLibImport: LuaLibImportKind.None }; @@ -183,7 +183,7 @@ test.each([ { input: "1*(3+4*2)", lua: "1 * (3 + 4 * 2);" }, { input: "10-(4+5)", lua: "10 - (4 + 5);" }, ])("Binary expressions ordering parentheses (%p)", ({ input, lua }) => { - expect(util.transpileString(input)).toBe(lua); + expect(util.transpileString(input)).toBe("local ____ = " + lua); }); test.each([ @@ -208,11 +208,11 @@ test("Binary Comma Statement in For Loop", () => { }); test("Null Expression", () => { - expect(util.transpileString("null")).toBe("nil;"); + expect(util.transpileString("null")).toBe("local ____ = nil;"); }); test("Undefined Expression", () => { - expect(util.transpileString("undefined")).toBe("nil;"); + expect(util.transpileString("undefined")).toBe("local ____ = nil;"); }); test.each([ @@ -553,3 +553,28 @@ test("Unsupported object literal element error", () => { ), ); }); + +test.each([ + '"foobar"', + "17", + "true", + "{}", + "[]", + "[].length", + "foo() + foo()", + "!foo()", + "foo()", + "typeof foo", + '"bar" in bar', + "foo as Function", + "Math.log2(2)", + "Math.log10(2)", +])("Expression statements (%p)", input => { + const code = ` + function foo() { return 17; } + const bar = {}; + ${input}; + return 1; + `; + expect(util.transpileAndExecute(code)).toBe(1); +}); diff --git a/test/unit/math.spec.ts b/test/unit/math.spec.ts index f44429f7f..fefd4f310 100644 --- a/test/unit/math.spec.ts +++ b/test/unit/math.spec.ts @@ -5,11 +5,13 @@ test.each([ { inp: "Math.sin()", expected: "math.sin();" }, { inp: "Math.min()", expected: "math.min();" }, { inp: "Math.atan2(2, 3)", expected: "math.atan(2 / 3);" }, - { inp: "Math.log2(3)", expected: `(math.log(3) / ${Math.LN2});` }, - { inp: "Math.log10(3)", expected: `(math.log(3) / ${Math.LN10});` }, + { inp: "Math.log2(3)", expected: `(function() return math.log(3) / ${Math.LN2}; end)();` }, + { inp: "Math.log10(3)", expected: `(function() return math.log(3) / ${Math.LN10}; end)();` }, + { inp: "const x = Math.log2(3)", expected: `local x = (math.log(3) / ${Math.LN2});` }, + { inp: "const x = Math.log10(3)", expected: `local x = (math.log(3) / ${Math.LN10});` }, { inp: "Math.log1p(3)", expected: "math.log(1 + 3);" }, { inp: "Math.round(3.3)", expected: "math.floor(3.3 + 0.5);" }, - { inp: "Math.PI", expected: "math.pi;" }, + { inp: "Math.PI", expected: "local ____ = math.pi;" }, ])("Math (%p)", ({ inp, expected }) => { const lua = util.transpileString(inp); diff --git a/test/unit/spreadElement.spec.ts b/test/unit/spreadElement.spec.ts index 42559ec2a..dcdc84c5f 100644 --- a/test/unit/spreadElement.spec.ts +++ b/test/unit/spreadElement.spec.ts @@ -21,17 +21,17 @@ test("Spread Element Lua 5.1", () => { test("Spread Element Lua 5.2", () => { const options = { luaTarget: LuaTarget.Lua52, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); - expect(lua).toBe("{table.unpack({\n 0,\n 1,\n 2,\n})};"); + expect(lua).toBe("local ____ = {table.unpack({\n 0,\n 1,\n 2,\n})};"); }); test("Spread Element Lua 5.3", () => { const options = { luaTarget: LuaTarget.Lua53, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); - expect(lua).toBe("{table.unpack({\n 0,\n 1,\n 2,\n})};"); + expect(lua).toBe("local ____ = {table.unpack({\n 0,\n 1,\n 2,\n})};"); }); test("Spread Element Lua JIT", () => { const options = { luaTarget: "JiT" as LuaTarget, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[...[0, 1, 2]]`, options); - expect(lua).toBe("{unpack({\n 0,\n 1,\n 2,\n})};"); + expect(lua).toBe("local ____ = {unpack({\n 0,\n 1,\n 2,\n})};"); });