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
271 lines • 11.9 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 CompilerError_1 = require("../errors/CompilerError");
const typeUtilities_1 = require("../typeUtilities");
const utility_1 = require("../utility");
function getVariableName(state, lhs, names, values, preStatements, postStatements) {
if (lhs) {
let varName = "";
if (ts.TypeGuards.isArrayBindingPattern(lhs) || ts.TypeGuards.isObjectBindingPattern(lhs)) {
varName = state.getNewId();
_1.getBindingData(state, names, values, preStatements, postStatements, lhs, varName);
}
else if (ts.TypeGuards.isIdentifier(lhs)) {
varName = lhs.getText();
_1.checkReserved(varName, lhs);
}
if (varName) {
return varName;
}
}
throw new CompilerError_1.CompilerError("Unexpected for..of initializer", lhs, CompilerError_1.CompilerErrorType.BadForOfInitializer, true);
}
function asDeclarationListOrThrow(initializer) {
if (!ts.TypeGuards.isVariableDeclarationList(initializer)) {
const initKindName = initializer.getKindName();
throw new CompilerError_1.CompilerError(`ForOf Loop has an unexpected initializer! (${initKindName})`, initializer, CompilerError_1.CompilerErrorType.UnexpectedInitializer, true);
}
return initializer;
}
function getSingleDeclarationOrThrow(initializer) {
const declarations = initializer.getDeclarations();
if (declarations.length !== 1) {
throw new CompilerError_1.CompilerError("Expected a single declaration in ForOf loop", initializer, CompilerError_1.CompilerErrorType.BadForOfInitializer, true);
}
return declarations[0];
}
var ForOfLoopType;
(function (ForOfLoopType) {
ForOfLoopType[ForOfLoopType["Keys"] = 0] = "Keys";
ForOfLoopType[ForOfLoopType["Values"] = 1] = "Values";
ForOfLoopType[ForOfLoopType["Entries"] = 2] = "Entries";
ForOfLoopType[ForOfLoopType["Array"] = 3] = "Array";
ForOfLoopType[ForOfLoopType["ArrayEntries"] = 4] = "ArrayEntries";
ForOfLoopType[ForOfLoopType["String"] = 5] = "String";
ForOfLoopType[ForOfLoopType["IterableFunction"] = 6] = "IterableFunction";
ForOfLoopType[ForOfLoopType["Symbol_iterator"] = 7] = "Symbol_iterator";
})(ForOfLoopType || (ForOfLoopType = {}));
function* propertyAccessExpressionTypeIter(state, exp) {
while (ts.TypeGuards.isCallExpression(exp)) {
const subExp = utility_1.skipNodesDownwards(exp.getExpression());
if (!ts.TypeGuards.isPropertyAccessExpression(subExp)) {
break;
}
yield { exp: subExp, type: _1.getPropertyAccessExpressionType(state, subExp) };
exp = utility_1.skipNodesDownwards(subExp.getExpression());
}
}
function getLoopType(state, node, reversed = false, backwards = false) {
const exp = utility_1.skipNodesDownwards(node.getExpression());
const expType = typeUtilities_1.getType(exp);
const iter = propertyAccessExpressionTypeIter(state, exp);
let data = iter.next();
if (!data.done) {
let subExp = data.value.exp;
switch (data.value.type) {
case 7 /* ObjectConstructor */: {
const iterExp = utility_1.skipNodesDownwards(exp.getArguments()[0]);
switch (subExp.getName()) {
case "keys":
return [iterExp, ForOfLoopType.Keys, reversed, backwards];
case "entries":
return [iterExp, ForOfLoopType.Entries, reversed, backwards];
case "values":
return [iterExp, ForOfLoopType.Values, reversed, backwards];
}
break;
}
case 0 /* Array */: {
switch (subExp.getName()) {
case "entries": {
do {
subExp = data.value.exp;
reversed = !reversed;
data = iter.next();
} while (!data.done &&
(data.value.type === 0 /* Array */ && data.value.exp.getName() === "reverse"));
return [
utility_1.skipNodesDownwards(subExp.getExpression()),
ForOfLoopType.ArrayEntries,
!reversed,
backwards,
];
}
case "reverse": {
const [lowerExp, lowerLoopExpType, isReversed, isBackwards] = getLoopType(state, subExp, reversed, !backwards);
switch (lowerLoopExpType) {
case ForOfLoopType.Array:
case ForOfLoopType.ArrayEntries:
return [lowerExp, lowerLoopExpType, isReversed, isBackwards];
}
return [utility_1.skipNodesDownwards(subExp.getExpression()), ForOfLoopType.Array, reversed, !backwards];
}
}
break;
}
}
}
if (typeUtilities_1.isMapType(expType)) {
return [exp, ForOfLoopType.Entries, reversed, backwards];
}
else if (typeUtilities_1.isSetType(expType)) {
return [exp, ForOfLoopType.Keys, reversed, backwards];
}
else if (typeUtilities_1.isArrayType(expType)) {
return [exp, ForOfLoopType.Array, reversed, backwards];
}
else if (typeUtilities_1.isStringType(expType)) {
return [exp, ForOfLoopType.String, reversed, backwards];
}
else if (typeUtilities_1.isIterableFunction(expType)) {
return [exp, ForOfLoopType.IterableFunction, reversed, backwards];
}
else {
return [exp, ForOfLoopType.Symbol_iterator, reversed, backwards];
}
}
function compileForOfStatement(state, node) {
state.enterPrecedingStatementContext();
const initializer = asDeclarationListOrThrow(node.getInitializer());
const declaration = getSingleDeclarationOrThrow(initializer);
const statement = node.getStatement();
const [exp, loopType, isReversed, isBackwards] = getLoopType(state, node);
const lhs = declaration.getNameNode();
let varName;
/** The key to be used in the for loop. If it is the empty string, it is irrelevant. */
let key = "";
/** The value to be used in the for loop. If it is the empty string, it is irrelevant. */
let value = "";
/** The expression to iterate through */
let expStr = _1.compileExpression(state, exp);
let result = "";
const names = new Array();
const values = new Array();
const preStatements = new Array();
const postStatements = new Array();
/** Whether we should iterate as a simple for loop (defaults to a for..in loop) */
let isNumericForLoop = false;
if (loopType === ForOfLoopType.Entries || loopType === ForOfLoopType.ArrayEntries) {
if (loopType === ForOfLoopType.ArrayEntries) {
expStr = _1.getReadableExpressionName(state, exp, expStr);
isNumericForLoop = true;
}
else {
expStr = `pairs(${expStr})`;
}
if (ts.TypeGuards.isArrayBindingPattern(lhs)) {
const [first, second] = lhs.getElements();
if (first && ts.TypeGuards.isBindingElement(first)) {
key = getVariableName(state, first.getNameNode(), names, values, preStatements, postStatements);
}
if (second && ts.TypeGuards.isBindingElement(second)) {
value = getVariableName(state, second.getNameNode(), names, values, preStatements, postStatements);
}
}
else {
if (!ts.TypeGuards.isIdentifier(lhs)) {
throw new CompilerError_1.CompilerError("Unexpected for..of initializer", lhs, CompilerError_1.CompilerErrorType.BadForOfInitializer, true);
}
key = state.getNewId();
value = state.getNewId();
varName = getVariableName(state, lhs, names, values, preStatements, postStatements);
preStatements.push(`local ${varName} = {${key}, ${value}};`);
}
}
else {
varName = getVariableName(state, lhs, names, values, preStatements, postStatements);
switch (loopType) {
case ForOfLoopType.Keys:
key = varName;
expStr = `pairs(${expStr})`;
break;
case ForOfLoopType.Values:
value = varName;
expStr = `pairs(${expStr})`;
break;
case ForOfLoopType.Array:
value = varName;
expStr = _1.getReadableExpressionName(state, exp, expStr);
isNumericForLoop = true;
break;
case ForOfLoopType.String:
key = varName;
expStr = `(${expStr}):gmatch(".")`;
break;
case ForOfLoopType.IterableFunction:
key = varName;
break;
case ForOfLoopType.Symbol_iterator: {
if (!typeUtilities_1.isIterableIterator(typeUtilities_1.getType(exp), exp)) {
expStr = _1.getReadableExpressionName(state, exp, expStr);
expStr = `${expStr}[TS.Symbol_iterator](${expStr})`;
}
const loopVar = state.getNewId();
key = loopVar;
expStr = `${expStr}.next`;
preStatements.push(`if ${loopVar}.done then break end;`);
preStatements.push(`local ${varName} = ${loopVar}.value;`);
break;
}
}
}
if (isNumericForLoop) {
let accessor;
let loopEndValue = `#${expStr}`;
if (key) {
accessor =
value && isReversed
? `${(loopEndValue = state.pushPrecedingStatementToNewId(node, loopEndValue))} - ${key}`
: `${key} + 1`;
if (isBackwards) {
result += state.indent + `for ${key} = ${loopEndValue} - 1, 0, -1 do\n`;
}
else {
result += state.indent + `for ${key} = 0, ${loopEndValue} - 1 do\n`;
}
}
else {
accessor = state.getNewId();
if (isReversed ? !isBackwards : isBackwards) {
result += state.indent + `for ${accessor} = ${loopEndValue}, 1, -1 do\n`;
}
else {
result += state.indent + `for ${accessor} = 1, ${loopEndValue} do\n`;
}
}
state.pushIndent();
if (value) {
result += state.indent + `local ${value} = ${expStr}[${accessor}];\n`;
}
}
else {
result += state.indent + `for ${key || "_"}${value ? `, ${value}` : ""} in ${expStr} do\n`;
state.pushIndent();
}
for (const myStatement of preStatements) {
result += state.indent + myStatement + "\n";
}
_1.concatNamesAndValues(state, names, values, true, str => {
result += str;
});
for (const myStatement of postStatements) {
result += state.indent + myStatement + "\n";
}
state.pushIdStack();
result += _1.compileLoopBody(state, statement);
state.popIndent();
result += state.indent + `end;\n`;
state.popIdStack();
return state.exitPrecedingStatementContextAndJoin() + result;
}
exports.compileForOfStatement = compileForOfStatement;
//# sourceMappingURL=forOf.js.map