2222import org .codehaus .groovy .ast .ClassHelper ;
2323import org .codehaus .groovy .ast .ClassNode ;
2424import org .codehaus .groovy .ast .Variable ;
25+ import org .codehaus .groovy .ast .expr .ArgumentListExpression ;
2526import org .codehaus .groovy .ast .expr .ArrayExpression ;
2627import org .codehaus .groovy .ast .expr .BinaryExpression ;
2728import org .codehaus .groovy .ast .expr .ClassExpression ;
3536import org .codehaus .groovy .ast .expr .PostfixExpression ;
3637import org .codehaus .groovy .ast .expr .PrefixExpression ;
3738import org .codehaus .groovy .ast .expr .PropertyExpression ;
39+ import org .codehaus .groovy .ast .expr .StaticMethodCallExpression ;
3840import org .codehaus .groovy .ast .expr .TernaryExpression ;
3941import org .codehaus .groovy .ast .expr .TupleExpression ;
4042import org .codehaus .groovy .ast .expr .VariableExpression ;
@@ -205,15 +207,15 @@ public void eval(final BinaryExpression expression) {
205207 break ;
206208
207209 case BITWISE_AND_EQUAL :
208- evaluateBinaryExpressionWithAssignment ( "and" , expression );
210+ evaluateCompoundAssign ( "andAssign" , "and" , expression );
209211 break ;
210212
211213 case BITWISE_OR :
212214 evaluateBinaryExpression ("or" , expression );
213215 break ;
214216
215217 case BITWISE_OR_EQUAL :
216- evaluateBinaryExpressionWithAssignment ( "or" , expression );
218+ evaluateCompoundAssign ( "orAssign" , "or" , expression );
217219 break ;
218220
219221 case BITWISE_XOR :
@@ -225,31 +227,31 @@ public void eval(final BinaryExpression expression) {
225227 break ;
226228
227229 case BITWISE_XOR_EQUAL :
228- evaluateBinaryExpressionWithAssignment ( "xor" , expression );
230+ evaluateCompoundAssign ( "xorAssign" , "xor" , expression );
229231 break ;
230232
231233 case PLUS :
232234 evaluateBinaryExpression ("plus" , expression );
233235 break ;
234236
235237 case PLUS_EQUAL :
236- evaluateBinaryExpressionWithAssignment ( "plus" , expression );
238+ evaluateCompoundAssign ( "plusAssign" , "plus" , expression );
237239 break ;
238240
239241 case MINUS :
240242 evaluateBinaryExpression ("minus" , expression );
241243 break ;
242244
243245 case MINUS_EQUAL :
244- evaluateBinaryExpressionWithAssignment ( "minus" , expression );
246+ evaluateCompoundAssign ( "minusAssign" , "minus" , expression );
245247 break ;
246248
247249 case MULTIPLY :
248250 evaluateBinaryExpression ("multiply" , expression );
249251 break ;
250252
251253 case MULTIPLY_EQUAL :
252- evaluateBinaryExpressionWithAssignment ( "multiply" , expression );
254+ evaluateCompoundAssign ( "multiplyAssign" , "multiply" , expression );
253255 break ;
254256
255257 case DIVIDE :
@@ -259,14 +261,15 @@ public void eval(final BinaryExpression expression) {
259261 case DIVIDE_EQUAL :
260262 //SPG don't use divide since BigInteger implements directly
261263 //and we want to dispatch through DefaultGroovyMethods to get a BigDecimal result
262- evaluateBinaryExpressionWithAssignment ( "div" , expression );
264+ evaluateCompoundAssign ( "divAssign" , "div" , expression );
263265 break ;
264266
265267 case INTDIV :
266268 evaluateBinaryExpression ("intdiv" , expression );
267269 break ;
268270
269271 case INTDIV_EQUAL :
272+ // GEP-15 explicitly excludes \= (no intdivAssign convention)
270273 evaluateBinaryExpressionWithAssignment ("intdiv" , expression );
271274 break ;
272275
@@ -275,23 +278,25 @@ public void eval(final BinaryExpression expression) {
275278 break ;
276279
277280 case MOD_EQUAL :
278- evaluateBinaryExpressionWithAssignment ("mod" , expression );
281+ // GEP-15 maps both MOD_EQUAL and REMAINDER_EQUAL to remainderAssign for consistency
282+ // with getOperationName collapse, even though current parser only emits REMAINDER_EQUAL.
283+ evaluateCompoundAssign ("remainderAssign" , "mod" , expression );
279284 break ;
280285
281286 case REMAINDER :
282287 evaluateBinaryExpression ("remainder" , expression );
283288 break ;
284289
285290 case REMAINDER_EQUAL :
286- evaluateBinaryExpressionWithAssignment ( "remainder" , expression );
291+ evaluateCompoundAssign ( "remainderAssign" , "remainder" , expression );
287292 break ;
288293
289294 case POWER :
290295 evaluateBinaryExpression ("power" , expression );
291296 break ;
292297
293298 case POWER_EQUAL :
294- evaluateBinaryExpressionWithAssignment ( "power" , expression );
299+ evaluateCompoundAssign ( "powerAssign" , "power" , expression );
295300 break ;
296301
297302 case ELVIS_EQUAL :
@@ -303,23 +308,23 @@ public void eval(final BinaryExpression expression) {
303308 break ;
304309
305310 case LEFT_SHIFT_EQUAL :
306- evaluateBinaryExpressionWithAssignment ( "leftShift" , expression );
311+ evaluateCompoundAssign ( "leftShiftAssign" , "leftShift" , expression );
307312 break ;
308313
309314 case RIGHT_SHIFT :
310315 evaluateBinaryExpression ("rightShift" , expression );
311316 break ;
312317
313318 case RIGHT_SHIFT_EQUAL :
314- evaluateBinaryExpressionWithAssignment ( "rightShift" , expression );
319+ evaluateCompoundAssign ( "rightShiftAssign" , "rightShift" , expression );
315320 break ;
316321
317322 case RIGHT_SHIFT_UNSIGNED :
318323 evaluateBinaryExpression ("rightShiftUnsigned" , expression );
319324 break ;
320325
321326 case RIGHT_SHIFT_UNSIGNED_EQUAL :
322- evaluateBinaryExpressionWithAssignment ( "rightShiftUnsigned" , expression );
327+ evaluateCompoundAssign ( "rightShiftUnsignedAssign" , "rightShiftUnsigned" , expression );
323328 break ;
324329
325330 case KEYWORD_INSTANCEOF :
@@ -755,6 +760,44 @@ protected void evaluateBinaryExpressionWithAssignment(final String method, final
755760 controller .getCompileStack ().popLHS ();
756761 }
757762
763+ /**
764+ * GEP-15: dynamic-mode compound-assign codegen. Routes through
765+ * {@link ScriptBytecodeAdapter#compoundAssign(Object, Object, String, String)}
766+ * which dispatches to {@code assignName} when the receiver responds to it,
767+ * and falls back to {@code baseName} otherwise. The caller stores the helper's
768+ * return value into the LHS — for the in-place branch this is a no-op store
769+ * of the receiver back to itself; for the fallback branch it is the usual
770+ * "x = x.op(y)" assignment.
771+ */
772+ protected void evaluateCompoundAssign (final String assignName , final String baseName , final BinaryExpression expression ) {
773+ Expression leftExpression = expression .getLeftExpression ();
774+ if (leftExpression instanceof BinaryExpression bexp
775+ && bexp .getOperation ().getType () == LEFT_SQUARE_BRACKET ) {
776+ // Subscript LHS (e.g. a[i] += b) is intentionally out of scope for GEP-15;
777+ // keep the legacy getAt/putAt-based path.
778+ evaluateArrayAssignmentWithOperator (baseName , expression , bexp );
779+ return ;
780+ }
781+
782+ StaticMethodCallExpression helperCall = new StaticMethodCallExpression (
783+ ClassHelper .make (ScriptBytecodeAdapter .class ),
784+ "compoundAssign" ,
785+ new ArgumentListExpression (new Expression []{
786+ leftExpression ,
787+ expression .getRightExpression (),
788+ new ConstantExpression (assignName ),
789+ new ConstantExpression (baseName )
790+ })
791+ );
792+ helperCall .setSourcePosition (expression );
793+ helperCall .visit (controller .getAcg ());
794+
795+ controller .getOperandStack ().dup ();
796+ controller .getCompileStack ().pushLHS (true );
797+ leftExpression .visit (controller .getAcg ());
798+ controller .getCompileStack ().popLHS ();
799+ }
800+
758801 private void evaluateInstanceof (final BinaryExpression expression ) {
759802 CompileStack compileStack = controller .getCompileStack ();
760803 OperandStack operandStack = controller .getOperandStack ();
0 commit comments