roblox-ts
Version:
<div align="center"><img width=25% src="https://i.imgur.com/yCjHmng.png"></div> <h1 align="center"><a href="https://roblox-ts.github.io/">roblox-ts</a></h1> <div align="center">A TypeScript-to-Lua Compiler for Roblox</div> <br> <div align="center"> <a hr
218 lines • 8.83 kB
JavaScript
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const ts = __importStar(require("ts-morph"));
const CompilerError_1 = require("./errors/CompilerError");
const utility_1 = require("./utility");
function canBePushedToReusableId(node) {
if (ts.TypeGuards.isThisExpression(node) ||
ts.TypeGuards.isIdentifier(node) ||
ts.TypeGuards.isSuperExpression(node)) {
return true;
}
else if (ts.TypeGuards.isPrefixUnaryExpression(node) || ts.TypeGuards.isPostfixUnaryExpression(node)) {
return canBePushedToReusableId(node.getOperand());
}
else if (ts.TypeGuards.isPropertyAccessExpression(node)) {
return canBePushedToReusableId(node.getNameNode());
}
else if (ts.TypeGuards.isElementAccessExpression(node)) {
return canBePushedToReusableId(node.getArgumentExpressionOrThrow());
}
else {
return false;
}
}
class CompilerState {
constructor(rootDirPath, outDirPath, projectInfo, rojoProject, modulesDir, runtimeOverride) {
this.rootDirPath = rootDirPath;
this.outDirPath = outDirPath;
this.projectInfo = projectInfo;
this.rojoProject = rojoProject;
this.modulesDir = modulesDir;
this.runtimeOverride = runtimeOverride;
this.declarationContext = new Map();
this.currentConditionalContext = "";
this.precedingStatementContexts = new Array();
// indent
this.indent = "";
// id stack
this.idStack = new Array();
// hoist stack
this.hoistStack = new Array();
// export stack
this.exportStack = new Array();
// in the form: { ORIGINAL_IDENTIFIER = REPLACEMENT_VALUE }
// For example, this is used for exported/namespace values
// which should be represented differently in Lua than they
// can be represented in TS
this.variableAliases = new Map();
this.namespaceStack = new Map();
this.continueId = -1;
this.isModule = false;
this.scriptContext = utility_1.ScriptContext.None;
this.roactIndent = 0;
this.hasRoactImport = false;
this.usesTSLibrary = false;
}
pushToDeclarationOrNewId(node, expStr, condition = dec => dec.set !== "return") {
const declaration = this.declarationContext.get(node);
let id;
const isReturn = declaration && declaration.set === "return";
if (declaration && condition(declaration)) {
this.declarationContext.delete(node);
({ set: id } = declaration);
const context = this.getCurrentPrecedingStatementContext(node);
context.push(this.indent +
`${declaration.needsLocalizing ? "local " : ""}${id}${expStr ? ` ${isReturn ? "" : "= "}${expStr}` : ""};\n`);
context.isPushed = declaration.isIdentifier;
}
else {
id = this.pushPrecedingStatementToReuseableId(node, expStr);
}
return id;
}
getCurrentPrecedingStatementContext(node) {
const currentContext = this.precedingStatementContexts[this.precedingStatementContexts.length - 1];
if (!currentContext) {
const kind = node.getKindName();
const ancestorName = node
.getAncestors()
.find(ancestor => ts.TypeGuards.isStatement(ancestor) || ts.TypeGuards.isStatementedNode(ancestor))
.getKindName();
throw new CompilerError_1.CompilerError(`roblox-ts does not support using a ${kind} which requires preceding statements in a ${ancestorName}.`, node, CompilerError_1.CompilerErrorType.BadExpression, true);
}
return currentContext;
}
enterPrecedingStatementContext(newContext = new Array()) {
newContext.isPushed = false;
this.precedingStatementContexts.push(newContext);
return newContext;
}
exitPrecedingStatementContext() {
return this.precedingStatementContexts.pop();
}
exitPrecedingStatementContextAndJoin(numTabs = 0) {
return utility_1.joinIndentedLines(this.exitPrecedingStatementContext(), numTabs);
}
pushPrecedingStatements(node, ...statements) {
const context = this.getCurrentPrecedingStatementContext(node);
context.isPushed = false;
return context.push(...statements);
}
pushPrecedingStatementToNewId(node, compiledSource, newId = this.getNewId()) {
const currentContext = this.getCurrentPrecedingStatementContext(node);
currentContext.push(this.indent + `local ${newId}${compiledSource ? ` = ${compiledSource}` : ""};\n`);
currentContext.isPushed = true;
return newId;
}
pushPrecedingStatementToReuseableId(node, compiledSource, nextCachedStrs) {
if (compiledSource === "" || !canBePushedToReusableId(node)) {
return this.pushPrecedingStatementToNewId(node, compiledSource);
}
/** Gets the top PreStatement to compare to */
let previousTop;
for (let i = this.precedingStatementContexts.length - 1; 0 <= i; i--) {
const context = this.precedingStatementContexts[i];
const topPreStatement = context[context.length - 1];
if (topPreStatement) {
previousTop = [...context].reverse();
break;
}
}
for (const cache of [previousTop, nextCachedStrs]) {
/** If we would write a duplicate `local _5 = i`, skip it */
if (cache) {
for (const str of cache) {
const matchesRegex = str.match(/^(\t*)local ([a-zA-Z_][a-zA-Z0-9_]*) = ([^;]+);\n$/);
// iterate only through non-state changing pushed id statements
if (!matchesRegex) {
break;
}
const [, indentation, currentId, data] = matchesRegex;
if (indentation === this.indent && data === compiledSource) {
this.getCurrentPrecedingStatementContext(node).isPushed = true;
return currentId;
}
}
}
}
return this.pushPrecedingStatementToNewId(node, compiledSource);
}
pushIndent() {
this.indent += "\t";
}
popIndent() {
this.indent = this.indent.substr(1);
}
pushIdStack() {
this.idStack.push(0);
}
popIdStack() {
this.idStack.pop();
}
getNewId() {
const sum = this.idStack.reduce((accum, value) => accum + value);
this.idStack[this.idStack.length - 1]++;
return `_${sum}`;
}
pushHoistStack(name) {
this.hoistStack[this.hoistStack.length - 1].add(name);
}
popHoistStack(result) {
const top = this.hoistStack.pop();
if (top) {
const hoists = [...top];
const namedHoists = new Array();
const declareHoists = new Array();
hoists.forEach(v => (v.includes("=") ? declareHoists : namedHoists).push(v));
if (namedHoists && namedHoists.length > 0) {
result = this.indent + `local ${namedHoists.join(", ")};\n` + result;
}
if (declareHoists && declareHoists.length > 0) {
result = this.indent + `${declareHoists.join(";\n" + this.indent)};\n` + result;
}
}
return result;
}
pushExport(name, node) {
if (!node.hasExportKeyword()) {
return;
}
const ancestorName = this.getExportContextName(node);
const alias = node.hasDefaultKeyword() ? "_default" : name;
this.exportStack[this.exportStack.length - 1].add(`${ancestorName}.${alias} = ${name};\n`);
}
getNameForContext(myNamespace) {
let name;
if (myNamespace) {
name = myNamespace.getName();
name = this.namespaceStack.get(name) || name;
}
else {
name = "_exports";
this.isModule = true;
}
return name;
}
getExportContextName(node) {
return this.getNameForContext(node.getFirstAncestorByKind(ts.SyntaxKind.ModuleDeclaration));
}
getAlias(name) {
const alias = this.variableAliases.get(name);
if (alias !== undefined) {
return alias;
}
else {
return name;
}
}
}
exports.CompilerState = CompilerState;
//# sourceMappingURL=CompilerState.js.map