diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 59dcac611..094d841da 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -1932,9 +1932,12 @@ void handleCompoundAssignment(BinaryOperatorNode node) { case "/=" -> emit(isIntegerEnabled() ? Opcodes.INTEGER_DIV_ASSIGN : Opcodes.DIVIDE_ASSIGN); case "%=" -> emit(isIntegerEnabled() ? Opcodes.INTEGER_MOD_ASSIGN : Opcodes.MODULUS_ASSIGN); case ".=" -> emit(Opcodes.STRING_CONCAT_ASSIGN); - case "&=", "binary&=" -> emit(Opcodes.BITWISE_AND_ASSIGN); // Numeric bitwise AND - case "|=", "binary|=" -> emit(Opcodes.BITWISE_OR_ASSIGN); // Numeric bitwise OR - case "^=", "binary^=" -> emit(Opcodes.BITWISE_XOR_ASSIGN); // Numeric bitwise XOR + case "&=" -> emit(Opcodes.BITWISE_AND_ASSIGN); // Bitwise AND (dispatch) + case "|=" -> emit(Opcodes.BITWISE_OR_ASSIGN); // Bitwise OR (dispatch) + case "^=" -> emit(Opcodes.BITWISE_XOR_ASSIGN); // Bitwise XOR (dispatch) + case "binary&=" -> emit(Opcodes.BINARY_AND_ASSIGN); // Numeric-only bitwise AND + case "binary|=" -> emit(Opcodes.BINARY_OR_ASSIGN); // Numeric-only bitwise OR + case "binary^=" -> emit(Opcodes.BINARY_XOR_ASSIGN); // Numeric-only bitwise XOR case "&.=" -> emit(Opcodes.STRING_BITWISE_AND_ASSIGN); // String bitwise AND case "|.=" -> emit(Opcodes.STRING_BITWISE_OR_ASSIGN); // String bitwise OR case "^.=" -> emit(Opcodes.STRING_BITWISE_XOR_ASSIGN); // String bitwise XOR diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index a5d166f7e..795603ef2 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1333,6 +1333,24 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c pc = OpcodeHandlerExtended.executeStringBitwiseXorAssign(bytecode, pc, registers); } + case Opcodes.BINARY_AND_ASSIGN -> { + // Numeric-only bitwise AND assign (use feature "bitwise") + // Format: BINARY_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBinaryAndAssign(bytecode, pc, registers); + } + + case Opcodes.BINARY_OR_ASSIGN -> { + // Numeric-only bitwise OR assign (use feature "bitwise") + // Format: BINARY_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBinaryOrAssign(bytecode, pc, registers); + } + + case Opcodes.BINARY_XOR_ASSIGN -> { + // Numeric-only bitwise XOR assign (use feature "bitwise") + // Format: BINARY_XOR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBinaryXorAssign(bytecode, pc, registers); + } + case Opcodes.BITWISE_AND_BINARY -> { // Numeric bitwise AND: rd = rs1 binary& rs2 // Format: BITWISE_AND_BINARY rd rs1 rs2 diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java index 6ece26fa9..7389af20b 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java @@ -358,9 +358,8 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rs2); } case "binary&" -> { - // Numeric bitwise AND (use integer): rs1 binary& rs2 - // Same as & but explicitly numeric - bytecodeCompiler.emit(Opcodes.BITWISE_AND_BINARY); + // Numeric bitwise AND (use feature "bitwise"): always numeric + bytecodeCompiler.emit(Opcodes.BINARY_AND); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(rs1); bytecodeCompiler.emitReg(rs2); @@ -373,9 +372,8 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rs2); } case "binary|" -> { - // Numeric bitwise OR (use integer): rs1 binary| rs2 - // Same as | but explicitly numeric - bytecodeCompiler.emit(Opcodes.BITWISE_OR_BINARY); + // Numeric bitwise OR (use feature "bitwise"): always numeric + bytecodeCompiler.emit(Opcodes.BINARY_OR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(rs1); bytecodeCompiler.emitReg(rs2); @@ -388,9 +386,8 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rs2); } case "binary^" -> { - // Numeric bitwise XOR (use integer): rs1 binary^ rs2 - // Same as ^ but explicitly numeric - bytecodeCompiler.emit(Opcodes.BITWISE_XOR_BINARY); + // Numeric bitwise XOR (use feature "bitwise"): always numeric + bytecodeCompiler.emit(Opcodes.BINARY_XOR); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(rs1); bytecodeCompiler.emitReg(rs2); diff --git a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java index d4bba7646..72242db49 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java @@ -415,6 +415,21 @@ public static String disassemble(InterpretedCode interpretedCode) { rs = interpretedCode.bytecode[pc++]; sb.append("STRING_BITWISE_XOR_ASSIGN r").append(rd).append(" ^.= r").append(rs).append("\n"); break; + case Opcodes.BINARY_AND_ASSIGN: + rd = interpretedCode.bytecode[pc++]; + rs = interpretedCode.bytecode[pc++]; + sb.append("BINARY_AND_ASSIGN r").append(rd).append(" binary&= r").append(rs).append("\n"); + break; + case Opcodes.BINARY_OR_ASSIGN: + rd = interpretedCode.bytecode[pc++]; + rs = interpretedCode.bytecode[pc++]; + sb.append("BINARY_OR_ASSIGN r").append(rd).append(" binary|= r").append(rs).append("\n"); + break; + case Opcodes.BINARY_XOR_ASSIGN: + rd = interpretedCode.bytecode[pc++]; + rs = interpretedCode.bytecode[pc++]; + sb.append("BINARY_XOR_ASSIGN r").append(rd).append(" binary^= r").append(rs).append("\n"); + break; case Opcodes.BITWISE_AND_BINARY: rd = interpretedCode.bytecode[pc++]; int andRs1 = interpretedCode.bytecode[pc++]; diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index 60918e057..15542b091 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -1073,4 +1073,58 @@ public static int executeModulusAssign(int[] bytecode, int pc, RuntimeBase[] reg registers[rd] = MathOperators.modulusAssignWarn(s1, s2); return pc; } + + /** + * Execute numeric-only bitwise AND assign (use feature "bitwise"). + * Format: BINARY_AND_ASSIGN rd rs + */ + public static int executeBinaryAndAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { + registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); + } + RuntimeScalar result = BitwiseOperators.bitwiseAndBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute numeric-only bitwise OR assign (use feature "bitwise"). + * Format: BINARY_OR_ASSIGN rd rs + */ + public static int executeBinaryOrAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { + registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); + } + RuntimeScalar result = BitwiseOperators.bitwiseOrBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute numeric-only bitwise XOR assign (use feature "bitwise"). + * Format: BINARY_XOR_ASSIGN rd rs + */ + public static int executeBinaryXorAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { + registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); + } + RuntimeScalar result = BitwiseOperators.bitwiseXorBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 4f7f9cd5f..d276c5539 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -2242,6 +2242,24 @@ public class Opcodes { */ public static final short SCOPE_EXIT_CLEANUP_ARRAY = 467; + /** + * Numeric-only bitwise AND assign (use feature "bitwise"): rd &= rs (always numeric). + * Format: BINARY_AND_ASSIGN rd rs + */ + public static final short BINARY_AND_ASSIGN = 468; + + /** + * Numeric-only bitwise OR assign (use feature "bitwise"): rd |= rs (always numeric). + * Format: BINARY_OR_ASSIGN rd rs + */ + public static final short BINARY_OR_ASSIGN = 469; + + /** + * Numeric-only bitwise XOR assign (use feature "bitwise"): rd ^= rs (always numeric). + * Format: BINARY_XOR_ASSIGN rd rs + */ + public static final short BINARY_XOR_ASSIGN = 470; + private Opcodes() { } // Utility class - no instantiation } diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java index ed3d4f3cc..c61e4c4ea 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -248,7 +248,7 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { // - Marked as package (true), OR // - Unknown (null) but NOT followed by '(' - like 'new NonExistentClass' if ((isPackage != null && !isPackage) - || (isPackage == null && !isKnownSub && token.text.equals("(") && !packageName.contains("::")) + || (isPackage == null && !isKnownSub && token.text.equals("(") && !packageName.contains("::") && subExists) || (subExists && packageName.contains("::") && token.text.equals("(") && !(isPackage != null && isPackage))) { parser.tokenIndex = currentIndex2; diff --git a/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java b/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java index be554e872..4b1d70898 100644 --- a/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java @@ -56,11 +56,16 @@ public static RuntimeScalar bitwiseAnd(RuntimeScalar runtimeScalar, RuntimeScala RuntimeScalarCache.scalarEmptyString, "uninitialized"); } - // In Perl, if either operand is a reference or doesn't look like a number, use string operations - if (!ScalarUtils.looksLikeNumber(val1) || !ScalarUtils.looksLikeNumber(val2)) { - return bitwiseAndDot(val1, val2); - } - return bitwiseAndBinary(val1, val2); + // In Perl, bitwise ops dispatch based on internal type flags (SvNIOKp): + // - If either operand has a numeric type (IOK/NOK), use numeric bitwise + // - If both are non-numeric (strings from pack/vec, etc.), use string bitwise + int vt1 = val1.type; + int vt2 = val2.type; + if (vt1 == RuntimeScalarType.INTEGER || vt1 == RuntimeScalarType.DOUBLE || + vt2 == RuntimeScalarType.INTEGER || vt2 == RuntimeScalarType.DOUBLE) { + return bitwiseAndBinary(val1, val2); + } + return bitwiseAndDot(val1, val2); } /** @@ -115,11 +120,16 @@ public static RuntimeScalar bitwiseOr(RuntimeScalar runtimeScalar, RuntimeScalar t2 == RuntimeScalarType.TIED_SCALAR ? arg2.tiedFetch() : t2 == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) arg2.value : arg2; - // In Perl, if either operand is a reference or doesn't look like a number, use string operations - if (!ScalarUtils.looksLikeNumber(val1) || !ScalarUtils.looksLikeNumber(val2)) { - return bitwiseOrDot(val1, val2); + // In Perl, bitwise ops dispatch based on internal type flags (SvNIOKp): + // - If either operand has a numeric type (IOK/NOK), use numeric bitwise + // - If both are non-numeric (strings from pack/vec, etc.), use string bitwise + int vt1 = val1.type; + int vt2 = val2.type; + if (vt1 == RuntimeScalarType.INTEGER || vt1 == RuntimeScalarType.DOUBLE || + vt2 == RuntimeScalarType.INTEGER || vt2 == RuntimeScalarType.DOUBLE) { + return bitwiseOrBinary(val1, val2); } - return bitwiseOrBinary(val1, val2); + return bitwiseOrDot(val1, val2); } /** @@ -178,13 +188,15 @@ public static RuntimeScalar bitwiseXor(RuntimeScalar runtimeScalar, RuntimeScala t2 == RuntimeScalarType.TIED_SCALAR ? arg2.tiedFetch() : t2 == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) arg2.value : arg2; - // Use numeric XOR only if BOTH operands look like numbers - // For everything else (strings, blessed objects, references, etc.), use string XOR - if (ScalarUtils.looksLikeNumber(val1) && ScalarUtils.looksLikeNumber(val2)) { - // Both are pure numbers (INTEGER or DOUBLE), use numeric XOR + // In Perl, bitwise ops dispatch based on internal type flags (SvNIOKp): + // - If either operand has a numeric type (IOK/NOK), use numeric bitwise + // - If both are non-numeric (strings from pack/vec, etc.), use string bitwise + int vt1 = val1.type; + int vt2 = val2.type; + if (vt1 == RuntimeScalarType.INTEGER || vt1 == RuntimeScalarType.DOUBLE || + vt2 == RuntimeScalarType.INTEGER || vt2 == RuntimeScalarType.DOUBLE) { return bitwiseXorBinary(val1, val2); } - // At least one is a string, reference, or blessed object, use string XOR return bitwiseXorDot(val1, val2); } @@ -228,11 +240,14 @@ public static RuntimeScalar bitwiseNot(RuntimeScalar runtimeScalar) { runtimeScalar.type == RuntimeScalarType.TIED_SCALAR ? runtimeScalar.tiedFetch() : runtimeScalar.type == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) runtimeScalar.value : runtimeScalar; - // In Perl, if the operand is a reference or doesn't look like a number, use string operations - if (!ScalarUtils.looksLikeNumber(val)) { - return bitwiseNotDot(val); + // In Perl, ~$val dispatches based on internal type flags (SvNIOKp): + // - If the operand has a numeric type (IOK/NOK), use numeric NOT + // - If it's a string, use string NOT (character-by-character) + int vt = val.type; + if (vt == RuntimeScalarType.INTEGER || vt == RuntimeScalarType.DOUBLE) { + return bitwiseNotBinary(val); } - return bitwiseNotBinary(val); + return bitwiseNotDot(val); } /** @@ -278,8 +293,11 @@ public static RuntimeScalar integerBitwiseNot(RuntimeScalar runtimeScalar) { runtimeScalar.type == RuntimeScalarType.TIED_SCALAR ? runtimeScalar.tiedFetch() : runtimeScalar.type == RuntimeScalarType.READONLY_SCALAR ? (RuntimeScalar) runtimeScalar.value : runtimeScalar; - // In Perl, if the operand is a reference or doesn't look like a number, use string operations - if (!ScalarUtils.looksLikeNumber(val)) { + // In Perl, ~$val dispatches based on internal type flags (SvNIOKp): + // - If the operand has a numeric type (IOK/NOK), use numeric NOT + // - If it's a string, use string NOT (character-by-character) + int vt = val.type; + if (vt != RuntimeScalarType.INTEGER && vt != RuntimeScalarType.DOUBLE) { return bitwiseNotDot(val); } diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/PackWriter.java b/src/main/java/org/perlonjava/runtime/operators/pack/PackWriter.java index 6cfb56f2f..d51e8f96c 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/PackWriter.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/PackWriter.java @@ -572,6 +572,7 @@ public static void writeBitString(PackBuffer output, String str, int count, char int bitIndex = 0; int byteValue = 0; int bitsToProcess = Math.min(str.length(), count); + int bytesWritten = 0; for (int i = 0; i < bitsToProcess; i++) { char c = str.charAt(i); @@ -583,12 +584,19 @@ public static void writeBitString(PackBuffer output, String str, int count, char bitIndex++; if (bitIndex == 8) { output.write(byteValue); + bytesWritten++; bitIndex = 0; byteValue = 0; } } if (bitIndex > 0) { output.write(byteValue); + bytesWritten++; + } + // Zero-pad to fill the requested count (Perl zero-fills to ceil(count/8) bytes) + int totalBytes = (count + 7) / 8; + for (int i = bytesWritten; i < totalBytes; i++) { + output.write(0); } } }