typescript-to-lua
Version:
A generic TypeScript to Lua transpiler. Write your code in TypeScript and publish Lua!
224 lines • 10.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoopContinued = exports.ScopeType = void 0;
exports.walkScopesUp = walkScopesUp;
exports.markSymbolAsReferencedInCurrentScopes = markSymbolAsReferencedInCurrentScopes;
exports.peekScope = peekScope;
exports.findScope = findScope;
exports.addScopeVariableDeclaration = addScopeVariableDeclaration;
exports.hasReferencedUndefinedLocalFunction = hasReferencedUndefinedLocalFunction;
exports.hasReferencedSymbol = hasReferencedSymbol;
exports.isFunctionScopeWithDefinition = isFunctionScopeWithDefinition;
exports.separateHoistedStatements = separateHoistedStatements;
exports.performHoisting = performHoisting;
const ts = require("typescript");
const lua = require("../../LuaAST");
const utils_1 = require("../../utils");
const symbols_1 = require("./symbols");
const typescript_1 = require("./typescript");
var ScopeType;
(function (ScopeType) {
ScopeType[ScopeType["File"] = 1] = "File";
ScopeType[ScopeType["Function"] = 2] = "Function";
ScopeType[ScopeType["Switch"] = 4] = "Switch";
ScopeType[ScopeType["Loop"] = 8] = "Loop";
ScopeType[ScopeType["Conditional"] = 16] = "Conditional";
ScopeType[ScopeType["Block"] = 32] = "Block";
ScopeType[ScopeType["Try"] = 64] = "Try";
ScopeType[ScopeType["Catch"] = 128] = "Catch";
ScopeType[ScopeType["LoopInitializer"] = 256] = "LoopInitializer";
})(ScopeType || (exports.ScopeType = ScopeType = {}));
var LoopContinued;
(function (LoopContinued) {
LoopContinued[LoopContinued["WithGoto"] = 0] = "WithGoto";
LoopContinued[LoopContinued["WithRepeatBreak"] = 1] = "WithRepeatBreak";
LoopContinued[LoopContinued["WithContinue"] = 2] = "WithContinue";
})(LoopContinued || (exports.LoopContinued = LoopContinued = {}));
function* walkScopesUp(context) {
const scopeStack = context.scopeStack;
for (let i = scopeStack.length - 1; i >= 0; --i) {
const scope = scopeStack[i];
yield scope;
}
}
function markSymbolAsReferencedInCurrentScopes(context, symbolId, identifier) {
for (const scope of context.scopeStack) {
if (!scope.referencedSymbols) {
scope.referencedSymbols = new Map();
}
const references = (0, utils_1.getOrUpdate)(scope.referencedSymbols, symbolId, () => []);
references.push(identifier);
}
}
function peekScope(context) {
const scopeStack = context.scopeStack;
const scope = scopeStack[scopeStack.length - 1];
(0, utils_1.assert)(scope);
return scope;
}
function findScope(context, scopeTypes) {
for (let i = context.scopeStack.length - 1; i >= 0; --i) {
const scope = context.scopeStack[i];
if (scopeTypes & scope.type) {
return scope;
}
}
}
function addScopeVariableDeclaration(scope, declaration) {
if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
}
scope.variableDeclarations.push(declaration);
}
function isHoistableFunctionDeclaredInScope(symbol, scopeNode) {
var _a;
return (_a = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _a === void 0 ? void 0 : _a.some(d => ts.isFunctionDeclaration(d) && (0, typescript_1.findFirstNodeAbove)(d, (n) => n === scopeNode));
}
// Checks for references to local functions which haven't been defined yet,
// and thus will be hoisted above the current position.
function hasReferencedUndefinedLocalFunction(context, scope) {
var _a;
if (!scope.referencedSymbols || !scope.node) {
return false;
}
for (const [symbolId, nodes] of scope.referencedSymbols) {
const type = context.checker.getTypeAtLocation(nodes[0]);
if (!((_a = scope.functionDefinitions) === null || _a === void 0 ? void 0 : _a.has(symbolId)) &&
type.getCallSignatures().length > 0 &&
isHoistableFunctionDeclaredInScope(type.symbol, scope.node)) {
return true;
}
}
return false;
}
function hasReferencedSymbol(context, scope, symbol) {
if (!scope.referencedSymbols) {
return;
}
for (const nodes of scope.referencedSymbols.values()) {
if (nodes.some(node => context.checker.getSymbolAtLocation(node) === symbol)) {
return true;
}
}
return false;
}
function isFunctionScopeWithDefinition(scope) {
return scope.node !== undefined && ts.isFunctionLike(scope.node);
}
function separateHoistedStatements(context, statements) {
const scope = peekScope(context);
const allHoistedStatments = [];
const allHoistedIdentifiers = [];
let { unhoistedStatements, hoistedStatements, hoistedIdentifiers } = hoistFunctionDefinitions(context, scope, statements);
allHoistedStatments.push(...hoistedStatements);
allHoistedIdentifiers.push(...hoistedIdentifiers);
({ unhoistedStatements, hoistedIdentifiers } = hoistVariableDeclarations(context, scope, unhoistedStatements));
allHoistedIdentifiers.push(...hoistedIdentifiers);
({ unhoistedStatements, hoistedStatements } = hoistImportStatements(scope, unhoistedStatements));
allHoistedStatments.unshift(...hoistedStatements);
return {
statements: unhoistedStatements,
hoistedStatements: allHoistedStatments,
hoistedIdentifiers: allHoistedIdentifiers,
};
}
function performHoisting(context, statements) {
const result = separateHoistedStatements(context, statements);
const modifiedStatements = [...result.hoistedStatements, ...result.statements];
if (result.hoistedIdentifiers.length > 0) {
modifiedStatements.unshift(lua.createVariableDeclarationStatement(result.hoistedIdentifiers));
}
return modifiedStatements;
}
function shouldHoistSymbol(context, symbolId, scope) {
// Always hoist in top-level of switch statements
if (scope.type === ScopeType.Switch) {
return true;
}
const symbolInfo = (0, symbols_1.getSymbolInfo)(context, symbolId);
if (!symbolInfo) {
return false;
}
const declaration = (0, typescript_1.getFirstDeclarationInFile)(symbolInfo.symbol, context.sourceFile);
if (!declaration) {
return false;
}
if (symbolInfo.firstSeenAtPos < declaration.pos) {
return true;
}
if (scope.functionDefinitions) {
for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) {
(0, utils_1.assert)(functionDefinition.definition);
const { line, column } = lua.getOriginalPos(functionDefinition.definition);
if (line !== undefined && column !== undefined) {
const definitionPos = ts.getPositionOfLineAndCharacter(context.sourceFile, line, column);
if (functionSymbolId !== symbolId && // Don't recurse into self
declaration.pos < definitionPos && // Ignore functions before symbol declaration
functionDefinition.referencedSymbols.has(symbolId) &&
shouldHoistSymbol(context, functionSymbolId, scope)) {
return true;
}
}
}
}
return false;
}
function hoistVariableDeclarations(context, scope, statements) {
if (!scope.variableDeclarations) {
return { unhoistedStatements: statements, hoistedIdentifiers: [] };
}
const unhoistedStatements = [...statements];
const hoistedIdentifiers = [];
for (const declaration of scope.variableDeclarations) {
const symbols = declaration.left.map(i => i.symbolId).filter(utils_1.isNonNull);
if (symbols.some(s => shouldHoistSymbol(context, s, scope))) {
const index = unhoistedStatements.indexOf(declaration);
if (index < 0) {
continue; // statements array may not contain all statements in the scope (switch-case)
}
if (declaration.right) {
const assignment = lua.createAssignmentStatement(declaration.left, declaration.right);
lua.setNodePosition(assignment, declaration); // Preserve position info for sourcemap
unhoistedStatements.splice(index, 1, assignment);
}
else {
unhoistedStatements.splice(index, 1);
}
hoistedIdentifiers.push(...declaration.left);
}
}
return { unhoistedStatements, hoistedIdentifiers };
}
function hoistFunctionDefinitions(context, scope, statements) {
if (!scope.functionDefinitions) {
return { unhoistedStatements: statements, hoistedStatements: [], hoistedIdentifiers: [] };
}
const unhoistedStatements = [...statements];
const hoistedStatements = [];
const hoistedIdentifiers = [];
for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) {
(0, utils_1.assert)(functionDefinition.definition);
if (shouldHoistSymbol(context, functionSymbolId, scope)) {
const index = unhoistedStatements.indexOf(functionDefinition.definition);
if (index < 0) {
continue; // statements array may not contain all statements in the scope (switch-case)
}
unhoistedStatements.splice(index, 1);
if (lua.isVariableDeclarationStatement(functionDefinition.definition)) {
// Separate function definition and variable declaration
(0, utils_1.assert)(functionDefinition.definition.right);
hoistedIdentifiers.push(...functionDefinition.definition.left);
hoistedStatements.push(lua.createAssignmentStatement(functionDefinition.definition.left, functionDefinition.definition.right));
}
else {
hoistedStatements.push(functionDefinition.definition);
}
}
}
return { unhoistedStatements, hoistedStatements, hoistedIdentifiers };
}
function hoistImportStatements(scope, statements) {
var _a;
return { unhoistedStatements: statements, hoistedStatements: (_a = scope.importStatements) !== null && _a !== void 0 ? _a : [] };
}
//# sourceMappingURL=scope.js.map