55import org .perlonjava .runtime .RuntimeScalar ;
66import org .perlonjava .runtime .RuntimeScalarCache ;
77import org .perlonjava .runtime .RuntimeScalarType ;
8+ import org .perlonjava .runtime .ScalarUtils ;
89
910/**
1011 * This class provides methods for performing bitwise operations on RuntimeScalar objects.
@@ -16,6 +17,7 @@ public class BitwiseOperators {
1617 /**
1718 * Performs a bitwise AND operation on two RuntimeScalar objects.
1819 * If both arguments are strings, it performs the operation character by character.
20+ * In Perl, references and non-numeric values are stringified before bitwise operations.
1921 *
2022 * @param runtimeScalar The first operand.
2123 * @param arg2 The second operand.
@@ -33,7 +35,8 @@ public static RuntimeScalar bitwiseAnd(RuntimeScalar runtimeScalar, RuntimeScala
3335 RuntimeScalarCache .scalarEmptyString );
3436 }
3537
36- if (runtimeScalar .isString () && arg2 .isString ()) {
38+ // In Perl, if either operand is a reference or doesn't look like a number, use string operations
39+ if (!ScalarUtils .looksLikeNumber (runtimeScalar ) || !ScalarUtils .looksLikeNumber (arg2 )) {
3740 return bitwiseAndDot (runtimeScalar , arg2 );
3841 }
3942 return bitwiseAndBinary (runtimeScalar , arg2 );
@@ -60,13 +63,15 @@ public static RuntimeScalar bitwiseAndBinary(RuntimeScalar runtimeScalar, Runtim
6063 /**
6164 * Performs a bitwise OR operation on two RuntimeScalar objects.
6265 * If both arguments are strings, it performs the operation character by character.
66+ * In Perl, references and non-numeric values are stringified before bitwise operations.
6367 *
6468 * @param runtimeScalar The first operand.
6569 * @param arg2 The second operand.
6670 * @return A new RuntimeScalar with the result of the bitwise OR operation.
6771 */
6872 public static RuntimeScalar bitwiseOr (RuntimeScalar runtimeScalar , RuntimeScalar arg2 ) {
69- if (runtimeScalar .isString () && arg2 .isString ()) {
73+ // In Perl, if either operand is a reference or doesn't look like a number, use string operations
74+ if (!ScalarUtils .looksLikeNumber (runtimeScalar ) || !ScalarUtils .looksLikeNumber (arg2 )) {
7075 return bitwiseOrDot (runtimeScalar , arg2 );
7176 }
7277 return bitwiseOrBinary (runtimeScalar , arg2 );
@@ -95,24 +100,22 @@ public static RuntimeScalar bitwiseOrBinary(RuntimeScalar runtimeScalar, Runtime
95100 * <p>
96101 * Perl's XOR behavior:
97102 * - If both operands are pure numeric types (INTEGER/DOUBLE), use numeric XOR
98- * - Otherwise (strings, blessed objects, etc.), use string XOR
103+ * - Otherwise (strings, blessed objects, references, etc.), use string XOR
99104 * <p>
100- * Note: isString() returns true for STRING types, but blessed objects need
101- * special handling - they should use string XOR after stringification.
105+ * Note: References and non-numeric values are stringified before bitwise operations.
102106 *
103107 * @param runtimeScalar The first operand.
104108 * @param arg2 The second operand.
105109 * @return A new RuntimeScalar with the result of the bitwise XOR operation.
106110 */
107111 public static RuntimeScalar bitwiseXor (RuntimeScalar runtimeScalar , RuntimeScalar arg2 ) {
108- // Use numeric XOR only if BOTH operands are pure numeric types (not strings)
109- // For everything else (strings, blessed objects, etc.), use string XOR
110- if (!runtimeScalar .isString () && !arg2 .isString () &&
111- !runtimeScalar .isBlessed () && !arg2 .isBlessed ()) {
112+ // Use numeric XOR only if BOTH operands look like numbers
113+ // For everything else (strings, blessed objects, references, etc.), use string XOR
114+ if (ScalarUtils .looksLikeNumber (runtimeScalar ) && ScalarUtils .looksLikeNumber (arg2 )) {
112115 // Both are pure numbers (INTEGER or DOUBLE), use numeric XOR
113116 return bitwiseXorBinary (runtimeScalar , arg2 );
114117 }
115- // At least one is a string or blessed object, use string XOR
118+ // At least one is a string, reference, or blessed object, use string XOR
116119 return bitwiseXorDot (runtimeScalar , arg2 );
117120 }
118121
@@ -137,12 +140,14 @@ public static RuntimeScalar bitwiseXorBinary(RuntimeScalar runtimeScalar, Runtim
137140 /**
138141 * Performs a bitwise NOT operation on a RuntimeScalar object.
139142 * If the argument is a string, it performs the operation character by character.
143+ * In Perl, references and non-numeric values are stringified before bitwise operations.
140144 *
141145 * @param runtimeScalar The operand.
142146 * @return A new RuntimeScalar with the result of the bitwise NOT operation.
143147 */
144148 public static RuntimeScalar bitwiseNot (RuntimeScalar runtimeScalar ) {
145- if (runtimeScalar .isString ()) {
149+ // In Perl, if the operand is a reference or doesn't look like a number, use string operations
150+ if (!ScalarUtils .looksLikeNumber (runtimeScalar )) {
146151 return bitwiseNotDot (runtimeScalar );
147152 }
148153 return bitwiseNotBinary (runtimeScalar );
@@ -272,6 +277,8 @@ public static RuntimeScalar bitwiseNotDot(RuntimeScalar runtimeScalar) {
272277
273278 /**
274279 * Performs a left shift operation on a RuntimeScalar object.
280+ * Perl shifts treat negative numbers as unsigned (UV) by default.
281+ * Negative shift amounts reverse the direction (left shift becomes right shift).
275282 *
276283 * @param runtimeScalar The operand to be shifted.
277284 * @param arg2 The number of positions to shift.
@@ -313,39 +320,34 @@ public static RuntimeScalar shiftLeft(RuntimeScalar runtimeScalar, RuntimeScalar
313320 }
314321
315322 long value = runtimeScalar .getLong ();
316- int shift = arg2 .getInt ();
317-
318- // For shifts that would overflow long, use double to match pow() behavior
319- if (shift >= 63 ) {
320- if (shift >= 1024 ) {
321- // Prevent infinity for extremely large shifts
322- return new RuntimeScalar (0.0 );
323- }
324- if (value == 0 ) {
325- return new RuntimeScalar (0L );
326- }
327- // Use double arithmetic for large shifts to match pow() behavior
328- double result = value * Math .pow (2.0 , shift );
329- return new RuntimeScalar (result );
323+ long shift = arg2 .getLong ();
324+
325+ // Handle negative shift (reverse direction: left shift becomes right shift)
326+ if (shift < 0 ) {
327+ shift = -shift ;
328+ return shiftRightInternal (value , shift , false );
330329 }
331330
332- // For smaller shifts, check if the result would overflow
333- if (shift > 0 ) {
334- // Check if the shift would cause overflow
335- long result = value << shift ;
336- // If sign changed unexpectedly (overflow), use double
337- if ((value > 0 && result < 0 ) || (value < 0 && result > 0 )) {
338- double doubleResult = value * Math .pow (2.0 , shift );
339- return new RuntimeScalar (doubleResult );
340- }
341- return new RuntimeScalar (result );
331+ // Perl uses 32-bit word size for shift operations
332+ // Shifts >= 32 return 0
333+ if (shift >= 32 ) {
334+ return RuntimeScalarCache .scalarZero ;
342335 }
343-
344- return new RuntimeScalar (value << shift );
336+
337+ // Treat value as unsigned 32-bit (UV semantics)
338+ // Mask to 32 bits first to handle negative numbers correctly
339+ long unsignedValue = value & 0xFFFFFFFFL ;
340+
341+ // Perform the shift
342+ long result = (unsignedValue << shift ) & 0xFFFFFFFFL ;
343+
344+ return new RuntimeScalar (result );
345345 }
346346
347347 /**
348348 * Performs a right shift operation on a RuntimeScalar object.
349+ * Perl shifts treat negative numbers as unsigned (UV) by default.
350+ * Negative shift amounts reverse the direction (right shift becomes left shift).
349351 *
350352 * @param runtimeScalar The operand to be shifted.
351353 * @param arg2 The number of positions to shift.
@@ -375,31 +377,169 @@ public static RuntimeScalar shiftRight(RuntimeScalar runtimeScalar, RuntimeScala
375377 if (doubleValue > 0 ) {
376378 // +Inf should convert to UV_MAX (32-bit), then shift right
377379 long uvMax = 4294967295L ; // 2^32 - 1
378- int shift = arg2 .getInt ();
380+ long shift = arg2 .getLong ();
379381 if (shift >= 32 ) {
380- return new RuntimeScalar ( 0L ) ;
382+ return RuntimeScalarCache . scalarZero ;
381383 }
382- return new RuntimeScalar (uvMax >> shift );
384+ return new RuntimeScalar (uvMax >>> shift );
383385 } else {
384386 // -Inf should convert to 0 for unsigned interpretation
385- return new RuntimeScalar ( 0L ) ;
387+ return RuntimeScalarCache . scalarZero ;
386388 }
387389 }
388390 if (Double .isNaN (doubleValue )) {
389391 // NaN should convert to 0
390- return new RuntimeScalar ( 0L ) ;
392+ return RuntimeScalarCache . scalarZero ;
391393 }
392394 }
393395
394- // Use long for consistency
395396 long value = runtimeScalar .getLong ();
396- int shift = arg2 .getInt ();
397+ long shift = arg2 .getLong ();
398+
399+ // Handle negative shift (reverse direction: right shift becomes left shift)
400+ if (shift < 0 ) {
401+ shift = -shift ;
402+ // For left shift with negative amount, treat as normal left shift
403+ if (shift >= 32 ) {
404+ return RuntimeScalarCache .scalarZero ;
405+ }
406+ long unsignedValue = value & 0xFFFFFFFFL ;
407+ long result = (unsignedValue << shift ) & 0xFFFFFFFFL ;
408+ return new RuntimeScalar (result );
409+ }
410+
411+ return shiftRightInternal (value , shift , false );
412+ }
413+
414+ /**
415+ * Internal helper for right shift operations.
416+ *
417+ * @param value The value to shift
418+ * @param shift The shift amount (must be non-negative)
419+ * @param signed If true, use signed (arithmetic) shift; if false, use unsigned (logical) shift
420+ * @return A new RuntimeScalar with the shifted value
421+ */
422+ private static RuntimeScalar shiftRightInternal (long value , long shift , boolean signed ) {
423+ // Perl uses 32-bit word size for shift operations
424+ // Unsigned shifts >= 32 return 0
425+ if (shift >= 32 ) {
426+ if (signed ) {
427+ // For signed right shift, stick to -1 or 0
428+ return new RuntimeScalar (value < 0 ? -1 : 0 );
429+ }
430+ return RuntimeScalarCache .scalarZero ;
431+ }
432+
433+ if (signed ) {
434+ // Signed (arithmetic) shift - sign bit propagates
435+ // First convert to signed 32-bit, then shift, then mask
436+ int signedValue = (int ) value ;
437+ long result = signedValue >> shift ;
438+ return new RuntimeScalar (result );
439+ } else {
440+ // Unsigned (logical) shift - zero fill
441+ // Treat as unsigned 32-bit value
442+ long unsignedValue = value & 0xFFFFFFFFL ;
443+ long result = unsignedValue >>> shift ;
444+ return new RuntimeScalar (result );
445+ }
446+ }
447+
448+ /**
449+ * Performs a left shift operation with signed (integer) semantics.
450+ * This is used when "use integer" pragma is in effect.
451+ *
452+ * @param runtimeScalar The operand to be shifted.
453+ * @param arg2 The number of positions to shift.
454+ * @return A new RuntimeScalar with the result of the integer left shift operation.
455+ */
456+ public static RuntimeScalar integerShiftLeft (RuntimeScalar runtimeScalar , RuntimeScalar arg2 ) {
457+ // Check for uninitialized values and generate warnings
458+ if (!runtimeScalar .getDefinedBoolean ()) {
459+ WarnDie .warn (new RuntimeScalar ("Use of uninitialized value in left bitshift (<<)" ),
460+ RuntimeScalarCache .scalarEmptyString );
461+ }
462+ if (!arg2 .getDefinedBoolean ()) {
463+ WarnDie .warn (new RuntimeScalar ("Use of uninitialized value in left bitshift (<<)" ),
464+ RuntimeScalarCache .scalarEmptyString );
465+ }
466+
467+ // Convert string type to number if necessary
468+ if (runtimeScalar .isString ()) {
469+ runtimeScalar = NumberParser .parseNumber (runtimeScalar );
470+ }
397471
398- // Handle shifts >= 64 (long size in bits)
399- if (shift >= 64 ) {
400- return new RuntimeScalar (value < 0 ? -1L : 0L );
472+ int value = runtimeScalar .getInt ();
473+ long shift = arg2 .getLong ();
474+
475+ // Handle negative shift (reverse direction: left becomes right)
476+ if (shift < 0 ) {
477+ shift = -shift ;
478+ // For shifts >= 32, stick to -1 or 0 depending on sign
479+ if (shift >= 32 ) {
480+ return new RuntimeScalar (value < 0 ? -1 : 0 );
481+ }
482+ // Perform signed (arithmetic) right shift
483+ int result = value >> (int )shift ;
484+ return new RuntimeScalar (result );
401485 }
402486
403- return new RuntimeScalar (value >> shift );
487+ // Shifts >= 32 return 0
488+ if (shift >= 32 ) {
489+ return RuntimeScalarCache .scalarZero ;
490+ }
491+
492+ // Perform signed left shift
493+ int result = value << (int )shift ;
494+ return new RuntimeScalar (result );
495+ }
496+
497+ /**
498+ * Performs a right shift operation with signed (integer) semantics.
499+ * This is used when "use integer" pragma is in effect.
500+ *
501+ * @param runtimeScalar The operand to be shifted.
502+ * @param arg2 The number of positions to shift.
503+ * @return A new RuntimeScalar with the result of the integer right shift operation.
504+ */
505+ public static RuntimeScalar integerShiftRight (RuntimeScalar runtimeScalar , RuntimeScalar arg2 ) {
506+ // Check for uninitialized values and generate warnings
507+ if (!runtimeScalar .getDefinedBoolean ()) {
508+ WarnDie .warn (new RuntimeScalar ("Use of uninitialized value in right bitshift (>>)" ),
509+ RuntimeScalarCache .scalarEmptyString );
510+ }
511+ if (!arg2 .getDefinedBoolean ()) {
512+ WarnDie .warn (new RuntimeScalar ("Use of uninitialized value in right bitshift (>>)" ),
513+ RuntimeScalarCache .scalarEmptyString );
514+ }
515+
516+ // Convert string type to number if necessary
517+ if (runtimeScalar .isString ()) {
518+ runtimeScalar = NumberParser .parseNumber (runtimeScalar );
519+ }
520+
521+ int value = runtimeScalar .getInt ();
522+ long shift = arg2 .getLong ();
523+
524+ // Handle negative shift (reverse direction: right becomes left)
525+ if (shift < 0 ) {
526+ shift = -shift ;
527+ // Shifts >= 32 return 0
528+ if (shift >= 32 ) {
529+ return RuntimeScalarCache .scalarZero ;
530+ }
531+ // Perform signed left shift
532+ int result = value << (int )shift ;
533+ return new RuntimeScalar (result );
534+ }
535+
536+ // For shifts >= 32, stick to -1 or 0 depending on sign
537+ if (shift >= 32 ) {
538+ return new RuntimeScalar (value < 0 ? -1 : 0 );
539+ }
540+
541+ // Perform signed (arithmetic) right shift
542+ int result = value >> (int )shift ;
543+ return new RuntimeScalar (result );
404544 }
405545}
0 commit comments