diff --git a/Makefile b/Makefile index 244afa93e..e028ecbcf 100644 --- a/Makefile +++ b/Makefile @@ -24,9 +24,9 @@ endif # Development build - forces recompilation (use during active development) dev: wrapper ifeq ($(OS),Windows_NT) - gradlew.bat clean compileJava installDist + gradlew.bat clean compileJava shadowJar installDist else - ./gradlew clean compileJava installDist + ./gradlew clean compileJava shadowJar installDist endif # Default test target - fast unit tests using perl_test_runner.pl diff --git a/dev/tools/generate_opcode_handlers.pl b/dev/tools/generate_opcode_handlers.pl index a351b730e..8f17a40ff 100755 --- a/dev/tools/generate_opcode_handlers.pl +++ b/dev/tools/generate_opcode_handlers.pl @@ -8,7 +8,7 @@ my $opcodes_file = 'src/main/java/org/perlonjava/interpreter/Opcodes.java'; my $bytecode_interpreter_file = 'src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java'; my $interpreted_code_file = 'src/main/java/org/perlonjava/interpreter/InterpretedCode.java'; -my $bytecode_compiler_file = 'src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java'; +my $bytecode_compiler_file = 'src/main/java/org/perlonjava/interpreter/CompileOperator.java'; # Changed from BytecodeCompiler.java my $output_dir = 'src/main/java/org/perlonjava/interpreter'; # Read existing opcodes and LASTOP from Opcodes.java diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java index 2afe2e686..afb0cbb65 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java @@ -2606,23 +2606,32 @@ void compileVariableReference(OperatorNode node, String op) { if (node.operand instanceof IdentifierNode) { String varName = "%" + ((IdentifierNode) node.operand).name; - // Check if it's a lexical hash + // Get the hash register + int hashReg; if (hasVariable(varName)) { // Lexical hash - use existing register - lastResultReg = getVariableRegister(varName); - return; - } - - // Global hash - load it - int rd = allocateRegister(); - String globalHashName = NameNormalizer.normalizeVariableName(((IdentifierNode) node.operand).name, getCurrentPackage()); - int nameIdx = addToStringPool(globalHashName); + hashReg = getVariableRegister(varName); + } else { + // Global hash - load it + hashReg = allocateRegister(); + String globalHashName = NameNormalizer.normalizeVariableName(((IdentifierNode) node.operand).name, getCurrentPackage()); + int nameIdx = addToStringPool(globalHashName); - emit(Opcodes.LOAD_GLOBAL_HASH); - emitReg(rd); - emit(nameIdx); + emit(Opcodes.LOAD_GLOBAL_HASH); + emitReg(hashReg); + emit(nameIdx); + } - lastResultReg = rd; + // Check if we're in scalar context - if so, return hash as scalar (bucket info) + if (currentCallContext == RuntimeContextType.SCALAR) { + int rd = allocateRegister(); + emit(Opcodes.ARRAY_SIZE); // Works for hashes too - calls .scalar() + emitReg(rd); + emitReg(hashReg); + lastResultReg = rd; + } else { + lastResultReg = hashReg; + } } else { throwCompilerException("Unsupported % operand: " + node.operand.getClass().getSimpleName()); } @@ -2763,6 +2772,7 @@ private int getHighestVariableRegister() { * - Reserved registers (0-2) * - All variables in all scopes * - Captured variables (closures) + * - Registers protected by enterScope() (baseRegisterForStatement) * * NOTE: This assumes that by the end of a statement, all intermediate values * have either been stored in variables or used/discarded. Any value that needs @@ -2770,7 +2780,12 @@ private int getHighestVariableRegister() { */ private void recycleTemporaryRegisters() { // Find highest variable register and reset nextRegister to one past it - baseRegisterForStatement = getHighestVariableRegister() + 1; + int highestVarReg = getHighestVariableRegister() + 1; + + // CRITICAL: Never lower baseRegisterForStatement below what enterScope() set. + // This protects registers like foreach iterators that must survive across loop iterations. + // Use Math.max to preserve the higher value set by enterScope() + baseRegisterForStatement = Math.max(baseRegisterForStatement, highestVarReg); nextRegister = baseRegisterForStatement; // DO NOT reset lastResultReg - BlockNode needs it to return the last statement's result @@ -3275,10 +3290,8 @@ public void visit(For1Node node) { emitReg(iterReg); emitReg(listReg); - // Step 3: Enter new scope for loop variable - enterScope(); - - // Step 4: Declare loop variable in the new scope + // Step 3: Allocate loop variable register BEFORE entering scope + // This ensures both iterReg and varReg are protected from recycling int varReg = -1; if (node.variable != null && node.variable instanceof OperatorNode) { OperatorNode varOp = (OperatorNode) node.variable; @@ -3286,7 +3299,8 @@ public void visit(For1Node node) { OperatorNode sigilOp = (OperatorNode) varOp.operand; if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; - varReg = addVariable(varName, "my"); + // Don't add to scope yet - just allocate register + varReg = allocateRegister(); } } } @@ -3296,12 +3310,31 @@ public void visit(For1Node node) { varReg = allocateRegister(); } - // Step 5: Push loop info onto stack for last/next/redo + // Step 4: Enter new scope for loop variable + // Now baseRegisterForStatement will be set past both iterReg and varReg, + // protecting them from being recycled by recycleTemporaryRegisters() + enterScope(); + + // Step 5: If we have a named loop variable, add it to the scope now + if (node.variable != null && node.variable instanceof OperatorNode) { + OperatorNode varOp = (OperatorNode) node.variable; + if (varOp.operator.equals("my") && varOp.operand instanceof OperatorNode) { + OperatorNode sigilOp = (OperatorNode) varOp.operand; + if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { + String varName = "$" + ((IdentifierNode) sigilOp.operand).name; + // Add to scope and track for variableRegistry + variableScopes.peek().put(varName, varReg); + allDeclaredVariables.put(varName, varReg); + } + } + } + + // Step 6: Push loop info onto stack for last/next/redo int loopStartPc = bytecode.size(); LoopInfo loopInfo = new LoopInfo(node.labelName, loopStartPc, true); // true = foreach is a true loop loopStack.push(loopInfo); - // Step 6: Loop start - combined check/next/exit (superinstruction) + // Step 7: Loop start - combined check/next/exit (superinstruction) // Emit FOREACH_NEXT_OR_EXIT superinstruction // This combines: hasNext check, next() call, and conditional jump // Format: FOREACH_NEXT_OR_EXIT varReg, iterReg, exitTarget (absolute address) @@ -3311,23 +3344,23 @@ public void visit(For1Node node) { int loopEndJumpPc = bytecode.size(); emitInt(0); // placeholder for exit target (absolute, will be patched) - // Step 7: Execute body (redo jumps here) + // Step 8: Execute body (redo jumps here) if (node.body != null) { node.body.accept(this); } - // Step 8: Continue point (next jumps here) + // Step 9: Continue point (next jumps here) loopInfo.continuePc = bytecode.size(); - // Step 9: Jump back to loop start + // Step 10: Jump back to loop start emit(Opcodes.GOTO); emitInt(loopStartPc); - // Step 10: Loop end - patch the forward jump (last jumps here) + // Step 11: Loop end - patch the forward jump (last jumps here) int loopEndPc = bytecode.size(); patchJump(loopEndJumpPc, loopEndPc); - // Step 11: Patch all last/next/redo jumps + // Step 12: Patch all last/next/redo jumps for (int pc : loopInfo.breakPcs) { patchJump(pc, loopEndPc); } @@ -3338,7 +3371,7 @@ public void visit(For1Node node) { patchJump(pc, loopStartPc); } - // Step 12: Pop loop info and exit scope + // Step 13: Pop loop info and exit scope loopStack.pop(); exitScope(); diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index ef8d55bb9..c3e7ddffb 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -308,40 +308,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.CREATE_CLOSURE: { + case Opcodes.CREATE_CLOSURE: // Create closure with captured variables // Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ... - int rd = bytecode[pc++]; - int templateIdx = bytecode[pc++]; - int numCaptures = bytecode[pc++]; - - // Get the template InterpretedCode from constants - InterpretedCode template = (InterpretedCode) code.constants[templateIdx]; - - // Capture the current register values - RuntimeBase[] capturedVars = new RuntimeBase[numCaptures]; - for (int i = 0; i < numCaptures; i++) { - int captureReg = bytecode[pc++]; - capturedVars[i] = registers[captureReg]; - } - - // Create a new InterpretedCode with the captured variables - InterpretedCode closureCode = new InterpretedCode( - template.bytecode, - template.constants, - template.stringPool, - template.maxRegisters, - capturedVars, // The captured variables! - template.sourceName, - template.sourceLine, - template.pcToTokenIndex, - template.variableRegistry // Preserve variable registry - ); - - // Wrap in RuntimeScalar - registers[rd] = new RuntimeScalar((RuntimeCode) closureCode); + pc = OpcodeHandlerExtended.executeCreateClosure(bytecode, pc, registers, code); break; - } case Opcodes.SET_SCALAR: { // Set scalar value: registers[rd] = registers[rs] @@ -551,49 +522,23 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // ITERATOR OPERATIONS - For efficient foreach loops // ================================================================= - case Opcodes.ITERATOR_CREATE: { + case Opcodes.ITERATOR_CREATE: // Create iterator: rd = rs.iterator() - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase iterable = registers[rs]; - java.util.Iterator iterator = iterable.iterator(); - - // Store iterator as a constant (we need to preserve the Iterator object) - // Wrap in RuntimeScalar for storage - registers[rd] = new RuntimeScalar(iterator); + // Format: ITERATOR_CREATE rd rs + pc = OpcodeHandlerExtended.executeIteratorCreate(bytecode, pc, registers); break; - } - case Opcodes.ITERATOR_HAS_NEXT: { + case Opcodes.ITERATOR_HAS_NEXT: // Check iterator: rd = iterator.hasNext() - int rd = bytecode[pc++]; - int iterReg = bytecode[pc++]; - - RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; - @SuppressWarnings("unchecked") - java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; - - boolean hasNext = iterator.hasNext(); - registers[rd] = hasNext ? RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + // Format: ITERATOR_HAS_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorHasNext(bytecode, pc, registers); break; - } - case Opcodes.ITERATOR_NEXT: { + case Opcodes.ITERATOR_NEXT: // Get next element: rd = iterator.next() - int rd = bytecode[pc++]; - int iterReg = bytecode[pc++]; - - RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; - @SuppressWarnings("unchecked") - java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; - - RuntimeScalar next = iterator.next(); - registers[rd] = next; + // Format: ITERATOR_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorNext(bytecode, pc, registers); break; - } case Opcodes.FOREACH_NEXT_OR_EXIT: { // Superinstruction for foreach loops @@ -626,139 +571,65 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // COMPOUND ASSIGNMENT OPERATORS (with overload support) // ================================================================= - case Opcodes.SUBTRACT_ASSIGN: { - // Compound assignment: rd = rd -= rs (checks for (-= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.subtractAssign(s1, s2); + case Opcodes.SUBTRACT_ASSIGN: + // Compound assignment: rd -= rs + // Format: SUBTRACT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeSubtractAssign(bytecode, pc, registers); break; - } - case Opcodes.MULTIPLY_ASSIGN: { - // Compound assignment: rd = rd *= rs (checks for (*= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.multiplyAssign(s1, s2); + case Opcodes.MULTIPLY_ASSIGN: + // Compound assignment: rd *= rs + // Format: MULTIPLY_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeMultiplyAssign(bytecode, pc, registers); break; - } - - case Opcodes.DIVIDE_ASSIGN: { - // Compound assignment: rd = rd /= rs (checks for (/= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.divideAssign(s1, s2); + case Opcodes.DIVIDE_ASSIGN: + // Compound assignment: rd /= rs + // Format: DIVIDE_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeDivideAssign(bytecode, pc, registers); break; - } - - case Opcodes.MODULUS_ASSIGN: { - // Compound assignment: rd = rd %= rs (checks for (%= overload first) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.modulusAssign(s1, s2); + case Opcodes.MODULUS_ASSIGN: + // Compound assignment: rd %= rs + // Format: MODULUS_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeModulusAssign(bytecode, pc, registers); break; - } - case Opcodes.REPEAT_ASSIGN: { + case Opcodes.REPEAT_ASSIGN: // Compound assignment: rd x= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase result = Operator.repeat( - registers[rd], - (RuntimeScalar) registers[rs], - 1 // scalar context - ); - ((RuntimeScalar) registers[rd]).set((RuntimeScalar) result); + // Format: REPEAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeRepeatAssign(bytecode, pc, registers); break; - } - case Opcodes.POW_ASSIGN: { + case Opcodes.POW_ASSIGN: // Compound assignment: rd **= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase val1 = registers[rd]; - RuntimeBase val2 = registers[rs]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - RuntimeScalar result = MathOperators.pow(s1, s2); - ((RuntimeScalar) registers[rd]).set(result); + // Format: POW_ASSIGN rd rs + pc = OpcodeHandlerExtended.executePowAssign(bytecode, pc, registers); break; - } - case Opcodes.LEFT_SHIFT_ASSIGN: { + case Opcodes.LEFT_SHIFT_ASSIGN: // Compound assignment: rd <<= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - RuntimeScalar result = BitwiseOperators.shiftLeft(s1, s2); - s1.set(result); + // Format: LEFT_SHIFT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLeftShiftAssign(bytecode, pc, registers); break; - } - case Opcodes.RIGHT_SHIFT_ASSIGN: { + case Opcodes.RIGHT_SHIFT_ASSIGN: // Compound assignment: rd >>= rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - RuntimeScalar result = BitwiseOperators.shiftRight(s1, s2); - s1.set(result); + // Format: RIGHT_SHIFT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeRightShiftAssign(bytecode, pc, registers); break; - } - case Opcodes.LOGICAL_AND_ASSIGN: { - // Compound assignment: rd &&= rs (short-circuit: only evaluate rs if rd is true) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); - if (!s1.getBoolean()) { - // Left side is false, result is left side (no assignment needed) - break; - } - // Left side is true, assign right side - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); - ((RuntimeScalar) registers[rd]).set(s2); + case Opcodes.LOGICAL_AND_ASSIGN: + // Compound assignment: rd &&= rs (short-circuit) + // Format: LOGICAL_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalAndAssign(bytecode, pc, registers); break; - } - case Opcodes.LOGICAL_OR_ASSIGN: { - // Compound assignment: rd ||= rs (short-circuit: only evaluate rs if rd is false) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); - if (s1.getBoolean()) { - // Left side is true, result is left side (no assignment needed) - break; - } - // Left side is false, assign right side - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); - ((RuntimeScalar) registers[rd]).set(s2); + case Opcodes.LOGICAL_OR_ASSIGN: + // Compound assignment: rd ||= rs (short-circuit) + // Format: LOGICAL_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalOrAssign(bytecode, pc, registers); break; - } // ================================================================= // SHIFT OPERATIONS @@ -1122,77 +993,17 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // MISCELLANEOUS // ================================================================= - case Opcodes.PRINT: { + case Opcodes.PRINT: // Print to filehandle - // Format: [PRINT] [rs_content] [rs_filehandle] - int contentReg = bytecode[pc++]; - int filehandleReg = bytecode[pc++]; - - Object val = registers[contentReg]; - - // Filehandle should be scalar - convert if needed - RuntimeBase fhBase = registers[filehandleReg]; - RuntimeScalar fh = (fhBase instanceof RuntimeScalar) - ? (RuntimeScalar) fhBase - : fhBase.scalar(); - - RuntimeList list; - if (val instanceof RuntimeList) { - list = (RuntimeList) val; - } else if (val instanceof RuntimeArray) { - // Convert RuntimeArray to RuntimeList - list = new RuntimeList(); - for (RuntimeScalar elem : (RuntimeArray) val) { - list.add(elem); - } - } else if (val instanceof RuntimeScalar) { - // Convert scalar to single-element list - list = new RuntimeList(); - list.add((RuntimeScalar) val); - } else { - list = new RuntimeList(); - } - - // Call IOOperator.print() - org.perlonjava.operators.IOOperator.print(list, fh); + // Format: PRINT contentReg filehandleReg + pc = OpcodeHandlerExtended.executePrint(bytecode, pc, registers); break; - } - case Opcodes.SAY: { + case Opcodes.SAY: // Say to filehandle - // Format: [SAY] [rs_content] [rs_filehandle] - int contentReg = bytecode[pc++]; - int filehandleReg = bytecode[pc++]; - - Object val = registers[contentReg]; - - // Filehandle should be scalar - convert if needed - RuntimeBase fhBase = registers[filehandleReg]; - RuntimeScalar fh = (fhBase instanceof RuntimeScalar) - ? (RuntimeScalar) fhBase - : fhBase.scalar(); - - RuntimeList list; - if (val instanceof RuntimeList) { - list = (RuntimeList) val; - } else if (val instanceof RuntimeArray) { - // Convert RuntimeArray to RuntimeList - list = new RuntimeList(); - for (RuntimeScalar elem : (RuntimeArray) val) { - list.add(elem); - } - } else if (val instanceof RuntimeScalar) { - // Convert scalar to single-element list - list = new RuntimeList(); - list.add((RuntimeScalar) val); - } else { - list = new RuntimeList(); - } - - // Call IOOperator.say() - org.perlonjava.operators.IOOperator.say(list, fh); + // Format: SAY contentReg filehandleReg + pc = OpcodeHandlerExtended.executeSay(bytecode, pc, registers); break; - } // ================================================================= // SUPERINSTRUCTIONS - Eliminate MOVE overhead @@ -1233,387 +1044,135 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.STRING_CONCAT_ASSIGN: { - // String concatenation and assign: rd .= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = StringOperators.stringConcat( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_CONCAT_ASSIGN: + // String concatenation and assign: rd .= rs + // Format: STRING_CONCAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringConcatAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_AND_ASSIGN: { - // Bitwise AND assignment: rd &= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseAndBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.BITWISE_AND_ASSIGN: + // Bitwise AND assignment: rd &= rs + // Format: BITWISE_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseAndAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_OR_ASSIGN: { - // Bitwise OR assignment: rd |= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseOrBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.BITWISE_OR_ASSIGN: + // Bitwise OR assignment: rd |= rs + // Format: BITWISE_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseOrAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_XOR_ASSIGN: { - // Bitwise XOR assignment: rd ^= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseXorBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.BITWISE_XOR_ASSIGN: + // Bitwise XOR assignment: rd ^= rs + // Format: BITWISE_XOR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseXorAssign(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_AND_ASSIGN: { - // String bitwise AND assignment: rd &.= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseAndDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_BITWISE_AND_ASSIGN: + // String bitwise AND assignment: rd &.= rs + // Format: STRING_BITWISE_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringBitwiseAndAssign(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_OR_ASSIGN: { - // String bitwise OR assignment: rd |.= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseOrDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_BITWISE_OR_ASSIGN: + // String bitwise OR assignment: rd |.= rs + // Format: STRING_BITWISE_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringBitwiseOrAssign(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_XOR_ASSIGN: { - // String bitwise XOR assignment: rd ^.= rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar result = org.perlonjava.operators.BitwiseOperators.bitwiseXorDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - ((RuntimeScalar) registers[rd]).set(result); + case Opcodes.STRING_BITWISE_XOR_ASSIGN: + // String bitwise XOR assignment: rd ^.= rs + // Format: STRING_BITWISE_XOR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringBitwiseXorAssign(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_AND_BINARY: { + case Opcodes.BITWISE_AND_BINARY: // Numeric bitwise AND: rd = rs1 binary& rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseAndBinary( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: BITWISE_AND_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseAndBinary(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_OR_BINARY: { + case Opcodes.BITWISE_OR_BINARY: // Numeric bitwise OR: rd = rs1 binary| rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseOrBinary( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: BITWISE_OR_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseOrBinary(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_XOR_BINARY: { + case Opcodes.BITWISE_XOR_BINARY: // Numeric bitwise XOR: rd = rs1 binary^ rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseXorBinary( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: BITWISE_XOR_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseXorBinary(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_AND: { + case Opcodes.STRING_BITWISE_AND: // String bitwise AND: rd = rs1 &. rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseAndDot( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: STRING_BITWISE_AND rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseAnd(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_OR: { + case Opcodes.STRING_BITWISE_OR: // String bitwise OR: rd = rs1 |. rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseOrDot( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: STRING_BITWISE_OR rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseOr(bytecode, pc, registers); break; - } - case Opcodes.STRING_BITWISE_XOR: { + case Opcodes.STRING_BITWISE_XOR: // String bitwise XOR: rd = rs1 ^. rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseXorDot( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); + // Format: STRING_BITWISE_XOR rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseXor(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_NOT_BINARY: { + case Opcodes.BITWISE_NOT_BINARY: // Numeric bitwise NOT: rd = binary~ rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseNotBinary( - (RuntimeScalar) registers[rs] - ); + // Format: BITWISE_NOT_BINARY rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotBinary(bytecode, pc, registers); break; - } - case Opcodes.BITWISE_NOT_STRING: { + case Opcodes.BITWISE_NOT_STRING: // String bitwise NOT: rd = ~. rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.BitwiseOperators.bitwiseNotDot( - (RuntimeScalar) registers[rs] - ); + // Format: BITWISE_NOT_STRING rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotString(bytecode, pc, registers); break; - } // File test and stat operations - case Opcodes.STAT: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.Stat.stat((RuntimeScalar) registers[rs], ctx); - break; - } - - case Opcodes.LSTAT: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.Stat.lstat((RuntimeScalar) registers[rs], ctx); - break; - } - - case Opcodes.FILETEST_R: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-r", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_W: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-w", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_X: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-x", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_O: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-o", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_R_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-R", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_W_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-W", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_X_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-X", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_O_REAL: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-O", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_E: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-e", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_Z: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-z", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_S: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-s", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_F: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-f", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_D: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-d", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_L: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-l", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_P: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-p", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_S_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-S", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_B: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-b", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_C: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-c", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_T: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-t", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_U: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-u", (RuntimeScalar) registers[rs]); + case Opcodes.STAT: + pc = OpcodeHandlerExtended.executeStat(bytecode, pc, registers); + break; + + case Opcodes.LSTAT: + pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers); + break; + + // File test operations (opcodes 190-216) - delegated to handler + case Opcodes.FILETEST_R: + case Opcodes.FILETEST_W: + case Opcodes.FILETEST_X: + case Opcodes.FILETEST_O: + case Opcodes.FILETEST_R_REAL: + case Opcodes.FILETEST_W_REAL: + case Opcodes.FILETEST_X_REAL: + case Opcodes.FILETEST_O_REAL: + case Opcodes.FILETEST_E: + case Opcodes.FILETEST_Z: + case Opcodes.FILETEST_S: + case Opcodes.FILETEST_F: + case Opcodes.FILETEST_D: + case Opcodes.FILETEST_L: + case Opcodes.FILETEST_P: + case Opcodes.FILETEST_S_UPPER: + case Opcodes.FILETEST_B: + case Opcodes.FILETEST_C: + case Opcodes.FILETEST_T: + case Opcodes.FILETEST_U: + case Opcodes.FILETEST_G: + case Opcodes.FILETEST_K: + case Opcodes.FILETEST_T_UPPER: + case Opcodes.FILETEST_B_UPPER: + case Opcodes.FILETEST_M: + case Opcodes.FILETEST_A: + case Opcodes.FILETEST_C_UPPER: + pc = OpcodeHandlerFileTest.executeFileTest(bytecode, pc, registers, opcode); break; - } - - case Opcodes.FILETEST_G: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-g", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_K: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-k", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_T_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-T", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_B_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-B", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_M: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-M", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_A: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-A", (RuntimeScalar) registers[rs]); - break; - } - - case Opcodes.FILETEST_C_UPPER: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.FileTestOperator.fileTest("-C", (RuntimeScalar) registers[rs]); - break; - } case Opcodes.PUSH_LOCAL_VARIABLE: { // Push variable to local stack: DynamicVariableManager.pushLocalVariable(rs) @@ -1630,150 +1189,89 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } - case Opcodes.OPEN: { + case Opcodes.OPEN: // Open file: rd = IOOperator.open(ctx, args...) - int rd = bytecode[pc++]; - int ctx = bytecode[pc++]; - int argsReg = bytecode[pc++]; - RuntimeArray argsArray = (RuntimeArray) registers[argsReg]; - RuntimeBase[] argsVarargs = argsArray.elements.toArray(new RuntimeBase[0]); - registers[rd] = org.perlonjava.operators.IOOperator.open(ctx, argsVarargs); + // Format: OPEN rd ctx argsReg + pc = OpcodeHandlerExtended.executeOpen(bytecode, pc, registers); break; - } - case Opcodes.READLINE: { - // Read line from filehandle: rd = Readline.readline(fh_ref, ctx) - int rd = bytecode[pc++]; - int fhReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.Readline.readline( - (RuntimeScalar) registers[fhReg], ctx - ); + case Opcodes.READLINE: + // Read line from filehandle + // Format: READLINE rd fhReg ctx + pc = OpcodeHandlerExtended.executeReadline(bytecode, pc, registers); break; - } - case Opcodes.MATCH_REGEX: { - // Match regex: rd = RuntimeRegex.matchRegex(quotedRegex, string, ctx) - int rd = bytecode[pc++]; - int stringReg = bytecode[pc++]; - int regexReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = org.perlonjava.regex.RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], // quotedRegex first - (RuntimeScalar) registers[stringReg], // string second - ctx - ); + case Opcodes.MATCH_REGEX: + // Match regex + // Format: MATCH_REGEX rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegex(bytecode, pc, registers); break; - } - case Opcodes.MATCH_REGEX_NOT: { - // Negated regex match: rd = !RuntimeRegex.matchRegex(quotedRegex, string, ctx) - int rd = bytecode[pc++]; - int stringReg = bytecode[pc++]; - int regexReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - RuntimeBase matchResult = org.perlonjava.regex.RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], // quotedRegex first - (RuntimeScalar) registers[stringReg], // string second - ctx - ); - // Negate the boolean result - registers[rd] = new RuntimeScalar(matchResult.scalar().getBoolean() ? 0 : 1); + case Opcodes.MATCH_REGEX_NOT: + // Negated regex match + // Format: MATCH_REGEX_NOT rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegexNot(bytecode, pc, registers); break; - } - case Opcodes.CHOMP: { + case Opcodes.CHOMP: // Chomp: rd = rs.chomp() - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = registers[rs].chomp(); + // Format: CHOMP rd rs + pc = OpcodeHandlerExtended.executeChomp(bytecode, pc, registers); break; - } - case Opcodes.WANTARRAY: { - // Get wantarray context: rd = Operator.wantarray(wantarrayReg) - int rd = bytecode[pc++]; - int wantarrayReg = bytecode[pc++]; - int ctx = ((RuntimeScalar) registers[wantarrayReg]).getInt(); - registers[rd] = org.perlonjava.operators.Operator.wantarray(ctx); + case Opcodes.WANTARRAY: + // Get wantarray context + // Format: WANTARRAY rd wantarrayReg + pc = OpcodeHandlerExtended.executeWantarray(bytecode, pc, registers); break; - } - case Opcodes.REQUIRE: { - // Require module or version: rd = ModuleOperators.require(rs) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.ModuleOperators.require((RuntimeScalar) registers[rs]); + case Opcodes.REQUIRE: + // Require module or version + // Format: REQUIRE rd rs + pc = OpcodeHandlerExtended.executeRequire(bytecode, pc, registers); break; - } - case Opcodes.POS: { - // Get regex position: rd = rs.pos() - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = ((RuntimeScalar) registers[rs]).pos(); + case Opcodes.POS: + // Get regex position + // Format: POS rd rs + pc = OpcodeHandlerExtended.executePos(bytecode, pc, registers); break; - } - case Opcodes.INDEX: { - // Find substring position: rd = StringOperators.index(str, substr, pos) - int rd = bytecode[pc++]; - int strReg = bytecode[pc++]; - int substrReg = bytecode[pc++]; - int posReg = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.StringOperators.index( - (RuntimeScalar) registers[strReg], - (RuntimeScalar) registers[substrReg], - (RuntimeScalar) registers[posReg] - ); + case Opcodes.INDEX: + // Find substring position + // Format: INDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeIndex(bytecode, pc, registers); break; - } - case Opcodes.RINDEX: { - // Find substring position from end: rd = StringOperators.rindex(str, substr, pos) - int rd = bytecode[pc++]; - int strReg = bytecode[pc++]; - int substrReg = bytecode[pc++]; - int posReg = bytecode[pc++]; - registers[rd] = org.perlonjava.operators.StringOperators.rindex( - (RuntimeScalar) registers[strReg], - (RuntimeScalar) registers[substrReg], - (RuntimeScalar) registers[posReg] - ); + case Opcodes.RINDEX: + // Find substring position from end + // Format: RINDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeRindex(bytecode, pc, registers); break; - } - case Opcodes.PRE_AUTOINCREMENT: { + case Opcodes.PRE_AUTOINCREMENT: // Pre-increment: ++rd - int rd = bytecode[pc++]; - ((RuntimeScalar) registers[rd]).preAutoIncrement(); + // Format: PRE_AUTOINCREMENT rd + pc = OpcodeHandlerExtended.executePreAutoIncrement(bytecode, pc, registers); break; - } - case Opcodes.POST_AUTOINCREMENT: { + case Opcodes.POST_AUTOINCREMENT: // Post-increment: rd = rs++ - // The postAutoIncrement() method increments the variable and returns the OLD value - int rd = bytecode[pc++]; // Destination register for old value - int rs = bytecode[pc++]; // Source variable register - registers[rd] = ((RuntimeScalar) registers[rs]).postAutoIncrement(); + // Format: POST_AUTOINCREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoIncrement(bytecode, pc, registers); break; - } - case Opcodes.PRE_AUTODECREMENT: { + case Opcodes.PRE_AUTODECREMENT: // Pre-decrement: --rd - int rd = bytecode[pc++]; - ((RuntimeScalar) registers[rd]).preAutoDecrement(); + // Format: PRE_AUTODECREMENT rd + pc = OpcodeHandlerExtended.executePreAutoDecrement(bytecode, pc, registers); break; - } - case Opcodes.POST_AUTODECREMENT: { + case Opcodes.POST_AUTODECREMENT: // Post-decrement: rd = rs-- - // The postAutoDecrement() method decrements the variable and returns the OLD value - int rd = bytecode[pc++]; // Destination register for old value - int rs = bytecode[pc++]; // Source variable register - registers[rd] = ((RuntimeScalar) registers[rs]).postAutoDecrement(); + // Format: POST_AUTODECREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoDecrement(bytecode, pc, registers); break; - } // ================================================================= // ERROR HANDLING @@ -2075,7 +1573,13 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c RuntimeList list = listBase.getList(); RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; RuntimeList result = org.perlonjava.operators.ListOperators.grep(list, closure, ctx); - registers[rd] = result; + + // In scalar context, return the count of elements + if (ctx == RuntimeContextType.SCALAR) { + registers[rd] = new RuntimeScalar(result.elements.size()); + } else { + registers[rd] = result; + } break; } @@ -2337,6 +1841,30 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.SPRINTF: + // sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) + // Format: SPRINTF rd formatReg argsListReg + pc = OpcodeHandlerExtended.executeSprintf(bytecode, pc, registers); + break; + + case Opcodes.CHOP: + // chop($x): rd = StringOperators.chopScalar(scalarReg) + // Format: CHOP rd scalarReg + pc = OpcodeHandlerExtended.executeChop(bytecode, pc, registers); + break; + + case Opcodes.GET_REPLACEMENT_REGEX: + // Get replacement regex: rd = RuntimeRegex.getReplacementRegex(pattern, replacement, flags) + // Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg + pc = OpcodeHandlerExtended.executeGetReplacementRegex(bytecode, pc, registers); + break; + + case Opcodes.SUBSTR_VAR: + // substr with variable args: rd = Operator.substr(ctx, args...) + // Format: SUBSTR_VAR rd argsListReg ctx + pc = OpcodeHandlerExtended.executeSubstrVar(bytecode, pc, registers); + break; + // GENERATED_HANDLERS_END default: diff --git a/src/main/java/org/perlonjava/interpreter/CompileAssignment.java b/src/main/java/org/perlonjava/interpreter/CompileAssignment.java index acbcef366..5905797f1 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileAssignment.java +++ b/src/main/java/org/perlonjava/interpreter/CompileAssignment.java @@ -745,7 +745,17 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = arrayReg; + // In scalar context, return the array size; in list context, return the array + if (savedContext == RuntimeContextType.SCALAR) { + // Convert array to scalar (returns size) + int sizeReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(sizeReg); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.lastResultReg = sizeReg; + } else { + bytecodeCompiler.lastResultReg = arrayReg; + } } else if (leftOp.operator.equals("%") && leftOp.operand instanceof IdentifierNode) { // Hash assignment: %hash = ... String varName = "%" + ((IdentifierNode) leftOp.operand).name; @@ -769,7 +779,17 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(hashReg); bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = hashReg; + // In scalar context, return the hash size; in list context, return the hash + if (savedContext == RuntimeContextType.SCALAR) { + // Convert hash to scalar (returns bucket info like "3/8") + int sizeReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(sizeReg); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.lastResultReg = sizeReg; + } else { + bytecodeCompiler.lastResultReg = hashReg; + } } else if (leftOp.operator.equals("our")) { // Assignment to our variable: our $x = value or our @x = value or our %x = value // Compile the our declaration first (which loads the global into a register) @@ -889,6 +909,40 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = valueReg; + } else if (leftOp.operator.equals("@") && leftOp.operand instanceof OperatorNode) { + // Array dereference assignment: @$r = ... + // The operand should be a scalar variable containing an array reference + OperatorNode derefOp = (OperatorNode) leftOp.operand; + + if (derefOp.operator.equals("$")) { + // Compile the scalar to get the array reference + derefOp.accept(bytecodeCompiler); + int scalarRefReg = bytecodeCompiler.lastResultReg; + + // Dereference to get the actual array + int arrayReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(scalarRefReg); + + // Assign the value to the dereferenced array + bytecodeCompiler.emit(Opcodes.ARRAY_SET_FROM_LIST); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(valueReg); + + // In scalar context, return array size; in list context, return the array + if (savedContext == RuntimeContextType.SCALAR) { + int sizeReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(sizeReg); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.lastResultReg = sizeReg; + } else { + bytecodeCompiler.lastResultReg = arrayReg; + } + } else { + bytecodeCompiler.throwCompilerException("Assignment to unsupported array dereference"); + } } else { bytecodeCompiler.throwCompilerException("Assignment to unsupported operator: " + leftOp.operator); } diff --git a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java index 10fa9a1a7..13f936bfa 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperator.java @@ -37,6 +37,54 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato return; } + // Handle sprintf early (special handling for list of arguments) + if (node.operator.equals("sprintf")) { + // sprintf $format, @args + // left = format string + // right = ListNode of arguments + + // Compile the format string (left operand) + node.left.accept(bytecodeCompiler); + int formatReg = bytecodeCompiler.lastResultReg; + + // Compile the arguments (right operand) into a list + int argsListReg = bytecodeCompiler.allocateRegister(); + if (node.right instanceof ListNode) { + ListNode argsList = (ListNode) node.right; + // Compile each argument + java.util.List argRegs = new java.util.ArrayList<>(); + for (Node arg : argsList.elements) { + arg.accept(bytecodeCompiler); + argRegs.add(bytecodeCompiler.lastResultReg); + } + // Create list with arguments: CREATE_LIST rd count [regs...] + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(argRegs.size()); // emit count + for (int argReg : argRegs) { + bytecodeCompiler.emitReg(argReg); + } + } else { + // Single argument - wrap in list + node.right.accept(bytecodeCompiler); + int argReg = bytecodeCompiler.lastResultReg; + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(1); // emit count = 1 + bytecodeCompiler.emitReg(argReg); + } + + // Call sprintf + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.SPRINTF); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(formatReg); + bytecodeCompiler.emitReg(argsListReg); + + bytecodeCompiler.lastResultReg = rd; + return; + } + // Handle compound assignment operators (+=, -=, *=, /=, %=, .=, &=, |=, ^=, &.=, |.=, ^.=, binary&=, binary|=, binary^=, x=, **=, <<=, >>=, &&=, ||=) if (node.operator.equals("+=") || node.operator.equals("-=") || node.operator.equals("*=") || node.operator.equals("/=") || @@ -485,6 +533,51 @@ else if (node.right instanceof BinaryOperatorNode) { return; } + // Handle =~ and !~ binding with regex operators + // When we have: $string =~ s/pattern/replacement/flags + // The right side is: OperatorNode(replaceRegex, ListNode[pattern, replacement, flags]) + // We need to add $string to the operand list and compile the operator + if ((node.operator.equals("=~") || node.operator.equals("!~")) + && node.right instanceof OperatorNode) { + OperatorNode rightOp = (OperatorNode) node.right; + if (rightOp.operand instanceof ListNode + && !rightOp.operator.equals("quoteRegex")) { + // Check if it's a regex operator (replaceRegex, matchRegex, tr, transliterate) + if (rightOp.operator.equals("replaceRegex") + || rightOp.operator.equals("matchRegex") + || rightOp.operator.equals("tr") + || rightOp.operator.equals("transliterate")) { + + // Create a copy of the operand list and add the left side (string) + ListNode originalList = (ListNode) rightOp.operand; + ListNode boundList = new ListNode(new java.util.ArrayList<>(originalList.elements), originalList.tokenIndex); + boundList.elements.add(node.left); + + // Create a new OperatorNode with the modified operand list + OperatorNode boundOp = new OperatorNode(rightOp.operator, boundList, rightOp.tokenIndex); + + // For !~, we need to negate the result + if (node.operator.equals("!~")) { + // Compile the bound operator + boundOp.accept(bytecodeCompiler); + int matchReg = bytecodeCompiler.lastResultReg; + + // Negate the result + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.NOT); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(matchReg); + bytecodeCompiler.lastResultReg = rd; + } else { + // For =~, just compile the bound operator + boundOp.accept(bytecodeCompiler); + } + + return; + } + } + } + // Compile left and right operands (for non-short-circuit operators) node.left.accept(bytecodeCompiler); int rs1 = bytecodeCompiler.lastResultReg; diff --git a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java index a146e6f5c..944f7095e 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java +++ b/src/main/java/org/perlonjava/interpreter/CompileBinaryOperatorHelper.java @@ -255,7 +255,7 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(rs2); // List register bytecodeCompiler.emitReg(rs1); // Closure register - bytecodeCompiler.emit(RuntimeContextType.LIST); // Grep uses list context + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); // Use current context } case "sort" -> { // Sort operator: sort { block } list diff --git a/src/main/java/org/perlonjava/interpreter/CompileOperator.java b/src/main/java/org/perlonjava/interpreter/CompileOperator.java index ae7626c8c..e946740bb 100644 --- a/src/main/java/org/perlonjava/interpreter/CompileOperator.java +++ b/src/main/java/org/perlonjava/interpreter/CompileOperator.java @@ -1496,8 +1496,14 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("keys requires a hash argument"); } - // Compile the hash operand - node.operand.accept(bytecodeCompiler); + // Compile the hash operand in LIST context (to avoid scalar conversion) + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; + try { + node.operand.accept(bytecodeCompiler); + } finally { + bytecodeCompiler.currentCallContext = savedContext; + } int hashReg = bytecodeCompiler.lastResultReg; // Emit HASH_KEYS @@ -1506,6 +1512,45 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); + // keys() returns a list in list context, scalar count in scalar context + // The RuntimeHash.keys() method returns a RuntimeList + // In scalar context, convert to scalar (count) + if (savedContext == RuntimeContextType.SCALAR) { + int scalarReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(scalarReg); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.lastResultReg = scalarReg; + } else { + bytecodeCompiler.lastResultReg = rd; + } + } else if (op.equals("chop")) { + // chop $x - remove last character, modifies argument in place + // operand: ListNode containing scalar variable reference + if (node.operand == null) { + bytecodeCompiler.throwCompilerException("chop requires an argument"); + } + + // Extract the actual operand from ListNode if needed + Node actualOperand = node.operand; + if (actualOperand instanceof ListNode) { + ListNode list = (ListNode) actualOperand; + if (list.elements.isEmpty()) { + bytecodeCompiler.throwCompilerException("chop requires an argument"); + } + actualOperand = list.elements.get(0); + } + + // Compile the operand (should be an lvalue) + actualOperand.accept(bytecodeCompiler); + int scalarReg = bytecodeCompiler.lastResultReg; + + // Call chopScalar and store result back + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.CHOP); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(scalarReg); + bytecodeCompiler.lastResultReg = rd; } else if (op.equals("values")) { // values %hash @@ -1514,8 +1559,14 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("values requires a hash argument"); } - // Compile the hash operand - node.operand.accept(bytecodeCompiler); + // Compile the hash operand in LIST context (to avoid scalar conversion) + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.LIST; + try { + node.operand.accept(bytecodeCompiler); + } finally { + bytecodeCompiler.currentCallContext = savedContext; + } int hashReg = bytecodeCompiler.lastResultReg; // Emit HASH_VALUES @@ -1524,7 +1575,18 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(hashReg); - bytecodeCompiler.lastResultReg = rd; + // values() returns a list in list context, scalar count in scalar context + // The RuntimeHash.values() method returns a RuntimeList + // In scalar context, convert to scalar (count) + if (savedContext == RuntimeContextType.SCALAR) { + int scalarReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.ARRAY_SIZE); + bytecodeCompiler.emitReg(scalarReg); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.lastResultReg = scalarReg; + } else { + bytecodeCompiler.lastResultReg = rd; + } } else if (op.equals("$#")) { // $#array - get last index of array (size - 1) // operand: array variable (OperatorNode("@" ...) or IdentifierNode) @@ -1668,8 +1730,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else if (op.equals("matchRegex")) { - // m/pattern/flags - create a regex and return it (for use with =~) - // operand: ListNode containing pattern string and flags string + // m/pattern/flags - create a regex and optionally match against a string + // operand: ListNode containing pattern, flags, and optionally string (from =~ binding) if (node.operand == null || !(node.operand instanceof ListNode)) { bytecodeCompiler.throwCompilerException("matchRegex requires pattern and flags"); } @@ -1688,12 +1750,127 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int flagsReg = bytecodeCompiler.lastResultReg; // Create quoted regex using QUOTE_REGEX opcode - int rd = bytecodeCompiler.allocateRegister(); + int regexReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.QUOTE_REGEX); - bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(regexReg); + bytecodeCompiler.emitReg(patternReg); + bytecodeCompiler.emitReg(flagsReg); + + // Check if a string was provided (from =~ binding) + if (args.elements.size() > 2) { + // String provided - perform the match + args.elements.get(2).accept(bytecodeCompiler); + int stringReg = bytecodeCompiler.lastResultReg; + + // Call MATCH_REGEX to perform the match + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.MATCH_REGEX); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emitReg(regexReg); + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + + bytecodeCompiler.lastResultReg = rd; + } else { + // No string provided - just return the regex object + bytecodeCompiler.lastResultReg = regexReg; + } + } else if (op.equals("replaceRegex")) { + // s/pattern/replacement/flags - regex substitution + // operand: ListNode containing [pattern, replacement, flags] or [pattern, replacement, flags, string] + if (node.operand == null || !(node.operand instanceof ListNode)) { + bytecodeCompiler.throwCompilerException("replaceRegex requires pattern, replacement, and flags"); + } + + ListNode args = (ListNode) node.operand; + if (args.elements.size() < 3) { + bytecodeCompiler.throwCompilerException("replaceRegex requires pattern, replacement, and flags"); + } + + // Compile pattern + args.elements.get(0).accept(bytecodeCompiler); + int patternReg = bytecodeCompiler.lastResultReg; + + // Compile replacement + args.elements.get(1).accept(bytecodeCompiler); + int replacementReg = bytecodeCompiler.lastResultReg; + + // Compile flags + args.elements.get(2).accept(bytecodeCompiler); + int flagsReg = bytecodeCompiler.lastResultReg; + + // Create replacement regex using GET_REPLACEMENT_REGEX opcode + int regexReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.GET_REPLACEMENT_REGEX); + bytecodeCompiler.emitReg(regexReg); bytecodeCompiler.emitReg(patternReg); + bytecodeCompiler.emitReg(replacementReg); bytecodeCompiler.emitReg(flagsReg); + // Get the string to operate on (element 3 if provided, else $_) + int stringReg; + if (args.elements.size() > 3) { + // String provided in operand list (from =~ binding) + args.elements.get(3).accept(bytecodeCompiler); + stringReg = bytecodeCompiler.lastResultReg; + } else { + // Use $_ as default + String varName = "$_"; + if (bytecodeCompiler.hasVariable(varName)) { + stringReg = bytecodeCompiler.getVariableRegister(varName); + } else { + stringReg = bytecodeCompiler.allocateRegister(); + int nameIdx = bytecodeCompiler.addToStringPool("main::_"); + bytecodeCompiler.emit(Opcodes.LOAD_GLOBAL_SCALAR); + bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emit(nameIdx); + } + } + + // Apply the regex match (which handles replacement) + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.MATCH_REGEX); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emitReg(regexReg); + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + + bytecodeCompiler.lastResultReg = rd; + } else if (op.equals("substr")) { + // substr($string, $offset, $length, $replacement) + // operand is a ListNode with 2-4 elements + if (node.operand == null || !(node.operand instanceof ListNode)) { + bytecodeCompiler.throwCompilerException("substr requires arguments"); + } + + ListNode args = (ListNode) node.operand; + if (args.elements.size() < 2) { + bytecodeCompiler.throwCompilerException("substr requires at least 2 arguments"); + } + + // Compile arguments + java.util.List argRegs = new java.util.ArrayList<>(); + for (Node arg : args.elements) { + arg.accept(bytecodeCompiler); + argRegs.add(bytecodeCompiler.lastResultReg); + } + + // Create list with arguments: CREATE_LIST rd count [regs...] + int argsListReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(argRegs.size()); // emit count + for (int argReg : argRegs) { + bytecodeCompiler.emitReg(argReg); + } + + // Call SUBSTR_VAR opcode + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.SUBSTR_VAR); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(argsListReg); + bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); + bytecodeCompiler.lastResultReg = rd; } else if (op.equals("chomp")) { // chomp($x) or chomp - remove trailing newlines @@ -1773,6 +1950,43 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(2); // Register 2 contains the calling context bytecodeCompiler.lastResultReg = rd; + } else if (op.equals("sprintf")) { + // sprintf($format, @args) - SprintfOperator.sprintf + if (node.operand instanceof ListNode) { + ListNode list = (ListNode) node.operand; + if (list.elements.isEmpty()) { + bytecodeCompiler.throwCompilerException("sprintf requires a format argument"); + } + + // First argument is the format string + list.elements.get(0).accept(bytecodeCompiler); + int formatReg = bytecodeCompiler.lastResultReg; + + // Compile remaining arguments and collect their registers + java.util.List argRegs = new java.util.ArrayList<>(); + for (int i = 1; i < list.elements.size(); i++) { + list.elements.get(i).accept(bytecodeCompiler); + argRegs.add(bytecodeCompiler.lastResultReg); + } + + // Create a RuntimeList with the arguments + int listReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.CREATE_LIST); + bytecodeCompiler.emitReg(listReg); + for (int argReg : argRegs) { + bytecodeCompiler.emitReg(argReg); + } + + // Call sprintf with format and arg list + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.SPRINTF); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(formatReg); + bytecodeCompiler.emitReg(listReg); + bytecodeCompiler.lastResultReg = rd; + } else { + bytecodeCompiler.throwCompilerException("sprintf requires arguments"); + } // GENERATED_OPERATORS_START } else if (op.equals("int")) { // int($x) - MathOperators.integer diff --git a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java index e526d1be6..3f5eabada 100644 --- a/src/main/java/org/perlonjava/interpreter/InterpretedCode.java +++ b/src/main/java/org/perlonjava/interpreter/InterpretedCode.java @@ -714,6 +714,30 @@ public String disassemble() { int keyReg = bytecode[pc++]; sb.append("GLOB_SLOT_GET r").append(rd).append(" = r").append(globReg2).append("{r").append(keyReg).append("}\n"); break; + case Opcodes.SPRINTF: + rd = bytecode[pc++]; + int formatReg = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + sb.append("SPRINTF r").append(rd).append(" = sprintf(r").append(formatReg).append(", r").append(argsListReg).append(")\n"); + break; + case Opcodes.CHOP: + rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + sb.append("CHOP r").append(rd).append(" = chop(r").append(scalarReg).append(")\n"); + break; + case Opcodes.GET_REPLACEMENT_REGEX: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; // pattern + rs2 = bytecode[pc++]; // replacement + int rs3 = bytecode[pc++]; // flags + sb.append("GET_REPLACEMENT_REGEX r").append(rd).append(" = getReplacementRegex(r").append(rs1).append(", r").append(rs2).append(", r").append(rs3).append(")\n"); + break; + case Opcodes.SUBSTR_VAR: + rd = bytecode[pc++]; + int substrArgsReg = bytecode[pc++]; + int substrCtx = bytecode[pc++]; + sb.append("SUBSTR_VAR r").append(rd).append(" = substr(r").append(substrArgsReg).append(", ctx=").append(substrCtx).append(")\n"); + break; case Opcodes.PUSH_LOCAL_VARIABLE: rs = bytecode[pc++]; sb.append("PUSH_LOCAL_VARIABLE r").append(rs).append("\n"); diff --git a/src/main/java/org/perlonjava/interpreter/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerExtended.java new file mode 100644 index 000000000..72ac301d6 --- /dev/null +++ b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerExtended.java @@ -0,0 +1,921 @@ +package org.perlonjava.interpreter; + +import org.perlonjava.operators.*; +import org.perlonjava.regex.RuntimeRegex; +import org.perlonjava.runtime.*; + +/** + * Extended opcode handlers for recently added operations. + * + * Extracted from BytecodeInterpreter.execute() to reduce method size + * and keep it under the 8KB JIT compilation limit. + * + * Handles: SPRINTF, CHOP, GET_REPLACEMENT_REGEX, SUBSTR_VAR, and other + * less-frequently-used string/regex operations. + */ +public class OpcodeHandlerExtended { + + /** + * Execute sprintf operation. + * Format: SPRINTF rd formatReg argsListReg + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeSprintf(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int formatReg = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + + RuntimeScalar format = (RuntimeScalar) registers[formatReg]; + RuntimeList argsList = (RuntimeList) registers[argsListReg]; + + registers[rd] = SprintfOperator.sprintf(format, argsList); + return pc; + } + + /** + * Execute chop operation. + * Format: CHOP rd scalarReg + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeChop(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int scalarReg = bytecode[pc++]; + + RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; + registers[rd] = StringOperators.chopScalar(scalar); + return pc; + } + + /** + * Execute get replacement regex operation. + * Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeGetReplacementRegex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int patternReg = bytecode[pc++]; + int replacementReg = bytecode[pc++]; + int flagsReg = bytecode[pc++]; + + RuntimeScalar pattern = (RuntimeScalar) registers[patternReg]; + RuntimeScalar replacement = (RuntimeScalar) registers[replacementReg]; + RuntimeScalar flags = (RuntimeScalar) registers[flagsReg]; + + registers[rd] = RuntimeRegex.getReplacementRegex(pattern, replacement, flags); + return pc; + } + + /** + * Execute substr with variable arguments. + * Format: SUBSTR_VAR rd argsListReg ctx + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeSubstrVar(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int argsListReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + + RuntimeList argsList = (RuntimeList) registers[argsListReg]; + RuntimeBase[] substrArgs = argsList.elements.toArray(new RuntimeBase[0]); + + registers[rd] = Operator.substr(ctx, substrArgs); + return pc; + } + + /** + * Execute repeat assign operation. + * Format: REPEAT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeRepeatAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase result = Operator.repeat( + registers[rd], + (RuntimeScalar) registers[rs], + 1 // scalar context + ); + ((RuntimeScalar) registers[rd]).set((RuntimeScalar) result); + return pc; + } + + /** + * Execute power assign operation. + * Format: POW_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executePowAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + RuntimeScalar result = MathOperators.pow(s1, s2); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute left shift assign operation. + * Format: LEFT_SHIFT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeLeftShiftAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + RuntimeScalar s2 = (RuntimeScalar) registers[rs]; + RuntimeScalar result = BitwiseOperators.shiftLeft(s1, s2); + s1.set(result); + return pc; + } + + /** + * Execute right shift assign operation. + * Format: RIGHT_SHIFT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeRightShiftAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + RuntimeScalar s2 = (RuntimeScalar) registers[rs]; + RuntimeScalar result = BitwiseOperators.shiftRight(s1, s2); + s1.set(result); + return pc; + } + + /** + * Execute logical AND assign operation. + * Format: LOGICAL_AND_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeLogicalAndAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); + if (!s1.getBoolean()) { + // Left side is false, result is left side (no assignment needed) + return pc; + } + // Left side is true, assign right side + RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); + ((RuntimeScalar) registers[rd]).set(s2); + return pc; + } + + /** + * Execute logical OR assign operation. + * Format: LOGICAL_OR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeLogicalOrAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); + if (s1.getBoolean()) { + // Left side is true, result is left side (no assignment needed) + return pc; + } + // Left side is false, assign right side + RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); + ((RuntimeScalar) registers[rd]).set(s2); + return pc; + } + + /** + * Execute string concatenation assign operation. + * Format: STRING_CONCAT_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringConcatAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = StringOperators.stringConcat( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute bitwise AND assign operation. + * Format: BITWISE_AND_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeBitwiseAndAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseAndBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute bitwise OR assign operation. + * Format: BITWISE_OR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeBitwiseOrAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseOrBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute bitwise XOR assign operation. + * Format: BITWISE_XOR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeBitwiseXorAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseXorBinary( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute string bitwise AND assign operation. + * Format: STRING_BITWISE_AND_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringBitwiseAndAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseAndDot( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute string bitwise OR assign operation. + * Format: STRING_BITWISE_OR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringBitwiseOrAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseOrDot( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + /** + * Execute string bitwise XOR assign operation. + * Format: STRING_BITWISE_XOR_ASSIGN rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @return Updated program counter + */ + public static int executeStringBitwiseXorAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar result = BitwiseOperators.bitwiseXorDot( + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] + ); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + // Bitwise binary operators (non-assignment) + + /** + * Execute bitwise AND binary operation. + * Format: BITWISE_AND_BINARY rd rs1 rs2 + */ + public static int executeBitwiseAndBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseAndBinary( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute bitwise OR binary operation. + * Format: BITWISE_OR_BINARY rd rs1 rs2 + */ + public static int executeBitwiseOrBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseOrBinary( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute bitwise XOR binary operation. + * Format: BITWISE_XOR_BINARY rd rs1 rs2 + */ + public static int executeBitwiseXorBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseXorBinary( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute string bitwise AND operation. + * Format: STRING_BITWISE_AND rd rs1 rs2 + */ + public static int executeStringBitwiseAnd(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseAndDot( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute string bitwise OR operation. + * Format: STRING_BITWISE_OR rd rs1 rs2 + */ + public static int executeStringBitwiseOr(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseOrDot( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute string bitwise XOR operation. + * Format: STRING_BITWISE_XOR rd rs1 rs2 + */ + public static int executeStringBitwiseXor(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseXorDot( + (RuntimeScalar) registers[rs1], + (RuntimeScalar) registers[rs2] + ); + return pc; + } + + /** + * Execute bitwise NOT binary operation. + * Format: BITWISE_NOT_BINARY rd rs + */ + public static int executeBitwiseNotBinary(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseNotBinary((RuntimeScalar) registers[rs]); + return pc; + } + + /** + * Execute bitwise NOT string operation. + * Format: BITWISE_NOT_STRING rd rs + */ + public static int executeBitwiseNotString(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = BitwiseOperators.bitwiseNotDot((RuntimeScalar) registers[rs]); + return pc; + } + + /** + * Execute stat operation. + * Format: STAT rd rs ctx + */ + public static int executeStat(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Stat.stat((RuntimeScalar) registers[rs], ctx); + return pc; + } + + /** + * Execute lstat operation. + * Format: LSTAT rd rs ctx + */ + public static int executeLstat(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Stat.lstat((RuntimeScalar) registers[rs], ctx); + return pc; + } + + /** + * Execute print operation. + * Format: PRINT contentReg filehandleReg + */ + public static int executePrint(short[] bytecode, int pc, RuntimeBase[] registers) { + int contentReg = bytecode[pc++]; + int filehandleReg = bytecode[pc++]; + + Object val = registers[contentReg]; + + // Filehandle should be scalar - convert if needed + RuntimeBase fhBase = registers[filehandleReg]; + RuntimeScalar fh = (fhBase instanceof RuntimeScalar) + ? (RuntimeScalar) fhBase + : fhBase.scalar(); + + RuntimeList list; + if (val instanceof RuntimeList) { + list = (RuntimeList) val; + } else if (val instanceof RuntimeArray) { + // Convert RuntimeArray to RuntimeList + list = new RuntimeList(); + for (RuntimeScalar elem : (RuntimeArray) val) { + list.add(elem); + } + } else if (val instanceof RuntimeScalar) { + // Convert scalar to single-element list + list = new RuntimeList(); + list.add((RuntimeScalar) val); + } else { + list = new RuntimeList(); + } + + // Call IOOperator.print() + IOOperator.print(list, fh); + return pc; + } + + /** + * Execute say operation. + * Format: SAY contentReg filehandleReg + */ + public static int executeSay(short[] bytecode, int pc, RuntimeBase[] registers) { + int contentReg = bytecode[pc++]; + int filehandleReg = bytecode[pc++]; + + Object val = registers[contentReg]; + + // Filehandle should be scalar - convert if needed + RuntimeBase fhBase = registers[filehandleReg]; + RuntimeScalar fh = (fhBase instanceof RuntimeScalar) + ? (RuntimeScalar) fhBase + : fhBase.scalar(); + + RuntimeList list; + if (val instanceof RuntimeList) { + list = (RuntimeList) val; + } else if (val instanceof RuntimeArray) { + // Convert RuntimeArray to RuntimeList + list = new RuntimeList(); + for (RuntimeScalar elem : (RuntimeArray) val) { + list.add(elem); + } + } else if (val instanceof RuntimeScalar) { + // Convert scalar to single-element list + list = new RuntimeList(); + list.add((RuntimeScalar) val); + } else { + list = new RuntimeList(); + } + + // Call IOOperator.say() + IOOperator.say(list, fh); + return pc; + } + + /** + * Execute chomp operation. + * Format: CHOMP rd rs + */ + public static int executeChomp(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = registers[rs].chomp(); + return pc; + } + + /** + * Execute wantarray operation. + * Format: WANTARRAY rd wantarrayReg + */ + public static int executeWantarray(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int wantarrayReg = bytecode[pc++]; + int ctx = ((RuntimeScalar) registers[wantarrayReg]).getInt(); + registers[rd] = Operator.wantarray(ctx); + return pc; + } + + /** + * Execute require operation. + * Format: REQUIRE rd rs + */ + public static int executeRequire(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ModuleOperators.require((RuntimeScalar) registers[rs]); + return pc; + } + + /** + * Execute pos operation. + * Format: POS rd rs + */ + public static int executePos(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ((RuntimeScalar) registers[rs]).pos(); + return pc; + } + + /** + * Execute index operation. + * Format: INDEX rd strReg substrReg posReg + */ + public static int executeIndex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int strReg = bytecode[pc++]; + int substrReg = bytecode[pc++]; + int posReg = bytecode[pc++]; + registers[rd] = StringOperators.index( + (RuntimeScalar) registers[strReg], + (RuntimeScalar) registers[substrReg], + (RuntimeScalar) registers[posReg] + ); + return pc; + } + + /** + * Execute rindex operation. + * Format: RINDEX rd strReg substrReg posReg + */ + public static int executeRindex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int strReg = bytecode[pc++]; + int substrReg = bytecode[pc++]; + int posReg = bytecode[pc++]; + registers[rd] = StringOperators.rindex( + (RuntimeScalar) registers[strReg], + (RuntimeScalar) registers[substrReg], + (RuntimeScalar) registers[posReg] + ); + return pc; + } + + /** + * Execute pre-increment operation. + * Format: PRE_AUTOINCREMENT rd + */ + public static int executePreAutoIncrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + ((RuntimeScalar) registers[rd]).preAutoIncrement(); + return pc; + } + + /** + * Execute post-increment operation. + * Format: POST_AUTOINCREMENT rd rs + */ + public static int executePostAutoIncrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ((RuntimeScalar) registers[rs]).postAutoIncrement(); + return pc; + } + + /** + * Execute pre-decrement operation. + * Format: PRE_AUTODECREMENT rd + */ + public static int executePreAutoDecrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + ((RuntimeScalar) registers[rd]).preAutoDecrement(); + return pc; + } + + /** + * Execute post-decrement operation. + * Format: POST_AUTODECREMENT rd rs + */ + public static int executePostAutoDecrement(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = ((RuntimeScalar) registers[rs]).postAutoDecrement(); + return pc; + } + + /** + * Execute open operation. + * Format: OPEN rd ctx argsReg + */ + public static int executeOpen(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int ctx = bytecode[pc++]; + int argsReg = bytecode[pc++]; + RuntimeArray argsArray = (RuntimeArray) registers[argsReg]; + RuntimeBase[] argsVarargs = argsArray.elements.toArray(new RuntimeBase[0]); + registers[rd] = IOOperator.open(ctx, argsVarargs); + return pc; + } + + /** + * Execute readline operation. + * Format: READLINE rd fhReg ctx + */ + public static int executeReadline(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int fhReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Readline.readline((RuntimeScalar) registers[fhReg], ctx); + return pc; + } + + /** + * Execute match regex operation. + * Format: MATCH_REGEX rd stringReg regexReg ctx + */ + public static int executeMatchRegex(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int stringReg = bytecode[pc++]; + int regexReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = RuntimeRegex.matchRegex( + (RuntimeScalar) registers[regexReg], // quotedRegex first + (RuntimeScalar) registers[stringReg], // string second + ctx + ); + return pc; + } + + /** + * Execute negated match regex operation. + * Format: MATCH_REGEX_NOT rd stringReg regexReg ctx + */ + public static int executeMatchRegexNot(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int stringReg = bytecode[pc++]; + int regexReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + RuntimeBase matchResult = RuntimeRegex.matchRegex( + (RuntimeScalar) registers[regexReg], // quotedRegex first + (RuntimeScalar) registers[stringReg], // string second + ctx + ); + // Negate the boolean result + registers[rd] = new RuntimeScalar(matchResult.scalar().getBoolean() ? 0 : 1); + return pc; + } + + /** + * Execute create closure operation. + * Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ... + */ + public static int executeCreateClosure(short[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int templateIdx = bytecode[pc++]; + int numCaptures = bytecode[pc++]; + + // Get the template InterpretedCode from constants + InterpretedCode template = (InterpretedCode) code.constants[templateIdx]; + + // Capture the current register values + RuntimeBase[] capturedVars = new RuntimeBase[numCaptures]; + for (int i = 0; i < numCaptures; i++) { + int captureReg = bytecode[pc++]; + capturedVars[i] = registers[captureReg]; + } + + // Create a new InterpretedCode with the captured variables + InterpretedCode closureCode = new InterpretedCode( + template.bytecode, + template.constants, + template.stringPool, + template.maxRegisters, + capturedVars, // The captured variables! + template.sourceName, + template.sourceLine, + template.pcToTokenIndex, + template.variableRegistry // Preserve variable registry + ); + + // Wrap in RuntimeScalar + registers[rd] = new RuntimeScalar((RuntimeCode) closureCode); + return pc; + } + + /** + * Execute iterator create operation. + * Format: ITERATOR_CREATE rd rs + */ + public static int executeIteratorCreate(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase iterable = registers[rs]; + java.util.Iterator iterator = iterable.iterator(); + + // Store iterator as a constant (preserve the Iterator object) + // Wrap in RuntimeScalar for storage + registers[rd] = new RuntimeScalar(iterator); + return pc; + } + + /** + * Execute iterator has next operation. + * Format: ITERATOR_HAS_NEXT rd iterReg + */ + public static int executeIteratorHasNext(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int iterReg = bytecode[pc++]; + + RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; + @SuppressWarnings("unchecked") + java.util.Iterator iterator = + (java.util.Iterator) iterScalar.value; + + boolean hasNext = iterator.hasNext(); + registers[rd] = hasNext ? RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + return pc; + } + + /** + * Execute iterator next operation. + * Format: ITERATOR_NEXT rd iterReg + */ + public static int executeIteratorNext(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int iterReg = bytecode[pc++]; + + RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; + @SuppressWarnings("unchecked") + java.util.Iterator iterator = + (java.util.Iterator) iterScalar.value; + + RuntimeScalar next = iterator.next(); + registers[rd] = next; + return pc; + } + + /** + * Execute subtract assign operation. + * Format: SUBTRACT_ASSIGN rd rs + */ + public static int executeSubtractAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.subtractAssign(s1, s2); + return pc; + } + + /** + * Execute multiply assign operation. + * Format: MULTIPLY_ASSIGN rd rs + */ + public static int executeMultiplyAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.multiplyAssign(s1, s2); + return pc; + } + + /** + * Execute divide assign operation. + * Format: DIVIDE_ASSIGN rd rs + */ + public static int executeDivideAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.divideAssign(s1, s2); + return pc; + } + + /** + * Execute modulus assign operation. + * Format: MODULUS_ASSIGN rd rs + */ + public static int executeModulusAssign(short[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + RuntimeBase val1 = registers[rd]; + RuntimeBase val2 = registers[rs]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.modulusAssign(s1, s2); + return pc; + } +} diff --git a/src/main/java/org/perlonjava/interpreter/OpcodeHandlerFileTest.java b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerFileTest.java new file mode 100644 index 000000000..9774cd194 --- /dev/null +++ b/src/main/java/org/perlonjava/interpreter/OpcodeHandlerFileTest.java @@ -0,0 +1,63 @@ +package org.perlonjava.interpreter; + +import org.perlonjava.operators.FileTestOperator; +import org.perlonjava.runtime.*; + +/** + * File test opcode handlers. + * + * Extracted from BytecodeInterpreter.execute() to reduce method size. + * Handles all file test operations (opcodes 190-216). + */ +public class OpcodeHandlerFileTest { + + /** + * Execute file test operation based on opcode. + * Format: FILETEST_* rd rs + * + * @param bytecode The bytecode array + * @param pc Current program counter + * @param registers Register file + * @param opcode The file test opcode (190-216) + * @return Updated program counter + */ + public static int executeFileTest(short[] bytecode, int pc, RuntimeBase[] registers, short opcode) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + + // Map opcode to test flag + String testFlag = switch (opcode) { + case Opcodes.FILETEST_R -> "-r"; + case Opcodes.FILETEST_W -> "-w"; + case Opcodes.FILETEST_X -> "-x"; + case Opcodes.FILETEST_O -> "-o"; + case Opcodes.FILETEST_R_REAL -> "-R"; + case Opcodes.FILETEST_W_REAL -> "-W"; + case Opcodes.FILETEST_X_REAL -> "-X"; + case Opcodes.FILETEST_O_REAL -> "-O"; + case Opcodes.FILETEST_E -> "-e"; + case Opcodes.FILETEST_Z -> "-z"; + case Opcodes.FILETEST_S -> "-s"; + case Opcodes.FILETEST_F -> "-f"; + case Opcodes.FILETEST_D -> "-d"; + case Opcodes.FILETEST_L -> "-l"; + case Opcodes.FILETEST_P -> "-p"; + case Opcodes.FILETEST_S_UPPER -> "-S"; + case Opcodes.FILETEST_B -> "-b"; + case Opcodes.FILETEST_C -> "-c"; + case Opcodes.FILETEST_T -> "-t"; + case Opcodes.FILETEST_U -> "-u"; + case Opcodes.FILETEST_G -> "-g"; + case Opcodes.FILETEST_K -> "-k"; + case Opcodes.FILETEST_T_UPPER -> "-T"; + case Opcodes.FILETEST_B_UPPER -> "-B"; + case Opcodes.FILETEST_M -> "-M"; + case Opcodes.FILETEST_A -> "-A"; + case Opcodes.FILETEST_C_UPPER -> "-C"; + default -> throw new RuntimeException("Unknown file test opcode: " + opcode); + }; + + registers[rd] = FileTestOperator.fileTest(testFlag, (RuntimeScalar) registers[rs]); + return pc; + } +} diff --git a/src/main/java/org/perlonjava/interpreter/Opcodes.java b/src/main/java/org/perlonjava/interpreter/Opcodes.java index 3e3293730..5d97a61d9 100644 --- a/src/main/java/org/perlonjava/interpreter/Opcodes.java +++ b/src/main/java/org/perlonjava/interpreter/Opcodes.java @@ -866,10 +866,38 @@ public class Opcodes { * Used for *X{HASH} style access to glob slots */ public static final short GLOB_SLOT_GET = 230; + /** Store via symbolic reference: GlobalVariable.getGlobalVariable(nameReg.toString()).set(valueReg) + * Format: STORE_SYMBOLIC_SCALAR nameReg valueReg */ + public static final short STORE_SYMBOLIC_SCALAR = 231; + + /** Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get() + * Format: LOAD_SYMBOLIC_SCALAR rd nameReg */ + public static final short LOAD_SYMBOLIC_SCALAR = 232; + + /** File test on cached handle '_': rd = FileTestOperator.fileTestLastHandle(operator) + * Format: FILETEST_LASTHANDLE rd operator_string_idx */ + public static final short FILETEST_LASTHANDLE = 233; + + /** sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) + * Format: SPRINTF rd formatReg argsListReg */ + public static final short SPRINTF = 234; + + /** chop($x): rd = StringOperators.chopScalar(scalarReg) - modifies in place + * Format: CHOP rd scalarReg */ + public static final short CHOP = 235; + + /** Get replacement regex: rd = RuntimeRegex.getReplacementRegex(pattern, replacement, flags) + * Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg */ + public static final short GET_REPLACEMENT_REGEX = 236; + + /** substr with variable args: rd = Operator.substr(ctx, args...) + * Format: SUBSTR_VAR rd argsListReg ctx */ + public static final short SUBSTR_VAR = 237; + // ================================================================= // BUILT-IN FUNCTION OPCODES - after LASTOP // Last manually-assigned opcode (for tool reference) - private static final short LASTOP = 230; + private static final short LASTOP = 237; // ================================================================= // Generated by dev/tools/generate_opcode_handlers.pl @@ -924,18 +952,6 @@ public class Opcodes { public static final short CHDIR = LASTOP + 42; public static final short EXIT = LASTOP + 43; - /** Store via symbolic reference: GlobalVariable.getGlobalVariable(nameReg.toString()).set(valueReg) - * Format: STORE_SYMBOLIC_SCALAR nameReg valueReg */ - public static final short STORE_SYMBOLIC_SCALAR = LASTOP + 44; - - /** Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get() - * Format: LOAD_SYMBOLIC_SCALAR rd nameReg */ - public static final short LOAD_SYMBOLIC_SCALAR = LASTOP + 45; - - /** File test on cached handle '_': rd = FileTestOperator.fileTestLastHandle(operator) - * Format: FILETEST_LASTHANDLE rd operator_string_idx */ - public static final short FILETEST_LASTHANDLE = LASTOP + 46; - // GENERATED_OPCODES_END