UNPKG

typescript-to-lua

Version:

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

229 lines 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.captureThisValue = captureThisValue; exports.isOptionalContinuation = isOptionalContinuation; exports.getOptionalContinuationData = getOptionalContinuationData; exports.transformOptionalChain = transformOptionalChain; exports.transformOptionalChainWithCapture = transformOptionalChainWithCapture; exports.transformOptionalDeleteExpression = transformOptionalDeleteExpression; const ts = require("typescript"); const lua = require("../../LuaAST"); const context_1 = require("../context"); const utils_1 = require("../../utils"); const preceding_statements_1 = require("../utils/preceding-statements"); const access_1 = require("./access"); const expression_list_1 = require("./expression-list"); const typescript_1 = require("../utils/typescript"); const expression_statement_1 = require("./expression-statement"); function skipNonNullChains(chain) { while (ts.isNonNullChain(chain)) { chain = chain.expression; } return chain; } function flattenChain(chain) { chain = skipNonNullChains(chain); const links = [chain]; while (!chain.questionDotToken && !ts.isTaggedTemplateExpression(chain)) { const nextLink = chain.expression; (0, utils_1.assert)(ts.isOptionalChain(nextLink)); chain = skipNonNullChains(nextLink); links.unshift(chain); } return { expression: chain.expression, chain: links }; } function transformExpressionWithThisValueCapture(context, node, thisValueCapture) { if (ts.isParenthesizedExpression(node)) { return transformExpressionWithThisValueCapture(context, node.expression, thisValueCapture); } if (ts.isPropertyAccessExpression(node)) { return (0, access_1.transformPropertyAccessExpressionWithCapture)(context, node, thisValueCapture); } if (ts.isElementAccessExpression(node)) { return (0, access_1.transformElementAccessExpressionWithCapture)(context, node, thisValueCapture); } return { expression: context.transformExpression(node) }; } // returns thisValueCapture exactly if a temp variable was used. function captureThisValue(context, expression, thisValueCapture, tsOriginal) { if (!(0, expression_list_1.shouldMoveToTemp)(context, expression, tsOriginal)) { return expression; } const tempAssignment = lua.createAssignmentStatement(thisValueCapture, expression, tsOriginal); context.addPrecedingStatements(tempAssignment); return thisValueCapture; } const optionalContinuations = new WeakMap(); // should be translated verbatim to lua function createOptionalContinuationIdentifier(text, tsOriginal) { const identifier = ts.factory.createIdentifier(text); ts.setOriginalNode(identifier, tsOriginal); optionalContinuations.set(identifier, { usedIdentifiers: [], }); return identifier; } function isOptionalContinuation(node) { return ts.isIdentifier(node) && optionalContinuations.has(node); } function getOptionalContinuationData(identifier) { return optionalContinuations.get(identifier); } function transformOptionalChain(context, node) { return transformOptionalChainWithCapture(context, node, undefined).expression; } function transformOptionalChainWithCapture(context, tsNode, thisValueCapture, isDelete) { const luaTempName = context.createTempName("opt"); const { expression: tsLeftExpression, chain } = flattenChain(tsNode); // build temp.b.c.d const tsTemp = createOptionalContinuationIdentifier(luaTempName, tsLeftExpression); let tsRightExpression = tsTemp; for (const link of chain) { if (ts.isPropertyAccessExpression(link)) { tsRightExpression = ts.factory.createPropertyAccessExpression(tsRightExpression, link.name); } else if (ts.isElementAccessExpression(link)) { tsRightExpression = ts.factory.createElementAccessExpression(tsRightExpression, link.argumentExpression); } else if (ts.isCallExpression(link)) { tsRightExpression = ts.factory.createCallExpression(tsRightExpression, undefined, link.arguments); } else { (0, utils_1.assertNever)(link); } ts.setOriginalNode(tsRightExpression, link); } if (isDelete) { tsRightExpression = ts.factory.createDeleteExpression(tsRightExpression); ts.setOriginalNode(tsRightExpression, isDelete); } // transform right expression first to check if thisValue capture is needed // capture and return thisValue if requested from outside let returnThisValue; const { precedingStatements: rightPrecedingStatements, result: rightExpression } = (0, preceding_statements_1.transformInPrecedingStatementScope)(context, () => { if (!thisValueCapture) { return context.transformExpression(tsRightExpression); } const { expression: result, thisValue } = transformExpressionWithThisValueCapture(context, tsRightExpression, thisValueCapture); returnThisValue = thisValue; return result; }); // transform left expression, handle thisValue if needed by rightExpression const thisValueCaptureName = context.createTempName("this"); const leftThisValueTemp = lua.createIdentifier(thisValueCaptureName, undefined, context_1.tempSymbolId); let capturedThisValue; const optionalContinuationData = getOptionalContinuationData(tsTemp); const rightContextualCall = optionalContinuationData === null || optionalContinuationData === void 0 ? void 0 : optionalContinuationData.contextualCall; const { precedingStatements: leftPrecedingStatements, result: leftExpression } = (0, preceding_statements_1.transformInPrecedingStatementScope)(context, () => { let result; if (rightContextualCall) { ({ expression: result, thisValue: capturedThisValue } = transformExpressionWithThisValueCapture(context, tsLeftExpression, leftThisValueTemp)); } else { result = context.transformExpression(tsLeftExpression); } return result; }); // handle super calls by passing self as context function getLeftMostChainItem(node) { if (ts.isPropertyAccessExpression(node)) { return getLeftMostChainItem(node.expression); } else { return node; } } if (getLeftMostChainItem(tsLeftExpression).kind === ts.SyntaxKind.SuperKeyword) { capturedThisValue = lua.createIdentifier("self"); } // handle context if (rightContextualCall) { if (capturedThisValue) { rightContextualCall.params[0] = capturedThisValue; if (capturedThisValue === leftThisValueTemp) { context.addPrecedingStatements(lua.createVariableDeclarationStatement(leftThisValueTemp)); } } else { if (context.isStrict) { rightContextualCall.params[0] = lua.createNilLiteral(); } else { const identifier = lua.createIdentifier("_G"); if (rightPrecedingStatements.length === 0) { rightContextualCall.params[0] = identifier; } else { const tempContext = context.createTempNameForLuaExpression(identifier); rightPrecedingStatements.unshift(lua.createVariableDeclarationStatement(tempContext, identifier)); rightContextualCall.params[0] = tempContext; } } } } // evaluate optional chain context.addPrecedingStatements(leftPrecedingStatements); // try use existing variable instead of creating new one, if possible let leftIdentifier; const usedLuaIdentifiers = optionalContinuationData === null || optionalContinuationData === void 0 ? void 0 : optionalContinuationData.usedIdentifiers; const reuseLeftIdentifier = usedLuaIdentifiers && usedLuaIdentifiers.length > 0 && lua.isIdentifier(leftExpression) && (rightPrecedingStatements.length === 0 || !(0, expression_list_1.shouldMoveToTemp)(context, leftExpression, tsLeftExpression)); if (reuseLeftIdentifier) { leftIdentifier = leftExpression; for (const usedIdentifier of usedLuaIdentifiers) { usedIdentifier.text = leftIdentifier.text; } } else { leftIdentifier = lua.createIdentifier(luaTempName, undefined, context_1.tempSymbolId); context.addPrecedingStatements(lua.createVariableDeclarationStatement(leftIdentifier, leftExpression)); } if (!(0, typescript_1.expressionResultIsUsed)(tsNode) || isDelete) { // if left ~= nil then // <right preceding statements> // <right expression> // end const innerExpression = (0, expression_statement_1.wrapInStatement)(rightExpression); const innerStatements = rightPrecedingStatements; if (innerExpression) innerStatements.push(innerExpression); context.addPrecedingStatements(lua.createIfStatement(lua.createBinaryExpression(leftIdentifier, lua.createNilLiteral(), lua.SyntaxKind.InequalityOperator), lua.createBlock(innerStatements))); return { expression: lua.createNilLiteral(), thisValue: returnThisValue }; } else if (rightPrecedingStatements.length === 0 && !(0, typescript_1.canBeFalsyWhenNotNull)(context, context.checker.getTypeAtLocation(tsLeftExpression))) { // return a && a.b return { expression: lua.createBinaryExpression(leftIdentifier, rightExpression, lua.SyntaxKind.AndOperator, tsNode), thisValue: returnThisValue, }; } else { let resultIdentifier; if (!reuseLeftIdentifier) { // reuse temp variable for output resultIdentifier = leftIdentifier; } else { resultIdentifier = lua.createIdentifier(context.createTempName("opt_result"), undefined, context_1.tempSymbolId); context.addPrecedingStatements(lua.createVariableDeclarationStatement(resultIdentifier)); } // if left ~= nil then // <right preceding statements> // result = <right expression> // end // return result context.addPrecedingStatements(lua.createIfStatement(lua.createBinaryExpression(leftIdentifier, lua.createNilLiteral(), lua.SyntaxKind.InequalityOperator), lua.createBlock([ ...rightPrecedingStatements, lua.createAssignmentStatement(resultIdentifier, rightExpression), ]))); return { expression: resultIdentifier, thisValue: returnThisValue }; } } function transformOptionalDeleteExpression(context, node, innerExpression) { transformOptionalChainWithCapture(context, innerExpression, undefined, node); return lua.createBooleanLiteral(true, node); } //# sourceMappingURL=optional-chaining.js.map