typescript-to-lua
Version:
A generic TypeScript to Lua transpiler. Write your code in TypeScript and publish Lua!
229 lines • 11 kB
JavaScript
;
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