Skip to content

Commit a3d4f5e

Browse files
authored
Merge pull request #57 from fglock/fix-bop-parser
Fix bitwise operators: reference stringification, shift edge cases, a…
2 parents 055561a + 255da62 commit a3d4f5e

File tree

3 files changed

+215
-51
lines changed

3 files changed

+215
-51
lines changed

src/main/java/org/perlonjava/codegen/EmitBinaryOperator.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ static void handleBinaryOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo
4848
right = new StringNode(identifierNode.name, node.tokenIndex);
4949
}
5050

51-
// Special case for modulus and division operators under "use integer"
51+
// Special case for modulus, division, and shift operators under "use integer"
5252
if (emitterVisitor.ctx.symbolTable.isStrictOptionEnabled(Strict.HINT_INTEGER)) {
5353
if (node.operator.equals("%")) {
5454
// Use integer modulus when "use integer" is in effect
@@ -74,6 +74,30 @@ static void handleBinaryOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo
7474
false);
7575
EmitOperator.handleVoidContext(emitterVisitor);
7676
return;
77+
} else if (node.operator.equals("<<")) {
78+
// Use integer left shift when "use integer" is in effect
79+
node.left.accept(scalarVisitor); // left parameter
80+
right.accept(scalarVisitor); // right parameter
81+
emitterVisitor.ctx.mv.visitMethodInsn(
82+
Opcodes.INVOKESTATIC,
83+
"org/perlonjava/operators/BitwiseOperators",
84+
"integerShiftLeft",
85+
"(Lorg/perlonjava/runtime/RuntimeScalar;Lorg/perlonjava/runtime/RuntimeScalar;)Lorg/perlonjava/runtime/RuntimeScalar;",
86+
false);
87+
EmitOperator.handleVoidContext(emitterVisitor);
88+
return;
89+
} else if (node.operator.equals(">>")) {
90+
// Use integer right shift when "use integer" is in effect
91+
node.left.accept(scalarVisitor); // left parameter
92+
right.accept(scalarVisitor); // right parameter
93+
emitterVisitor.ctx.mv.visitMethodInsn(
94+
Opcodes.INVOKESTATIC,
95+
"org/perlonjava/operators/BitwiseOperators",
96+
"integerShiftRight",
97+
"(Lorg/perlonjava/runtime/RuntimeScalar;Lorg/perlonjava/runtime/RuntimeScalar;)Lorg/perlonjava/runtime/RuntimeScalar;",
98+
false);
99+
EmitOperator.handleVoidContext(emitterVisitor);
100+
return;
77101
}
78102
}
79103

src/main/java/org/perlonjava/codegen/EmitBinaryOperatorNode.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public static void emitBinaryOperatorNode(EmitterVisitor emitterVisitor, BinaryO
8181
"==", "!=", "eq", "ne" -> EmitOperatorChained.emitChainedComparison(emitterVisitor, node);
8282

8383
// Binary operators
84-
case "%", "&", "&.", "*", "**", "+", "-", "/", "^^", "xor",
85-
"<<", "<=>", ">>", "^", "^.", "|", "|.",
84+
case "%", "&", "&.", "binary&", "*", "**", "+", "-", "/", "^^", "xor",
85+
"<<", "<=>", ">>", "^", "^.", "binary^", "|", "|.", "binary|",
8686
"bless", "cmp", "isa" -> EmitBinaryOperator.handleBinaryOperator(emitterVisitor, node,
8787
OperatorHandler.get(node.operator));
8888

src/main/java/org/perlonjava/operators/BitwiseOperators.java

Lines changed: 188 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.perlonjava.runtime.RuntimeScalar;
66
import org.perlonjava.runtime.RuntimeScalarCache;
77
import 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

Comments
 (0)