diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 9bc655566..e5222c0bc 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -200,4 +200,32 @@ export class TSHelper { } return true; } + + // Returns true for expressions that may have effects when evaluated + public static isExpressionWithEvaluationEffect(node: ts.Expression): boolean { + return !(ts.isLiteralExpression(node) || ts.isIdentifier(node)); + } + + // If expression is property/element access with possible effects from being evaluated, returns true along with the + // separated object and index expressions. + public static isAccessExpressionWithEvaluationEffects(node: ts.Expression, checker: ts.TypeChecker): + [boolean, ts.Expression, ts.Expression] { + if (ts.isElementAccessExpression(node) + && (this.isExpressionWithEvaluationEffect(node.expression) + || this.isExpressionWithEvaluationEffect(node.argumentExpression))) { + const type = checker.getTypeAtLocation(node.expression); + if (this.isArrayType(type, checker)) { + // Offset arrays by one + const oneLit = ts.createNumericLiteral("1"); + const exp = ts.createParen(node.argumentExpression); + const addExp = ts.createBinary(exp, ts.SyntaxKind.PlusToken, oneLit); + return [true, node.expression, addExp]; + } else { + return [true, node.expression, node.argumentExpression]; + } + } else if (ts.isPropertyAccessExpression(node) && this.isExpressionWithEvaluationEffect(node.expression)) { + return [true, node.expression, ts.createStringLiteral(node.name.text)]; + } + return [false, null, null]; + } } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index b2bad2685..271f47472 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -826,11 +826,10 @@ export abstract class LuaTranspiler { // Check if this is an assignment token, then handle accordingly const [isAssignment, operator] = tsHelper.isBinaryAssignmentToken(node.operatorToken.kind); if (isAssignment) { - const binaryExpression = ts.createBinary(node.left, operator, node.right); - binaryExpression.pos = node.operatorToken.pos; return this.transpileAssignmentExpression( node.left, - binaryExpression, + operator, + node.right, tsHelper.isExpressionStatement(node), false ); @@ -840,10 +839,6 @@ export abstract class LuaTranspiler { const lhs = this.transpileExpression(node.left, true); const rhs = this.transpileExpression(node.right, true); - if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken && !tsHelper.isExpressionStatement(node)) { - return `(function() ${lhs} = ${rhs}; return ${lhs} end)()`; - } - let result = ""; // Transpile Bitops @@ -904,21 +899,7 @@ export abstract class LuaTranspiler { result = `${lhs}<=${rhs}`; break; case ts.SyntaxKind.EqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, rhs); - } - - if (ts.isArrayLiteralExpression(node.left)) { - // Destructing assignment - const vars = node.left.elements.map(e => this.transpileExpression(e)).join(","); - if (tsHelper.isTupleReturnCall(node.right, this.checker)) { - return `${vars} = ${rhs}`; - } else { - return `${vars} = ${this.transpileDestructingAssignmentValue(node.right)}`; - } - } - - result = `${lhs} = ${rhs}`; + result = this.transpileAssignment(node, lhs, rhs); break; case ts.SyntaxKind.EqualsEqualsToken: case ts.SyntaxKind.EqualsEqualsEqualsToken: @@ -947,6 +928,44 @@ export abstract class LuaTranspiler { } } + public transpileAssignment(node: ts.BinaryExpression, lhs: string, rhs: string): string { + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, rhs); + } + + if (ts.isArrayLiteralExpression(node.left)) { + // Destructing assignment + const vars = node.left.elements.map(e => this.transpileExpression(e)).join(","); + const vals = tsHelper.isTupleReturnCall(node.right, this.checker) + ? rhs : this.transpileDestructingAssignmentValue(node.right); + if (tsHelper.isExpressionStatement(node)) { + // In JS, the right side of a destructuring assignment is evaluated before the left + const tmps = node.left.elements.map((_, i) => `__TS_tmp${i}`).join(","); + return `do local ${tmps} = ${vals}; ${vars} = ${tmps} end`; + } else { + return `(function(...) ${vars} = ...; return {...} end)(${vals})`; + } + } + + if (tsHelper.isExpressionStatement(node)) { + return `${lhs} = ${rhs}`; + } + + const [hasEffects, objExp, indexExp] = tsHelper.isAccessExpressionWithEvaluationEffects( + node.left, this.checker); + if (hasEffects) { + // Property/element access expressions need to have individual parts cached + const obj = this.transpileExpression(objExp); + const index = this.transpileExpression(indexExp); + return `(function(o, i, v) o[i] = v; return v end)(${obj}, ${index}, ${rhs})`; + } else if (tsHelper.isExpressionWithEvaluationEffect(node.right)) { + // Cache right-hand express in temp + return `(function() local __TS_tmp = ${rhs}; ${lhs} = __TS_tmp; return __TS_tmp end)()`; + } else { + return `(function() ${lhs} = ${rhs}; return ${rhs} end)()`; + } + } + public transpileUnaryBitOperation(node: ts.PrefixUnaryExpression, operand: string): string { throw TSTLErrors.UnsupportedForTarget("Bitwise operations", this.options.luaTarget, node); } @@ -979,31 +998,78 @@ export abstract class LuaTranspiler { `function() return ${val1} end`, `function() return ${val2} end`); } + public transpileBinaryAssignmentExpression( + assignee: ts.Expression, + lhs: ts.Expression, + operator: ts.BinaryOperator, + rhs: ts.Expression, + pos: number): string { + const opExp = ts.createBinary(lhs, operator, rhs); + opExp.pos = pos; + const assignExp = ts.createAssignment(assignee, opExp); + return this.transpileBinaryExpression(assignExp); + } + public transpileAssignmentExpression( lhs: ts.Expression, - replacementExpression: ts.BinaryExpression, + operator: ts.BinaryOperator, + rhs: ts.Expression, isStatement: boolean, returnValueBefore: boolean): string { - // Assign replacement expression to its left-hand side - const assignment = ts.createAssignment(lhs, replacementExpression); - - if (isStatement) { - return this.transpileExpression(assignment); - } else { + let statement: string; // Assignment statement + let result: string; // Result if expression + let wrap = true; // Wrap statement in do...end + const pos = (lhs.parent ? lhs.parent : lhs).pos; + const [hasEffects, objExp, indexExp] = tsHelper.isAccessExpressionWithEvaluationEffects(lhs, this.checker); + if (hasEffects) { + // Complex property/element accesses need to cache object/index expressions to avoid repeating side-effects + const objIdent = ts.createIdentifier("__TS_obj"); + const indexIdent = ts.createIdentifier("__TS_index"); + const tempIdent = ts.createIdentifier("__TS_tmp"); + const accessExp = ts.createElementAccess(objIdent, indexIdent); + rhs = ts.createParen(rhs); + const obj = this.transpileExpression(objExp); + const index = this.transpileExpression(indexExp); + let assignTemp: string; + let assign: string; if (returnValueBefore) { - const oldValueName = `__originalValue${this.transpileExpression(lhs)}${this.genVarCounter++}`; - const oldValueAssignment = ts.createVariableStatement( - [], [ts.createVariableDeclaration(oldValueName, undefined, lhs)]); - - const valueReturn = ts.createReturn(ts.createIdentifier(oldValueName)); - - return this.transpileExpression(ts.createImmediatelyInvokedArrowFunction( - [oldValueAssignment, ts.createStatement(assignment), valueReturn])); + // Postfix + assignTemp = this.transpileBinaryExpression(ts.createAssignment(tempIdent, accessExp)); + assign = this.transpileBinaryAssignmentExpression(accessExp, tempIdent, operator, rhs, pos); } else { - const valueReturn = ts.createReturn(lhs); - return this.transpileExpression( - ts.createImmediatelyInvokedArrowFunction([ts.createStatement(assignment), valueReturn])); + assignTemp = this.transpileBinaryAssignmentExpression(tempIdent, accessExp, operator, rhs, pos); + assign = this.transpileBinaryExpression(ts.createAssignment(accessExp, tempIdent)); } + statement = `local __TS_obj, __TS_index = ${obj}, ${index}; local ${assignTemp}; ${assign}`; + result = "__TS_tmp"; + + } else if (!isStatement && returnValueBefore) { + // Postfix expressions need to cache original value in temp + const tempIdent = ts.createIdentifier("__TS_tmp"); + const assignTemp = this.transpileBinaryExpression(ts.createAssignment(tempIdent, lhs)); + const assign = this.transpileBinaryAssignmentExpression(lhs, tempIdent, operator, rhs, pos); + statement = `local ${assignTemp}; ${assign}`; + result = "__TS_tmp"; + + } else if (!isStatement && (ts.isPropertyAccessExpression(lhs) || ts.isElementAccessExpression(lhs))) { + // Simple property/element access expressions need to cache in temp to avoid double-evaluation + const tempIdent = ts.createIdentifier("__TS_tmp"); + const assignTemp = this.transpileBinaryAssignmentExpression(tempIdent, lhs, operator, rhs, pos); + const assign = this.transpileBinaryExpression(ts.createAssignment(lhs, tempIdent)); + statement = `local ${assignTemp}; ${assign}`; + result = "__TS_tmp"; + + } else { + // Simple statements/expressions + statement = this.transpileBinaryAssignmentExpression(lhs, lhs, operator, rhs, pos); + result = this.transpileExpression(lhs); + wrap = false; + } + + if (isStatement) { + return wrap ? `do ${statement}; end` : statement; + } else { + return `(function() ${statement}; return ${result} end)()`; } } @@ -1013,7 +1079,8 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.PlusPlusToken: { return this.transpileAssignmentExpression( node.operand, - ts.createBinary(node.operand, ts.SyntaxKind.PlusToken, ts.createLiteral(1)), + ts.SyntaxKind.PlusToken, + ts.createLiteral(1), tsHelper.isExpressionStatement(node), true ); @@ -1021,7 +1088,8 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.MinusMinusToken: { return this.transpileAssignmentExpression( node.operand, - ts.createBinary(node.operand, ts.SyntaxKind.MinusToken, ts.createLiteral(1)), + ts.SyntaxKind.MinusToken, + ts.createLiteral(1), tsHelper.isExpressionStatement(node), true ); @@ -1039,7 +1107,8 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.PlusPlusToken: { return this.transpileAssignmentExpression( node.operand, - ts.createBinary(node.operand, ts.SyntaxKind.PlusToken, ts.createLiteral(1)), + ts.SyntaxKind.PlusToken, + ts.createLiteral(1), tsHelper.isExpressionStatement(node), false ); @@ -1047,7 +1116,8 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.MinusMinusToken: { return this.transpileAssignmentExpression( node.operand, - ts.createBinary(node.operand, ts.SyntaxKind.MinusToken, ts.createLiteral(1)), + ts.SyntaxKind.MinusToken, + ts.createLiteral(1), tsHelper.isExpressionStatement(node), false ); diff --git a/test/translation/lua/assignments.lua b/test/translation/lua/assignments.lua new file mode 100644 index 000000000..6f4e5f9bd --- /dev/null +++ b/test/translation/lua/assignments.lua @@ -0,0 +1,143 @@ +x = y; +x = obj.prop; +x = arr[(0)+1]; +x = ((function() local __TS_tmp = obj.prop; y = __TS_tmp; return __TS_tmp end)()); +x = obj.prop; +obj.prop = x; +arr[(0)+1] = x; +obj.prop = arr[(0)+1]; +obj.prop = ((function() arr[(0)+1] = x; return x end)()); +xTup = getTup(); +xTup = ({ getTupRet() }); +do local __TS_tmp0,__TS_tmp1 = table.unpack(getTup()); xTup[(1)+1],xTup[(0)+1] = __TS_tmp0,__TS_tmp1 end; +do local __TS_tmp0,__TS_tmp1 = getTupRet(); xTup[(1)+1],xTup[(0)+1] = __TS_tmp0,__TS_tmp1 end; +xTup = {yTup[(1)+1],yTup[(0)+1]}; +do local __TS_tmp0,__TS_tmp1 = table.unpack({yTup[(1)+1],yTup[(0)+1]}); xTup[(0)+1],xTup[(1)+1] = __TS_tmp0,__TS_tmp1 end; +x = (x+1); +x = (x+1); +x = (x-1); +x = (x-1); +x = (x+y); +x = (x-y); +x = (x*y); +y = (y/x); +y = (y%x); +y = (y^x); +x = (x | y); +x = (x & y); +x = (x ~ y); +x = (x << y); +x = (x >> y); +obj.prop = (obj.prop+1); +obj.prop = (obj.prop+1); +obj.prop = (obj.prop-1); +obj.prop = (obj.prop-1); +obj.prop = (obj.prop+arr[(0)+1]); +obj.prop = (obj.prop-arr[(0)+1]); +obj.prop = (obj.prop*arr[(0)+1]); +arr[(0)+1] = (arr[(0)+1]/obj.prop); +arr[(0)+1] = (arr[(0)+1]%obj.prop); +arr[(0)+1] = (arr[(0)+1]^obj.prop); +obj.prop = (obj.prop | arr[(0)+1]); +obj.prop = (obj.prop & arr[(0)+1]); +obj.prop = (obj.prop ~ arr[(0)+1]); +obj.prop = (obj.prop << arr[(0)+1]); +obj.prop = (obj.prop >> arr[(0)+1]); +do local __TS_obj, __TS_index = arr, (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = arr, (getIndex())+1; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp-(1)); end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]-(1)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp-(1)); end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]+(getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]-(getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]*(getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]/(getObj().prop)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]%(getObj().prop)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]^(getObj().prop)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] | (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] & (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] ~ (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] << (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] >> (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; end; +z = ((function() x = y; return y end)()); +z = ((function() obj.prop = x; return x end)()); +z = ((function(o, i, v) o[i] = v; return v end)(getObj(), "prop", x)); +z = ((function() arr[(0)+1] = x; return x end)()); +z = ((function(o, i, v) o[i] = v; return v end)(getArr(), (getIndex())+1, x)); +z = ((function() local __TS_tmp = obj.prop; x = __TS_tmp; return __TS_tmp end)()); +z = ((function() local __TS_tmp = getObj().prop; x = __TS_tmp; return __TS_tmp end)()); +z = ((function() local __TS_tmp = arr[(0)+1]; x = __TS_tmp; return __TS_tmp end)()); +z = ((function() local __TS_tmp = arr[(getIndex())+1]; x = __TS_tmp; return __TS_tmp end)()); +z = ((function() local __TS_tmp = getArr()[(getIndex())+1]; x = __TS_tmp; return __TS_tmp end)()); +z = (function() x = (x+1); return x end)(); +z = (function() local __TS_tmp = x; x = (__TS_tmp+1); return __TS_tmp end)(); +z = (function() x = (x-1); return x end)(); +z = (function() local __TS_tmp = x; x = (__TS_tmp-1); return __TS_tmp end)(); +z = (function() x = (x+y); return x end)(); +z = (function() x = (x-y); return x end)(); +z = (function() x = (x*y); return x end)(); +z = (function() y = (y/x); return y end)(); +z = (function() y = (y%x); return y end)(); +z = (function() y = (y^x); return y end)(); +z = (function() x = (x | y); return x end)(); +z = (function() x = (x & y); return x end)(); +z = (function() x = (x ~ y); return x end)(); +z = (function() x = (x << y); return x end)(); +z = (function() x = (x >> y); return x end)(); +z = (x+((function() y = (y+7); return y end)())); +z = (x+((function() y = (y+7); return y end)())); +z = ((function() local __TS_tmp = x; x = (__TS_tmp+1); return __TS_tmp end)()+((function() y = (y+7); return y end)())); +z = (function() local __TS_tmp = (obj.prop+1); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = obj.prop; obj.prop = (__TS_tmp+1); return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop-1); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = obj.prop; obj.prop = (__TS_tmp-1); return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop+arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop-arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop*arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (arr[(0)+1]/obj.prop); arr[(0)+1] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (arr[(0)+1]%obj.prop); arr[(0)+1] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (arr[(0)+1]^obj.prop); arr[(0)+1] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop | arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop & arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop ~ arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop << arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_tmp = (obj.prop >> arr[(0)+1]); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = (obj.prop+((function() local __TS_tmp = (arr[(0)+1]+7); arr[(0)+1] = __TS_tmp; return __TS_tmp end)())); +z = (function() local __TS_tmp = (obj.prop+((function() local __TS_tmp = (arr[(0)+1]+7); arr[(0)+1] = __TS_tmp; return __TS_tmp end)())); obj.prop = __TS_tmp; return __TS_tmp end)(); +z = ((function() local __TS_tmp = obj.prop; obj.prop = (__TS_tmp+1); return __TS_tmp end)()+((function() local __TS_tmp = (arr[(0)+1]+7); arr[(0)+1] = __TS_tmp; return __TS_tmp end)())); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]-(1)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp-(1)); return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]+(getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]-(getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]*(getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]/(getObj().prop)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]%(getObj().prop)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]^(getObj().prop)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] | (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] & (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] ~ (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] << (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index] >> (getArr()[(getIndex())+1])); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (getObj().prop+((function() local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(7)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)())); +z = (function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = (__TS_obj[__TS_index]+(((function() local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(7)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)()))); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = ((function() local __TS_obj, __TS_index = getObj(), "prop"; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); return __TS_tmp end)()+((function() local __TS_obj, __TS_index = getArr(), (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(7)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)())); +do local __TS_obj, __TS_index = getObj().arr, (0)+1; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = getObj().arr, (0)+1; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); end; +do local __TS_obj, __TS_index = getObj().arr, (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(13)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = arr2[(0)+1], (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = arr2[(0)+1], (getIndex())+1; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); end; +do local __TS_obj, __TS_index = arr2[(getIndex())+1], (0)+1; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; end; +do local __TS_obj, __TS_index = arr2[(getIndex())+1], (0)+1; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); end; +z = (function() local __TS_obj, __TS_index = getObj().arr, (0)+1; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj().arr, (0)+1; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = getObj().arr, (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(13)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = arr2[(0)+1], (getIndex())+1; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = arr2[(0)+1], (getIndex())+1; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = arr2[(getIndex())+1], (0)+1; local __TS_tmp = (__TS_obj[__TS_index]+(1)); __TS_obj[__TS_index] = __TS_tmp; return __TS_tmp end)(); +z = (function() local __TS_obj, __TS_index = arr2[(getIndex())+1], (0)+1; local __TS_tmp = __TS_obj[__TS_index]; __TS_obj[__TS_index] = (__TS_tmp+(1)); return __TS_tmp end)(); +z = ((function() local __TS_tmp = arr2[(0)+1][(getIndex())+1]; x = __TS_tmp; return __TS_tmp end)()); +z = ((function(o, i, v) o[i] = v; return v end)(getObj().arr, (0)+1, x)); +z = ((function(o, i, v) o[i] = v; return v end)(getObj().arr, (0)+1, arr2[(0)+1][(getIndex())+1])); diff --git a/test/translation/lua/tupleReturn.lua b/test/translation/lua/tupleReturn.lua index d485f97a7..7d33868fc 100644 --- a/test/translation/lua/tupleReturn.lua +++ b/test/translation/lua/tupleReturn.lua @@ -5,8 +5,8 @@ tupleReturn(); noTupleReturn(); local a,b=tupleReturn(); local c,d=table.unpack(noTupleReturn()); -a,b = tupleReturn(); -c,d = table.unpack(noTupleReturn()); +do local __TS_tmp0,__TS_tmp1 = tupleReturn(); a,b = __TS_tmp0,__TS_tmp1 end; +do local __TS_tmp0,__TS_tmp1 = table.unpack(noTupleReturn()); c,d = __TS_tmp0,__TS_tmp1 end; local e = ({ tupleReturn() }); local f = noTupleReturn(); e = ({ tupleReturn() }); diff --git a/test/translation/ts/assignments.ts b/test/translation/ts/assignments.ts new file mode 100644 index 000000000..a8e51e59a --- /dev/null +++ b/test/translation/ts/assignments.ts @@ -0,0 +1,157 @@ +declare let x: number; +declare let y: number; +declare let z: number; +declare let obj: {prop: number, arr: number[]}; +declare function getObj(): typeof obj; +declare let arr: number[]; +declare let arr2: number[][]; +declare function getArr(): typeof arr; +declare function getIndex(): number; +declare let xTup: [number, number]; +declare let yTup: [number, number]; +declare function getTup(): [number, number]; +/** !TupleReturn */ +declare function getTupRet(): [number, number]; +x = y; +x = obj.prop; +x = arr[0]; +x = y = obj.prop; +x = obj.prop; +obj.prop = x; +arr[0] = x; +obj.prop = arr[0]; +obj.prop = arr[0] = x; +xTup = getTup(); +xTup = getTupRet(); +[xTup[1], xTup[0]] = getTup(); +[xTup[1], xTup[0]] = getTupRet(); +xTup = [yTup[1], yTup[0]]; +[xTup[0], xTup[1]] = [yTup[1], yTup[0]]; +++x; +x++; +--x; +x--; +x += y; +x -= y; +x *= y; +y /= x; +y %= x; +y **= x; +x |= y; +x &= y; +x ^= y; +x <<= y; +x >>= y; +++obj.prop; +obj.prop++; +--obj.prop; +obj.prop--; +obj.prop += arr[0]; +obj.prop -= arr[0]; +obj.prop *= arr[0]; +arr[0] /= obj.prop; +arr[0] %= obj.prop; +arr[0] **= obj.prop; +obj.prop |= arr[0]; +obj.prop &= arr[0]; +obj.prop ^= arr[0]; +obj.prop <<= arr[0]; +obj.prop >>= arr[0]; +++arr[getIndex()]; +arr[getIndex()]--; +++getObj().prop; +getObj().prop++; +--getObj().prop; +getObj().prop--; +getObj().prop += getArr()[getIndex()]; +getObj().prop -= getArr()[getIndex()]; +getObj().prop *= getArr()[getIndex()]; +getArr()[getIndex()] /= getObj().prop; +getArr()[getIndex()] %= getObj().prop; +getArr()[getIndex()] **= getObj().prop; +getObj().prop |= getArr()[getIndex()]; +getObj().prop &= getArr()[getIndex()]; +getObj().prop ^= getArr()[getIndex()]; +getObj().prop <<= getArr()[getIndex()]; +getObj().prop >>= getArr()[getIndex()]; +z = x = y; +z = obj.prop = x; +z = getObj().prop = x; +z = arr[0] = x; +z = getArr()[getIndex()] = x; +z = x = obj.prop; +z = x = getObj().prop; +z = x = arr[0]; +z = x = arr[getIndex()]; +z = x = getArr()[getIndex()]; +z = ++x; +z = x++; +z = --x; +z = x--; +z = x += y; +z = x -= y; +z = x *= y; +z = y /= x; +z = y %= x; +z = y **= x; +z = x |= y; +z = x &= y; +z = x ^= y; +z = x <<= y; +z = x >>= y; +z = x + (y += 7); +z = x + (y += 7); +z = x++ + (y += 7); +z = ++obj.prop; +z = obj.prop++; +z = --obj.prop; +z = obj.prop--; +z = obj.prop += arr[0]; +z = obj.prop -= arr[0]; +z = obj.prop *= arr[0]; +z = arr[0] /= obj.prop; +z = arr[0] %= obj.prop; +z = arr[0] **= obj.prop; +z = obj.prop |= arr[0]; +z = obj.prop &= arr[0]; +z = obj.prop ^= arr[0]; +z = obj.prop <<= arr[0]; +z = obj.prop >>= arr[0]; +z = obj.prop + (arr[0] += 7); +z = obj.prop += (arr[0] += 7); +z = obj.prop++ + (arr[0] += 7); +z = ++getObj().prop; +z = getObj().prop++; +z = --getObj().prop; +z = getObj().prop--; +z = getObj().prop += getArr()[getIndex()]; +z = getObj().prop -= getArr()[getIndex()]; +z = getObj().prop *= getArr()[getIndex()]; +z = getArr()[getIndex()] /= getObj().prop; +z = getArr()[getIndex()] %= getObj().prop; +z = getArr()[getIndex()] **= getObj().prop; +z = getObj().prop |= getArr()[getIndex()]; +z = getObj().prop &= getArr()[getIndex()]; +z = getObj().prop ^= getArr()[getIndex()]; +z = getObj().prop <<= getArr()[getIndex()]; +z = getObj().prop >>= getArr()[getIndex()]; +z = getObj().prop + (getArr()[getIndex()] += 7); +z = getObj().prop += (getArr()[getIndex()] += 7); +z = getObj().prop++ + (getArr()[getIndex()] += 7); +++getObj().arr[0]; +getObj().arr[0]++; +getObj().arr[getIndex()] += 13; +++arr2[0][getIndex()]; +arr2[0][getIndex()]++; +++arr2[getIndex()][0]; +arr2[getIndex()][0]++; +z = ++getObj().arr[0]; +z = getObj().arr[0]++; +z = getObj().arr[getIndex()] += 13; +z = ++arr2[0][getIndex()]; +z = arr2[0][getIndex()]++; +z = ++arr2[getIndex()][0]; +z = arr2[getIndex()][0]++; +z = x = arr2[0][getIndex()]; +z = getObj().arr[0] = x; +z = getObj().arr[0] = arr2[0][getIndex()]; diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 892b6186b..2fa26da68 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -276,12 +276,58 @@ export class ExpressionTests { Expect(result).toEqual(expected); } - @TestCase("a + b++", 10) - @TestCase("a + (b += 5)", 15) - @Test("Mixed assignment expressions") - public mixedAssignmentExpressions(expression: string, expected: number): void { - const result = util.transpileAndExecute(`let a = 4; let b = 6; return ${expression};`); + @TestCase("x = y", "y") + @Test("Assignment expressions") + public assignmentExpression(expression: string, expected: string): void { + const result = util.transpileAndExecute(`let x = "x"; let y = "y"; return ${expression};`); + Expect(result).toBe(expected); + } + + @TestCase("x = o.p", "o") + @TestCase("x = a[0]", "a") + @TestCase("x = y = o.p", "o") + @TestCase("x = o.p", "o") + @Test("Assignment expressions using temp") + public assignmentWithTempExpression(expression: string, expected: string): void { + const result = util.transpileAndExecute( + `let x = "x"; + let y = "y"; + let o = {p: "o"}; + let a = ["a"]; + return ${expression};`); + Expect(result).toBe(expected); + } + + @TestCase("o.p = x", "x") + @TestCase("a[0] = x", "x") + @TestCase("o.p = a[0]", "a") + @TestCase("o.p = a[0] = x", "x") + @Test("Property assignment expressions") + public propertyAssignmentExpression(expression: string, expected: string): void { + const result = util.transpileAndExecute( + `let x = "x"; + let o = {p: "o"}; + let a = ["a"]; + return ${expression};`); + Expect(result).toBe(expected); + } + @TestCase("x = t()", "t0,t1") + @TestCase("x = tr()", "tr0,tr1") + @TestCase("[x[1], x[0]] = t()", "t0,t1") + @TestCase("[x[1], x[0]] = tr()", "tr0,tr1") + @TestCase("x = [y[1], y[0]]", "y1,y0") + @TestCase("[x[0], x[1]] = [y[1], y[0]]", "y1,y0") + @Test("Tuple assignment expressions") + public tupleAssignmentExpression(expression: string, expected: string): void { + const result = util.transpileAndExecute( + `let x: [string, string] = ["x0", "x1"]; + let y: [string, string] = ["y0", "y1"]; + function t(): [string, string] { return ["t0", "t1"] }; + /** !TupleReturn */ + function tr(): [string, string] { return ["tr0", "tr1"] }; + const r = ${expression}; + return \`\${r[0]},\${r[1]}\``); Expect(result).toBe(expected); } diff --git a/test/unit/math.spec.ts b/test/unit/math.spec.ts index 7e93f7f5b..bc3bbd10f 100644 --- a/test/unit/math.spec.ts +++ b/test/unit/math.spec.ts @@ -19,4 +19,223 @@ export class MathTests { // Assert Expect(lua).toBe(expected); } + + @TestCase("++x", "x=4;y=6") + @TestCase("x++", "x=4;y=6") + @TestCase("--x", "x=2;y=6") + @TestCase("x--", "x=2;y=6") + @TestCase("x += y", "x=9;y=6") + @TestCase("x -= y", "x=-3;y=6") + @TestCase("x *= y", "x=18;y=6") + @TestCase("y /= x", "x=3;y=2.0") + @TestCase("y %= x", "x=3;y=0") + @TestCase("y **= x", "x=3;y=216.0") + @TestCase("x |= y", "x=7;y=6") + @TestCase("x &= y", "x=2;y=6") + @TestCase("x ^= y", "x=5;y=6") + @TestCase("x <<= y", "x=192;y=6") + @TestCase("x >>= y", "x=0;y=6") + @Test("Operator assignment statements") + public opAssignmentStatement(statement: string, expected: string): void { + const result = util.transpileAndExecute( + `let x = 3; + let y = 6; + ${statement}; + return \`x=\${x};y=\${y}\``); + Expect(result).toBe(expected); + } + + @TestCase("++o.p", "o=4;a=6") + @TestCase("o.p++", "o=4;a=6") + @TestCase("--o.p", "o=2;a=6") + @TestCase("o.p--", "o=2;a=6") + @TestCase("o.p += a[0]", "o=9;a=6") + @TestCase("o.p -= a[0]", "o=-3;a=6") + @TestCase("o.p *= a[0]", "o=18;a=6") + @TestCase("a[0] /= o.p", "o=3;a=2.0") + @TestCase("a[0] %= o.p", "o=3;a=0") + @TestCase("a[0] **= o.p", "o=3;a=216.0") + @TestCase("o.p |= a[0]", "o=7;a=6") + @TestCase("o.p &= a[0]", "o=2;a=6") + @TestCase("o.p ^= a[0]", "o=5;a=6") + @TestCase("o.p <<= a[0]", "o=192;a=6") + @TestCase("o.p >>= a[0]", "o=0;a=6") + @Test("Operator assignment to simple property statements") + public opSimplePropAssignmentStatement(statement: string, expected: string): void { + const result = util.transpileAndExecute( + `let o = {p: 3}; + let a = [6]; + ${statement}; + return \`o=\${o.p};a=\${a[0]}\``); + Expect(result).toBe(expected); + } + + @TestCase("++o.p.d", "o=4;a=[6,11],[7,13]") + @TestCase("o.p.d++", "o=4;a=[6,11],[7,13]") + @TestCase("--o.p.d", "o=2;a=[6,11],[7,13]") + @TestCase("o.p.d--", "o=2;a=[6,11],[7,13]") + @TestCase("o.p.d += a[0][0]", "o=9;a=[6,11],[7,13]") + @TestCase("o.p.d -= a[0][0]", "o=-3;a=[6,11],[7,13]") + @TestCase("o.p.d *= a[0][0]", "o=18;a=[6,11],[7,13]") + @TestCase("a[0][0] /= o.p.d", "o=3;a=[2.0,11],[7,13]") + @TestCase("a[0][0] %= o.p.d", "o=3;a=[0,11],[7,13]") + @TestCase("a[0][0] **= o.p.d", "o=3;a=[216.0,11],[7,13]") + @TestCase("o.p.d |= a[0][0]", "o=7;a=[6,11],[7,13]") + @TestCase("o.p.d &= a[0][0]", "o=2;a=[6,11],[7,13]") + @TestCase("o.p.d ^= a[0][0]", "o=5;a=[6,11],[7,13]") + @TestCase("o.p.d <<= a[0][0]", "o=192;a=[6,11],[7,13]") + @TestCase("o.p.d >>= a[0][0]", "o=0;a=[6,11],[7,13]") + @Test("Operator assignment to deep property statements") + public opDeepPropAssignmentStatement(statement: string, expected: string): void { + const result = util.transpileAndExecute( + `let o = {p: {d: 3}}; + let a = [[6,11], [7,13]]; + ${statement}; + return \`o=\${o.p.d};a=[\${a[0][0]},\${a[0][1]}],[\${a[1][0]},\${a[1][1]}]\``); + Expect(result).toBe(expected); + } + + @TestCase("++of().p", "o=4;a=6") + @TestCase("of().p++", "o=4;a=6") + @TestCase("--of().p", "o=2;a=6") + @TestCase("of().p--", "o=2;a=6") + @TestCase("of().p += af()[i()]", "o=9;a=6") + @TestCase("of().p -= af()[i()]", "o=-3;a=6") + @TestCase("of().p *= af()[i()]", "o=18;a=6") + @TestCase("af()[i()] /= of().p", "o=3;a=2.0") + @TestCase("af()[i()] %= of().p", "o=3;a=0") + @TestCase("af()[i()] **= of().p", "o=3;a=216.0") + @TestCase("of().p |= af()[i()]", "o=7;a=6") + @TestCase("of().p &= af()[i()]", "o=2;a=6") + @TestCase("of().p ^= af()[i()]", "o=5;a=6") + @TestCase("of().p <<= af()[i()]", "o=192;a=6") + @TestCase("of().p >>= af()[i()]", "o=0;a=6") + @Test("Operator assignment to complex property statements") + public opComplexPropAssignmentStatement(statement: string, expected: string): void { + const result = util.transpileAndExecute( + `let o = {p: 3}; + let a = [6]; + function of() { return o; } + function af() { return a; } + function i() { return 0; } + ${statement}; + return \`o=\${o.p};a=\${a[0]}\``); + Expect(result).toBe(expected); + } + + @TestCase("++of().p.d", "o=4;a=[7,6],[11,13];i=0") + @TestCase("of().p.d++", "o=4;a=[7,6],[11,13];i=0") + @TestCase("--of().p.d", "o=2;a=[7,6],[11,13];i=0") + @TestCase("of().p.d--", "o=2;a=[7,6],[11,13];i=0") + @TestCase("of().p.d += af()[i()][i()]", "o=9;a=[7,6],[11,13];i=2") + @TestCase("of().p.d -= af()[i()][i()]", "o=-3;a=[7,6],[11,13];i=2") + @TestCase("of().p.d *= af()[i()][i()]", "o=18;a=[7,6],[11,13];i=2") + @TestCase("af()[i()][i()] /= of().p.d", "o=3;a=[7,2.0],[11,13];i=2") + @TestCase("af()[i()][i()] %= of().p.d", "o=3;a=[7,0],[11,13];i=2") + @TestCase("af()[i()][i()] **= of().p.d", "o=3;a=[7,216.0],[11,13];i=2") + @TestCase("of().p.d |= af()[i()][i()]", "o=7;a=[7,6],[11,13];i=2") + @TestCase("of().p.d &= af()[i()][i()]", "o=2;a=[7,6],[11,13];i=2") + @TestCase("of().p.d ^= af()[i()][i()]", "o=5;a=[7,6],[11,13];i=2") + @TestCase("of().p.d <<= af()[i()][i()]", "o=192;a=[7,6],[11,13];i=2") + @TestCase("of().p.d >>= af()[i()][i()]", "o=0;a=[7,6],[11,13];i=2") + @Test("Operator assignment to complex deep property statements") + public opComplexDeepPropAssignmentStatement(statement: string, expected: string): void { + const result = util.transpileAndExecute( + `let o = {p: {d: 3}}; + let a = [[7, 6], [11, 13]]; + function of() { return o; } + function af() { return a; } + let _i = 0; + function i() { return _i++; } + ${statement}; + return \`o=\${o.p.d};a=[\${a[0][0]},\${a[0][1]}],[\${a[1][0]},\${a[1][1]}];i=\${_i}\``); + Expect(result).toBe(expected); + } + + @TestCase("++x", "4;x=4;y=6") + @TestCase("x++", "3;x=4;y=6") + @TestCase("--x", "2;x=2;y=6") + @TestCase("x--", "3;x=2;y=6") + @TestCase("x += y", "9;x=9;y=6") + @TestCase("x -= y", "-3;x=-3;y=6") + @TestCase("x *= y", "18;x=18;y=6") + @TestCase("y /= x", "2.0;x=3;y=2.0") + @TestCase("y %= x", "0;x=3;y=0") + @TestCase("y **= x", "216.0;x=3;y=216.0") + @TestCase("x |= y", "7;x=7;y=6") + @TestCase("x &= y", "2;x=2;y=6") + @TestCase("x ^= y", "5;x=5;y=6") + @TestCase("x <<= y", "192;x=192;y=6") + @TestCase("x >>= y", "0;x=0;y=6") + @TestCase("x + (y += 7)", "16;x=3;y=13") + @TestCase("x + (y += 7)", "16;x=3;y=13") + @TestCase("x++ + (y += 7)", "16;x=4;y=13") + @Test("Operator assignment expressions") + public opAssignmentExpression(expression: string, expected: string): void { + const result = util.transpileAndExecute( + `let x = 3; + let y = 6; + const r = ${expression}; + return \`\${r};x=\${x};y=\${y}\``); + Expect(result).toBe(expected); + } + + @TestCase("++o.p", "4;o=4;a=6") + @TestCase("o.p++", "3;o=4;a=6") + @TestCase("--o.p", "2;o=2;a=6") + @TestCase("o.p--", "3;o=2;a=6") + @TestCase("o.p += a[0]", "9;o=9;a=6") + @TestCase("o.p -= a[0]", "-3;o=-3;a=6") + @TestCase("o.p *= a[0]", "18;o=18;a=6") + @TestCase("a[0] /= o.p", "2.0;o=3;a=2.0") + @TestCase("a[0] %= o.p", "0;o=3;a=0") + @TestCase("a[0] **= o.p", "216.0;o=3;a=216.0") + @TestCase("o.p |= a[0]", "7;o=7;a=6") + @TestCase("o.p &= a[0]", "2;o=2;a=6") + @TestCase("o.p ^= a[0]", "5;o=5;a=6") + @TestCase("o.p <<= a[0]", "192;o=192;a=6") + @TestCase("o.p >>= a[0]", "0;o=0;a=6") + @TestCase("o.p + (a[0] += 7)", "16;o=3;a=13") + @TestCase("o.p += (a[0] += 7)", "16;o=16;a=13") + @TestCase("o.p++ + (a[0] += 7)", "16;o=4;a=13") + @Test("Operator assignment to simple property expressions") + public opSimplePropAssignmentExpression(expression: string, expected: string): void { + const result = util.transpileAndExecute( + `let o = {p: 3}; + let a = [6]; + const r = ${expression}; + return \`\${r};o=\${o.p};a=\${a[0]}\``); + Expect(result).toBe(expected); + } + + @TestCase("++of().p", "4;o=4;a=6") + @TestCase("of().p++", "3;o=4;a=6") + @TestCase("--of().p", "2;o=2;a=6") + @TestCase("of().p--", "3;o=2;a=6") + @TestCase("of().p += af()[i()]", "9;o=9;a=6") + @TestCase("of().p -= af()[i()]", "-3;o=-3;a=6") + @TestCase("of().p *= af()[i()]", "18;o=18;a=6") + @TestCase("af()[i()] /= of().p", "2.0;o=3;a=2.0") + @TestCase("af()[i()] %= of().p", "0;o=3;a=0") + @TestCase("af()[i()] **= of().p", "216.0;o=3;a=216.0") + @TestCase("of().p |= af()[i()]", "7;o=7;a=6") + @TestCase("of().p &= af()[i()]", "2;o=2;a=6") + @TestCase("of().p ^= af()[i()]", "5;o=5;a=6") + @TestCase("of().p <<= af()[i()]", "192;o=192;a=6") + @TestCase("of().p >>= af()[i()]", "0;o=0;a=6") + @TestCase("of().p + (af()[i()] += 7)", "16;o=3;a=13") + @TestCase("of().p += (af()[i()] += 7)", "16;o=16;a=13") + @TestCase("of().p++ + (af()[i()] += 7)", "16;o=4;a=13") + @Test("Operator assignment to complex property expressions") + public opComplexPropAssignmentExpression(expression: string, expected: string): void { + const result = util.transpileAndExecute( + `let o = {p: 3}; + let a = [6]; + function of() { return o; } + function af() { return a; } + function i() { return 0; } + const r = ${expression}; + return \`\${r};o=\${o.p};a=\${a[0]}\``); + Expect(result).toBe(expected); + } }