2323import org .codehaus .groovy .ast .ClassNode ;
2424import org .codehaus .groovy .ast .MultipleAssignmentMetadata ;
2525import org .codehaus .groovy .ast .Variable ;
26+ import org .codehaus .groovy .ast .expr .ArgumentListExpression ;
2627import org .codehaus .groovy .ast .expr .ArrayExpression ;
2728import org .codehaus .groovy .ast .expr .BinaryExpression ;
2829import org .codehaus .groovy .ast .expr .ClassExpression ;
3637import org .codehaus .groovy .ast .expr .PostfixExpression ;
3738import org .codehaus .groovy .ast .expr .PrefixExpression ;
3839import org .codehaus .groovy .ast .expr .PropertyExpression ;
40+ import org .codehaus .groovy .ast .expr .StaticMethodCallExpression ;
3941import org .codehaus .groovy .ast .expr .TernaryExpression ;
4042import org .codehaus .groovy .ast .expr .TupleExpression ;
4143import org .codehaus .groovy .ast .expr .VariableExpression ;
@@ -209,15 +211,15 @@ public void eval(final BinaryExpression expression) {
209211 break ;
210212
211213 case BITWISE_AND_EQUAL :
212- evaluateBinaryExpressionWithAssignment ( "and" , expression );
214+ evaluateCompoundAssign ( "andAssign" , "and" , expression );
213215 break ;
214216
215217 case BITWISE_OR :
216218 evaluateBinaryExpression ("or" , expression );
217219 break ;
218220
219221 case BITWISE_OR_EQUAL :
220- evaluateBinaryExpressionWithAssignment ( "or" , expression );
222+ evaluateCompoundAssign ( "orAssign" , "or" , expression );
221223 break ;
222224
223225 case BITWISE_XOR :
@@ -229,31 +231,31 @@ public void eval(final BinaryExpression expression) {
229231 break ;
230232
231233 case BITWISE_XOR_EQUAL :
232- evaluateBinaryExpressionWithAssignment ( "xor" , expression );
234+ evaluateCompoundAssign ( "xorAssign" , "xor" , expression );
233235 break ;
234236
235237 case PLUS :
236238 evaluateBinaryExpression ("plus" , expression );
237239 break ;
238240
239241 case PLUS_EQUAL :
240- evaluateBinaryExpressionWithAssignment ( "plus" , expression );
242+ evaluateCompoundAssign ( "plusAssign" , "plus" , expression );
241243 break ;
242244
243245 case MINUS :
244246 evaluateBinaryExpression ("minus" , expression );
245247 break ;
246248
247249 case MINUS_EQUAL :
248- evaluateBinaryExpressionWithAssignment ( "minus" , expression );
250+ evaluateCompoundAssign ( "minusAssign" , "minus" , expression );
249251 break ;
250252
251253 case MULTIPLY :
252254 evaluateBinaryExpression ("multiply" , expression );
253255 break ;
254256
255257 case MULTIPLY_EQUAL :
256- evaluateBinaryExpressionWithAssignment ( "multiply" , expression );
258+ evaluateCompoundAssign ( "multiplyAssign" , "multiply" , expression );
257259 break ;
258260
259261 case DIVIDE :
@@ -263,14 +265,15 @@ public void eval(final BinaryExpression expression) {
263265 case DIVIDE_EQUAL :
264266 //SPG don't use divide since BigInteger implements directly
265267 //and we want to dispatch through DefaultGroovyMethods to get a BigDecimal result
266- evaluateBinaryExpressionWithAssignment ( "div" , expression );
268+ evaluateCompoundAssign ( "divAssign" , "div" , expression );
267269 break ;
268270
269271 case INTDIV :
270272 evaluateBinaryExpression ("intdiv" , expression );
271273 break ;
272274
273275 case INTDIV_EQUAL :
276+ // GEP-15 explicitly excludes \= (no intdivAssign convention)
274277 evaluateBinaryExpressionWithAssignment ("intdiv" , expression );
275278 break ;
276279
@@ -279,23 +282,25 @@ public void eval(final BinaryExpression expression) {
279282 break ;
280283
281284 case MOD_EQUAL :
282- evaluateBinaryExpressionWithAssignment ("mod" , expression );
285+ // GEP-15 maps both MOD_EQUAL and REMAINDER_EQUAL to remainderAssign for consistency
286+ // with getOperationName collapse, even though current parser only emits REMAINDER_EQUAL.
287+ evaluateCompoundAssign ("remainderAssign" , "mod" , expression );
283288 break ;
284289
285290 case REMAINDER :
286291 evaluateBinaryExpression ("remainder" , expression );
287292 break ;
288293
289294 case REMAINDER_EQUAL :
290- evaluateBinaryExpressionWithAssignment ( "remainder" , expression );
295+ evaluateCompoundAssign ( "remainderAssign" , "remainder" , expression );
291296 break ;
292297
293298 case POWER :
294299 evaluateBinaryExpression ("power" , expression );
295300 break ;
296301
297302 case POWER_EQUAL :
298- evaluateBinaryExpressionWithAssignment ( "power" , expression );
303+ evaluateCompoundAssign ( "powerAssign" , "power" , expression );
299304 break ;
300305
301306 case ELVIS_EQUAL :
@@ -307,23 +312,23 @@ public void eval(final BinaryExpression expression) {
307312 break ;
308313
309314 case LEFT_SHIFT_EQUAL :
310- evaluateBinaryExpressionWithAssignment ( "leftShift" , expression );
315+ evaluateCompoundAssign ( "leftShiftAssign" , "leftShift" , expression );
311316 break ;
312317
313318 case RIGHT_SHIFT :
314319 evaluateBinaryExpression ("rightShift" , expression );
315320 break ;
316321
317322 case RIGHT_SHIFT_EQUAL :
318- evaluateBinaryExpressionWithAssignment ( "rightShift" , expression );
323+ evaluateCompoundAssign ( "rightShiftAssign" , "rightShift" , expression );
319324 break ;
320325
321326 case RIGHT_SHIFT_UNSIGNED :
322327 evaluateBinaryExpression ("rightShiftUnsigned" , expression );
323328 break ;
324329
325330 case RIGHT_SHIFT_UNSIGNED_EQUAL :
326- evaluateBinaryExpressionWithAssignment ( "rightShiftUnsigned" , expression );
331+ evaluateCompoundAssign ( "rightShiftUnsignedAssign" , "rightShiftUnsigned" , expression );
327332 break ;
328333
329334 case KEYWORD_INSTANCEOF :
@@ -891,6 +896,44 @@ protected void evaluateBinaryExpressionWithAssignment(final String method, final
891896 controller .getCompileStack ().popLHS ();
892897 }
893898
899+ /**
900+ * GEP-15: dynamic-mode compound-assign codegen. Routes through
901+ * {@link ScriptBytecodeAdapter#compoundAssign(Object, Object, String, String)}
902+ * which dispatches to {@code assignName} when the receiver responds to it,
903+ * and falls back to {@code baseName} otherwise. The caller stores the helper's
904+ * return value into the LHS — for the in-place branch this is a no-op store
905+ * of the receiver back to itself; for the fallback branch it is the usual
906+ * "x = x.op(y)" assignment.
907+ */
908+ protected void evaluateCompoundAssign (final String assignName , final String baseName , final BinaryExpression expression ) {
909+ Expression leftExpression = expression .getLeftExpression ();
910+ if (leftExpression instanceof BinaryExpression bexp
911+ && bexp .getOperation ().getType () == LEFT_SQUARE_BRACKET ) {
912+ // Subscript LHS (e.g. a[i] += b) is intentionally out of scope for GEP-15;
913+ // keep the legacy getAt/putAt-based path.
914+ evaluateArrayAssignmentWithOperator (baseName , expression , bexp );
915+ return ;
916+ }
917+
918+ StaticMethodCallExpression helperCall = new StaticMethodCallExpression (
919+ ClassHelper .make (ScriptBytecodeAdapter .class ),
920+ "compoundAssign" ,
921+ new ArgumentListExpression (new Expression []{
922+ leftExpression ,
923+ expression .getRightExpression (),
924+ new ConstantExpression (assignName ),
925+ new ConstantExpression (baseName )
926+ })
927+ );
928+ helperCall .setSourcePosition (expression );
929+ helperCall .visit (controller .getAcg ());
930+
931+ controller .getOperandStack ().dup ();
932+ controller .getCompileStack ().pushLHS (true );
933+ leftExpression .visit (controller .getAcg ());
934+ controller .getCompileStack ().popLHS ();
935+ }
936+
894937 private void evaluateInstanceof (final BinaryExpression expression ) {
895938 CompileStack compileStack = controller .getCompileStack ();
896939 OperandStack operandStack = controller .getOperandStack ();
0 commit comments