Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions src/LuaAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,17 @@ export function cloneNode<T extends Node>(node: T): T {
return Object.assign({}, node);
}

export function setNodePosition<T extends Node>(node: T, position: TextRange): T {
node.line = position.line;
node.column = position.column;

return node;
}

export function setNodeOriginal<T extends Node>(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;
Expand Down Expand Up @@ -589,20 +595,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
Declaration = 0x2, // Prefer declaration syntax `function foo()` over assignment syntax `foo = function()`
}

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 {
Expand All @@ -614,6 +619,7 @@ export function createFunctionExpression(
params?: Identifier[],
dots?: DotsLiteral,
restParamName?: Identifier,
flags = FunctionExpressionFlags.None,
tsOriginal?: ts.Node,
parent?: Node
): FunctionExpression
Expand All @@ -627,6 +633,7 @@ export function createFunctionExpression(
expression.dots = dots;
setParent(restParamName, expression);
expression.restParamName = restParamName;
expression.flags = flags;
return expression;
}

Expand Down Expand Up @@ -862,3 +869,27 @@ 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]);
}

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;
}
138 changes: 89 additions & 49 deletions src/LuaPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,25 +213,37 @@ export class LuaPrinter {

private printDoStatement(statement: tstl.DoStatement): SourceNode {
const chunks: SourceChunk[] = [];
chunks.push(this.indent("do\n"));
this.pushIndent();
chunks.push(...this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s)));
this.popIndent();
chunks.push(this.indent("end\n"));

if (statement.statements && statement.statements.length > 0) {
chunks.push(this.indent("do\n"));
this.pushIndent();
chunks.push(...this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s)));
this.popIndent();
chunks.push(this.indent("end\n"));
}

return this.concatNodes(...chunks);
}

private printVariableDeclarationStatement(statement: tstl.VariableDeclarationStatement): SourceNode {
const chunks: SourceChunk[] = [];

chunks.push(this.indent("local "));
chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e))));

if (statement.right) {
chunks.push(" = ");
chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e))));
if (tstl.isFunctionDefinition(statement)) {
// Print all local functions as `local function foo()` instead of `local foo = function` to allow recursion
chunks.push(this.printFunctionDefinition(statement));
chunks.push("\n");

} else {
chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e))));

if (statement.right) {
chunks.push(" = ");
chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e))));
}
chunks.push(";\n");
}
chunks.push(";\n");

return this.concatNodes(...chunks);
}
Expand All @@ -240,6 +252,19 @@ export class LuaPrinter {
const chunks: SourceChunk[] = [];

chunks.push(this.indent());

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.printFunctionDefinition(statement));
chunks.push("\n");
return this.createSourceNode(statement, chunks);
}
}

chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e))));
chunks.push(" = ");
chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e))));
Expand Down Expand Up @@ -359,7 +384,6 @@ export class LuaPrinter {
const chunks: SourceChunk[] = [];

chunks.push(...this.joinChunks(", ", statement.expressions.map(e => this.printExpression(e))));

chunks.push(";\n");

return this.createSourceNode(statement, [this.indent(), "return ", ...chunks]);
Expand Down Expand Up @@ -436,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))
: [];
Expand All @@ -445,10 +469,46 @@ export class LuaPrinter {
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(...this.printFunctionParameters(expression));
chunks.push(")");

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 chunks: SourceChunk[] = [];

chunks.push("function ");
chunks.push(this.printExpression(statement.left[0]));
chunks.push("(");
chunks.push(...this.printFunctionParameters(expression));
chunks.push(")\n");

this.pushIndent();
Expand Down Expand Up @@ -482,14 +542,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("}");
Expand All @@ -501,41 +565,21 @@ export class LuaPrinter {
const chunks: SourceChunk[] = [];

chunks.push(this.printOperator(expression.operator));

if (this.needsParentheses(expression.operand)) {
chunks.push("(", this.printExpression(expression.operand), ")");
} else {
chunks.push(this.printExpression(expression.operand));
}
chunks.push(this.printExpression(expression.operand));

return this.createSourceNode(expression, chunks);
}

private printBinaryExpression(expression: tstl.BinaryExpression): SourceNode {
const chunks: SourceChunk[] = [];

if (this.needsParentheses(expression.left)) {
chunks.push("(", this.printExpression(expression.left), ")");
} else {
chunks.push(this.printExpression(expression.left));
}

chunks.push(this.printExpression(expression.left));
chunks.push(" ", this.printOperator(expression.operator), " ");

if (this.needsParentheses(expression.right)) {
chunks.push("(", this.printExpression(expression.right), ")");
} else {
chunks.push(this.printExpression(expression.right));
}
chunks.push(this.printExpression(expression.right));

return this.createSourceNode(expression, chunks);
}

private needsParentheses(expression: tstl.Expression): boolean {
return tstl.isBinaryExpression(expression) || tstl.isUnaryExpression(expression)
|| tstl.isFunctionExpression(expression);
}

private printParenthesizedExpression(expression: tstl.ParenthesizedExpression): SourceNode {
return this.createSourceNode(expression, ["(", this.printExpression(expression.innerEpxression), ")"]);
}
Expand All @@ -544,11 +588,7 @@ export class LuaPrinter {
const chunks = [];
const parameterChunks = this.joinChunks(", ", expression.params.map(e => this.printExpression(e)));

if (this.needsParentheses(expression.expression)) {
chunks.push("(", this.printExpression(expression.expression), ")(", ...parameterChunks, ")");
} else {
chunks.push(this.printExpression(expression.expression), "(", ...parameterChunks, ")");
}
chunks.push(this.printExpression(expression.expression), "(", ...parameterChunks, ")");

return this.concatNodes(...chunks);
}
Expand Down
Loading