UNPKG

typescript-to-lua

Version:

A generic TypeScript to Lua transpiler. Write your code in TypeScript and publish Lua!

271 lines 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformYieldExpression = exports.transformFunctionDeclaration = void 0; exports.createCallableTable = createCallableTable; exports.isFunctionTypeWithProperties = isFunctionTypeWithProperties; exports.transformFunctionBodyContent = transformFunctionBodyContent; exports.transformFunctionBodyHeader = transformFunctionBodyHeader; exports.transformFunctionBody = transformFunctionBody; exports.transformParameters = transformParameters; exports.transformFunctionToExpression = transformFunctionToExpression; exports.transformFunctionLikeDeclaration = transformFunctionLikeDeclaration; const ts = require("typescript"); const CompilerOptions_1 = require("../../CompilerOptions"); const lua = require("../../LuaAST"); const utils_1 = require("../../utils"); const export_1 = require("../utils/export"); const function_context_1 = require("../utils/function-context"); const language_extensions_1 = require("../utils/language-extensions"); const lua_ast_1 = require("../utils/lua-ast"); const lualib_1 = require("../utils/lualib"); const preceding_statements_1 = require("../utils/preceding-statements"); const scope_1 = require("../utils/scope"); const typescript_1 = require("../utils/typescript"); const async_await_1 = require("./async-await"); const identifier_1 = require("./identifier"); const return_1 = require("./return"); const variable_declaration_1 = require("./variable-declaration"); function transformParameterDefaultValueDeclaration(context, parameterName, value, tsOriginal) { const { precedingStatements: statements, result: parameterValue } = (0, preceding_statements_1.transformInPrecedingStatementScope)(context, () => context.transformExpression(value)); if (!lua.isNilLiteral(parameterValue)) { statements.push(lua.createAssignmentStatement(parameterName, parameterValue)); } if (statements.length === 0) return undefined; const nilCondition = lua.createBinaryExpression(parameterName, lua.createNilLiteral(), lua.SyntaxKind.EqualityOperator); const ifBlock = lua.createBlock(statements, tsOriginal); return lua.createIfStatement(nilCondition, ifBlock, undefined, tsOriginal); } function isRestParameterReferenced(identifier, scope) { if (!identifier.symbolId) { return true; } if (scope.referencedSymbols === undefined) { return false; } const references = scope.referencedSymbols.get(identifier.symbolId); return references !== undefined && references.length > 0; } function createCallableTable(functionExpression) { var _a; // __call metamethod receives the table as the first argument, so we need to add a dummy parameter if (lua.isFunctionExpression(functionExpression)) { (_a = functionExpression.params) === null || _a === void 0 ? void 0 : _a.unshift(lua.createAnonymousIdentifier()); } else { // functionExpression may have been replaced (lib functions, etc...), // so we create a forwarding function to eat the extra argument functionExpression = lua.createFunctionExpression(lua.createBlock([ lua.createReturnStatement([lua.createCallExpression(functionExpression, [lua.createDotsLiteral()])]), ]), [lua.createAnonymousIdentifier()], lua.createDotsLiteral(), lua.NodeFlags.Inline); } return lua.createCallExpression(lua.createIdentifier("setmetatable"), [ lua.createTableExpression(), lua.createTableExpression([ lua.createTableFieldExpression(functionExpression, lua.createStringLiteral("__call")), ]), ]); } function isFunctionTypeWithProperties(context, functionType) { if (functionType.isUnion()) { return functionType.types.some(t => isFunctionTypeWithProperties(context, t)); } else { return ((0, typescript_1.isFunctionType)(functionType) && functionType.getProperties().length > 0 && (0, language_extensions_1.getExtensionKindForType)(context, functionType) === undefined // ignore TSTL extension functions like $range ); } } function transformFunctionBodyContent(context, body) { if (!ts.isBlock(body)) { const { precedingStatements, result: returnStatement } = (0, preceding_statements_1.transformInPrecedingStatementScope)(context, () => (0, return_1.transformExpressionBodyToReturnStatement)(context, body)); return [...precedingStatements, returnStatement]; } const bodyStatements = (0, scope_1.performHoisting)(context, context.transformStatements(body.statements)); return bodyStatements; } function transformFunctionBodyHeader(context, bodyScope, parameters, spreadIdentifier) { const headerStatements = []; // Add default parameters and object binding patterns const bindingPatternDeclarations = []; let bindPatternIndex = 0; for (const declaration of parameters) { if (ts.isObjectBindingPattern(declaration.name) || ts.isArrayBindingPattern(declaration.name)) { const identifier = lua.createIdentifier(`____bindingPattern${bindPatternIndex++}`); if (declaration.initializer !== undefined) { // Default binding parameter const initializer = transformParameterDefaultValueDeclaration(context, identifier, declaration.initializer); if (initializer) headerStatements.push(initializer); } // Binding pattern const name = declaration.name; const { precedingStatements, result: bindings } = (0, preceding_statements_1.transformInPrecedingStatementScope)(context, () => (0, variable_declaration_1.transformBindingPattern)(context, name, identifier)); bindingPatternDeclarations.push(...precedingStatements, ...bindings); } else if (declaration.initializer !== undefined) { // Default parameter const initializer = transformParameterDefaultValueDeclaration(context, (0, identifier_1.transformIdentifier)(context, declaration.name), declaration.initializer); if (initializer) headerStatements.push(initializer); } } // Push spread operator here if (spreadIdentifier && isRestParameterReferenced(spreadIdentifier, bodyScope)) { const spreadTable = context.luaTarget === CompilerOptions_1.LuaTarget.Lua50 ? lua.createArgLiteral() : (0, lua_ast_1.wrapInTable)(lua.createDotsLiteral()); headerStatements.push(lua.createVariableDeclarationStatement(spreadIdentifier, spreadTable)); } // Binding pattern statements need to be after spread table is declared headerStatements.push(...bindingPatternDeclarations); return headerStatements; } function transformFunctionBody(context, parameters, body, spreadIdentifier, node) { const scope = context.pushScope(scope_1.ScopeType.Function); scope.node = node; let bodyStatements = transformFunctionBodyContent(context, body); if (node && (0, async_await_1.isAsyncFunction)(node)) { bodyStatements = [lua.createReturnStatement([(0, async_await_1.wrapInAsyncAwaiter)(context, bodyStatements)])]; } const headerStatements = transformFunctionBodyHeader(context, scope, parameters, spreadIdentifier); context.popScope(); return [[...headerStatements, ...bodyStatements], scope]; } function transformParameters(context, parameters, functionContext) { // Build parameter string const paramNames = []; if (functionContext) { paramNames.push(functionContext); } let restParamName; let dotsLiteral; let identifierIndex = 0; // Only push parameter name to paramName array if it isn't a spread parameter for (const param of parameters) { if (ts.isIdentifier(param.name) && ts.identifierToKeywordKind(param.name) === ts.SyntaxKind.ThisKeyword) { continue; } // Binding patterns become ____bindingPattern0, ____bindingPattern1, etc as function parameters // See transformFunctionBody for how these values are destructured const paramName = ts.isObjectBindingPattern(param.name) || ts.isArrayBindingPattern(param.name) ? lua.createIdentifier(`____bindingPattern${identifierIndex++}`) : (0, identifier_1.transformIdentifier)(context, param.name); // This parameter is a spread parameter (...param) if (!param.dotDotDotToken) { paramNames.push(paramName); } else { restParamName = paramName; // Push the spread operator into the paramNames array dotsLiteral = lua.createDotsLiteral(); } } return [paramNames, dotsLiteral, restParamName]; } function transformFunctionToExpression(context, node) { var _a; (0, utils_1.assert)(node.body); const type = context.checker.getTypeAtLocation(node); let functionContext; const firstParam = node.parameters[0]; const hasThisVoidParameter = firstParam && ts.isIdentifier(firstParam.name) && ts.identifierToKeywordKind(firstParam.name) === ts.SyntaxKind.ThisKeyword && ((_a = firstParam.type) === null || _a === void 0 ? void 0 : _a.kind) === ts.SyntaxKind.VoidKeyword; if (!hasThisVoidParameter && (0, function_context_1.getFunctionContextType)(context, type) !== function_context_1.ContextType.Void) { if (ts.isArrowFunction(node)) { // dummy context for arrow functions with parameters if (node.parameters.length > 0) { functionContext = lua.createAnonymousIdentifier(); } } else { // self context functionContext = (0, lua_ast_1.createSelfIdentifier)(); } } let flags = lua.NodeFlags.None; if (!ts.isBlock(node.body)) flags |= lua.NodeFlags.Inline; if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) { flags |= lua.NodeFlags.Declaration; } const [paramNames, dotsLiteral, spreadIdentifier] = transformParameters(context, node.parameters, functionContext); const [transformedBody, functionScope] = transformFunctionBody(context, node.parameters, node.body, spreadIdentifier, node); const functionExpression = lua.createFunctionExpression(lua.createBlock(transformedBody), paramNames, dotsLiteral, flags, node); return [ node.asteriskToken ? (0, lualib_1.transformLuaLibFunction)(context, lualib_1.LuaLibFeature.Generator, undefined, functionExpression) : functionExpression, functionScope, ]; } function transformFunctionLikeDeclaration(node, context) { if (node.body === undefined) { // This code can be reached only from object methods, which is TypeScript error return lua.createNilLiteral(); } const [functionExpression, functionScope] = transformFunctionToExpression(context, node); const isNamedFunctionExpression = ts.isFunctionExpression(node) && node.name; // Handle named function expressions which reference themselves if (isNamedFunctionExpression && functionScope.referencedSymbols) { const symbol = context.checker.getSymbolAtLocation(node.name); if (symbol) { // TODO: Not using symbol ids because of https://github.com/microsoft/TypeScript/issues/37131 const isReferenced = [...functionScope.referencedSymbols].some(([, nodes]) => nodes.some(n => { var _a; return ((_a = context.checker.getSymbolAtLocation(n)) === null || _a === void 0 ? void 0 : _a.valueDeclaration) === symbol.valueDeclaration; })); // Only handle if the name is actually referenced inside the function if (isReferenced) { const nameIdentifier = (0, identifier_1.transformIdentifier)(context, node.name); if (isFunctionTypeWithProperties(context, context.checker.getTypeAtLocation(node))) { context.addPrecedingStatements([ lua.createVariableDeclarationStatement(nameIdentifier), lua.createAssignmentStatement(nameIdentifier, createCallableTable(functionExpression)), ]); } else { context.addPrecedingStatements(lua.createVariableDeclarationStatement(nameIdentifier, functionExpression)); } return lua.cloneIdentifier(nameIdentifier); } } } return isNamedFunctionExpression && isFunctionTypeWithProperties(context, context.checker.getTypeAtLocation(node)) ? createCallableTable(functionExpression) : functionExpression; } const transformFunctionDeclaration = (node, context) => { var _a; // Don't transform functions without body (overload declarations) if (node.body === undefined) { return undefined; } if ((0, export_1.hasDefaultExportModifier)(node)) { return lua.createAssignmentStatement(lua.createTableIndexExpression((0, lua_ast_1.createExportsIdentifier)(), (0, export_1.createDefaultExportStringLiteral)(node)), transformFunctionLikeDeclaration(node, context)); } const [functionExpression, functionScope] = transformFunctionToExpression(context, node); // Name being undefined without default export is a TypeScript error const name = node.name ? (0, identifier_1.transformIdentifier)(context, node.name) : lua.createAnonymousIdentifier(); // Remember symbols referenced in this function for hoisting later if (name.symbolId !== undefined) { const scope = (0, scope_1.peekScope)(context); if (!scope.functionDefinitions) { scope.functionDefinitions = new Map(); } const functionInfo = { referencedSymbols: (_a = functionScope.referencedSymbols) !== null && _a !== void 0 ? _a : new Map() }; scope.functionDefinitions.set(name.symbolId, functionInfo); } // Wrap functions with properties into a callable table const wrappedFunction = node.name && isFunctionTypeWithProperties(context, context.checker.getTypeAtLocation(node.name)) ? createCallableTable(functionExpression) : functionExpression; return (0, lua_ast_1.createLocalOrExportedOrGlobalDeclaration)(context, name, wrappedFunction, node); }; exports.transformFunctionDeclaration = transformFunctionDeclaration; const transformYieldExpression = (expression, context) => { const parameters = expression.expression ? [context.transformExpression(expression.expression)] : []; return expression.asteriskToken ? (0, lualib_1.transformLuaLibFunction)(context, lualib_1.LuaLibFeature.DelegatedYield, expression, ...parameters) : lua.createCallExpression(lua.createTableIndexExpression(lua.createIdentifier("coroutine"), lua.createStringLiteral("yield")), parameters, expression); }; exports.transformYieldExpression = transformYieldExpression; //# sourceMappingURL=function.js.map