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
394 lines • 18.6 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 getLuaBarExpression(state, node, lhsStr, rhsStr) {
state.usesTSLibrary = true;
const rhs = utility_1.skipNodesDownwards(node.getRight());
if (ts.TypeGuards.isNumericLiteral(rhs) && rhs.getLiteralValue() === 0) {
return `TS.bit_truncate(${lhsStr})`;
}
else {
return `TS.bit_or(${lhsStr}, ${rhsStr})`;
}
}
function getLuaBitExpression(state, lhsStr, rhsStr, name) {
state.usesTSLibrary = true;
return `TS.bit_${name}(${lhsStr}, ${rhsStr})`;
}
function getLuaAddExpression(node, lhsStr, rhsStr, wrap = false) {
if (wrap) {
rhsStr = `(${rhsStr})`;
}
const lhsType = typeUtilities_1.getType(node.getLeft());
const rhsType = typeUtilities_1.getType(node.getRight());
const lhsIsStr = typeUtilities_1.isStringType(lhsType);
const lhsIsNum = typeUtilities_1.isNumberType(lhsType);
const rhsIsStr = typeUtilities_1.isStringType(rhsType);
const rhsIsNum = typeUtilities_1.isNumberType(rhsType);
if (lhsIsStr || rhsIsStr) {
if (!lhsIsStr && !lhsIsNum) {
lhsStr = "tostring(" + lhsStr + ")";
}
if (!rhsIsStr && !rhsIsNum) {
rhsStr = "tostring(" + rhsStr + ")";
}
return `${lhsStr} .. ${rhsStr}`;
}
else if (lhsIsNum && rhsIsNum) {
return `${lhsStr} + ${rhsStr}`;
}
else {
throw new CompilerError_1.CompilerError(`Unexpected types for addition: ${lhsType.getText()} + ${rhsType.getText()}`, node, CompilerError_1.CompilerErrorType.BadAddition, true);
}
}
function isSetToken(opKind) {
return (opKind === ts.SyntaxKind.EqualsToken ||
opKind === ts.SyntaxKind.BarEqualsToken ||
opKind === ts.SyntaxKind.AmpersandEqualsToken ||
opKind === ts.SyntaxKind.CaretEqualsToken ||
opKind === ts.SyntaxKind.LessThanLessThanEqualsToken ||
opKind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
opKind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken ||
opKind === ts.SyntaxKind.PlusEqualsToken ||
opKind === ts.SyntaxKind.MinusEqualsToken ||
opKind === ts.SyntaxKind.AsteriskEqualsToken ||
opKind === ts.SyntaxKind.SlashEqualsToken ||
opKind === ts.SyntaxKind.AsteriskAsteriskEqualsToken ||
opKind === ts.SyntaxKind.PercentEqualsToken);
}
exports.isSetToken = isSetToken;
function compileBinaryLiteral(state, node, lhs, rhs) {
const names = new Array();
const values = new Array();
const preStatements = new Array();
const postStatements = new Array();
let rootId;
if ((ts.TypeGuards.isIdentifier(rhs) && !_1.isIdentifierDefinedInExportLet(rhs)) ||
ts.TypeGuards.isThisExpression(rhs) ||
ts.TypeGuards.isSuperExpression(rhs)) {
rootId = _1.compileExpression(state, rhs);
}
else {
rootId = state.getNewId();
preStatements.push(`local ${rootId} = ${_1.compileExpression(state, rhs)};`);
}
_1.getBindingData(state, names, values, preStatements, postStatements, lhs, rootId, _1.getAccessorForBindingPatternType(rhs));
const parent = utility_1.skipNodesUpwards(node.getParentOrThrow());
if (ts.TypeGuards.isExpressionStatement(parent) || ts.TypeGuards.isForStatement(parent)) {
let result = "";
preStatements.forEach(statementStr => (result += state.indent + statementStr + "\n"));
_1.concatNamesAndValues(state, names, values, false, declaration => (result += declaration));
postStatements.forEach(statementStr => (result += state.indent + statementStr + "\n"));
return result.replace(/;\n$/, ""); // terrible hack
}
else {
preStatements.forEach(statementStr => state.pushPrecedingStatements(rhs, state.indent + statementStr + "\n"));
_1.concatNamesAndValues(state, names, values, false, declaration => state.pushPrecedingStatements(node, declaration));
postStatements.forEach(statementStr => state.pushPrecedingStatements(lhs, state.indent + statementStr + "\n"));
return rootId;
}
}
function compileBinaryExpression(state, node) {
const nodeParent = utility_1.skipNodesUpwards(node.getParentOrThrow());
const parentKind = nodeParent.getKind();
const isStatement = parentKind === ts.SyntaxKind.ExpressionStatement || parentKind === ts.SyntaxKind.ForStatement;
const opToken = node.getOperatorToken();
const opKind = opToken.getKind();
const isEqualsOperation = opKind === ts.SyntaxKind.EqualsToken;
const rawRhs = node.getRight();
const lhs = utility_1.skipNodesDownwards(node.getLeft(), true);
const rhs = utility_1.skipNodesDownwards(rawRhs, true);
let lhsStr;
let rhsStr;
if (!isEqualsOperation) {
_1.checkNonAny(lhs);
_1.checkNonAny(rhs);
}
// binding patterns
if (ts.TypeGuards.isArrayLiteralExpression(lhs)) {
const isFlatBinding = lhs.getElements().every(v => ts.TypeGuards.isIdentifier(v));
if (isFlatBinding && rhs && ts.TypeGuards.isCallExpression(rhs) && typeUtilities_1.isTupleReturnTypeCall(rhs)) {
// FIXME: Still broken for nested destructuring of non-arrays.
// BUT this change makes it LESS broken than before. (try nested destructuring a string here)
// e.g. [[[[[[[a]]]]]]] = func() where func() returns a LuaTuple<[string]>
let result = "";
const preStatements = new Array();
const postStatements = new Array();
const names = lhs
.getElements()
.map(element => {
if (ts.TypeGuards.isOmittedExpression(element)) {
return "_";
}
else if (ts.TypeGuards.isArrayLiteralExpression(element) ||
ts.TypeGuards.isObjectLiteralExpression(element)) {
let rootId;
rootId = state.getNewId();
_1.getBindingData(state, [], [], preStatements, postStatements, element, rootId, _1.getAccessorForBindingPatternType(rhs));
return rootId;
}
else {
return _1.compileExpression(state, element);
}
})
.filter(s => s !== "");
const values = [_1.compileCallExpression(state, rhs, true)];
_1.concatNamesAndValues(state, names, values, false, declaration => (result += declaration + ";\n"), false, false);
preStatements.forEach(statementStr => (result += state.indent + statementStr + "\n"));
postStatements.forEach(statementStr => (result += state.indent + statementStr + "\n"));
if (isStatement) {
return result.replace(/;\n$/, ""); // terrible hack
}
else {
throw new CompilerError_1.CompilerError("Cannot use a LuaTuple destructuring expression outside an ExpressionStatement!", node, CompilerError_1.CompilerErrorType.BadLuaTupleStatement);
}
}
else {
return compileBinaryLiteral(state, node, lhs, rhs);
}
}
else if (ts.TypeGuards.isObjectLiteralExpression(lhs)) {
return compileBinaryLiteral(state, node, lhs, rhs);
}
if (isSetToken(opKind)) {
let isLhsIdentifier = ts.TypeGuards.isIdentifier(lhs) && !_1.isIdentifierDefinedInExportLet(lhs);
let rhsStrContext;
let hasOpenContext = false;
let stashedInnerStr;
const upperContext = state.getCurrentPrecedingStatementContext(node);
if (ts.TypeGuards.isElementAccessExpression(lhs)) {
const compileLhsStr = _1.compileElementAccessDataTypeExpression(state, lhs, _1.getWritableOperandName(state, lhs, true).expStr);
let innerStr = _1.compileElementAccessBracketExpression(state, lhs);
if (!typeUtilities_1.isConstantExpression(lhs.getArgumentExpressionOrThrow(), 0) &&
// Always push array values, even when isPushed, because we need to increment by 1
(!upperContext.isPushed || typeUtilities_1.isArrayType(typeUtilities_1.getType(lhs.getExpression())))) {
const previousInner = innerStr;
const previouslyPushed = upperContext.isPushed;
stashedInnerStr = () => {
// In this case, this will ALWAYS pop the `pushPrecedingStatementToNewId` immediately below
upperContext.pop();
upperContext.isPushed = previouslyPushed;
return compileLhsStr(previousInner);
};
innerStr = state.pushPrecedingStatementToNewId(lhs, innerStr);
}
lhsStr = compileLhsStr(innerStr);
}
else if (!isEqualsOperation) {
const newLhsStrData = _1.getWritableOperandName(state, lhs);
lhsStr = newLhsStrData.expStr;
isLhsIdentifier = newLhsStrData.isIdentifier;
}
else {
lhsStr = _1.compileExpression(state, lhs);
}
const currentContext = state.enterPrecedingStatementContext();
if (isEqualsOperation) {
if (isStatement) {
state.declarationContext.set(rawRhs, {
isIdentifier: isLhsIdentifier,
set: lhsStr,
});
hasOpenContext = true;
}
const previousLength = currentContext.length;
rhsStr = _1.compileExpression(state, rhs);
if (state.declarationContext.delete(rawRhs)) {
hasOpenContext = false;
}
if (stashedInnerStr && currentContext.length - previousLength === 0) {
lhsStr = stashedInnerStr();
}
}
else {
rhsStr = _1.compileExpression(state, rhs);
}
rhsStrContext = state.exitPrecedingStatementContext();
const previouslhs = isEqualsOperation
? ""
: isStatement && rhsStrContext.length === 0
? lhsStr
: state.pushPrecedingStatementToReuseableId(lhs, lhsStr, rhsStrContext);
let { isPushed } = rhsStrContext;
state.pushPrecedingStatements(rhs, ...rhsStrContext);
if (opKind === ts.SyntaxKind.BarEqualsToken) {
rhsStr = getLuaBarExpression(state, node, previouslhs, rhsStr);
}
else if (opKind === ts.SyntaxKind.AmpersandEqualsToken) {
rhsStr = getLuaBitExpression(state, previouslhs, rhsStr, "and");
}
else if (opKind === ts.SyntaxKind.CaretEqualsToken) {
rhsStr = getLuaBitExpression(state, previouslhs, rhsStr, "xor");
}
else if (opKind === ts.SyntaxKind.LessThanLessThanEqualsToken) {
rhsStr = getLuaBitExpression(state, previouslhs, rhsStr, "lsh");
}
else if (opKind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken) {
rhsStr = getLuaBitExpression(state, previouslhs, rhsStr, "rsh");
}
else if (opKind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
rhsStr = getLuaBitExpression(state, previouslhs, rhsStr, "lrsh");
}
else if (opKind === ts.SyntaxKind.PlusEqualsToken) {
rhsStr = getLuaAddExpression(node, previouslhs, rhsStr, true);
}
else if (opKind === ts.SyntaxKind.MinusEqualsToken) {
rhsStr = `${previouslhs} - (${rhsStr})`;
}
else if (opKind === ts.SyntaxKind.AsteriskEqualsToken) {
rhsStr = `${previouslhs} * (${rhsStr})`;
}
else if (opKind === ts.SyntaxKind.SlashEqualsToken) {
rhsStr = `${previouslhs} / (${rhsStr})`;
}
else if (opKind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
rhsStr = `${previouslhs} ^ (${rhsStr})`;
}
else if (opKind === ts.SyntaxKind.PercentEqualsToken) {
rhsStr = `${previouslhs} % (${rhsStr})`;
}
else if (!isEqualsOperation) {
throw new CompilerError_1.CompilerError(`Unexpected BinaryExpression ( ${opToken.getText()} ) in compileBinaryExpression #1`, opToken, CompilerError_1.CompilerErrorType.BadBinaryExpression, true);
}
const unUsedStatement = !isEqualsOperation || !hasOpenContext;
if (isStatement) {
return unUsedStatement ? `${lhsStr} = ${rhsStr}` : "";
}
else {
let returnStr = lhsStr;
if (!isLhsIdentifier) {
if (hasOpenContext) {
if ((isPushed && isEqualsOperation) || typeUtilities_1.isConstantExpression(lhs, 0)) {
returnStr = rhsStr = lhsStr;
}
else {
returnStr = rhsStr = state.pushToDeclarationOrNewId(node, lhsStr, declaration => declaration.isIdentifier);
({ isPushed } = upperContext);
}
}
else {
if ((isPushed && isEqualsOperation) || typeUtilities_1.isConstantExpression(rhs, 0)) {
returnStr = rhsStr;
}
else {
returnStr = rhsStr = state.pushToDeclarationOrNewId(node, rhsStr, declaration => declaration.isIdentifier);
({ isPushed } = upperContext);
}
}
}
if (unUsedStatement) {
if (!isPushed && !isStatement && !isEqualsOperation) {
returnStr = rhsStr = state.pushToDeclarationOrNewId(node, rhsStr, declaration => declaration.isIdentifier);
}
// preserve isPushed
upperContext.push(state.indent + `${lhsStr} = ${rhsStr};\n`);
}
return returnStr;
}
}
else {
state.enterPrecedingStatementContext();
lhsStr = _1.compileExpression(state, lhs);
const lhsContext = state.exitPrecedingStatementContext();
state.enterPrecedingStatementContext();
rhsStr = _1.compileExpression(state, rhs);
const rhsContext = state.exitPrecedingStatementContext();
state.pushPrecedingStatements(lhs, ...lhsContext);
if (rhsContext.length > 0) {
if (typeUtilities_1.shouldPushToPrecedingStatement(lhs, lhsStr, lhsContext)) {
lhsStr = state.pushPrecedingStatementToReuseableId(lhs, lhsStr, rhsContext);
}
state.pushPrecedingStatements(rhs, ...rhsContext);
}
}
if (opKind === ts.SyntaxKind.EqualsEqualsToken) {
throw new CompilerError_1.CompilerError("operator '==' is not supported! Use '===' instead.", opToken, CompilerError_1.CompilerErrorType.NoEqualsEquals);
}
else if (opKind === ts.SyntaxKind.EqualsEqualsEqualsToken) {
return `${lhsStr} == ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.ExclamationEqualsToken) {
throw new CompilerError_1.CompilerError("operator '!=' is not supported! Use '!==' instead.", opToken, CompilerError_1.CompilerErrorType.NoExclamationEquals);
}
else if (opKind === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
return `${lhsStr} ~= ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.BarToken) {
return getLuaBarExpression(state, node, lhsStr, rhsStr);
}
else if (opKind === ts.SyntaxKind.AmpersandToken) {
return getLuaBitExpression(state, lhsStr, rhsStr, "and");
}
else if (opKind === ts.SyntaxKind.CaretToken) {
return getLuaBitExpression(state, lhsStr, rhsStr, "xor");
}
else if (opKind === ts.SyntaxKind.LessThanLessThanToken) {
return getLuaBitExpression(state, lhsStr, rhsStr, "lsh");
}
else if (opKind === ts.SyntaxKind.GreaterThanGreaterThanToken) {
return getLuaBitExpression(state, lhsStr, rhsStr, "rsh");
}
else if (opKind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) {
return getLuaBitExpression(state, lhsStr, rhsStr, "lrsh");
}
else if (opKind === ts.SyntaxKind.PlusToken) {
return getLuaAddExpression(node, lhsStr, rhsStr);
}
else if (opKind === ts.SyntaxKind.MinusToken) {
return `${lhsStr} - ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.AsteriskToken) {
return `${lhsStr} * ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.SlashToken) {
return `${lhsStr} / ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.AsteriskAsteriskToken) {
return `${lhsStr} ^ ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.InKeyword) {
// doesn't need parenthesis because In is restrictive
return `${rhsStr}[${lhsStr}] ~= nil`;
}
else if (opKind === ts.SyntaxKind.AmpersandAmpersandToken) {
return `${lhsStr} and ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.BarBarToken) {
return `${lhsStr} or ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.GreaterThanToken) {
return `${lhsStr} > ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.LessThanToken) {
return `${lhsStr} < ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.GreaterThanEqualsToken) {
return `${lhsStr} >= ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.LessThanEqualsToken) {
return `${lhsStr} <= ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.PercentToken) {
return `${lhsStr} % ${rhsStr}`;
}
else if (opKind === ts.SyntaxKind.InstanceOfKeyword) {
state.usesTSLibrary = true;
return `TS.instanceof(${lhsStr}, ${rhsStr})`;
}
else {
throw new CompilerError_1.CompilerError(`Unexpected BinaryExpression (${node.getOperatorToken().getKindName()}) in compileBinaryExpression #2`, opToken, CompilerError_1.CompilerErrorType.BadBinaryExpression, true);
}
}
exports.compileBinaryExpression = compileBinaryExpression;
//# sourceMappingURL=binary.js.map