From d646b082802e47521cc2e65dea975f8d606f7c41 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 30 Mar 2019 15:35:47 -0600 Subject: [PATCH 01/13] removed empty string literals from template strings and unneeded parenthesis from expressions --- src/LuaPrinter.ts | 23 +---- src/LuaTransformer.ts | 94 ++++++++++++------- .../__snapshots__/transformation.spec.ts.snap | 10 +- test/unit/expressions.spec.ts | 6 +- 4 files changed, 72 insertions(+), 61 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index b178a306a..e4a095570 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -337,39 +337,24 @@ export class LuaPrinter { } private printUnaryExpression(expression: tstl.UnaryExpression): string { - const operand = this.needsParentheses(expression.operand) - ? `(${this.printExpression(expression.operand)})` - : this.printExpression(expression.operand); + const operand = this.printExpression(expression.operand); return `${this.printOperator(expression.operator)}${operand}`; } private printBinaryExpression(expression: tstl.BinaryExpression): string { - const left = this.needsParentheses(expression.left) - ? `(${this.printExpression(expression.left)})` - : this.printExpression(expression.left); - - const right = this.needsParentheses(expression.right) - ? `(${this.printExpression(expression.right)})` - : this.printExpression(expression.right); - + const left = this.printExpression(expression.left); + const right = this.printExpression(expression.right); const operator = this.printOperator(expression.operator); return `${left} ${operator} ${right}`; } - private needsParentheses(expression: tstl.Expression): boolean { - return tstl.isBinaryExpression(expression) || tstl.isUnaryExpression(expression) - || tstl.isFunctionExpression(expression); - } - private printParenthesizedExpression(expression: tstl.ParenthesizedExpression): string { return `(${this.printExpression(expression.innerEpxression)})`; } private printCallExpression(expression: tstl.CallExpression): string { const params = expression.params ? expression.params.map(e => this.printExpression(e)).join(", ") : ""; - return this.needsParentheses(expression.expression) - ? `(${this.printExpression(expression.expression)})(${params})` - : `${this.printExpression(expression.expression)}(${params})`; + return `${this.printExpression(expression.expression)}(${params})`; } private printMethodCallExpression(expression: tstl.MethodCallExpression): string { diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 3a7d8dcad..65cad80c9 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1851,7 +1851,10 @@ export class LuaTransformer { public transformDoStatement(statement: ts.DoStatement): tstl.RepeatStatement { return tstl.createRepeatStatement( tstl.createBlock(this.transformLoopBody(statement)), - tstl.createUnaryExpression(this.transformExpression(statement.expression), tstl.SyntaxKind.NotOperator), + tstl.createUnaryExpression( + tstl.createParenthesizedExpression(this.transformExpression(statement.expression)), + tstl.SyntaxKind.NotOperator + ), statement ); } @@ -2204,7 +2207,10 @@ export class LuaTransformer { result.push(catchAssignment); - const notTryResult = tstl.createUnaryExpression(tryResult, tstl.SyntaxKind.NotOperator); + const notTryResult = tstl.createUnaryExpression( + tstl.createParenthesizedExpression(tryResult), + tstl.SyntaxKind.NotOperator + ); result.push(tstl.createIfStatement(notTryResult, this.transformBlock(statement.catchClause.block))); } else { @@ -2859,10 +2865,10 @@ export class LuaTransformer { const val1Function = this.wrapInFunctionCall(val1); const val2Function = this.wrapInFunctionCall(val2); - // ((condition and (() => v1)) or (() => v2))() + // (condition and (() => v1) or (() => v2))() const conditionAnd = tstl.createBinaryExpression(condition, val1Function, tstl.SyntaxKind.AndOperator); const orExpression = tstl.createBinaryExpression(conditionAnd, val2Function, tstl.SyntaxKind.OrOperator); - return tstl.createCallExpression(orExpression, [], expression); + return tstl.createCallExpression(tstl.createParenthesizedExpression(orExpression), [], expression); } public transformConditionalExpression(expression: ts.ConditionalExpression): tstl.Expression { @@ -2874,7 +2880,7 @@ export class LuaTransformer { const val1 = this.transformExpression(expression.whenTrue); const val2 = this.transformExpression(expression.whenFalse); - // (condition and v1) or v2 + // condition and v1 or v2 const conditionAnd = tstl.createBinaryExpression(condition, val1, tstl.SyntaxKind.AndOperator); return tstl.createBinaryExpression( conditionAnd, @@ -3230,8 +3236,10 @@ export class LuaTransformer { parameters = this.transformArguments(node.arguments, signature); const rawGetIdentifier = tstl.createIdentifier("rawget"); const rawGetCall = tstl.createCallExpression(rawGetIdentifier, [expr, ...parameters]); - return tstl.createBinaryExpression( - rawGetCall, tstl.createNilLiteral(), tstl.SyntaxKind.InequalityOperator, node); + return tstl.createParenthesizedExpression( + tstl.createBinaryExpression( + rawGetCall, tstl.createNilLiteral(), tstl.SyntaxKind.InequalityOperator, node) + ); } else { const parameters = this.transformArguments(node.arguments, signature); const table = this.transformExpression(node.expression.expression); @@ -3567,20 +3575,24 @@ export class LuaTransformer { node.arguments.length === 1 ? this.createStringCall("find", node, caller, params[0]) : this.createStringCall( - "find", node, caller, params[0], - this.expressionPlusOne(params[1]), - tstl.createBooleanLiteral(true) + "find", node, caller, params[0], + this.expressionPlusOne(params[1]), + tstl.createBooleanLiteral(true) ); - return tstl.createBinaryExpression( + return tstl.createParenthesizedExpression( tstl.createBinaryExpression( - stringExpression, - tstl.createNumericLiteral(0), - tstl.SyntaxKind.OrOperator - ), - tstl.createNumericLiteral(1), - tstl.SyntaxKind.SubractionOperator, - node + tstl.createParenthesizedExpression( + tstl.createBinaryExpression( + stringExpression, + tstl.createNumericLiteral(0), + tstl.SyntaxKind.OrOperator + ) + ), + tstl.createNumericLiteral(1), + tstl.SyntaxKind.SubractionOperator, + node + ) ); case "substr": if (node.arguments.length === 1) { @@ -3589,7 +3601,11 @@ export class LuaTransformer { } else { const arg1 = params[0]; const arg2 = params[1]; - const sumArg = tstl.createBinaryExpression(arg1, arg2, tstl.SyntaxKind.AdditionOperator); + const sumArg = tstl.createBinaryExpression( + tstl.createParenthesizedExpression(arg1), + arg2, + tstl.SyntaxKind.AdditionOperator + ); return this.createStringCall("sub", node, caller, this.expressionPlusOne(arg1), sumArg); } case "substring": @@ -3951,11 +3967,13 @@ export class LuaTransformer { const condition = tstl.createBinaryExpression(typeCall, tableString, tstl.SyntaxKind.EqualityOperator); const andClause = tstl.createBinaryExpression(condition, objectString, tstl.SyntaxKind.AndOperator); - return tstl.createBinaryExpression( - andClause, - tstl.cloneNode(typeCall), - tstl.SyntaxKind.OrOperator, - node + return tstl.createParenthesizedExpression( + tstl.createBinaryExpression( + andClause, + tstl.cloneNode(typeCall), + tstl.SyntaxKind.OrOperator, + node + ) ); } @@ -3994,24 +4012,29 @@ export class LuaTransformer { return this.createSelfIdentifier(thisKeyword); } - public transformTemplateExpression(expression: ts.TemplateExpression): tstl.BinaryExpression { - const parts: tstl.Expression[] = [tstl.createStringLiteral(tsHelper.escapeString(expression.head.text))]; + public transformTemplateExpression(expression: ts.TemplateExpression): tstl.Expression { + const parts: tstl.Expression[] = []; + const head = tsHelper.escapeString(expression.head.text); + if (head.length > 0) { + parts.push(tstl.createStringLiteral(head, expression.head)); + } expression.templateSpans.forEach(span => { const expr = this.transformExpression(span.expression); - const text = tstl.createStringLiteral(tsHelper.escapeString(span.literal.text)); + parts.push(tstl.createCallExpression(tstl.createIdentifier("tostring"), [expr])); - // tostring(expr).."text" - parts.push(tstl.createBinaryExpression( - tstl.createCallExpression(tstl.createIdentifier("tostring"), [expr]), - text, - tstl.SyntaxKind.ConcatOperator) - ); + const text = tsHelper.escapeString(span.literal.text); + if (text.length > 0) { + parts.push(tstl.createStringLiteral(text, span.literal)); + } }); + if (parts.length === 1) { + return parts[0]; + } return parts.reduce((prev, current) => tstl.createBinaryExpression( prev, current, tstl.SyntaxKind.ConcatOperator) - ) as tstl.BinaryExpression; + ); } public transformPropertyName(propertyName: ts.PropertyName): tstl.Expression { @@ -4401,6 +4424,9 @@ export class LuaTransformer { } private expressionPlusOne(expression: tstl.Expression): tstl.BinaryExpression { + if (tstl.isBinaryExpression(expression)) { + expression = tstl.createParenthesizedExpression(expression); + } return tstl.createBinaryExpression(expression, tstl.createNumericLiteral(1), tstl.SyntaxKind.AdditionOperator); } diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 9c88af876..4595d0e21 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -14,7 +14,7 @@ local backQuoteInTemplateString = \\"\` \` \`\\"; local escapedCharsInQuotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" \\\\' \`\\"; local escapedCharsInDoubleQUotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" \\\\'\\"; local escapedCharsInTemplateString = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" \\\\' \`\\"; -local nonEmptyTemplateString = \\"Level 0: \\\\n\\\\t \\" .. (tostring(\\"Level 1: \\\\n\\\\t\\\\t \\" .. (tostring(\\"Level 3: \\\\n\\\\t\\\\t\\\\t \\" .. (tostring(\\"Last level \\\\n --\\") .. \\" \\\\n --\\")) .. \\" \\\\n --\\")) .. \\" \\\\n --\\");" +local nonEmptyTemplateString = \\"Level 0: \\\\n\\\\t \\" .. tostring(\\"Level 1: \\\\n\\\\t\\\\t \\" .. tostring(\\"Level 3: \\\\n\\\\t\\\\t\\\\t \\" .. tostring(\\"Last level \\\\n --\\") .. \\" \\\\n --\\") .. \\" \\\\n --\\") .. \\" \\\\n --\\";" `; exports[`Transformation (classExtension1) 1`] = ` @@ -94,7 +94,7 @@ exports[`Transformation (continueNested) 1`] = ` local i = 0; while i < 5 do do - if (i % 2) == 0 then + if i % 2 == 0 then goto __continue1; end do @@ -121,7 +121,7 @@ exports[`Transformation (continueNestedConcurrent) 1`] = ` local i = 0; while i < 5 do do - if (i % 2) == 0 then + if i % 2 == 0 then goto __continue1; end do @@ -560,7 +560,7 @@ exports[`Transformation (tryCatch) 1`] = ` local ____TS_try, er = pcall(function() local a = 42; end); - if not ____TS_try then + if not (____TS_try) then local b = \\"fail\\"; end end" @@ -571,7 +571,7 @@ exports[`Transformation (tryCatchFinally) 1`] = ` local ____TS_try, er = pcall(function() local a = 42; end); - if not ____TS_try then + if not (____TS_try) then local b = \\"fail\\"; end do diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index e1acbfa18..486f8f3b3 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -177,10 +177,10 @@ test.each(["a>>b", "a>>=b"])("Unsupported bitop 5.3 (%p)", input => { test.each([ { input: "1+1", lua: "1 + 1;" }, - { input: "-1+1", lua: "(-1) + 1;" }, - { input: "1*30+4", lua: "(1 * 30) + 4;" }, + { input: "-1+1", lua: "-1 + 1;" }, + { input: "1*30+4", lua: "1 * 30 + 4;" }, { input: "1*(3+4)", lua: "1 * (3 + 4);" }, - { input: "1*(3+4*2)", lua: "1 * (3 + (4 * 2));" }, + { input: "1*(3+4*2)", lua: "1 * (3 + 4 * 2);" }, ])("Binary expressions ordering parentheses (%p)", ({ input, lua }) => { expect(util.transpileString(input)).toBe(lua); }); From 84c3765b1e059cb717bcb0b9da856d615b706d07 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sun, 31 Mar 2019 07:22:16 -0600 Subject: [PATCH 02/13] stripping parenthesis from casts --- src/LuaTransformer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 65cad80c9..ac966142a 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3078,6 +3078,10 @@ export class LuaTransformer { } public transformParenthesizedExpression(expression: ts.ParenthesizedExpression): tstl.Expression { + if (ts.isAssertionExpression(expression.expression)) { + // Strip parenthesis from casts + return this.transformExpression(expression.expression); + } return tstl.createParenthesizedExpression( this.transformExpression(expression.expression), expression From 7dc00f86c2d6a1aee31931c2789d333e048dd0e1 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Mon, 1 Apr 2019 06:09:15 -0600 Subject: [PATCH 03/13] fixed indenting --- src/LuaTransformer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ac966142a..ee879720b 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3579,10 +3579,10 @@ export class LuaTransformer { node.arguments.length === 1 ? this.createStringCall("find", node, caller, params[0]) : this.createStringCall( - "find", node, caller, params[0], - this.expressionPlusOne(params[1]), - tstl.createBooleanLiteral(true) - ); + "find", node, caller, params[0], + this.expressionPlusOne(params[1]), + tstl.createBooleanLiteral(true) + ); return tstl.createParenthesizedExpression( tstl.createBinaryExpression( From 5f80e1eb311598095be74b9fc916a37a3ea8df35 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Mon, 1 Apr 2019 13:33:57 -0600 Subject: [PATCH 04/13] additional binary expression test --- test/unit/expressions.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 486f8f3b3..072accf9b 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -181,6 +181,7 @@ test.each([ { input: "1*30+4", lua: "1 * 30 + 4;" }, { input: "1*(3+4)", lua: "1 * (3 + 4);" }, { 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); }); From 553dc28e2ecd4ecaeac691ec303ba91334c7541e Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Tue, 2 Apr 2019 07:29:24 -0600 Subject: [PATCH 05/13] function cleanup - using `function foo()` syntax instead of `foo = function()` - Exception made for assigned function expressions - inlining arrow functions with no body - also stripping empty do...end statements --- src/LuaAST.ts | 17 +- src/LuaPrinter.ts | 49 ++++- src/LuaTransformer.ts | 64 +++--- .../__snapshots__/transformation.spec.ts.snap | 187 ++++++++---------- 4 files changed, 176 insertions(+), 141 deletions(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index 58534b996..9de74c624 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -575,20 +575,19 @@ export function createStringLiteral(value: string | ts.__String, tsOriginal?: ts return expression; } -// There is no export function statement/declaration because those are just syntax sugar -// -// `function f () body end` becomes `f = function () body` end -// `function t.a.b.c.f () body end` becomes `t.a.b.c.f = function () body end` -// `local function f () body end` becomes `local f; f = function () body end` NOT `local f = function () body end` -// See https://www.lua.org/manual/5.3/manual.html 3.4.11 -// -// We should probably create helper functions to create the different export function declarations +export enum FunctionExpressionFlags { + None = 0x0, + Inline = 0x1, // Keep function on same line + Expression = 0x2, // Prefer assignment to expression syntax `foo = function()` instead of `function foo()` +} + export interface FunctionExpression extends Expression { kind: SyntaxKind.FunctionExpression; params?: Identifier[]; dots?: DotsLiteral; restParamName?: Identifier; body: Block; + flags: FunctionExpressionFlags; } export function isFunctionExpression(node: Node): node is FunctionExpression { @@ -600,6 +599,7 @@ export function createFunctionExpression( params?: Identifier[], dots?: DotsLiteral, restParamName?: Identifier, + flags = FunctionExpressionFlags.None, tsOriginal?: ts.Node, parent?: Node ): FunctionExpression @@ -613,6 +613,7 @@ export function createFunctionExpression( expression.dots = dots; setParent(restParamName, expression); expression.restParamName = restParamName; + expression.flags = flags; return expression; } diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index e4a095570..0fd8beaf6 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -3,6 +3,19 @@ import * as tstl from "./LuaAST"; import { LuaLib, LuaLibFeature } from "./LuaLib"; import { TSHelper as tsHelper } from "./TSHelper"; +type FunctionDefinition = (tstl.VariableDeclarationStatement | tstl.AssignmentStatement) & { + right: [tstl.FunctionExpression]; +}; + +function isFunctionDeclaration(statement: tstl.VariableDeclarationStatement | tstl.AssignmentStatement) + : statement is FunctionDefinition +{ + return statement.left.length === 1 + && statement.right + && statement.right.length === 1 + && tstl.isFunctionExpression(statement.right[0]); +} + export class LuaPrinter { /* tslint:disable:object-literal-sort-keys */ private static operatorMap: {[key in tstl.Operator]: string} = { @@ -115,6 +128,10 @@ export class LuaPrinter { } private printDoStatement(statement: tstl.DoStatement): string { + if (!statement.statements || statement.statements.length === 0) { + return ""; + } + let result = this.indent("do\n"); this.pushIndent(); result += this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s)).join(""); @@ -125,6 +142,11 @@ export class LuaPrinter { } private printVariableDeclarationStatement(statement: tstl.VariableDeclarationStatement): string { + if (isFunctionDeclaration(statement)) { + const name = this.printExpression(statement.left[0]); + return this.indent(`local ${this.printFunctionExpression(statement.right[0], name)}\n`); + } + const left = this.indent(`local ${statement.left.map(e => this.printExpression(e)).join(", ")}`); if (statement.right) { return left + ` = ${statement.right.map(e => this.printExpression(e)).join(", ")};\n`; @@ -134,6 +156,15 @@ export class LuaPrinter { } private printVariableAssignmentStatement(statement: tstl.AssignmentStatement): string { + if (isFunctionDeclaration(statement) + && (statement.right[0].flags & tstl.FunctionExpressionFlags.Expression) === 0) + { + const name = this.printExpression(statement.left[0]); + if (!name.match(/[^A-Za-z0-9_\.]/)) { + return this.indent(`${this.printFunctionExpression(statement.right[0], name)}\n`); + } + } + return this.indent( `${statement.left.map(e => this.printExpression(e)).join(", ")} = ` + `${statement.right.map(e => this.printExpression(e)).join(", ")};\n`); @@ -299,17 +330,23 @@ export class LuaPrinter { } } - private printFunctionExpression(expression: tstl.FunctionExpression): string { + private printFunctionExpression(expression: tstl.FunctionExpression, name?: string): string { const paramterArr: string[] = expression.params ? expression.params.map(i => this.printIdentifier(i)) : []; if (expression.dots) { paramterArr.push(this.printDotsLiteral(expression.dots)); } - let result = `function(${paramterArr.join(", ")})\n`; - this.pushIndent(); - result += this.printBlock(expression.body); - this.popIndent(); - result += this.indent("end"); + let result = `function${name ? ` ${name}` : ""}(${paramterArr.join(", ")})`; + + if ((expression.flags & tstl.FunctionExpressionFlags.Inline) !== 0) { + result += ` ${this.printBlock(expression.body).trim()} end`; + + } else { + this.pushIndent(); + result += "\n" + this.printBlock(expression.body); + this.popIndent(); + result += this.indent("end"); + } return result; } diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ee879720b..31e4fe360 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -25,7 +25,7 @@ interface SymbolInfo { interface FunctionDefinitionInfo { referencedSymbols: Set; - assignment?: tstl.AssignmentStatement; + definition?: tstl.VariableDeclarationStatement | tstl.AssignmentStatement; } interface Scope { @@ -882,6 +882,7 @@ export class LuaTransformer { undefined, tstl.createDotsLiteral(), undefined, + tstl.FunctionExpressionFlags.None, statement ) ); @@ -1665,7 +1666,7 @@ export class LuaTransformer { table = this.transformIdentifier(statement.initializer); } else { // Contain the expression in a temporary variable - table = tstl.createIdentifier("____"); + table = tstl.createAnnonymousIdentifier(); statements.push(tstl.createVariableDeclarationStatement( table, this.transformExpression(statement.initializer))); } @@ -2297,9 +2298,8 @@ export class LuaTransformer { case ts.SyntaxKind.DeleteExpression: return this.transformDeleteExpression(expression as ts.DeleteExpression); case ts.SyntaxKind.FunctionExpression: - return this.transformFunctionExpression(expression as ts.ArrowFunction, this.createSelfIdentifier()); case ts.SyntaxKind.ArrowFunction: - return this.transformFunctionExpression(expression as ts.ArrowFunction, tstl.createIdentifier("____")); + return this.transformFunctionExpression(expression as ts.ArrowFunction); case ts.SyntaxKind.NewExpression: return this.transformNewExpression(expression as ts.NewExpression); case ts.SyntaxKind.ParenthesizedExpression: @@ -2984,7 +2984,7 @@ export class LuaTransformer { const identifier = this.transformIdentifier(element.name); properties.push(tstl.createTableFieldExpression(identifier, name, element)); } else if (ts.isMethodDeclaration(element)) { - const expression = this.transformFunctionExpression(element, this.createSelfIdentifier()); + const expression = this.transformFunctionExpression(element); properties.push(tstl.createTableFieldExpression(expression, name, element)); } else { throw TSTLErrors.UnsupportedKind("object literal element", element.kind, node); @@ -3009,18 +3009,28 @@ export class LuaTransformer { ); } - public transformFunctionExpression( - node: ts.FunctionLikeDeclaration, - context: tstl.Identifier | undefined - ): ExpressionVisitResult - { + public transformFunctionExpression(node: ts.FunctionLikeDeclaration): ExpressionVisitResult { const type = this.checker.getTypeAtLocation(node); - const hasContext = tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void; + + let context: tstl.Identifier | undefined; + if (tsHelper.getFunctionContextType(type, this.checker) !== ContextType.Void) { + if (ts.isArrowFunction(node)) { + // dummy context for arrow functions with parameters + if (node.parameters.length > 0) { + context = tstl.createAnnonymousIdentifier(); + } + } else { + // self context + context = this.createSelfIdentifier(); + } + } + // Build parameter string - const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters( - node.parameters, - hasContext ? context : undefined - ); + const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters(node.parameters, context); + + let flags = !ts.isFunctionDeclaration(node) + ? tstl.FunctionExpressionFlags.Expression + : tstl.FunctionExpressionFlags.None; let body: ts.Block; if (ts.isBlock(node.body)) { @@ -3030,6 +3040,7 @@ export class LuaTransformer { body = ts.createBlock([returnExpression]); returnExpression.parent = body; body.parent = node.body.parent; + flags |= tstl.FunctionExpressionFlags.Inline; } const [transformedBody] = this.transformFunctionBody(node.parameters, body, spreadIdentifier); @@ -3038,6 +3049,7 @@ export class LuaTransformer { paramNames, dotsLiteral, spreadIdentifier, + flags, node ); } @@ -4294,11 +4306,13 @@ export class LuaTransformer { } if ((this.isModule || this.currentNamespace || insideFunction || isLetOrConst) && isFirstDeclaration) { // local - const isFunctionType = functionDeclaration - || (tsOriginal && ts.isVariableDeclaration(tsOriginal) && tsOriginal.initializer - && tsHelper.isFunctionTypeAtLocation(tsOriginal.initializer, this.checker)); - if (isFunctionType) { - // Split declaration and assignment for cuntions to allow recursion + const isPossibleWrappedFunction = !functionDeclaration + && tsOriginal + && ts.isVariableDeclaration(tsOriginal) + && tsOriginal.initializer + && tsHelper.isFunctionTypeAtLocation(tsOriginal.initializer, this.checker); + if (isPossibleWrappedFunction) { + // Split declaration and assignment for wrapped function types to allow recursion declaration = tstl.createVariableDeclarationStatement(lhs, undefined, tsOriginal, parent); assignment = tstl.createAssignmentStatement(lhs, rhs, tsOriginal, parent); @@ -4329,7 +4343,7 @@ export class LuaTransformer { // Remember function definitions for hoisting later const functionSymbolId = (lhs as tstl.Identifier).symbolId; if (functionSymbolId !== undefined) { - this.peekScope().functionDefinitions.get(functionSymbolId).assignment = assignment; + this.peekScope().functionDefinitions.get(functionSymbolId).definition = declaration || assignment; } } @@ -4500,7 +4514,7 @@ export class LuaTransformer { if (scope.functionDefinitions) { for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { if (functionSymbolId !== symbolId // Don't recurse into self - && declaration.pos < functionDefinition.assignment.pos // Ignore functions before symbol declaration + && declaration.pos < functionDefinition.definition.pos // Ignore functions before symbol declaration && functionDefinition.referencedSymbols.has(symbolId) && this.shouldHoist(functionSymbolId, scope)) { @@ -4534,12 +4548,12 @@ export class LuaTransformer { } const result = statements.slice(); - const hoistedFunctions: tstl.AssignmentStatement[] = []; + const hoistedFunctions: Array = []; for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { if (this.shouldHoist(functionSymbolId, scope)) { - const i = result.indexOf(functionDefinition.assignment); + const i = result.indexOf(functionDefinition.definition); result.splice(i, 1); - hoistedFunctions.push(functionDefinition.assignment); + hoistedFunctions.push(functionDefinition.definition); } } if (hoistedFunctions.length > 0) { diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 4595d0e21..b47ecd7dc 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -18,27 +18,27 @@ local nonEmptyTemplateString = \\"Level 0: \\\\n\\\\t \\" .. tostring(\\"Level 1 `; exports[`Transformation (classExtension1) 1`] = ` -"MyClass.myFunction = function(self) -end;" +"function MyClass.myFunction(self) +end" `; exports[`Transformation (classExtension2) 1`] = ` -"TestClass.myFunction = function(self) -end;" +"function TestClass.myFunction(self) +end" `; exports[`Transformation (classExtension3) 1`] = ` -"RenamedTestClass.myFunction = function(self) -end; -RenamedMyClass.myFunction = function(self) -end;" +"function RenamedTestClass.myFunction(self) +end +function RenamedMyClass.myFunction(self) +end" `; exports[`Transformation (classExtension4) 1`] = ` "MyClass.test = \\"test\\"; MyClass.testP = \\"testP\\"; -MyClass.myFunction = function(self) -end;" +function MyClass.myFunction(self) +end" `; exports[`Transformation (classPureAbstract) 1`] = ` @@ -47,13 +47,13 @@ ClassB.__index = ClassB; ClassB.prototype = ClassB.prototype or {}; ClassB.prototype.__index = ClassB.prototype; ClassB.prototype.constructor = ClassB; -ClassB.new = function(...) +function ClassB.new(...) local self = setmetatable({}, ClassB.prototype); self:____constructor(...); return self; -end; -ClassB.prototype.____constructor = function(self) -end;" +end +function ClassB.prototype.____constructor(self) +end" `; exports[`Transformation (continue) 1`] = ` @@ -224,8 +224,6 @@ exports[`Transformation (for) 1`] = ` "do local i = 1; while i <= 100 do - do - end ::__continue1:: i = i + 1; end @@ -234,8 +232,6 @@ end" exports[`Transformation (forIn) 1`] = ` "for i in pairs({a = 1, b = 2, c = 3, d = 4}) do - do - end ::__continue1:: end" `; @@ -244,16 +240,14 @@ exports[`Transformation (forOf) 1`] = ` "local ____TS_array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for ____TS_index = 1, #____TS_array do local i = ____TS_array[____TS_index]; - do - end ::__continue1:: end" `; exports[`Transformation (functionRestArguments) 1`] = ` -"varargsFunction = function(self, a, ...) +"function varargsFunction(self, a, ...) local b = ({...}); -end;" +end" `; exports[`Transformation (getSetAccessors) 1`] = ` @@ -266,19 +260,19 @@ MyClass.prototype.__index = __TS__Index(MyClass.prototype); MyClass.prototype.____setters = {}; MyClass.prototype.__newindex = __TS__NewIndex(MyClass.prototype); MyClass.prototype.constructor = MyClass; -MyClass.new = function(...) +function MyClass.new(...) local self = setmetatable({}, MyClass.prototype); self:____constructor(...); return self; -end; -MyClass.prototype.____constructor = function(self) -end; -MyClass.prototype.____getters.field = function(self) +end +function MyClass.prototype.____constructor(self) +end +function MyClass.prototype.____getters.field(self) return self._field + 4; -end; -MyClass.prototype.____setters.field = function(self, v) +end +function MyClass.prototype.____setters.field(self, v) self._field = v * 2; -end; +end local instance = MyClass.new(); instance.field = 4; local b = instance.field; @@ -296,16 +290,16 @@ MyClass.__index = MyClass; MyClass.prototype = MyClass.prototype or {}; MyClass.prototype.__index = MyClass.prototype; MyClass.prototype.constructor = MyClass; -MyClass.new = function(...) +function MyClass.new(...) local self = setmetatable({}, MyClass.prototype); self:____constructor(...); return self; -end; -MyClass.prototype.____constructor = function(self) -end; -MyClass.prototype.varargsFunction = function(self, a, ...) +end +function MyClass.prototype.____constructor(self) +end +function MyClass.prototype.varargsFunction(self, a, ...) local b = ({...}); -end;" +end" `; exports[`Transformation (modulesChangedVariableExport) 1`] = ` @@ -321,13 +315,13 @@ exports.TestClass.__index = exports.TestClass; exports.TestClass.prototype = exports.TestClass.prototype or {}; exports.TestClass.prototype.__index = exports.TestClass.prototype; exports.TestClass.prototype.constructor = exports.TestClass; -exports.TestClass.new = function(...) +function exports.TestClass.new(...) local self = setmetatable({}, exports.TestClass.prototype); self:____constructor(...); return self; -end; -exports.TestClass.prototype.____constructor = function(self) -end; +end +function exports.TestClass.prototype.____constructor(self) +end return exports;" `; @@ -338,28 +332,28 @@ exports.TestClass.__index = exports.TestClass; exports.TestClass.prototype = exports.TestClass.prototype or {}; exports.TestClass.prototype.__index = exports.TestClass.prototype; exports.TestClass.prototype.constructor = exports.TestClass; -exports.TestClass.new = function(...) +function exports.TestClass.new(...) local self = setmetatable({}, exports.TestClass.prototype); self:____constructor(...); return self; -end; -exports.TestClass.prototype.____constructor = function(self) -end; -exports.TestClass.prototype.memberFunc = function(self) -end; +end +function exports.TestClass.prototype.____constructor(self) +end +function exports.TestClass.prototype.memberFunc(self) +end return exports;" `; exports[`Transformation (modulesFunctionExport) 1`] = ` "local exports = exports or {}; -exports.publicFunc = function(self) -end; +function exports.publicFunc(self) +end return exports;" `; exports[`Transformation (modulesFunctionNoExport) 1`] = ` -"publicFunc = function(self) -end;" +"function publicFunc(self) +end" `; exports[`Transformation (modulesImportAll) 1`] = `"local Test = require(\\"test\\");"`; @@ -406,8 +400,6 @@ exports[`Transformation (modulesNamespaceExport) 1`] = ` "local exports = exports or {}; exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; -do -end return exports;" `; @@ -433,26 +425,22 @@ do TestSpace.TestNestedSpace = TestSpace.TestNestedSpace or {}; local TestNestedSpace = TestSpace.TestNestedSpace; do - TestNestedSpace.innerFunc = function(self) - end; + function TestNestedSpace.innerFunc(self) + end end end return exports;" `; -exports[`Transformation (modulesNamespaceNoExport) 1`] = ` -"TestSpace = TestSpace or {}; -do -end" -`; +exports[`Transformation (modulesNamespaceNoExport) 1`] = `"TestSpace = TestSpace or {};"`; exports[`Transformation (modulesNamespaceWithMemberExport) 1`] = ` "local exports = exports or {}; exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; do - TestSpace.innerFunc = function(self) - end; + function TestSpace.innerFunc(self) + end end return exports;" `; @@ -462,9 +450,8 @@ exports[`Transformation (modulesNamespaceWithMemberNoExport) 1`] = ` exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; do - local innerFunc; - innerFunc = function(self) - end; + local function innerFunc(self) + end end return exports;" `; @@ -480,9 +467,8 @@ exports[`Transformation (modulesVariableNoExport) 1`] = `"local foo = \\"bar\\"; exports[`Transformation (namespace) 1`] = ` "myNamespace = myNamespace or {}; do - local nsMember; - nsMember = function(self) - end; + local function nsMember(self) + end end" `; @@ -492,30 +478,30 @@ MergedClass.__index = MergedClass; MergedClass.prototype = MergedClass.prototype or {}; MergedClass.prototype.__index = MergedClass.prototype; MergedClass.prototype.constructor = MergedClass; -MergedClass.new = function(...) +function MergedClass.new(...) local self = setmetatable({}, MergedClass.prototype); self:____constructor(...); return self; -end; -MergedClass.prototype.____constructor = function(self) - self.propertyFunc = function(____) +end +function MergedClass.prototype.____constructor(self) + self.propertyFunc = function() end; -end; -MergedClass.staticMethodA = function(self) -end; -MergedClass.staticMethodB = function(self) +end +function MergedClass.staticMethodA(self) +end +function MergedClass.staticMethodB(self) self:staticMethodA(); -end; -MergedClass.prototype.methodA = function(self) -end; -MergedClass.prototype.methodB = function(self) +end +function MergedClass.prototype.methodA(self) +end +function MergedClass.prototype.methodB(self) self:methodA(); self:propertyFunc(); -end; +end MergedClass = MergedClass or {}; do - MergedClass.namespaceFunc = function(self) - end; + function MergedClass.namespaceFunc(self) + end end local mergedClass = MergedClass.new(); mergedClass:methodB(); @@ -530,29 +516,26 @@ do myNamespace.myNestedNamespace = myNamespace.myNestedNamespace or {}; local myNestedNamespace = myNamespace.myNestedNamespace; do - local nsMember; - nsMember = function(self) - end; + local function nsMember(self) + end end end" `; exports[`Transformation (namespacePhantom) 1`] = ` -"nsMember = function(self) -end;" +"function nsMember(self) +end" `; exports[`Transformation (returnDefault) 1`] = ` -"myFunc = function(self) +"function myFunc(self) return; -end;" +end" `; exports[`Transformation (shorthandPropertyAssignment) 1`] = ` "local f; -f = function(____, x) - return ({x = x}); -end;" +f = function(____, x) return ({x = x}); end;" `; exports[`Transformation (tryCatch) 1`] = ` @@ -592,9 +575,9 @@ end" `; exports[`Transformation (tupleReturn) 1`] = ` -"tupleReturn = function(self) +"function tupleReturn(self) return 0, \\"foobar\\"; -end; +end tupleReturn(_G); noTupleReturn(_G); local a, b = tupleReturn(_G); @@ -607,19 +590,19 @@ e = ({tupleReturn(_G)}); f = noTupleReturn(_G); foo(_G, ({tupleReturn(_G)})); foo(_G, noTupleReturn(_G)); -tupleReturnFromVar = function(self) +function tupleReturnFromVar(self) local r = {1, \\"baz\\"}; return table.unpack(r); -end; -tupleReturnForward = function(self) +end +function tupleReturnForward(self) return tupleReturn(_G); -end; -tupleNoForward = function(self) +end +function tupleNoForward(self) return ({tupleReturn(_G)}); -end; -tupleReturnUnpack = function(self) +end +function tupleReturnUnpack(self) return table.unpack(tupleNoForward(_G)); -end;" +end" `; exports[`Transformation (typeAssert) 1`] = ` From 7f397d6b544e1b649970903b77d37ae411f14eb8 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Tue, 2 Apr 2019 08:08:25 -0600 Subject: [PATCH 06/13] removing continue tags from loops with no continue statements --- src/LuaTransformer.ts | 7 +++-- .../__snapshots__/transformation.spec.ts.snap | 13 ++------ test/unit/loops.spec.ts | 30 ++++++++++--------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 31e4fe360..8b518e604 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -34,6 +34,7 @@ interface Scope { referencedSymbols?: Set; variableDeclarations?: tstl.VariableDeclarationStatement[]; functionDefinitions?: Map; + loopContinued?: boolean; } export class LuaTransformer { @@ -1925,7 +1926,7 @@ export class LuaTransformer { const scope = this.popScope(); const scopeId = scope.id; - if (this.options.luaTarget === LuaTarget.Lua51) { + if (!scope.loopContinued) { return body; } @@ -2246,8 +2247,10 @@ export class LuaTransformer { throw TSTLErrors.UnsupportedForTarget("Continue statement", this.options.luaTarget, statement); } + const scope = this.findScope(ScopeType.Loop); + scope.loopContinued = true; return tstl.createGotoStatement( - `__continue${this.findScope(ScopeType.Loop).id}`, + `__continue${scope.id}`, statement ); } diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index b47ecd7dc..a76aafee2 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -149,10 +149,7 @@ end" exports[`Transformation (do) 1`] = ` "local e = 10; repeat - do - e = e - 1; - end - ::__continue1:: + e = e - 1; until not (e > 0);" `; @@ -224,7 +221,6 @@ exports[`Transformation (for) 1`] = ` "do local i = 1; while i <= 100 do - ::__continue1:: i = i + 1; end end" @@ -232,7 +228,6 @@ end" exports[`Transformation (forIn) 1`] = ` "for i in pairs({a = 1, b = 2, c = 3, d = 4}) do - ::__continue1:: end" `; @@ -240,7 +235,6 @@ exports[`Transformation (forOf) 1`] = ` "local ____TS_array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for ____TS_index = 1, #____TS_array do local i = ____TS_array[____TS_index]; - ::__continue1:: end" `; @@ -613,9 +607,6 @@ local test2 = 10;" exports[`Transformation (while) 1`] = ` "local d = 10; while d > 0 do - do - d = d - 1; - end - ::__continue1:: + d = d - 1; end" `; diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index d87f019fd..f0d3ff54c 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -763,21 +763,23 @@ test("forof forwarded lua iterator with tupleReturn", () => { }); test.each([ - "while (a < b) { i++; }", - "do { i++; } while (a < b)", - "for (let i = 0; i < 3; i++) {}", - "for (let a in b) {}", - "for (let a of b) {}", + "while (a < b) { i++; continue; }", + "do { i++; continue; } while (a < b)", + "for (let i = 0; i < 3; i++) { continue; }", + "for (let a in b) { continue; }", + "for (let a of b) { continue; }", ])("loop versions (%p)", loop => { - const lua51 = util.transpileString(loop, { luaTarget: LuaTarget.Lua51 }); - const lua52 = util.transpileString(loop, { luaTarget: LuaTarget.Lua52 }); - const lua53 = util.transpileString(loop, { luaTarget: LuaTarget.Lua53 }); - const luajit = util.transpileString(loop, { luaTarget: LuaTarget.LuaJIT }); - - expect(lua51.indexOf("::__continue1::") !== -1).toBe(false); // No labels in 5.1 - expect(lua52.indexOf("::__continue1::") !== -1).toBe(true); // Labels from 5.2 onwards - expect(lua53.indexOf("::__continue1::") !== -1).toBe(true); - expect(luajit.indexOf("::__continue1::") !== -1).toBe(true); + const lua51 = { luaTarget: LuaTarget.Lua51 }; + const lua52 = { luaTarget: LuaTarget.Lua52 }; + const lua53 = { luaTarget: LuaTarget.Lua53 }; + const luajit = { luaTarget: LuaTarget.LuaJIT }; + + expect(() => util.transpileString(loop, lua51)).toThrowError( + TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, undefined) + ); + expect(util.transpileString(loop, lua52).indexOf("::__continue1::") !== -1).toBe(true); + expect(util.transpileString(loop, lua53).indexOf("::__continue1::") !== -1).toBe(true); + expect(util.transpileString(loop, luajit).indexOf("::__continue1::") !== -1).toBe(true); }); test("for dead code after return", () => { From 67ec863c324598c46a8ee4bcec58ef54418a2519 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Tue, 2 Apr 2019 11:15:39 -0600 Subject: [PATCH 07/13] fixed lint error --- test/unit/loops.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index f0d3ff54c..3aadaefe2 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -775,7 +775,7 @@ test.each([ const luajit = { luaTarget: LuaTarget.LuaJIT }; expect(() => util.transpileString(loop, lua51)).toThrowError( - TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, undefined) + TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, undefined), ); expect(util.transpileString(loop, lua52).indexOf("::__continue1::") !== -1).toBe(true); expect(util.transpileString(loop, lua53).indexOf("::__continue1::") !== -1).toBe(true); From 2876a4fa0b27a3d9b91e90583d53b5a019395062 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Tue, 2 Apr 2019 17:25:18 -0600 Subject: [PATCH 08/13] addressed feedback and did some cleanup --- src/LuaAST.ts | 15 +++++++++++++- src/LuaPrinter.ts | 20 ++++--------------- src/LuaTransformer.ts | 46 +++++++++++++++++++++++++++++-------------- src/TSHelper.ts | 5 +++++ 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index bc741d141..e0b841384 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -592,7 +592,7 @@ export function createStringLiteral(value: string | ts.__String, tsOriginal?: ts export enum FunctionExpressionFlags { None = 0x0, Inline = 0x1, // Keep function on same line - Expression = 0x2, // Prefer assignment to expression syntax `foo = function()` instead of `function foo()` + Declaration = 0x2, // Prefer declaration syntax `function foo()` over assignment syntax `foo = function()` } export interface FunctionExpression extends Expression { @@ -863,3 +863,16 @@ export function createTableIndexExpression( } export type IdentifierOrTableIndexExpression = Identifier | TableIndexExpression; + +export type FunctionDefinition = (VariableDeclarationStatement | AssignmentStatement) & { + right: [FunctionExpression]; +}; + +export function isFunctionDefinition(statement: VariableDeclarationStatement | AssignmentStatement) + : statement is FunctionDefinition +{ + return statement.left.length === 1 + && statement.right + && statement.right.length === 1 + && isFunctionExpression(statement.right[0]); +} diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 99542f4a7..cd7133697 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -7,18 +7,6 @@ import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions"; import { LuaLib, LuaLibFeature } from "./LuaLib"; import { TSHelper as tsHelper } from "./TSHelper"; -type FunctionDefinition = (tstl.VariableDeclarationStatement | tstl.AssignmentStatement) & { - right: [tstl.FunctionExpression]; -}; - -function isFunctionDeclaration(statement: tstl.VariableDeclarationStatement | tstl.AssignmentStatement) - : statement is FunctionDefinition -{ - return statement.left.length === 1 - && statement.right - && statement.right.length === 1 - && tstl.isFunctionExpression(statement.right[0]); -} type SourceChunk = string | SourceNode; export class LuaPrinter { @@ -242,7 +230,7 @@ export class LuaPrinter { chunks.push(this.indent("local ")); - if (isFunctionDeclaration(statement)) { + if (tstl.isFunctionDefinition(statement)) { const name = this.printExpression(statement.left[0]); chunks.push(this.printFunctionExpression(statement.right[0], name)); chunks.push("\n"); @@ -265,11 +253,11 @@ export class LuaPrinter { chunks.push(this.indent()); - if (isFunctionDeclaration(statement) - && (statement.right[0].flags & tstl.FunctionExpressionFlags.Expression) === 0) + if (tstl.isFunctionDefinition(statement) + && (statement.right[0].flags & tstl.FunctionExpressionFlags.Declaration) !== 0) { const name = this.printExpression(statement.left[0]); - if (!name.toString().match(/[^A-Za-z0-9_\.]/)) { + if (tsHelper.isValidLuaFunctionDeclarationName(name.toString())) { chunks.push(this.printFunctionExpression(statement.right[0], name)); chunks.push("\n"); return this.createSourceNode(statement, chunks); diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index edc87fa9e..d200d1b93 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -537,7 +537,9 @@ export class LuaTransformer { const constructorFunction = tstl.createFunctionExpression( tstl.createBlock(constructorBody), [this.createSelfIdentifier()], - tstl.createDotsLiteral() + tstl.createDotsLiteral(), + undefined, + tstl.FunctionExpressionFlags.Declaration ); result.push(tstl.createAssignmentStatement( this.createConstructorName(className), @@ -899,7 +901,7 @@ export class LuaTransformer { undefined, tstl.createDotsLiteral(), undefined, - tstl.FunctionExpressionFlags.None, + tstl.FunctionExpressionFlags.Declaration, statement ), statement @@ -1016,7 +1018,13 @@ export class LuaTransformer { const result = tstl.createAssignmentStatement( this.createConstructorName(className), - tstl.createFunctionExpression(block, params, dotsLiteral, restParamName, undefined, undefined), + tstl.createFunctionExpression( + block, + params, + dotsLiteral, + restParamName, + tstl.FunctionExpressionFlags.Declaration + ), statement ); @@ -1034,7 +1042,10 @@ export class LuaTransformer { const [body] = this.transformFunctionBody(getAccessor.parameters, getAccessor.body); const accessorFunction = tstl.createFunctionExpression( tstl.createBlock(body), - [this.createSelfIdentifier()] + [this.createSelfIdentifier()], + undefined, + undefined, + tstl.FunctionExpressionFlags.Declaration ); const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className)); @@ -1069,7 +1080,8 @@ export class LuaTransformer { tstl.createBlock(body), params, dot, - restParam + restParam, + tstl.FunctionExpressionFlags.Declaration ); const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className)); @@ -1117,7 +1129,7 @@ export class LuaTransformer { paramNames, dots, restParamName, - tstl.FunctionExpressionFlags.None, + tstl.FunctionExpressionFlags.Declaration, node.body ); @@ -1633,7 +1645,13 @@ export class LuaTransformer { restParamName ); const block = tstl.createBlock(body); - const functionExpression = tstl.createFunctionExpression(block, params, dotsLiteral, restParamName); + const functionExpression = tstl.createFunctionExpression( + block, + params, + dotsLiteral, + restParamName, + tstl.FunctionExpressionFlags.Declaration + ); // Remember symbols referenced in this function for hoisting later if (!this.options.noHoisting && name.symbolId !== undefined) { const scope = this.peekScope(); @@ -3051,10 +3069,7 @@ export class LuaTransformer { // Build parameter string const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters(node.parameters, context); - let flags = !ts.isFunctionDeclaration(node) - ? tstl.FunctionExpressionFlags.Expression - : tstl.FunctionExpressionFlags.None; - + let flags = tstl.FunctionExpressionFlags.None; let body: ts.Block; if (ts.isBlock(node.body)) { body = node.body; @@ -3117,6 +3132,7 @@ export class LuaTransformer { // Strip parenthesis from casts return this.transformExpression(expression.expression); } + return tstl.createParenthesizedExpression( this.transformExpression(expression.expression), expression @@ -3646,7 +3662,7 @@ export class LuaTransformer { const arg2 = params[1]; const sumArg = tstl.createBinaryExpression( tstl.createParenthesizedExpression(arg1), - arg2, + tstl.createParenthesizedExpression(arg2), tstl.SyntaxKind.AdditionOperator ); return this.createStringCall("sub", node, caller, this.expressionPlusOne(arg1), sumArg); @@ -4057,10 +4073,12 @@ export class LuaTransformer { public transformTemplateExpression(expression: ts.TemplateExpression): tstl.Expression { const parts: tstl.Expression[] = []; + const head = tsHelper.escapeString(expression.head.text); if (head.length > 0) { parts.push(tstl.createStringLiteral(head, expression.head)); } + expression.templateSpans.forEach(span => { const expr = this.transformExpression(span.expression); parts.push(tstl.createCallExpression(tstl.createIdentifier("tostring"), [expr])); @@ -4070,9 +4088,7 @@ export class LuaTransformer { parts.push(tstl.createStringLiteral(text, span.literal)); } }); - if (parts.length === 1) { - return parts[0]; - } + return parts.reduce((prev, current) => tstl.createBinaryExpression( prev, current, diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 717ba023c..72a035a43 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -652,6 +652,11 @@ export class TSHelper { return match && match[0] === str; } + public static isValidLuaFunctionDeclarationName(str: string): boolean { + const match = str.match(/[a-zA-Z0-9_\.]+/); + return match && match[0] === str; + } + public static isFalsible(type: ts.Type, strictNullChecks: boolean): boolean { const falsibleFlags = ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral From 97952dbfbb653cc0e59b4cdfbbfc15f7e3853d0e Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Wed, 3 Apr 2019 07:08:53 -0600 Subject: [PATCH 09/13] formatting table literals and added some comments --- src/LuaPrinter.ts | 23 +++++++++++------ src/LuaTransformer.ts | 6 ++--- .../__snapshots__/transformation.spec.ts.snap | 25 ++++++++++++++++--- test/unit/assignments/assignments.spec.ts | 12 ++++----- test/unit/objectLiteral.spec.ts | 10 ++++---- test/unit/spreadElement.spec.ts | 8 +++--- 6 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index cd7133697..6cea12b32 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -231,6 +231,7 @@ export class LuaPrinter { chunks.push(this.indent("local ")); if (tstl.isFunctionDefinition(statement)) { + // Print all local functions as `local function foo()` instead of `local foo = function` to allow recursion const name = this.printExpression(statement.left[0]); chunks.push(this.printFunctionExpression(statement.right[0], name)); chunks.push("\n"); @@ -256,6 +257,7 @@ export class LuaPrinter { if (tstl.isFunctionDefinition(statement) && (statement.right[0].flags & tstl.FunctionExpressionFlags.Declaration) !== 0) { + // Use `function foo()` instead of `foo = function()` const name = this.printExpression(statement.left[0]); if (tsHelper.isValidLuaFunctionDeclarationName(name.toString())) { chunks.push(this.printFunctionExpression(statement.right[0], name)); @@ -493,6 +495,7 @@ export class LuaPrinter { && tstl.isReturnStatement(expression.body.statements[0]) && (expression.flags & tstl.FunctionExpressionFlags.Inline) !== 0) { + // Inline return-only functions with the flag chunks.push(" "); chunks.push(this.printReturnStatement(expression.body.statements[0] as tstl.ReturnStatement, true)); chunks.push(" end"); @@ -531,14 +534,18 @@ export class LuaPrinter { chunks.push("{"); - if (expression.fields) { - expression.fields.forEach((f, i) => { - if (i < expression.fields.length - 1) { - chunks.push(this.printTableFieldExpression(f), ", "); - } else { - chunks.push(this.printTableFieldExpression(f)); - } - }); + if (expression.fields && expression.fields.length > 0) { + if (expression.fields.length === 1) { + // Inline tables with only one entry + chunks.push(this.printTableFieldExpression(expression.fields[0])); + + } else { + chunks.push("\n"); + this.pushIndent(); + expression.fields.forEach(f => chunks.push(this.indent(), this.printTableFieldExpression(f), ",\n")); + this.popIndent(); + chunks.push(this.indent()); + } } chunks.push("}"); diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index d200d1b93..fab407541 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4621,10 +4621,8 @@ export class LuaTransformer { if (symbols.some(s => this.shouldHoist(s, scope))) { let assignment: tstl.AssignmentStatement | undefined; if (declaration.right) { - assignment = tstl.createAssignmentStatement( - declaration.left, - declaration.right - ); + assignment = tstl.createAssignmentStatement(declaration.left, declaration.right); + // Preserve position info for sourcemap assignment.line = declaration.line; assignment.column = declaration.column; } diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index a76aafee2..b0baaa3dd 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -227,12 +227,28 @@ end" `; exports[`Transformation (forIn) 1`] = ` -"for i in pairs({a = 1, b = 2, c = 3, d = 4}) do +"for i in pairs({ + a = 1, + b = 2, + c = 3, + d = 4, +}) do end" `; exports[`Transformation (forOf) 1`] = ` -"local ____TS_array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +"local ____TS_array = { + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, +}; for ____TS_index = 1, #____TS_array do local i = ____TS_array[____TS_index]; end" @@ -585,7 +601,10 @@ f = noTupleReturn(_G); foo(_G, ({tupleReturn(_G)})); foo(_G, noTupleReturn(_G)); function tupleReturnFromVar(self) - local r = {1, \\"baz\\"}; + local r = { + 1, + \\"baz\\", + }; return table.unpack(r); end function tupleReturnForward(self) diff --git a/test/unit/assignments/assignments.spec.ts b/test/unit/assignments/assignments.spec.ts index 4102b6d26..54f925d41 100644 --- a/test/unit/assignments/assignments.spec.ts +++ b/test/unit/assignments/assignments.spec.ts @@ -5,10 +5,10 @@ import * as util from "../../util"; test.each([ { inp: `"abc"`, out: `"abc"` }, { inp: "3", out: "3" }, - { inp: "[1,2,3]", out: "{1, 2, 3}" }, + { inp: "[1,2,3]", out: "{\n 1,\n 2,\n 3,\n}" }, { inp: "true", out: "true" }, { inp: "false", out: "false" }, - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"}` }, + { inp: `{a:3,b:"4"}`, out: `{\n a = 3,\n b = "4",\n}` }, ])("Const assignment (%p)", ({ inp, out }) => { const lua = util.transpileString(`const myvar = ${inp};`); expect(lua).toBe(`local myvar = ${out};`); @@ -17,10 +17,10 @@ test.each([ test.each([ { inp: `"abc"`, out: `"abc"` }, { inp: "3", out: "3" }, - { inp: "[1,2,3]", out: "{1, 2, 3}" }, + { inp: "[1,2,3]", out: "{\n 1,\n 2,\n 3,\n}" }, { inp: "true", out: "true" }, { inp: "false", out: "false" }, - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"}` }, + { inp: `{a:3,b:"4"}`, out: `{\n a = 3,\n b = "4",\n}` }, ])("Let assignment (%p)", ({ inp, out }) => { const lua = util.transpileString(`let myvar = ${inp};`); expect(lua).toBe(`local myvar = ${out};`); @@ -29,10 +29,10 @@ test.each([ test.each([ { inp: `"abc"`, out: `"abc"` }, { inp: "3", out: "3" }, - { inp: "[1,2,3]", out: "{1, 2, 3}" }, + { inp: "[1,2,3]", out: "{\n 1,\n 2,\n 3,\n}" }, { inp: "true", out: "true" }, { inp: "false", out: "false" }, - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"}` }, + { inp: `{a:3,b:"4"}`, out: `{\n a = 3,\n b = "4",\n}` }, ])("Var assignment (%p)", ({ inp, out }) => { const lua = util.transpileString(`var myvar = ${inp};`); expect(lua).toBe(`myvar = ${out};`); diff --git a/test/unit/objectLiteral.spec.ts b/test/unit/objectLiteral.spec.ts index 21ae4abf0..3cc8ad2c2 100644 --- a/test/unit/objectLiteral.spec.ts +++ b/test/unit/objectLiteral.spec.ts @@ -2,11 +2,11 @@ import * as util from "../util"; const fs = require("fs"); test.each([ - { inp: `{a:3,b:"4"}`, out: `{a = 3, b = "4"};` }, - { inp: `{"a":3,b:"4"}`, out: `{a = 3, b = "4"};` }, - { inp: `{["a"]:3,b:"4"}`, out: `{a = 3, b = "4"};` }, - { inp: `{["a"+123]:3,b:"4"}`, out: `{["a" .. 123] = 3, b = "4"};` }, - { inp: `{[myFunc()]:3,b:"4"}`, out: `{[myFunc(_G)] = 3, b = "4"};` }, + { inp: `{a:3,b:"4"}`, out: '{\n a = 3,\n b = "4",\n};' }, + { inp: `{"a":3,b:"4"}`, out: '{\n a = 3,\n b = "4",\n};' }, + { inp: `{["a"]:3,b:"4"}`, out: '{\n a = 3,\n b = "4",\n};' }, + { inp: `{["a"+123]:3,b:"4"}`, out: '{\n ["a" .. 123] = 3,\n b = "4",\n};' }, + { inp: `{[myFunc()]:3,b:"4"}`, out: '{\n [myFunc(_G)] = 3,\n b = "4",\n};' }, { inp: `{x}`, out: `{x = x};` }, ])("Object Literal (%p)", ({ inp, out }) => { const lua = util.transpileString(`const myvar = ${inp};`); diff --git a/test/unit/spreadElement.spec.ts b/test/unit/spreadElement.spec.ts index 3bb7ff981..42559ec2a 100644 --- a/test/unit/spreadElement.spec.ts +++ b/test/unit/spreadElement.spec.ts @@ -15,23 +15,23 @@ test("Spread Element Lua 5.1", () => { // Cant test functional because our VM doesn't run on 5.1 const options = { luaTarget: LuaTarget.Lua51, luaLibImport: LuaLibImportKind.None }; const lua = util.transpileString(`[].push(...${JSON.stringify([1, 2, 3])});`, options); - expect(lua).toBe("__TS__ArrayPush({}, unpack({1, 2, 3}));"); + expect(lua).toBe("__TS__ArrayPush({}, unpack({\n 1,\n 2,\n 3,\n}));"); }); 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({0, 1, 2})};"); + expect(lua).toBe("{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({0, 1, 2})};"); + expect(lua).toBe("{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({0, 1, 2})};"); + expect(lua).toBe("{unpack({\n 0,\n 1,\n 2,\n})};"); }); From 9be7670fefb98871892a6ee64b16d22275af2852 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Wed, 3 Apr 2019 13:36:04 -0600 Subject: [PATCH 10/13] updates based on feedback --- src/LuaAST.ts | 10 ++++-- src/LuaPrinter.ts | 69 +++++++++++++++++++++++++---------------- src/LuaTransformer.ts | 4 +-- test/unit/loops.spec.ts | 2 +- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index e0b841384..d42f938bd 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -121,11 +121,17 @@ export function cloneNode(node: T): T { return Object.assign({}, node); } +export function setNodePosition(node: T, position: TextRange): T { + node.line = position.line; + node.column = position.column; + + return node; +} + export function setNodeOriginal(node: T, tsOriginal: ts.Node): T { const sourcePosition = getSourcePosition(tsOriginal); if (sourcePosition) { - node.line = sourcePosition.line; - node.column = sourcePosition.column; + setNodePosition(node, sourcePosition); } return node; diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 6cea12b32..428ffc1a0 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -232,8 +232,7 @@ export class LuaPrinter { if (tstl.isFunctionDefinition(statement)) { // Print all local functions as `local function foo()` instead of `local foo = function` to allow recursion - const name = this.printExpression(statement.left[0]); - chunks.push(this.printFunctionExpression(statement.right[0], name)); + chunks.push(this.printFunctionDefinition(statement)); chunks.push("\n"); } else { @@ -260,7 +259,7 @@ export class LuaPrinter { // Use `function foo()` instead of `foo = function()` const name = this.printExpression(statement.left[0]); if (tsHelper.isValidLuaFunctionDeclarationName(name.toString())) { - chunks.push(this.printFunctionExpression(statement.right[0], name)); + chunks.push(this.printFunctionDefinition(statement)); chunks.push("\n"); return this.createSourceNode(statement, chunks); } @@ -468,7 +467,7 @@ export class LuaPrinter { } } - private printFunctionExpression(expression: tstl.FunctionExpression, name?: SourceChunk): SourceNode { + private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { const parameterChunks: SourceNode[] = expression.params ? expression.params.map(i => this.printIdentifier(i)) : []; @@ -479,35 +478,53 @@ export class LuaPrinter { const chunks: SourceChunk[] = []; - chunks.push("function"); + chunks.push("function("); + chunks.push(...this.joinChunks(", ", parameterChunks)); + chunks.push(")"); - if (name) { - chunks.push(" "); - chunks.push(name); + if (expression.body.statements && expression.body.statements.length === 1) { + const statement = expression.body.statements[0]; + if (tstl.isReturnStatement(statement) && (expression.flags & tstl.FunctionExpressionFlags.Inline) !== 0) { + // Inline return-only functions with the flag + chunks.push(" "); + chunks.push(this.printReturnStatement(statement, true)); + chunks.push(" end"); + return this.createSourceNode(expression, chunks); + } } - chunks.push("("); - chunks.push(...this.joinChunks(", ", parameterChunks)); - chunks.push(")"); + chunks.push("\n"); + this.pushIndent(); + chunks.push(this.printBlock(expression.body)); + this.popIndent(); + chunks.push(this.indent("end")); - if (expression.body.statements - && expression.body.statements.length === 1 - && tstl.isReturnStatement(expression.body.statements[0]) - && (expression.flags & tstl.FunctionExpressionFlags.Inline) !== 0) - { - // Inline return-only functions with the flag - chunks.push(" "); - chunks.push(this.printReturnStatement(expression.body.statements[0] as tstl.ReturnStatement, true)); - chunks.push(" end"); + return this.createSourceNode(expression, chunks); + } - } else { - chunks.push("\n"); - this.pushIndent(); - chunks.push(this.printBlock(expression.body)); - this.popIndent(); - chunks.push(this.indent("end")); + private printFunctionDefinition(statement: tstl.FunctionDefinition): SourceNode { + const expression = statement.right[0]; + const parameterChunks: SourceNode[] = expression.params + ? expression.params.map(i => this.printIdentifier(i)) + : []; + + if (expression.dots) { + parameterChunks.push(this.printDotsLiteral(expression.dots)); } + const chunks: SourceChunk[] = []; + + chunks.push("function "); + chunks.push(this.printExpression(statement.left[0])); + chunks.push("("); + chunks.push(...this.joinChunks(", ", parameterChunks)); + chunks.push(")\n"); + + this.pushIndent(); + chunks.push(this.printBlock(expression.body)); + this.popIndent(); + chunks.push(this.indent("end")); + return this.createSourceNode(expression, chunks); } diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index fab407541..9fc558448 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4622,9 +4622,7 @@ export class LuaTransformer { let assignment: tstl.AssignmentStatement | undefined; if (declaration.right) { assignment = tstl.createAssignmentStatement(declaration.left, declaration.right); - // Preserve position info for sourcemap - assignment.line = declaration.line; - assignment.column = declaration.column; + tstl.setNodePosition(assignment, declaration); // Preserve position info for sourcemap } const i = result.indexOf(declaration); if (i >= 0) { diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index 3aadaefe2..1b78813d8 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -768,7 +768,7 @@ test.each([ "for (let i = 0; i < 3; i++) { continue; }", "for (let a in b) { continue; }", "for (let a of b) { continue; }", -])("loop versions (%p)", loop => { +])("loop continue in different lua versions (%p)", loop => { const lua51 = { luaTarget: LuaTarget.Lua51 }; const lua52 = { luaTarget: LuaTarget.Lua52 }; const lua53 = { luaTarget: LuaTarget.Lua53 }; From 7dd2e0fad9abc10267793a168fc9641c4d44f087 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Wed, 3 Apr 2019 13:48:00 -0600 Subject: [PATCH 11/13] added comment to isValidFunctionDeclarationName --- src/TSHelper.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 72a035a43..d9ec50f2e 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -652,6 +652,9 @@ export class TSHelper { return match && match[0] === str; } + // Checks that a name is valid for use in lua function declaration syntax: + // 'foo.bar' => passes ('function foo.bar()' is valid) + // 'getFoo().bar' => fails ('function getFoo().bar()' would be illegal) public static isValidLuaFunctionDeclarationName(str: string): boolean { const match = str.match(/[a-zA-Z0-9_\.]+/); return match && match[0] === str; From abd3249db7d913f69aa0a45e131f532ca299a3a0 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Wed, 3 Apr 2019 14:54:33 -0600 Subject: [PATCH 12/13] refactored function expression stuff to avoid needing inline parameter on printReturnStatement --- src/LuaAST.ts | 11 ++++++++ src/LuaPrinter.ts | 72 ++++++++++++++++++++--------------------------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index d42f938bd..3ef1f8612 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -882,3 +882,14 @@ export function isFunctionDefinition(statement: VariableDeclarationStatement | A && statement.right.length === 1 && isFunctionExpression(statement.right[0]); } + +export type InlineFunctionExpression = FunctionExpression & { + body: { statements: [ReturnStatement]; }; +}; + +export function isInlineFunctionExpression(expression: FunctionExpression) : expression is InlineFunctionExpression { + return expression.body.statements + && expression.body.statements.length === 1 + && isReturnStatement(expression.body.statements[0]) + && (expression.flags & FunctionExpressionFlags.Inline) !== 0; +} diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 428ffc1a0..4c54e7747 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -376,24 +376,17 @@ export class LuaPrinter { return this.createSourceNode(statement, [this.indent("::"), statement.name, "::\n"]); } - private printReturnStatement(statement: tstl.ReturnStatement, inline?: boolean): SourceNode { + private printReturnStatement(statement: tstl.ReturnStatement): SourceNode { if (!statement.expressions || statement.expressions.length === 0) { - const ret = inline ? "return;" : this.indent("return;\n"); - return this.createSourceNode(statement, ret); + return this.createSourceNode(statement, this.indent("return;\n")); } const chunks: SourceChunk[] = []; - chunks.push("return "); chunks.push(...this.joinChunks(", ", statement.expressions.map(e => this.printExpression(e)))); - chunks.push(";"); - - if (!inline) { - chunks.unshift(this.indent()); - chunks.push("\n"); - } + chunks.push(";\n"); - return this.createSourceNode(statement, chunks); + return this.createSourceNode(statement, [this.indent(), "return ", ...chunks]); } private printBreakStatement(statement: tstl.BreakStatement): SourceNode { @@ -467,7 +460,7 @@ export class LuaPrinter { } } - private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { + private printFunctionParameters(expression: tstl.FunctionExpression): SourceChunk[] { const parameterChunks: SourceNode[] = expression.params ? expression.params.map(i => this.printIdentifier(i)) : []; @@ -475,50 +468,45 @@ export class LuaPrinter { if (expression.dots) { parameterChunks.push(this.printDotsLiteral(expression.dots)); } + return ["(", ...this.joinChunks(", ", parameterChunks), ")"]; + } + private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { const chunks: SourceChunk[] = []; - chunks.push("function("); - chunks.push(...this.joinChunks(", ", parameterChunks)); - chunks.push(")"); - - if (expression.body.statements && expression.body.statements.length === 1) { - const statement = expression.body.statements[0]; - if (tstl.isReturnStatement(statement) && (expression.flags & tstl.FunctionExpressionFlags.Inline) !== 0) { - // Inline return-only functions with the flag - chunks.push(" "); - chunks.push(this.printReturnStatement(statement, true)); - chunks.push(" end"); - return this.createSourceNode(expression, chunks); - } - } + chunks.push("function"); + chunks.push(...this.printFunctionParameters(expression)); - chunks.push("\n"); - this.pushIndent(); - chunks.push(this.printBlock(expression.body)); - this.popIndent(); - chunks.push(this.indent("end")); + if (tstl.isInlineFunctionExpression(expression)) { + const returnStatement = expression.body.statements[0]; + chunks.push(" "); + const returnNode: SourceChunk[] = [ + "return ", + ...this.joinChunks(", ", returnStatement.expressions.map(e => this.printExpression(e))), + ";", + ]; + chunks.push(this.createSourceNode(returnStatement, returnNode)); + chunks.push(" end"); + + } else { + chunks.push("\n"); + this.pushIndent(); + chunks.push(this.printBlock(expression.body)); + this.popIndent(); + chunks.push(this.indent("end")); + } return this.createSourceNode(expression, chunks); } private printFunctionDefinition(statement: tstl.FunctionDefinition): SourceNode { const expression = statement.right[0]; - const parameterChunks: SourceNode[] = expression.params - ? expression.params.map(i => this.printIdentifier(i)) - : []; - - if (expression.dots) { - parameterChunks.push(this.printDotsLiteral(expression.dots)); - } - const chunks: SourceChunk[] = []; chunks.push("function "); chunks.push(this.printExpression(statement.left[0])); - chunks.push("("); - chunks.push(...this.joinChunks(", ", parameterChunks)); - chunks.push(")\n"); + chunks.push(...this.printFunctionParameters(expression)); + chunks.push("\n"); this.pushIndent(); chunks.push(this.printBlock(expression.body)); From 2a3b9c96756e62e11e5930ff539c392f7d0e9e00 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Wed, 3 Apr 2019 15:27:00 -0600 Subject: [PATCH 13/13] moved parens out of printFunctionParameters --- src/LuaPrinter.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 4c54e7747..5538cca89 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -468,14 +468,16 @@ export class LuaPrinter { if (expression.dots) { parameterChunks.push(this.printDotsLiteral(expression.dots)); } - return ["(", ...this.joinChunks(", ", parameterChunks), ")"]; + + return this.joinChunks(", ", parameterChunks); } private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { const chunks: SourceChunk[] = []; - chunks.push("function"); + chunks.push("function("); chunks.push(...this.printFunctionParameters(expression)); + chunks.push(")"); if (tstl.isInlineFunctionExpression(expression)) { const returnStatement = expression.body.statements[0]; @@ -505,8 +507,9 @@ export class LuaPrinter { chunks.push("function "); chunks.push(this.printExpression(statement.left[0])); + chunks.push("("); chunks.push(...this.printFunctionParameters(expression)); - chunks.push("\n"); + chunks.push(")\n"); this.pushIndent(); chunks.push(this.printBlock(expression.body));