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
289 lines • 14.4 kB
JavaScript
;
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 _1 = require(".");
const typeUtilities_1 = require("../typeUtilities");
const utility_1 = require("../utility");
function isConstantNumberVariableOrLiteral(condValue) {
return (ts.TypeGuards.isNumericLiteral(condValue) ||
(typeUtilities_1.isNumberType(typeUtilities_1.getType(condValue)) &&
ts.TypeGuards.isIdentifier(condValue) &&
condValue.getDefinitions().every(a => {
const declNode = a.getDeclarationNode();
if (declNode && ts.TypeGuards.isVariableDeclaration(declNode)) {
const declParent = declNode.getParent();
if (ts.TypeGuards.isVariableDeclarationList(declParent)) {
return declParent.getDeclarationKind() === ts.VariableDeclarationKind.Const;
}
}
return false;
})));
}
function isExpressionConstantNumbers(node) {
const children = node.getChildren().map(child => utility_1.skipNodesDownwards(child));
return (children.length > 0 &&
children.every(child => isConstantNumberVariableOrLiteral(child) ||
child.getKind() === ts.SyntaxKind.PlusToken ||
child.getKind() === ts.SyntaxKind.MinusToken ||
child.getKind() === ts.SyntaxKind.AsteriskToken ||
child.getKind() === ts.SyntaxKind.SlashToken ||
child.getKind() === ts.SyntaxKind.AsteriskAsteriskToken ||
child.getKind() === ts.SyntaxKind.PercentToken ||
(ts.TypeGuards.isPrefixUnaryExpression(child) &&
child.getOperatorToken() === ts.SyntaxKind.MinusToken) ||
(ts.TypeGuards.isBinaryExpression(child) && isExpressionConstantNumbers(child))));
}
function getSignAndIncrementorForStatement(state, incrementor, id) {
let forIntervalStr = "";
let sign = "";
if (ts.TypeGuards.isBinaryExpression(incrementor)) {
// const lhs = skipNodesDownwards(incrementor.getLeft());
let rhs = utility_1.skipNodesDownwards(incrementor.getRight());
const operator = utility_1.skipNodesDownwards(incrementor.getOperatorToken());
if (typeUtilities_1.isNumberType(typeUtilities_1.getType(rhs)) &&
operator.getKind() === ts.SyntaxKind.EqualsToken &&
ts.TypeGuards.isBinaryExpression(rhs)) {
const rhsLhs = utility_1.skipNodesDownwards(rhs.getLeft());
const rhsOp = utility_1.skipNodesDownwards(rhs.getOperatorToken());
if (ts.TypeGuards.isIdentifier(rhsLhs) && utility_1.isIdentifierWhoseDefinitionMatchesNode(rhsLhs, id)) {
switch (rhsOp.getKind()) {
case ts.SyntaxKind.MinusToken:
sign = "-";
break;
case ts.SyntaxKind.PlusToken:
sign = "+";
break;
}
}
}
else {
switch (operator.getKind()) {
case ts.SyntaxKind.PlusEqualsToken:
sign = "+";
break;
case ts.SyntaxKind.MinusEqualsToken:
sign = "-";
break;
}
}
if (rhs && ts.TypeGuards.isPrefixUnaryExpression(rhs)) {
if (rhs.getOperatorToken() === ts.SyntaxKind.MinusToken) {
switch (sign) {
case "+":
sign = "-";
break;
case "-":
sign = "+";
break;
case "":
return ["", ""];
}
rhs = rhs.getOperand();
}
}
if (ts.TypeGuards.isNumericLiteral(rhs)) {
forIntervalStr = _1.compileExpression(state, rhs);
}
}
else if (incrementor.getOperatorToken() === ts.SyntaxKind.MinusMinusToken) {
forIntervalStr = "1";
sign = "-";
}
else {
forIntervalStr = "1";
sign = "+";
}
return [sign, forIntervalStr];
}
function getLimitInForStatement(state, condition, id) {
if (ts.TypeGuards.isBinaryExpression(condition)) {
const lhs = utility_1.skipNodesDownwards(condition.getLeft());
const operator = condition.getOperatorToken();
const rhs = utility_1.skipNodesDownwards(condition.getRight());
if (utility_1.isIdentifierWhoseDefinitionMatchesNode(lhs, id)) {
switch (operator.getKind()) {
case ts.SyntaxKind.GreaterThanEqualsToken: // >=
return [">=", rhs];
case ts.SyntaxKind.LessThanEqualsToken: // <=
return ["<=", rhs];
}
}
else if (utility_1.isIdentifierWhoseDefinitionMatchesNode(rhs, id)) {
switch (operator.getKind()) {
case ts.SyntaxKind.GreaterThanEqualsToken: // >=
return ["<=", lhs];
case ts.SyntaxKind.LessThanEqualsToken: // <=
return [">=", lhs];
}
}
}
return ["", undefined];
}
function safelyHandleExpressionsInForStatement(state, incrementor, incrementorStr, context, indentation) {
return (utility_1.joinIndentedLines(context, indentation) +
state.indent +
(context.isPushed
? incrementorStr
: _1.placeIncrementorInStatementIfExpression(state, incrementor, incrementorStr)));
}
function getSimpleForLoopString(state, initializer, forLoopVars, statement) {
let result = "";
state.popIndent();
const first = _1.compileVariableDeclarationList(state, initializer)
.trim()
.replace(/^local /, "")
.replace(/;$/, "");
result = state.indent + `for ${first}, ${forLoopVars} do\n`;
state.pushIndent();
result += _1.compileLoopBody(state, statement);
state.popIndent();
result += state.indent + `end;\n`;
return result;
}
function compileForStatement(state, node) {
state.pushIdStack();
const statement = node.getStatement();
const condition = utility_1.skipNodesDownwards(node.getCondition());
const incrementor = utility_1.skipNodesDownwards(node.getIncrementor());
const localizations = new Array();
const cleanups = new Array();
let result = state.indent + "do\n";
state.pushIndent();
const initializer = utility_1.skipNodesDownwards(node.getInitializer());
let conditionStr;
let incrementorStr;
let conditionContext;
let incrementorContext;
let initializerContext;
if (initializer) {
if (ts.TypeGuards.isVariableDeclarationList(initializer)) {
const declarations = initializer.getDeclarations();
const statementDescendants = statement.getDescendants();
if (initializer.getDeclarationKind() === ts.VariableDeclarationKind.Let) {
if (declarations.length === 1) {
const [declaration] = declarations;
const lhs = utility_1.skipNodesDownwards(declaration.getNameNode());
const rhs = utility_1.skipNodesDownwards(declaration.getInitializer());
if (ts.TypeGuards.isIdentifier(lhs) &&
!statementDescendants.some(statementDescendant => _1.expressionModifiesVariable(statementDescendant, lhs)) &&
incrementor &&
condition &&
rhs) {
// check if we can convert to a simple for loop
// IF there aren't any in-loop modifications to the let variable
// AND the let variable is a single numeric variable
// AND the incrementor is a simple +/- expression of the let var
// AND the conditional expression is a binary expression
// with one of these operators: <= >= < >
// AND the conditional expression compares the let var to a numeric literal
// OR the conditional expression compares the let var to an unchanging number
const rhsType = typeUtilities_1.getType(rhs);
if (typeUtilities_1.isNumberType(rhsType)) {
if (_1.expressionModifiesVariable(incrementor, lhs)) {
const [incrSign, incrValue] = getSignAndIncrementorForStatement(state, incrementor, lhs);
if (incrSign && incrValue) {
const [condSign, condValue] = getLimitInForStatement(state, condition, lhs);
// numeric literals, or constant number identifiers are safe
if (condValue &&
ts.TypeGuards.isExpression(condValue) &&
(isConstantNumberVariableOrLiteral(condValue) ||
isExpressionConstantNumbers(condValue))) {
if ((incrSign === "+" && condSign === "<=") ||
(incrSign === "-" && condSign === ">=")) {
const incrStr = incrSign === "-" ? incrSign + incrValue : incrValue;
return getSimpleForLoopString(state, initializer, _1.compileExpression(state, condValue) +
(incrStr === "1" ? "" : ", " + incrStr), statement);
}
}
}
}
}
}
}
}
state.enterPrecedingStatementContext();
const expStr = _1.compileVariableDeclarationList(state, initializer);
result += state.exitPrecedingStatementContextAndJoin() + expStr; // + ";\n";
// if we can't convert to a simple for loop:
// if it has any internal function declarations, make sure to locally scope variables
if (statementDescendants.some(_1.nodeHasParameters)) {
state.enterPrecedingStatementContext();
conditionStr = condition ? _1.compileExpression(state, condition) : "true";
conditionContext = state.exitPrecedingStatementContext();
state.enterPrecedingStatementContext();
incrementorStr = incrementor ? _1.compileExpression(state, incrementor) + ";\n" : undefined;
incrementorContext = state.exitPrecedingStatementContext();
declarations.forEach(declaration => {
const lhs = declaration.getChildAtIndex(0);
if (ts.TypeGuards.isIdentifier(lhs)) {
const name = lhs.getText();
const isLoopVarModified = statementDescendants.some(statementDescendant => _1.expressionModifiesVariable(statementDescendant, lhs));
const alias = state.getNewId();
state.pushIndent();
localizations.push(`local ${alias} = ${name};\n`);
state.popIndent();
// don't leak
const previous = state.variableAliases.get(name);
cleanups.push(() => {
if (previous) {
state.variableAliases.set(name, previous);
}
else {
state.variableAliases.delete(name);
}
if (isLoopVarModified) {
result += state.indent + `${name} = ${alias};\n`;
}
});
state.variableAliases.set(name, alias);
}
});
}
}
else if (ts.TypeGuards.isExpression(initializer)) {
state.enterPrecedingStatementContext();
const expStr = _1.compileExpression(state, initializer);
initializerContext = state.exitPrecedingStatementContext();
result += safelyHandleExpressionsInForStatement(state, initializer, expStr, initializerContext, 0) + ";\n";
}
}
// order matters
if (conditionStr === undefined) {
state.enterPrecedingStatementContext();
conditionStr = condition ? _1.compileExpression(state, condition) : "true";
conditionContext = state.exitPrecedingStatementContext();
state.enterPrecedingStatementContext();
incrementorStr = incrementor ? _1.compileExpression(state, incrementor) + ";\n" : undefined;
incrementorContext = state.exitPrecedingStatementContext();
}
const conditionContextHasStatements = conditionContext && conditionContext.length > 0;
result += state.indent + `while ${conditionContextHasStatements ? "true" : conditionStr} do\n`;
state.pushIndent();
if (conditionContextHasStatements) {
result += utility_1.joinIndentedLines(conditionContext, 1);
result += state.indent + `if not (${conditionStr}) then break; end;\n`;
}
for (const localization of localizations) {
result += state.indent + localization;
}
result += _1.compileLoopBody(state, statement);
cleanups.forEach(cleanup => cleanup());
if (incrementor && incrementorStr) {
result += safelyHandleExpressionsInForStatement(state, incrementor, incrementorStr, incrementorContext, 1);
}
state.popIndent();
result += state.indent + "end;\n";
state.popIndent();
result += state.indent + `end;\n`;
state.popIdStack();
return result;
}
exports.compileForStatement = compileForStatement;
//# sourceMappingURL=for.js.map