UNPKG

typescript-to-lua

Version:

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

217 lines 12.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.transformSwitchStatement = void 0; const ts = __importStar(require("typescript")); const lua = __importStar(require("../../LuaAST")); const preceding_statements_1 = require("../utils/preceding-statements"); const scope_1 = require("../utils/scope"); const binary_expression_1 = require("./binary-expression"); const containsBreakOrReturn = (nodes) => { for (const s of nodes) { if (ts.isBreakStatement(s) || ts.isReturnStatement(s)) { return true; } else if (ts.isBlock(s) && containsBreakOrReturn(s.statements)) { return true; } else if (s.kind === ts.SyntaxKind.SyntaxList) { // We cannot use getChildren() because that breaks when using synthetic nodes from transformers // So get children the long way const children = []; ts.forEachChild(s, c => children.push(c)); if (containsBreakOrReturn(children)) { return true; } } } return false; }; const createOrExpression = (context, left, right, rightPrecedingStatements) => { if (rightPrecedingStatements.length > 0) { return (0, binary_expression_1.createShortCircuitBinaryExpressionPrecedingStatements)(context, left, right, rightPrecedingStatements, ts.SyntaxKind.BarBarToken); } else { return { precedingStatements: rightPrecedingStatements, result: lua.createBinaryExpression(left, right, lua.SyntaxKind.OrOperator), }; } }; const coalesceCondition = (condition, conditionPrecedingStatements, switchVariable, expression, context) => { const { precedingStatements, result: transformedExpression } = (0, preceding_statements_1.transformInPrecedingStatementScope)(context, () => context.transformExpression(expression)); // Coalesce skipped statements const comparison = lua.createBinaryExpression(switchVariable, transformedExpression, lua.SyntaxKind.EqualityOperator); if (condition) { return createOrExpression(context, condition, comparison, precedingStatements); } // Next condition return { precedingStatements: [...conditionPrecedingStatements, ...precedingStatements], result: comparison }; }; const transformSwitchStatement = (statement, context) => { const scope = context.pushScope(scope_1.ScopeType.Switch, statement); // Give the switch and condition accumulator a unique name to prevent nested switches from acting up. const switchName = `____switch${scope.id}`; const conditionName = `____cond${scope.id}`; const switchVariable = lua.createIdentifier(switchName); const conditionVariable = lua.createIdentifier(conditionName); // If the switch only has a default clause, wrap it in a single do. // Otherwise, we need to generate a set of if statements to emulate the switch. const statements = []; const hoistedStatements = []; const hoistedIdentifiers = []; const clauses = statement.caseBlock.clauses; if (clauses.length === 1 && ts.isDefaultClause(clauses[0])) { const defaultClause = clauses[0].statements; if (defaultClause.length) { const { statements: defaultStatements, hoistedStatements: defaultHoistedStatements, hoistedIdentifiers: defaultHoistedIdentifiers, } = (0, scope_1.separateHoistedStatements)(context, context.transformStatements(defaultClause)); hoistedStatements.push(...defaultHoistedStatements); hoistedIdentifiers.push(...defaultHoistedIdentifiers); statements.push(lua.createDoStatement(defaultStatements)); } } else { // Build up the condition for each if statement let defaultTransformed = false; let isInitialCondition = true; let condition = undefined; let conditionPrecedingStatements = []; for (let i = 0; i < clauses.length; i++) { const clause = clauses[i]; const previousClause = clauses[i - 1]; // Skip redundant default clauses, will be handled in final default case if (i === 0 && ts.isDefaultClause(clause)) continue; if (ts.isDefaultClause(clause) && previousClause && containsBreakOrReturn(previousClause.statements)) { continue; } // Compute the condition for the if statement if (!ts.isDefaultClause(clause)) { const { precedingStatements, result } = coalesceCondition(condition, conditionPrecedingStatements, switchVariable, clause.expression, context); conditionPrecedingStatements = precedingStatements; condition = result; // Skip empty clauses unless final clause (i.e side-effects) if (i !== clauses.length - 1 && clause.statements.length === 0) continue; // Declare or assign condition variable if (isInitialCondition) { statements.push(...conditionPrecedingStatements, lua.createVariableDeclarationStatement(conditionVariable, condition)); } else { const { precedingStatements, result } = createOrExpression(context, conditionVariable, condition, conditionPrecedingStatements); conditionPrecedingStatements = precedingStatements; condition = result; statements.push(...conditionPrecedingStatements, lua.createAssignmentStatement(conditionVariable, condition)); } isInitialCondition = false; } else { // If the default is proceeded by empty clauses and will be emitted we may need to initialize the condition if (isInitialCondition) { statements.push(...conditionPrecedingStatements, lua.createVariableDeclarationStatement(conditionVariable, condition !== null && condition !== void 0 ? condition : lua.createBooleanLiteral(false))); // Clear condition ot ensure it is not evaluated twice condition = undefined; conditionPrecedingStatements = []; isInitialCondition = false; } // Allow default to fallthrough to final default clause if (i === clauses.length - 1) { // Evaluate the final condition that we may be skipping if (condition) { const { precedingStatements, result } = createOrExpression(context, conditionVariable, condition, conditionPrecedingStatements); conditionPrecedingStatements = precedingStatements; condition = result; statements.push(...conditionPrecedingStatements, lua.createAssignmentStatement(conditionVariable, condition)); } continue; } } // Transform the clause and append the final break statement if necessary const { statements: clauseStatements, hoistedStatements: clauseHoistedStatements, hoistedIdentifiers: clauseHoistedIdentifiers, } = (0, scope_1.separateHoistedStatements)(context, context.transformStatements(clause.statements)); if (i === clauses.length - 1 && !containsBreakOrReturn(clause.statements)) { clauseStatements.push(lua.createBreakStatement()); } hoistedStatements.push(...clauseHoistedStatements); hoistedIdentifiers.push(...clauseHoistedIdentifiers); // Remember that we transformed default clause so we don't duplicate hoisted statements later if (ts.isDefaultClause(clause)) { defaultTransformed = true; } // Push if statement for case statements.push(lua.createIfStatement(conditionVariable, lua.createBlock(clauseStatements))); // Clear condition for next clause condition = undefined; conditionPrecedingStatements = []; } // If no conditions above match, we need to create the final default case code-path, // as we only handle fallthrough into defaults in the previous if statement chain const start = clauses.findIndex(c => ts.isDefaultClause(c)); if (start >= 0) { // Find the last clause that we can fallthrough to const end = clauses.findIndex((clause, index) => index >= start && containsBreakOrReturn(clause.statements)); const { statements: defaultStatements, hoistedStatements: defaultHoistedStatements, hoistedIdentifiers: defaultHoistedIdentifiers, } = (0, scope_1.separateHoistedStatements)(context, context.transformStatements(clauses[start].statements)); // Only push hoisted statements if this is the first time we're transforming the default clause if (!defaultTransformed) { hoistedStatements.push(...defaultHoistedStatements); hoistedIdentifiers.push(...defaultHoistedIdentifiers); } // Combine the fallthrough statements for (const clause of clauses.slice(start + 1, end >= 0 ? end + 1 : undefined)) { let statements = context.transformStatements(clause.statements); // Drop hoisted statements as they were already added when clauses were initially transformed above ({ statements } = (0, scope_1.separateHoistedStatements)(context, statements)); defaultStatements.push(...statements); } // Add the default clause if it has any statements // The switch will always break on the final clause and skip execution if valid to do so if (defaultStatements.length) { statements.push(lua.createDoStatement(defaultStatements)); } } } // Hoist the variable, function, and import statements to the top of the switch statements.unshift(...hoistedStatements); if (hoistedIdentifiers.length > 0) { statements.unshift(lua.createVariableDeclarationStatement(hoistedIdentifiers)); } context.popScope(); // Add the switch expression after hoisting const expression = context.transformExpression(statement.expression); statements.unshift(lua.createVariableDeclarationStatement(switchVariable, expression)); // Wrap the statements in a repeat until true statement to facilitate dynamic break/returns return lua.createRepeatStatement(lua.createBlock(statements), lua.createBooleanLiteral(true)); }; exports.transformSwitchStatement = transformSwitchStatement; //# sourceMappingURL=switch.js.map