From dbaa25ae1524cdbace6e88a8b68fe9b9adfb2e6c Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 11:01:31 +0100 Subject: [PATCH 1/5] Fix check-bytecode-size.sh for renamed package path Update class path from org.perlonjava.interpreter to org.perlonjava.backend.bytecode after package reorganization. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- dev/tools/check-bytecode-size.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/tools/check-bytecode-size.sh b/dev/tools/check-bytecode-size.sh index ad21c936d..93484e0fb 100755 --- a/dev/tools/check-bytecode-size.sh +++ b/dev/tools/check-bytecode-size.sh @@ -73,13 +73,13 @@ echo "Target: under $MAX_SIZE bytes for reliable JIT compilation" echo "" # Check main execute() method -check_method "org.perlonjava.interpreter.BytecodeInterpreter" "execute" || FAILED=1 +check_method "org.perlonjava.backend.bytecode.BytecodeInterpreter" "execute" || FAILED=1 # Check secondary methods -check_method "org.perlonjava.interpreter.BytecodeInterpreter" "executeComparisons" || FAILED=1 -check_method "org.perlonjava.interpreter.BytecodeInterpreter" "executeArithmetic" || FAILED=1 -check_method "org.perlonjava.interpreter.BytecodeInterpreter" "executeCollections" || FAILED=1 -check_method "org.perlonjava.interpreter.BytecodeInterpreter" "executeTypeOps" || FAILED=1 +check_method "org.perlonjava.backend.bytecode.BytecodeInterpreter" "executeComparisons" || FAILED=1 +check_method "org.perlonjava.backend.bytecode.BytecodeInterpreter" "executeArithmetic" || FAILED=1 +check_method "org.perlonjava.backend.bytecode.BytecodeInterpreter" "executeCollections" || FAILED=1 +check_method "org.perlonjava.backend.bytecode.BytecodeInterpreter" "executeTypeOps" || FAILED=1 echo "" From 407c7c044a17ffa8246effcb5e9e41b4bebcd9a6 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 13:06:35 +0100 Subject: [PATCH 2/5] Extract 84 opcode handlers to InlineOpcodeHandler, add 76 missing disassembler mappings Reduce BytecodeInterpreter.execute() from 12007 to 7137 bytes (under 7500 JVM JIT limit) by extracting 84 inline case blocks into individual static methods in new InlineOpcodeHandler.java. Convert all switch statements to arrow-style (case X -> {}) for clarity and to eliminate break statements. Add comments explaining the nested try/catch/finally structure (outer try for cleanup, inner try for Perl eval/die semantics). Also add 76 missing opcode mappings to InterpretedCode disassembler and remove dead executeArithmetic() method (326 lines). Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/BytecodeInterpreter.java | 2260 ++++------------- .../backend/bytecode/InlineOpcodeHandler.java | 1246 +++++++++ .../backend/bytecode/InterpretedCode.java | 601 +++++ 3 files changed, 2304 insertions(+), 1803 deletions(-) create mode 100644 src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 2281d18c2..aa3587946 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -104,6 +104,16 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c String savedPackage = InterpreterState.currentPackage.get().toString(); InterpreterState.currentPackage.get().set(framePackageName); RegexState.save(); + // Structure: try { while(true) { try { ...dispatch... } catch { handle eval/die } } } finally { cleanup } + // + // Outer try/finally — cleanup only, no catch. + // Restores local variables, package name, and call stack on ANY exit (return, throw, etc.) + // + // Inner try/catch — implements Perl's eval { BLOCK } / die semantics. + // When Perl code calls `die` inside `eval { ... }`, the catch block sets $@ and + // uses `continue outer` to jump back to the top of the while(true) loop, resuming + // the bytecode dispatch at the eval's catch target PC. Without the while(true), + // `continue` would have nowhere to go after the catch block. try { outer: while (true) { @@ -121,11 +131,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // CONTROL FLOW // ================================================================= - case Opcodes.NOP: + case Opcodes.NOP -> { // No operation - break; + } - case Opcodes.RETURN: { + case Opcodes.RETURN -> { // Return from subroutine: return rd int retReg = bytecode[pc++]; RuntimeBase retVal = registers[retReg]; @@ -138,24 +148,20 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c return retList; } - case Opcodes.GOTO: { + case Opcodes.GOTO -> { // Unconditional jump: pc = offset int offset = readInt(bytecode, pc); pc = offset; // Registers persist across jump (unlike stack-based!) - break; } - case Opcodes.LAST: - case Opcodes.NEXT: - case Opcodes.REDO: { + case Opcodes.LAST, Opcodes.NEXT, Opcodes.REDO -> { // Loop control: jump to target PC // Format: opcode, target (absolute PC as int) int target = readInt(bytecode, pc); pc = target; - break; } - case Opcodes.GOTO_IF_FALSE: { + case Opcodes.GOTO_IF_FALSE -> { // Conditional jump: if (!rs) pc = offset int condReg = bytecode[pc++]; int target = readInt(bytecode, pc); @@ -170,10 +176,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c if (!cond.getBoolean()) { pc = target; // Jump - all registers stay valid! } - break; } - case Opcodes.GOTO_IF_TRUE: { + case Opcodes.GOTO_IF_TRUE -> { // Conditional jump: if (rs) pc = offset int condReg = bytecode[pc++]; int target = readInt(bytecode, pc); @@ -188,32 +193,29 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c if (cond.getBoolean()) { pc = target; } - break; } // ================================================================= // REGISTER OPERATIONS // ================================================================= - case Opcodes.ALIAS: { + case Opcodes.ALIAS -> { // Register alias: rd = rs (shares reference, does NOT copy value) // Must unwrap RuntimeScalarReadOnly to prevent read-only values in variable registers int dest = bytecode[pc++]; int src = bytecode[pc++]; RuntimeBase srcVal = registers[src]; registers[dest] = isImmutableProxy(srcVal) ? ensureMutableScalar(srcVal) : srcVal; - break; } - case Opcodes.LOAD_CONST: { + case Opcodes.LOAD_CONST -> { // Load from constant pool: rd = constants[index] int rd = bytecode[pc++]; int constIndex = bytecode[pc++]; registers[rd] = (RuntimeBase) code.constants[constIndex]; - break; } - case Opcodes.LOAD_INT: { + case Opcodes.LOAD_INT -> { // Load integer: rd = immediate (create NEW mutable scalar, not cached) int rd = bytecode[pc++]; int value = readInt(bytecode, pc); @@ -221,10 +223,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // Create NEW RuntimeScalar (mutable) instead of using cache // This is needed for local variables that may be modified (++/--) registers[rd] = new RuntimeScalar(value); - break; } - case Opcodes.LOAD_STRING: { + case Opcodes.LOAD_STRING -> { int rd = bytecode[pc++]; int strIndex = bytecode[pc++]; String s = code.stringPool[strIndex]; @@ -234,10 +235,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c && s.equals(rs.value))) { registers[rd] = new RuntimeScalar(s); } - break; } - case Opcodes.LOAD_BYTE_STRING: { + case Opcodes.LOAD_BYTE_STRING -> { int rd = bytecode[pc++]; int strIndex = bytecode[pc++]; String s = code.stringPool[strIndex]; @@ -250,71 +250,53 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c RuntimeScalar bs = new RuntimeScalar(s); bs.type = RuntimeScalarType.BYTE_STRING; registers[rd] = bs; - break; } - case Opcodes.LOAD_VSTRING: { + case Opcodes.LOAD_VSTRING -> { int rd = bytecode[pc++]; int strIndex = bytecode[pc++]; RuntimeScalar vs = new RuntimeScalar(code.stringPool[strIndex]); vs.type = RuntimeScalarType.VSTRING; registers[rd] = vs; - break; } - case Opcodes.GLOB_OP: { - // File glob: ScalarGlobOperator.evaluate(globId, pattern, ctx) - // Mirrors JVM EmitOperator.handleGlobBuiltin. - int rd = bytecode[pc++]; - int globId = bytecode[pc++]; - int patternReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - registers[rd] = ScalarGlobOperator.evaluate(globId, (RuntimeScalar) registers[patternReg], ctx); - break; + case Opcodes.GLOB_OP -> { + pc = InlineOpcodeHandler.executeGlobOp(bytecode, pc, registers); } - case Opcodes.LOAD_UNDEF: { + case Opcodes.LOAD_UNDEF -> { // Load undef: rd = new RuntimeScalar() int rd = bytecode[pc++]; registers[rd] = new RuntimeScalar(); - break; } - case Opcodes.UNDEFINE_SCALAR: { - // Undefine variable in-place: rd.undefine() - int rd = bytecode[pc++]; - if (isImmutableProxy(registers[rd])) { - registers[rd] = ensureMutableScalar(registers[rd]); - } - registers[rd].undefine(); - break; + case Opcodes.UNDEFINE_SCALAR -> { + pc = InlineOpcodeHandler.executeUndefineScalar(bytecode, pc, registers); } - case Opcodes.MY_SCALAR: { + case Opcodes.MY_SCALAR -> { // Lexical scalar assignment: rd = new RuntimeScalar(); rd.set(rs) int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeScalar newScalar = new RuntimeScalar(); registers[rs].addToScalar(newScalar); registers[rd] = newScalar; - break; } // ================================================================= // VARIABLE ACCESS - GLOBAL // ================================================================= - case Opcodes.LOAD_GLOBAL_SCALAR: { + case Opcodes.LOAD_GLOBAL_SCALAR -> { // Load global scalar: rd = GlobalVariable.getGlobalVariable(name) int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String name = code.stringPool[nameIdx]; // Uses SAME GlobalVariable as compiled code registers[rd] = GlobalVariable.getGlobalVariable(name); - break; } - case Opcodes.STORE_GLOBAL_SCALAR: { + case Opcodes.STORE_GLOBAL_SCALAR -> { // Store global scalar: GlobalVariable.getGlobalVariable(name).set(rs) int nameIdx = bytecode[pc++]; int srcReg = bytecode[pc++]; @@ -327,10 +309,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c : value.scalar(); GlobalVariable.getGlobalVariable(name).set(scalarValue); - break; } - case Opcodes.LOCAL_SCALAR_SAVE_LEVEL: { + case Opcodes.LOCAL_SCALAR_SAVE_LEVEL -> { // Superinstruction: save dynamic level BEFORE makeLocal, then localize. // Atomically: levelReg = getLocalLevel(), rd = makeLocal(name). // The pre-push level in levelReg is used by POP_LOCAL_LEVEL after the loop. @@ -341,33 +322,29 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c registers[levelReg] = new RuntimeScalar(DynamicVariableManager.getLocalLevel()); registers[rd] = GlobalRuntimeScalar.makeLocal(name); - break; } - case Opcodes.POP_LOCAL_LEVEL: { + case Opcodes.POP_LOCAL_LEVEL -> { // Restore DynamicVariableManager to a previously saved local level. // Matches JVM compiler's DynamicVariableManager.popToLocalLevel(savedLevel) call. int rs = bytecode[pc++]; int savedLevel = ((RuntimeScalar) registers[rs]).getInt(); DynamicVariableManager.popToLocalLevel(savedLevel); - break; } - case Opcodes.SAVE_REGEX_STATE: { + case Opcodes.SAVE_REGEX_STATE -> { pc++; regexStateStack.push(new RegexState()); - break; } - case Opcodes.RESTORE_REGEX_STATE: { + case Opcodes.RESTORE_REGEX_STATE -> { pc++; if (!regexStateStack.isEmpty()) { regexStateStack.pop().restore(); } - break; } - case Opcodes.FOREACH_GLOBAL_NEXT_OR_EXIT: { + case Opcodes.FOREACH_GLOBAL_NEXT_OR_EXIT -> { // Superinstruction: foreach loop step for a global loop variable (e.g. $_). // Combines: hasNext check, next() into varReg, aliasGlobalVariable, conditional jump. // Do-while layout: if hasNext jump to bodyTarget, else fall through to exit. @@ -394,10 +371,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c } else { registers[rd] = new RuntimeScalar(); } - break; } - case Opcodes.STORE_GLOBAL_ARRAY: { + case Opcodes.STORE_GLOBAL_ARRAY -> { // Store global array: GlobalVariable.getGlobalArray(name).setFromList(list) int nameIdx = bytecode[pc++]; int srcReg = bytecode[pc++]; @@ -422,10 +398,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c } else { globalArray.setFromList(value.getList()); } - break; } - case Opcodes.STORE_GLOBAL_HASH: { + case Opcodes.STORE_GLOBAL_HASH -> { // Store global hash: GlobalVariable.getGlobalHash(name).setFromList(list) int nameIdx = bytecode[pc++]; int srcReg = bytecode[pc++]; @@ -443,37 +418,33 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c } else { globalHash.setFromList(value.getList()); } - break; } - case Opcodes.LOAD_GLOBAL_ARRAY: { + case Opcodes.LOAD_GLOBAL_ARRAY -> { // Load global array: rd = GlobalVariable.getGlobalArray(name) int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String name = code.stringPool[nameIdx]; registers[rd] = GlobalVariable.getGlobalArray(name); - break; } - case Opcodes.LOAD_GLOBAL_HASH: { + case Opcodes.LOAD_GLOBAL_HASH -> { // Load global hash: rd = GlobalVariable.getGlobalHash(name) int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String name = code.stringPool[nameIdx]; registers[rd] = GlobalVariable.getGlobalHash(name); - break; } - case Opcodes.LOAD_GLOBAL_CODE: { + case Opcodes.LOAD_GLOBAL_CODE -> { // Load global code: rd = GlobalVariable.getGlobalCodeRef(name) int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String name = code.stringPool[nameIdx]; registers[rd] = GlobalVariable.getGlobalCodeRef(name); - break; } - case Opcodes.STORE_GLOBAL_CODE: { + case Opcodes.STORE_GLOBAL_CODE -> { // Store global code: GlobalVariable.globalCodeRefs.put(name, codeRef) int nameIdx = bytecode[pc++]; int codeReg = bytecode[pc++]; @@ -481,16 +452,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c RuntimeScalar codeRef = (RuntimeScalar) registers[codeReg]; // Store the code reference in the global namespace GlobalVariable.globalCodeRefs.put(name, codeRef); - break; } - case Opcodes.CREATE_CLOSURE: + case Opcodes.CREATE_CLOSURE -> { // Create closure with captured variables // Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ... pc = OpcodeHandlerExtended.executeCreateClosure(bytecode, pc, registers, code); - break; + } - case Opcodes.SET_SCALAR: { + case Opcodes.SET_SCALAR -> { // Set scalar value: registers[rd] = registers[rs] // Use addToScalar which properly handles special variables like $& // addToScalar calls getValueAsScalar() for ScalarSpecialVariable @@ -507,228 +477,100 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c rdScalar = rdVal.scalar(); } registers[rs].addToScalar(rdScalar); - break; } // ================================================================= // ARITHMETIC OPERATORS // ================================================================= - case Opcodes.ADD_SCALAR: { - // Addition: rd = rs1 + rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - - // Convert to scalar if needed - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - // Calls SAME method as compiled code - registers[rd] = MathOperators.add(s1, s2); - break; + case Opcodes.ADD_SCALAR -> { + pc = InlineOpcodeHandler.executeAddScalar(bytecode, pc, registers); } - case Opcodes.SUB_SCALAR: { - // Subtraction: rd = rs1 - rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - - // Convert to scalar if needed - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.subtract(s1, s2); - break; + case Opcodes.SUB_SCALAR -> { + pc = InlineOpcodeHandler.executeSubScalar(bytecode, pc, registers); } - case Opcodes.MUL_SCALAR: { - // Multiplication: rd = rs1 * rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - - // Convert to scalar if needed - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.multiply(s1, s2); - break; + case Opcodes.MUL_SCALAR -> { + pc = InlineOpcodeHandler.executeMulScalar(bytecode, pc, registers); } - case Opcodes.DIV_SCALAR: { - // Division: rd = rs1 / rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - - // Convert to scalar if needed - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.divide(s1, s2); - break; + case Opcodes.DIV_SCALAR -> { + pc = InlineOpcodeHandler.executeDivScalar(bytecode, pc, registers); } - case Opcodes.MOD_SCALAR: { - // Modulus: rd = rs1 % rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - - // Convert to scalar if needed - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.modulus(s1, s2); - break; + case Opcodes.MOD_SCALAR -> { + pc = InlineOpcodeHandler.executeModScalar(bytecode, pc, registers); } - case Opcodes.POW_SCALAR: { - // Exponentiation: rd = rs1 ** rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - - // Convert to scalar if needed - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - - registers[rd] = MathOperators.pow(s1, s2); - break; + case Opcodes.POW_SCALAR -> { + pc = InlineOpcodeHandler.executePowScalar(bytecode, pc, registers); } - case Opcodes.NEG_SCALAR: { - // Negation: rd = -rs - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = MathOperators.unaryMinus((RuntimeScalar) registers[rs]); - break; + case Opcodes.NEG_SCALAR -> { + pc = InlineOpcodeHandler.executeNegScalar(bytecode, pc, registers); } // Specialized unboxed operations (rare optimizations) - case Opcodes.ADD_SCALAR_INT: { - // Addition with immediate: rd = rs + immediate - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - int immediate = readInt(bytecode, pc); - pc += 1; - // Calls specialized unboxed method (rare optimization) - registers[rd] = MathOperators.add( - (RuntimeScalar) registers[rs], - immediate // primitive int, not RuntimeScalar - ); - break; + case Opcodes.ADD_SCALAR_INT -> { + pc = InlineOpcodeHandler.executeAddScalarInt(bytecode, pc, registers); } // ================================================================= // STRING OPERATORS // ================================================================= - case Opcodes.CONCAT: { - // String concatenation: rd = rs1 . rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeBase concatLeft = registers[rs1]; - RuntimeBase concatRight = registers[rs2]; - registers[rd] = StringOperators.stringConcat( - concatLeft instanceof RuntimeScalar ? (RuntimeScalar) concatLeft : concatLeft.scalar(), - concatRight instanceof RuntimeScalar ? (RuntimeScalar) concatRight : concatRight.scalar() - ); - break; + case Opcodes.CONCAT -> { + pc = InlineOpcodeHandler.executeConcat(bytecode, pc, registers); } - case Opcodes.REPEAT: { - // String/list repetition: rd = rs1 x rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeBase countVal = registers[rs2]; - RuntimeScalar count = (countVal instanceof RuntimeScalar) - ? (RuntimeScalar) countVal - : ((RuntimeList) countVal).scalar(); - int repeatCtx = (registers[rs1] instanceof RuntimeScalar) - ? RuntimeContextType.SCALAR : RuntimeContextType.LIST; - registers[rd] = Operator.repeat(registers[rs1], count, repeatCtx); - break; - } - - case Opcodes.LENGTH: { - // String length: rd = length(rs) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = StringOperators.length((RuntimeScalar) registers[rs]); - break; + case Opcodes.REPEAT -> { + pc = InlineOpcodeHandler.executeRepeat(bytecode, pc, registers); + } + + case Opcodes.LENGTH -> { + pc = InlineOpcodeHandler.executeLength(bytecode, pc, registers); } // ================================================================= // COMPARISON AND LOGICAL OPERATORS (opcodes 31-39) - Delegated // ================================================================= - case Opcodes.COMPARE_NUM: - case Opcodes.COMPARE_STR: - case Opcodes.EQ_NUM: - case Opcodes.NE_NUM: - case Opcodes.LT_NUM: - case Opcodes.GT_NUM: - case Opcodes.LE_NUM: - case Opcodes.GE_NUM: - case Opcodes.EQ_STR: - case Opcodes.NE_STR: - case Opcodes.NOT: + case Opcodes.COMPARE_NUM, Opcodes.COMPARE_STR, Opcodes.EQ_NUM, Opcodes.NE_NUM, Opcodes.LT_NUM, Opcodes.GT_NUM, Opcodes.LE_NUM, Opcodes.GE_NUM, Opcodes.EQ_STR, Opcodes.NE_STR, Opcodes.NOT -> { pc = executeComparisons(opcode, bytecode, pc, registers); - break; + } // ================================================================= // TYPE AND REFERENCE OPERATORS (opcodes 102-105) - Delegated // ================================================================= - case Opcodes.DEFINED: - case Opcodes.REF: - case Opcodes.BLESS: - case Opcodes.ISA: - case Opcodes.PROTOTYPE: - case Opcodes.QUOTE_REGEX: + case Opcodes.DEFINED, Opcodes.REF, Opcodes.BLESS, Opcodes.ISA, Opcodes.PROTOTYPE, Opcodes.QUOTE_REGEX -> { pc = executeTypeOps(opcode, bytecode, pc, registers, code); - break; + } // ================================================================= // ITERATOR OPERATIONS - For efficient foreach loops // ================================================================= - case Opcodes.ITERATOR_CREATE: + case Opcodes.ITERATOR_CREATE -> { // Create iterator: rd = rs.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() // 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() // Format: ITERATOR_NEXT rd iterReg pc = OpcodeHandlerExtended.executeIteratorNext(bytecode, pc, registers); - break; + } - case Opcodes.FOREACH_NEXT_OR_EXIT: { + case Opcodes.FOREACH_NEXT_OR_EXIT -> { // Superinstruction for foreach loops (do-while layout). // Combines: hasNext check, next() call, and conditional jump to body. // Format: FOREACH_NEXT_OR_EXIT rd, iterReg, bodyTarget @@ -752,404 +594,198 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c } else { registers[rd] = new RuntimeScalar(); } - break; } // ================================================================= // COMPOUND ASSIGNMENT OPERATORS (with overload support) // ================================================================= - case Opcodes.SUBTRACT_ASSIGN: + case Opcodes.SUBTRACT_ASSIGN -> { // Compound assignment: rd -= rs // Format: SUBTRACT_ASSIGN rd rs pc = OpcodeHandlerExtended.executeSubtractAssign(bytecode, pc, registers); - break; + } - case Opcodes.MULTIPLY_ASSIGN: + case Opcodes.MULTIPLY_ASSIGN -> { // Compound assignment: rd *= rs // Format: MULTIPLY_ASSIGN rd rs pc = OpcodeHandlerExtended.executeMultiplyAssign(bytecode, pc, registers); - break; + } - case Opcodes.DIVIDE_ASSIGN: + case Opcodes.DIVIDE_ASSIGN -> { // Compound assignment: rd /= rs // Format: DIVIDE_ASSIGN rd rs pc = OpcodeHandlerExtended.executeDivideAssign(bytecode, pc, registers); - break; + } - case Opcodes.MODULUS_ASSIGN: + 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 // Format: REPEAT_ASSIGN rd rs pc = OpcodeHandlerExtended.executeRepeatAssign(bytecode, pc, registers); - break; + } - case Opcodes.POW_ASSIGN: + case Opcodes.POW_ASSIGN -> { // Compound assignment: rd **= rs // 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 // Format: LEFT_SHIFT_ASSIGN rd rs pc = OpcodeHandlerExtended.executeLeftShiftAssign(bytecode, pc, registers); - break; + } - case Opcodes.RIGHT_SHIFT_ASSIGN: + case Opcodes.RIGHT_SHIFT_ASSIGN -> { pc = OpcodeHandlerExtended.executeRightShiftAssign(bytecode, pc, registers); - break; + } - case Opcodes.INTEGER_LEFT_SHIFT_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - s1.set(BitwiseOperators.integerShiftLeft(s1, (RuntimeScalar) registers[rs])); - break; + case Opcodes.INTEGER_LEFT_SHIFT_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerLeftShiftAssign(bytecode, pc, registers); } - case Opcodes.INTEGER_RIGHT_SHIFT_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - s1.set(BitwiseOperators.integerShiftRight(s1, (RuntimeScalar) registers[rs])); - break; + case Opcodes.INTEGER_RIGHT_SHIFT_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerRightShiftAssign(bytecode, pc, registers); } - case Opcodes.INTEGER_DIV_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - s1.set(MathOperators.integerDivide(s1, (RuntimeScalar) registers[rs])); - break; + case Opcodes.INTEGER_DIV_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerDivAssign(bytecode, pc, registers); } - case Opcodes.INTEGER_MOD_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - s1.set(MathOperators.integerModulus(s1, (RuntimeScalar) registers[rs])); - break; + case Opcodes.INTEGER_MOD_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerModAssign(bytecode, pc, registers); } - case Opcodes.LOGICAL_AND_ASSIGN: + 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: + case Opcodes.LOGICAL_OR_ASSIGN -> { // Compound assignment: rd ||= rs (short-circuit) // Format: LOGICAL_OR_ASSIGN rd rs pc = OpcodeHandlerExtended.executeLogicalOrAssign(bytecode, pc, registers); - break; + } - case Opcodes.DEFINED_OR_ASSIGN: + case Opcodes.DEFINED_OR_ASSIGN -> { // Compound assignment: rd //= rs (short-circuit) // Format: DEFINED_OR_ASSIGN rd rs pc = OpcodeHandlerExtended.executeDefinedOrAssign(bytecode, pc, registers); - break; + } // ================================================================= // SHIFT OPERATIONS // ================================================================= - case Opcodes.LEFT_SHIFT: { - // Left shift: rd = rs1 << rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rs1]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs2]; - registers[rd] = BitwiseOperators.shiftLeft(s1, s2); - break; + case Opcodes.LEFT_SHIFT -> { + pc = InlineOpcodeHandler.executeLeftShift(bytecode, pc, registers); } - case Opcodes.RIGHT_SHIFT: { - // Right shift: rd = rs1 >> rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rs1]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs2]; - registers[rd] = BitwiseOperators.shiftRight(s1, s2); - break; + case Opcodes.RIGHT_SHIFT -> { + pc = InlineOpcodeHandler.executeRightShift(bytecode, pc, registers); } - case Opcodes.INTEGER_LEFT_SHIFT: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); - RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); - registers[rd] = BitwiseOperators.integerShiftLeft(s1, s2); - break; + case Opcodes.INTEGER_LEFT_SHIFT -> { + pc = InlineOpcodeHandler.executeIntegerLeftShift(bytecode, pc, registers); } - case Opcodes.INTEGER_RIGHT_SHIFT: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); - RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); - registers[rd] = BitwiseOperators.integerShiftRight(s1, s2); - break; + case Opcodes.INTEGER_RIGHT_SHIFT -> { + pc = InlineOpcodeHandler.executeIntegerRightShift(bytecode, pc, registers); } - case Opcodes.INTEGER_DIV: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); - RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); - registers[rd] = MathOperators.integerDivide(s1, s2); - break; + case Opcodes.INTEGER_DIV -> { + pc = InlineOpcodeHandler.executeIntegerDiv(bytecode, pc, registers); } - case Opcodes.INTEGER_MOD: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); - RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); - registers[rd] = MathOperators.integerModulus(s1, s2); - break; + case Opcodes.INTEGER_MOD -> { + pc = InlineOpcodeHandler.executeIntegerMod(bytecode, pc, registers); } // ================================================================= // ARRAY OPERATIONS // ================================================================= - case Opcodes.ARRAY_GET: { - // Array element access: rd = array[index] - int rd = bytecode[pc++]; - int arrayReg = bytecode[pc++]; - int indexReg = bytecode[pc++]; - - RuntimeBase arrayBase = registers[arrayReg]; - RuntimeScalar idx = (RuntimeScalar) registers[indexReg]; - - if (arrayBase instanceof RuntimeArray) { - RuntimeArray arr = (RuntimeArray) arrayBase; - registers[rd] = arr.get(idx.getInt()); - } else if (arrayBase instanceof RuntimeList) { - RuntimeList list = (RuntimeList) arrayBase; - int index = idx.getInt(); - if (index < 0) index = list.elements.size() + index; - registers[rd] = (index >= 0 && index < list.elements.size()) - ? list.elements.get(index) - : new RuntimeScalar(); - } else { - throw new RuntimeException("ARRAY_GET: register " + arrayReg + " contains " + - (arrayBase == null ? "null" : arrayBase.getClass().getName()) + - " instead of RuntimeArray or RuntimeList"); - } - break; - } - - case Opcodes.ARRAY_SET: { - // Array element store: array[index] = value - int arrayReg = bytecode[pc++]; - int indexReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; - RuntimeArray arr = (RuntimeArray) registers[arrayReg]; - RuntimeScalar idx = (RuntimeScalar) registers[indexReg]; - RuntimeBase valueBase = registers[valueReg]; - RuntimeScalar val = (valueBase instanceof RuntimeScalar) - ? (RuntimeScalar) valueBase : valueBase.scalar(); - arr.get(idx.getInt()).set(val); - break; - } - - case Opcodes.ARRAY_PUSH: { - // Array push: push(@array, value) - int arrayReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; - RuntimeArray arr = (RuntimeArray) registers[arrayReg]; - RuntimeBase val = registers[valueReg]; - arr.push(val); - break; - } - - case Opcodes.ARRAY_POP: { - // Array pop: rd = pop(@array) - int rd = bytecode[pc++]; - int arrayReg = bytecode[pc++]; - RuntimeArray arr = (RuntimeArray) registers[arrayReg]; - registers[rd] = RuntimeArray.pop(arr); - break; + case Opcodes.ARRAY_GET -> { + pc = InlineOpcodeHandler.executeArrayGet(bytecode, pc, registers); } - case Opcodes.ARRAY_SHIFT: { - // Array shift: rd = shift(@array) - int rd = bytecode[pc++]; - int arrayReg = bytecode[pc++]; - RuntimeArray arr = (RuntimeArray) registers[arrayReg]; - registers[rd] = RuntimeArray.shift(arr); - break; - } - - case Opcodes.ARRAY_UNSHIFT: { - // Array unshift: unshift(@array, value) - int arrayReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; - RuntimeArray arr = (RuntimeArray) registers[arrayReg]; - RuntimeBase val = registers[valueReg]; - RuntimeArray.unshift(arr, val); - break; - } - - case Opcodes.ARRAY_SIZE: { - // Array size: rd = scalar(@array) or scalar(value) - // Use polymorphic scalar() method - arrays return size, scalars return themselves - // Special case for RuntimeList: return size, not last element - int rd = bytecode[pc++]; - int operandReg = bytecode[pc++]; - RuntimeBase operand = registers[operandReg]; - if (operand instanceof RuntimeList) { - // For RuntimeList in list assignment context, return the count - registers[rd] = new RuntimeScalar(((RuntimeList) operand).size()); - } else { - registers[rd] = operand.scalar(); - } - break; + case Opcodes.ARRAY_SET -> { + pc = InlineOpcodeHandler.executeArraySet(bytecode, pc, registers); } - case Opcodes.SET_ARRAY_LAST_INDEX: { - int arrayReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; - RuntimeArray.indexLastElem((RuntimeArray) registers[arrayReg]) - .set(((RuntimeScalar) registers[valueReg])); - break; + case Opcodes.ARRAY_PUSH -> { + pc = InlineOpcodeHandler.executeArrayPush(bytecode, pc, registers); } - case Opcodes.CREATE_ARRAY: { - // Create array reference from list: rd = new RuntimeArray(rs_list).createReference() - // Array literals always return references in Perl - int rd = bytecode[pc++]; - int listReg = bytecode[pc++]; - - // Convert to list (polymorphic - works for PerlRange, RuntimeList, etc.) - RuntimeBase source = registers[listReg]; - RuntimeArray array; - if (source instanceof RuntimeArray) { - // Already an array - pass through - array = (RuntimeArray) source; - } else { - // Convert to list, then to array (works for PerlRange, RuntimeList, etc.) - RuntimeList list = source.getList(); - array = new RuntimeArray(list); - } + case Opcodes.ARRAY_POP -> { + pc = InlineOpcodeHandler.executeArrayPop(bytecode, pc, registers); + } + + case Opcodes.ARRAY_SHIFT -> { + pc = InlineOpcodeHandler.executeArrayShift(bytecode, pc, registers); + } + + case Opcodes.ARRAY_UNSHIFT -> { + pc = InlineOpcodeHandler.executeArrayUnshift(bytecode, pc, registers); + } - // Create reference (array literals always return references!) - registers[rd] = array.createReference(); - break; + case Opcodes.ARRAY_SIZE -> { + pc = InlineOpcodeHandler.executeArraySize(bytecode, pc, registers); + } + + case Opcodes.SET_ARRAY_LAST_INDEX -> { + pc = InlineOpcodeHandler.executeSetArrayLastIndex(bytecode, pc, registers); + } + + case Opcodes.CREATE_ARRAY -> { + pc = InlineOpcodeHandler.executeCreateArray(bytecode, pc, registers); } // ================================================================= // HASH OPERATIONS // ================================================================= - case Opcodes.HASH_GET: { - // Hash element access: rd = hash{key} - int rd = bytecode[pc++]; - int hashReg = bytecode[pc++]; - int keyReg = bytecode[pc++]; - RuntimeHash hash = (RuntimeHash) registers[hashReg]; - RuntimeScalar key = (RuntimeScalar) registers[keyReg]; - // Uses RuntimeHash API directly - registers[rd] = hash.get(key); - break; - } - - case Opcodes.HASH_SET: { - // Hash element store: hash{key} = value - // Must copy the value into a new scalar for the hash element, - // because the source register may be modified in-place later - // (e.g. $hash{k} = $fix; $fix = {} would clear $hash{k} otherwise) - // Uses addToScalar to properly resolve special variables ($1, $2, etc.) - int hashReg = bytecode[pc++]; - int keyReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; - RuntimeHash hash = (RuntimeHash) registers[hashReg]; - RuntimeScalar key = (RuntimeScalar) registers[keyReg]; - RuntimeBase valBase = registers[valueReg]; - RuntimeScalar val = (valBase instanceof RuntimeScalar) ? (RuntimeScalar) valBase : valBase.scalar(); - RuntimeScalar copy = new RuntimeScalar(); - val.addToScalar(copy); - hash.put(key.toString(), copy); - break; - } - - case Opcodes.HASH_EXISTS: { - // Check if hash key exists: rd = exists $hash{key} - int rd = bytecode[pc++]; - int hashReg = bytecode[pc++]; - int keyReg = bytecode[pc++]; - RuntimeHash hash = (RuntimeHash) registers[hashReg]; - RuntimeScalar key = (RuntimeScalar) registers[keyReg]; - registers[rd] = hash.exists(key); - break; + case Opcodes.HASH_GET -> { + pc = InlineOpcodeHandler.executeHashGet(bytecode, pc, registers); } - case Opcodes.HASH_DELETE: { - // Delete hash key: rd = delete $hash{key} - int rd = bytecode[pc++]; - int hashReg = bytecode[pc++]; - int keyReg = bytecode[pc++]; - RuntimeHash hash = (RuntimeHash) registers[hashReg]; - RuntimeScalar key = (RuntimeScalar) registers[keyReg]; - registers[rd] = hash.delete(key); - break; + case Opcodes.HASH_SET -> { + pc = InlineOpcodeHandler.executeHashSet(bytecode, pc, registers); } - case Opcodes.ARRAY_EXISTS: { - int rd = bytecode[pc++]; - int arrayReg = bytecode[pc++]; - int indexReg = bytecode[pc++]; - RuntimeArray array = (RuntimeArray) registers[arrayReg]; - RuntimeScalar index = (RuntimeScalar) registers[indexReg]; - registers[rd] = array.exists(index); - break; + case Opcodes.HASH_EXISTS -> { + pc = InlineOpcodeHandler.executeHashExists(bytecode, pc, registers); } - case Opcodes.ARRAY_DELETE: { - int rd = bytecode[pc++]; - int arrayReg = bytecode[pc++]; - int indexReg = bytecode[pc++]; - RuntimeArray array = (RuntimeArray) registers[arrayReg]; - RuntimeScalar index = (RuntimeScalar) registers[indexReg]; - registers[rd] = array.delete(index); - break; - } - - case Opcodes.HASH_KEYS: { - // Get hash keys: rd = keys %hash - // Call .keys() on RuntimeBase so that scalars/undef throw the proper - // "Type of arg 1 to keys must be hash or array" Perl error. - int rd = bytecode[pc++]; - int hashReg = bytecode[pc++]; - registers[rd] = registers[hashReg].keys(); - break; + case Opcodes.HASH_DELETE -> { + pc = InlineOpcodeHandler.executeHashDelete(bytecode, pc, registers); } - case Opcodes.HASH_VALUES: { - // Get hash values: rd = values %hash - // Call .values() on RuntimeBase so that scalars/undef throw the proper - // "Type of arg 1 to values must be hash or array" Perl error. - int rd = bytecode[pc++]; - int hashReg = bytecode[pc++]; - registers[rd] = registers[hashReg].values(); - break; + case Opcodes.ARRAY_EXISTS -> { + pc = InlineOpcodeHandler.executeArrayExists(bytecode, pc, registers); + } + + case Opcodes.ARRAY_DELETE -> { + pc = InlineOpcodeHandler.executeArrayDelete(bytecode, pc, registers); + } + + case Opcodes.HASH_KEYS -> { + pc = InlineOpcodeHandler.executeHashKeys(bytecode, pc, registers); + } + + case Opcodes.HASH_VALUES -> { + pc = InlineOpcodeHandler.executeHashValues(bytecode, pc, registers); } // ================================================================= // SUBROUTINE CALLS // ================================================================= - case Opcodes.CALL_SUB: { + case Opcodes.CALL_SUB -> { // Call subroutine: rd = coderef->(args) // May return RuntimeControlFlowList! int rd = bytecode[pc++]; @@ -1206,10 +842,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c return result; } } - break; } - case Opcodes.CALL_METHOD: { + case Opcodes.CALL_METHOD -> { // Call method: rd = RuntimeCode.call(invocant, method, currentSub, args, context) // May return RuntimeControlFlowList! int rd = bytecode[pc++]; @@ -1264,475 +899,309 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c return result; } } - break; } // ================================================================= // CONTROL FLOW - SPECIAL (RuntimeControlFlowList) // ================================================================= - case Opcodes.CREATE_LAST: { - // Create LAST control flow: rd = RuntimeControlFlowList(LAST, label) - int rd = bytecode[pc++]; - int labelIdx = bytecode[pc++]; - String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; - registers[rd] = new RuntimeControlFlowList( - ControlFlowType.LAST, label, - code.sourceName, code.sourceLine - ); - break; + case Opcodes.CREATE_LAST -> { + pc = InlineOpcodeHandler.executeCreateLast(bytecode, pc, registers, code); } - case Opcodes.CREATE_NEXT: { - // Create NEXT control flow: rd = RuntimeControlFlowList(NEXT, label) - int rd = bytecode[pc++]; - int labelIdx = bytecode[pc++]; - String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; - registers[rd] = new RuntimeControlFlowList( - ControlFlowType.NEXT, label, - code.sourceName, code.sourceLine - ); - break; + case Opcodes.CREATE_NEXT -> { + pc = InlineOpcodeHandler.executeCreateNext(bytecode, pc, registers, code); } - case Opcodes.CREATE_REDO: { - // Create REDO control flow: rd = RuntimeControlFlowList(REDO, label) - int rd = bytecode[pc++]; - int labelIdx = bytecode[pc++]; - String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; - registers[rd] = new RuntimeControlFlowList( - ControlFlowType.REDO, label, - code.sourceName, code.sourceLine - ); - break; + case Opcodes.CREATE_REDO -> { + pc = InlineOpcodeHandler.executeCreateRedo(bytecode, pc, registers, code); } - case Opcodes.CREATE_GOTO: { - int rd = bytecode[pc++]; - int labelIdx = bytecode[pc++]; - String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; - registers[rd] = new RuntimeControlFlowList( - ControlFlowType.GOTO, label, - code.sourceName, code.sourceLine - ); - break; + case Opcodes.CREATE_GOTO -> { + pc = InlineOpcodeHandler.executeCreateGoto(bytecode, pc, registers, code); } - case Opcodes.IS_CONTROL_FLOW: { - // Check if value is control flow: rd = (rs instanceof RuntimeControlFlowList) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - boolean isControlFlow = registers[rs] instanceof RuntimeControlFlowList; - registers[rd] = isControlFlow ? - RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; - break; + case Opcodes.IS_CONTROL_FLOW -> { + pc = InlineOpcodeHandler.executeIsControlFlow(bytecode, pc, registers); } // ================================================================= // MISCELLANEOUS // ================================================================= - case Opcodes.PRINT: + case Opcodes.PRINT -> { // Print to filehandle // Format: PRINT contentReg filehandleReg pc = OpcodeHandlerExtended.executePrint(bytecode, pc, registers); - break; + } - case Opcodes.SAY: + case Opcodes.SAY -> { // Say to filehandle // Format: SAY contentReg filehandleReg pc = OpcodeHandlerExtended.executeSay(bytecode, pc, registers); - break; + } // ================================================================= // SUPERINSTRUCTIONS - Eliminate ALIAS overhead // ================================================================= - case Opcodes.INC_REG: { - // Increment register in-place: r++ - int rd = bytecode[pc++]; - RuntimeBase incResult = MathOperators.add((RuntimeScalar) registers[rd], 1); - registers[rd] = (isImmutableProxy(incResult)) ? ensureMutableScalar(incResult) : incResult; - break; + case Opcodes.INC_REG -> { + pc = InlineOpcodeHandler.executeIncReg(bytecode, pc, registers); } - case Opcodes.DEC_REG: { - // Decrement register in-place: r-- - int rd = bytecode[pc++]; - RuntimeBase decResult = MathOperators.subtract((RuntimeScalar) registers[rd], 1); - registers[rd] = (isImmutableProxy(decResult)) ? ensureMutableScalar(decResult) : decResult; - break; + case Opcodes.DEC_REG -> { + pc = InlineOpcodeHandler.executeDecReg(bytecode, pc, registers); } - case Opcodes.ADD_ASSIGN: { - // Add and assign: rd += rs (modifies rd in place) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - if (isImmutableProxy(registers[rd])) { - registers[rd] = ensureMutableScalar(registers[rd]); - } - MathOperators.addAssign( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] - ); - break; + case Opcodes.ADD_ASSIGN -> { + pc = InlineOpcodeHandler.executeAddAssign(bytecode, pc, registers); } - case Opcodes.ADD_ASSIGN_INT: { - // Add immediate and assign: rd += imm (modifies rd in place) - int rd = bytecode[pc++]; - int immediate = readInt(bytecode, pc); - pc += 1; - if (isImmutableProxy(registers[rd])) { - registers[rd] = ensureMutableScalar(registers[rd]); - } - RuntimeScalar result = MathOperators.add((RuntimeScalar) registers[rd], immediate); - ((RuntimeScalar) registers[rd]).set(result); - break; + case Opcodes.ADD_ASSIGN_INT -> { + pc = InlineOpcodeHandler.executeAddAssignInt(bytecode, pc, registers); } - case Opcodes.STRING_CONCAT_ASSIGN: + 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: + 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: + 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: + 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: + 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: + 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: + 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 // 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 // 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 // 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 // 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 // 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 // Format: STRING_BITWISE_XOR rd rs1 rs2 pc = OpcodeHandlerExtended.executeStringBitwiseXor(bytecode, pc, registers); - break; + } - case Opcodes.XOR_LOGICAL: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = Operator.xor((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); - break; + case Opcodes.XOR_LOGICAL -> { + pc = InlineOpcodeHandler.executeXorLogical(bytecode, pc, registers); } - case Opcodes.BITWISE_NOT_BINARY: + case Opcodes.BITWISE_NOT_BINARY -> { // Numeric bitwise NOT: rd = binary~ 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 // Format: BITWISE_NOT_STRING rd rs pc = OpcodeHandlerExtended.executeBitwiseNotString(bytecode, pc, registers); - break; + } // File test and stat operations - case Opcodes.STAT: + case Opcodes.STAT -> { pc = OpcodeHandlerExtended.executeStat(bytecode, pc, registers); - break; + } - case Opcodes.LSTAT: + case Opcodes.LSTAT -> { pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers); - break; + } - case Opcodes.STAT_LASTHANDLE: + case Opcodes.STAT_LASTHANDLE -> { pc = OpcodeHandlerExtended.executeStatLastHandle(bytecode, pc, registers); - break; + } - case Opcodes.LSTAT_LASTHANDLE: + case Opcodes.LSTAT_LASTHANDLE -> { pc = OpcodeHandlerExtended.executeLstatLastHandle(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: + case Opcodes.FILETEST_R, Opcodes.FILETEST_W, Opcodes.FILETEST_X, Opcodes.FILETEST_O, Opcodes.FILETEST_R_REAL, Opcodes.FILETEST_W_REAL, Opcodes.FILETEST_X_REAL, Opcodes.FILETEST_O_REAL, Opcodes.FILETEST_E, Opcodes.FILETEST_Z, Opcodes.FILETEST_S, Opcodes.FILETEST_F, Opcodes.FILETEST_D, Opcodes.FILETEST_L, Opcodes.FILETEST_P, Opcodes.FILETEST_S_UPPER, Opcodes.FILETEST_B, Opcodes.FILETEST_C, Opcodes.FILETEST_T, Opcodes.FILETEST_U, Opcodes.FILETEST_G, Opcodes.FILETEST_K, Opcodes.FILETEST_T_UPPER, Opcodes.FILETEST_B_UPPER, Opcodes.FILETEST_M, Opcodes.FILETEST_A, Opcodes.FILETEST_C_UPPER -> { pc = OpcodeHandlerFileTest.executeFileTest(bytecode, pc, registers, opcode); - break; + } - case Opcodes.PUSH_LOCAL_VARIABLE: { - // Push variable to local stack: DynamicVariableManager.pushLocalVariable(rs) - int rs = bytecode[pc++]; - DynamicVariableManager.pushLocalVariable(registers[rs]); - break; + case Opcodes.PUSH_LOCAL_VARIABLE -> { + pc = InlineOpcodeHandler.executePushLocalVariable(bytecode, pc, registers); } - case Opcodes.STORE_GLOB: { - int globReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; - Object val = registers[valueReg]; - RuntimeScalar scalarVal = (val instanceof RuntimeScalar) - ? (RuntimeScalar) val - : ((RuntimeList) val).scalar(); - ((RuntimeGlob) registers[globReg]).set(scalarVal); - break; + case Opcodes.STORE_GLOB -> { + pc = InlineOpcodeHandler.executeStoreGlob(bytecode, pc, registers); } - case Opcodes.OPEN: + case Opcodes.OPEN -> { // Open file: rd = IOOperator.open(ctx, args...) // Format: OPEN rd ctx argsReg pc = OpcodeHandlerExtended.executeOpen(bytecode, pc, registers); - break; + } - case Opcodes.READLINE: + case Opcodes.READLINE -> { // Read line from filehandle // Format: READLINE rd fhReg ctx pc = OpcodeHandlerExtended.executeReadline(bytecode, pc, registers); - break; + } - case Opcodes.MATCH_REGEX: + 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: + 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() // Format: CHOMP rd rs pc = OpcodeHandlerExtended.executeChomp(bytecode, pc, registers); - break; + } - case Opcodes.WANTARRAY: + case Opcodes.WANTARRAY -> { // Get wantarray context // Format: WANTARRAY rd wantarrayReg pc = OpcodeHandlerExtended.executeWantarray(bytecode, pc, registers); - break; + } - case Opcodes.REQUIRE: + case Opcodes.REQUIRE -> { // Require module or version // Format: REQUIRE rd rs pc = OpcodeHandlerExtended.executeRequire(bytecode, pc, registers); - break; + } - case Opcodes.POS: + case Opcodes.POS -> { // Get regex position // Format: POS rd rs pc = OpcodeHandlerExtended.executePos(bytecode, pc, registers); - break; + } - case Opcodes.INDEX: + case Opcodes.INDEX -> { // Find substring position // Format: INDEX rd strReg substrReg posReg pc = OpcodeHandlerExtended.executeIndex(bytecode, pc, registers); - break; + } - case Opcodes.RINDEX: + 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 // Format: PRE_AUTOINCREMENT rd pc = OpcodeHandlerExtended.executePreAutoIncrement(bytecode, pc, registers); - break; + } - case Opcodes.POST_AUTOINCREMENT: + case Opcodes.POST_AUTOINCREMENT -> { // Post-increment: rd = rs++ // Format: POST_AUTOINCREMENT rd rs pc = OpcodeHandlerExtended.executePostAutoIncrement(bytecode, pc, registers); - break; + } - case Opcodes.PRE_AUTODECREMENT: + case Opcodes.PRE_AUTODECREMENT -> { // Pre-decrement: --rd // Format: PRE_AUTODECREMENT rd pc = OpcodeHandlerExtended.executePreAutoDecrement(bytecode, pc, registers); - break; + } - case Opcodes.POST_AUTODECREMENT: + case Opcodes.POST_AUTODECREMENT -> { // Post-decrement: rd = rs-- // Format: POST_AUTODECREMENT rd rs pc = OpcodeHandlerExtended.executePostAutoDecrement(bytecode, pc, registers); - break; + } // ================================================================= // ERROR HANDLING // ================================================================= - case Opcodes.DIE: { - // Die with message and precomputed location: die(msgReg, locationReg) - int msgReg = bytecode[pc++]; - int locationReg = bytecode[pc++]; - RuntimeBase message = registers[msgReg]; - RuntimeScalar where = (RuntimeScalar) registers[locationReg]; - - // Call WarnDie.die() with precomputed location (zero overhead!) - WarnDie.die(message, where, code.sourceName, code.sourceLine); - - // Should never reach here (die throws exception) - throw new RuntimeException("die() did not throw exception"); + case Opcodes.DIE -> { + pc = InlineOpcodeHandler.executeDie(bytecode, pc, registers, code); } - case Opcodes.WARN: { - // Warn with message and precomputed location: warn(msgReg, locationReg) - int msgReg = bytecode[pc++]; - int locationReg = bytecode[pc++]; - RuntimeBase message = registers[msgReg]; - RuntimeScalar where = (RuntimeScalar) registers[locationReg]; - - // Call WarnDie.warn() with precomputed location - WarnDie.warn(message, where, code.sourceName, code.sourceLine); - break; + case Opcodes.WARN -> { + pc = InlineOpcodeHandler.executeWarn(bytecode, pc, registers, code); } // ================================================================= // REFERENCE OPERATIONS // ================================================================= - case Opcodes.CREATE_REF: { - // Create reference: rd = rs.createReference() - // For lists, create a list of references to each element - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase value = registers[rs]; - - if (value == null) { - // Null value - return undef - registers[rd] = RuntimeScalarCache.scalarUndef; - } else if (value instanceof RuntimeList list) { - if (list.size() == 1) { - registers[rd] = list.getFirst().createReference(); - } else { - registers[rd] = list.createListReference(); - } - } else { - registers[rd] = value.createReference(); - } - break; + case Opcodes.CREATE_REF -> { + pc = InlineOpcodeHandler.executeCreateRef(bytecode, pc, registers); } - case Opcodes.DEREF: { - // Dereference: rd = $$rs (scalar reference dereference) - // Can receive RuntimeScalar or RuntimeList - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase value = registers[rs]; - - // Only dereference if it's a RuntimeScalar with REFERENCE type - if (value instanceof RuntimeScalar) { - RuntimeScalar scalar = (RuntimeScalar) value; - if (scalar.type == RuntimeScalarType.REFERENCE) { - registers[rd] = scalar.scalarDeref(); - } else { - // Non-reference scalar, just copy - registers[rd] = value; - } - } else { - // RuntimeList or other types, pass through - registers[rd] = value; - } - break; + case Opcodes.DEREF -> { + pc = InlineOpcodeHandler.executeDeref(bytecode, pc, registers); } - case Opcodes.GET_TYPE: { - // Get type: rd = new RuntimeScalar(rs.type) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar value = (RuntimeScalar) registers[rs]; - // RuntimeScalar.type is an int constant from RuntimeScalarType - registers[rd] = new RuntimeScalar(value.type); - break; + case Opcodes.GET_TYPE -> { + pc = InlineOpcodeHandler.executeGetType(bytecode, pc, registers); } // ================================================================= // EVAL BLOCK SUPPORT // ================================================================= - case Opcodes.EVAL_TRY: { + case Opcodes.EVAL_TRY -> { // Start of eval block with exception handling // Format: [EVAL_TRY] [catch_target_high] [catch_target_low] // catch_target is absolute bytecode address (4 bytes) @@ -1748,10 +1217,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // Continue execution - if exception occurs, outer catch handler // will check evalCatchStack and jump to catchPc - break; } - case Opcodes.EVAL_END: { + case Opcodes.EVAL_END -> { // End of successful eval block - clear $@ and pop catch stack GlobalVariable.setGlobalVariable("main::@", ""); @@ -1759,10 +1227,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c if (!evalCatchStack.isEmpty()) { evalCatchStack.pop(); } - break; } - case Opcodes.EVAL_CATCH: { + case Opcodes.EVAL_CATCH -> { // Exception handler for eval block // Format: [EVAL_CATCH] [rd] // This is only reached when an exception is caught @@ -1772,295 +1239,99 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // WarnDie.catchEval() should have already been called to set $@ // Just store undef as the eval result registers[rd] = RuntimeScalarCache.scalarUndef; - break; } // ================================================================= // LABELED BLOCK SUPPORT // ================================================================= - case Opcodes.PUSH_LABELED_BLOCK: { + case Opcodes.PUSH_LABELED_BLOCK -> { int labelIdx = bytecode[pc++]; int exitPc = readInt(bytecode, pc); pc += 1; labeledBlockStack.push(new int[]{labelIdx, exitPc}); - break; } - case Opcodes.POP_LABELED_BLOCK: { + case Opcodes.POP_LABELED_BLOCK -> { if (!labeledBlockStack.isEmpty()) { labeledBlockStack.pop(); } - break; } // ================================================================= // LIST OPERATIONS // ================================================================= - case Opcodes.LIST_TO_COUNT: { - // Convert list/array to its count in scalar context (e.g. @arr used as number) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase val = registers[rs]; - if (val instanceof RuntimeList) { - registers[rd] = new RuntimeScalar(((RuntimeList) val).elements.size()); - } else if (val instanceof RuntimeArray) { - registers[rd] = new RuntimeScalar(((RuntimeArray) val).size()); - } else { - registers[rd] = val.scalar(); - } - break; + case Opcodes.LIST_TO_COUNT -> { + pc = InlineOpcodeHandler.executeListToCount(bytecode, pc, registers); } - case Opcodes.LIST_TO_SCALAR: { - // Convert list to scalar context: returns last element (Perl list-in-scalar semantics) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = registers[rs].scalar(); - break; + case Opcodes.LIST_TO_SCALAR -> { + pc = InlineOpcodeHandler.executeListToScalar(bytecode, pc, registers); } - case Opcodes.SCALAR_TO_LIST: { - // Convert value to RuntimeList, preserving aggregate types (PerlRange, RuntimeArray) - // so that consumers like Pack.pack() can iterate them via RuntimeList's iterator. - // List assignment flattening is handled by SET_FROM_LIST (setFromList method). - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase val = registers[rs]; - if (val instanceof RuntimeList) { - registers[rd] = val; - } else if (val instanceof RuntimeScalar) { - RuntimeList list = new RuntimeList(); - list.elements.add(val); - registers[rd] = list; - } else { - // RuntimeArray, PerlRange, etc. - wrap in list, preserving type - RuntimeList list = new RuntimeList(); - list.elements.add(val); - registers[rd] = list; - } - break; + case Opcodes.SCALAR_TO_LIST -> { + pc = InlineOpcodeHandler.executeScalarToList(bytecode, pc, registers); } - case Opcodes.CREATE_LIST: { - // Create RuntimeList from registers - // Format: [CREATE_LIST] [rd] [count] [rs1] [rs2] ... [rsN] - - int rd = bytecode[pc++]; - int count = bytecode[pc++]; - - // Optimize for common cases - if (count == 0) { - // Empty list - fastest path - registers[rd] = new RuntimeList(); - } else if (count == 1) { - // Single element - avoid loop overhead - int rs = bytecode[pc++]; - RuntimeList list = new RuntimeList(); - list.add(registers[rs]); - registers[rd] = list; - } else { - // Multiple elements - preallocate and populate - RuntimeList list = new RuntimeList(); - - // Read all register indices and add elements - for (int i = 0; i < count; i++) { - int rs = bytecode[pc++]; - list.add(registers[rs]); - } - - registers[rd] = list; - } - break; + case Opcodes.CREATE_LIST -> { + pc = InlineOpcodeHandler.executeCreateList(bytecode, pc, registers); } // ================================================================= // STRING OPERATIONS // ================================================================= - case Opcodes.JOIN: { - // String join: rd = join(separator, list) - int rd = bytecode[pc++]; - int separatorReg = bytecode[pc++]; - int listReg = bytecode[pc++]; - - // Separator should be scalar - convert if needed - RuntimeBase separatorBase = registers[separatorReg]; - RuntimeScalar separator = (separatorBase instanceof RuntimeScalar) - ? (RuntimeScalar) separatorBase - : separatorBase.scalar(); - - RuntimeBase list = registers[listReg]; - - // Call StringOperators.joinForInterpolation (doesn't warn on undef) - registers[rd] = StringOperators.joinForInterpolation(separator, list); - break; + case Opcodes.JOIN -> { + pc = InlineOpcodeHandler.executeJoin(bytecode, pc, registers); } - case Opcodes.SELECT: { - // Select default output filehandle: rd = IOOperator.select(list, SCALAR) - int rd = bytecode[pc++]; - int listReg = bytecode[pc++]; - - // registers[listReg] may be a RuntimeList (from CREATE_LIST) or a - // RuntimeScalar (from LOAD_UNDEF when an empty ListNode is compiled). - RuntimeBase listBase = registers[listReg]; - RuntimeList list = (listBase instanceof RuntimeList rl) - ? rl : listBase.getList(); - RuntimeScalar result = IOOperator.select(list, RuntimeContextType.SCALAR); - registers[rd] = result; - break; + case Opcodes.SELECT -> { + pc = InlineOpcodeHandler.executeSelect(bytecode, pc, registers); } - case Opcodes.RANGE: { - // Create range: rd = PerlRange.createRange(rs_start, rs_end) - int rd = bytecode[pc++]; - int startReg = bytecode[pc++]; - int endReg = bytecode[pc++]; - - RuntimeBase startBase = registers[startReg]; - RuntimeBase endBase = registers[endReg]; - - // Handle null registers by creating undef scalars - RuntimeScalar start = (startBase instanceof RuntimeScalar) ? (RuntimeScalar) startBase : - (startBase == null) ? new RuntimeScalar() : startBase.scalar(); - RuntimeScalar end = (endBase instanceof RuntimeScalar) ? (RuntimeScalar) endBase : - (endBase == null) ? new RuntimeScalar() : endBase.scalar(); - - PerlRange range = PerlRange.createRange(start, end); - registers[rd] = range; - break; + case Opcodes.RANGE -> { + pc = InlineOpcodeHandler.executeRange(bytecode, pc, registers); } - case Opcodes.CREATE_HASH: { - // Create hash reference from list: rd = RuntimeHash.createHash(rs_list).createReference() - // Hash literals always return references in Perl - // This flattens any arrays in the list and creates key-value pairs - int rd = bytecode[pc++]; - int listReg = bytecode[pc++]; - - RuntimeBase list = registers[listReg]; - RuntimeHash hash = RuntimeHash.createHash(list); - - // Create reference (hash literals always return references!) - registers[rd] = hash.createReference(); - break; + case Opcodes.CREATE_HASH -> { + pc = InlineOpcodeHandler.executeCreateHash(bytecode, pc, registers); } - case Opcodes.RAND: { - // Random number: rd = Random.rand(max) - int rd = bytecode[pc++]; - int maxReg = bytecode[pc++]; - - RuntimeScalar max = (RuntimeScalar) registers[maxReg]; - registers[rd] = Random.rand(max); - break; + case Opcodes.RAND -> { + pc = InlineOpcodeHandler.executeRand(bytecode, pc, registers); } - case Opcodes.MAP: { - // Map operator: rd = ListOperators.map(list, closure, ctx) - int rd = bytecode[pc++]; - int listReg = bytecode[pc++]; - int closureReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - - RuntimeBase listBase = registers[listReg]; - RuntimeList list = listBase.getList(); - RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; - RuntimeList result = ListOperators.map(list, closure, ctx); - registers[rd] = result; - break; + case Opcodes.MAP -> { + pc = InlineOpcodeHandler.executeMap(bytecode, pc, registers); } - case Opcodes.GREP: { - // Grep operator: rd = ListOperators.grep(list, closure, ctx) - int rd = bytecode[pc++]; - int listReg = bytecode[pc++]; - int closureReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - - RuntimeBase listBase = registers[listReg]; - RuntimeList list = listBase.getList(); - RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; - RuntimeList result = ListOperators.grep(list, closure, ctx); - registers[rd] = result; - break; + case Opcodes.GREP -> { + pc = InlineOpcodeHandler.executeGrep(bytecode, pc, registers); } - case Opcodes.SORT: { - // Sort operator: rd = ListOperators.sort(list, closure, package) - int rd = bytecode[pc++]; - int listReg = bytecode[pc++]; - int closureReg = bytecode[pc++]; - int packageIdx = readInt(bytecode, pc); - pc += 1; - - RuntimeBase listBase = registers[listReg]; - RuntimeList list = listBase.getList(); - RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; - String packageName = code.stringPool[packageIdx]; - RuntimeList result = ListOperators.sort(list, closure, packageName); - registers[rd] = result; - break; + case Opcodes.SORT -> { + pc = InlineOpcodeHandler.executeSort(bytecode, pc, registers, code); } - case Opcodes.NEW_ARRAY: { - // Create empty array: rd = new RuntimeArray() - int rd = bytecode[pc++]; - registers[rd] = new RuntimeArray(); - break; + case Opcodes.NEW_ARRAY -> { + pc = InlineOpcodeHandler.executeNewArray(bytecode, pc, registers); } - case Opcodes.NEW_HASH: { - // Create empty hash: rd = new RuntimeHash() - int rd = bytecode[pc++]; - registers[rd] = new RuntimeHash(); - break; + case Opcodes.NEW_HASH -> { + pc = InlineOpcodeHandler.executeNewHash(bytecode, pc, registers); } - case Opcodes.ARRAY_SET_FROM_LIST: { - // Set array content from list: array_reg.setFromList(list_reg) - // Format: [ARRAY_SET_FROM_LIST] [array_reg] [list_reg] - int arrayReg = bytecode[pc++]; - int listReg = bytecode[pc++]; - - RuntimeArray array = (RuntimeArray) registers[arrayReg]; - RuntimeBase listBase = registers[listReg]; - RuntimeList list = listBase.getList(); - - // setFromList clears and repopulates the array - array.setFromList(list); - break; + case Opcodes.ARRAY_SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeArraySetFromList(bytecode, pc, registers); } - case Opcodes.SET_FROM_LIST: { - // List assignment: rd = lhsList.setFromList(rhsList) - // Matches JVM backend's RuntimeBase.setFromList() call - int rd = bytecode[pc++]; - int lhsReg = bytecode[pc++]; - int rhsReg = bytecode[pc++]; - RuntimeList lhsList = (RuntimeList) registers[lhsReg]; - RuntimeBase rhsBase = registers[rhsReg]; - RuntimeList rhsList = (rhsBase instanceof RuntimeList rl) ? rl : rhsBase.getList(); - RuntimeArray result = lhsList.setFromList(rhsList); - registers[rd] = result; - break; + case Opcodes.SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeSetFromList(bytecode, pc, registers); } - case Opcodes.HASH_SET_FROM_LIST: { - // Set hash content from list: hash_reg = RuntimeHash.createHash(list_reg) - // Format: [HASH_SET_FROM_LIST] [hash_reg] [list_reg] - int hashReg = bytecode[pc++]; - int listReg = bytecode[pc++]; - - RuntimeHash existingHash = (RuntimeHash) registers[hashReg]; - RuntimeBase listBase = registers[listReg]; - - // Create new hash from list, then copy elements to existing hash - RuntimeHash newHash = RuntimeHash.createHash(listBase); - existingHash.elements = newHash.elements; - break; + case Opcodes.HASH_SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeHashSetFromList(bytecode, pc, registers); } // ================================================================= @@ -2070,81 +1341,33 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // Organized in CONTIGUOUS groups for JVM tableswitch optimization. // Group 1-2: Dereferencing and Slicing (114-121) - case Opcodes.DEREF_ARRAY: - case Opcodes.DEREF_HASH: - case Opcodes.DEREF_HASH_NONSTRICT: - case Opcodes.DEREF_ARRAY_NONSTRICT: - case Opcodes.ARRAY_SLICE: - case Opcodes.ARRAY_SLICE_SET: - case Opcodes.HASH_SLICE: - case Opcodes.HASH_SLICE_SET: - case Opcodes.HASH_SLICE_DELETE: - case Opcodes.HASH_KEYVALUE_SLICE: - case Opcodes.LIST_SLICE_FROM: + case Opcodes.DEREF_ARRAY, Opcodes.DEREF_HASH, Opcodes.DEREF_HASH_NONSTRICT, Opcodes.DEREF_ARRAY_NONSTRICT, Opcodes.ARRAY_SLICE, Opcodes.ARRAY_SLICE_SET, Opcodes.HASH_SLICE, Opcodes.HASH_SLICE_SET, Opcodes.HASH_SLICE_DELETE, Opcodes.HASH_KEYVALUE_SLICE, Opcodes.LIST_SLICE_FROM -> { pc = executeSliceOps(opcode, bytecode, pc, registers, code); - break; + } // Group 3-4: Array/String/Exists/Delete (122-127) - case Opcodes.SPLICE: - case Opcodes.REVERSE: - case Opcodes.SPLIT: - case Opcodes.LENGTH_OP: - case Opcodes.EXISTS: - case Opcodes.DELETE: + case Opcodes.SPLICE, Opcodes.REVERSE, Opcodes.SPLIT, Opcodes.LENGTH_OP, Opcodes.EXISTS, Opcodes.DELETE -> { pc = executeArrayStringOps(opcode, bytecode, pc, registers, code); - break; + } // Group 5: Closure/Scope (128-131) - case Opcodes.RETRIEVE_BEGIN_SCALAR: - case Opcodes.RETRIEVE_BEGIN_ARRAY: - case Opcodes.RETRIEVE_BEGIN_HASH: - case Opcodes.LOCAL_SCALAR: - case Opcodes.LOCAL_ARRAY: - case Opcodes.LOCAL_HASH: + case Opcodes.RETRIEVE_BEGIN_SCALAR, Opcodes.RETRIEVE_BEGIN_ARRAY, Opcodes.RETRIEVE_BEGIN_HASH, Opcodes.LOCAL_SCALAR, Opcodes.LOCAL_ARRAY, Opcodes.LOCAL_HASH -> { pc = executeScopeOps(opcode, bytecode, pc, registers, code); - break; + } // Group 6-8: System Calls and IPC (132-150) - case Opcodes.CHOWN: - case Opcodes.WAITPID: - case Opcodes.FORK: - case Opcodes.GETPPID: - case Opcodes.GETPGRP: - case Opcodes.SETPGRP: - case Opcodes.GETPRIORITY: - case Opcodes.SETPRIORITY: - case Opcodes.GETSOCKOPT: - case Opcodes.SETSOCKOPT: - case Opcodes.SYSCALL: - case Opcodes.SEMGET: - case Opcodes.SEMOP: - case Opcodes.MSGGET: - case Opcodes.MSGSND: - case Opcodes.MSGRCV: - case Opcodes.SHMGET: - case Opcodes.SHMREAD: - case Opcodes.SHMWRITE: + case Opcodes.CHOWN, Opcodes.WAITPID, Opcodes.FORK, Opcodes.GETPPID, Opcodes.GETPGRP, Opcodes.SETPGRP, Opcodes.GETPRIORITY, Opcodes.SETPRIORITY, Opcodes.GETSOCKOPT, Opcodes.SETSOCKOPT, Opcodes.SYSCALL, Opcodes.SEMGET, Opcodes.SEMOP, Opcodes.MSGGET, Opcodes.MSGSND, Opcodes.MSGRCV, Opcodes.SHMGET, Opcodes.SHMREAD, Opcodes.SHMWRITE -> { pc = executeSystemOps(opcode, bytecode, pc, registers); - break; + } // Group 9: Special I/O (151-154), glob ops, strict deref - case Opcodes.TIME_OP: { + case Opcodes.TIME_OP -> { int rd = bytecode[pc++]; registers[rd] = org.perlonjava.runtime.operators.Time.time(); - break; - } - case Opcodes.EVAL_STRING: - case Opcodes.SELECT_OP: - case Opcodes.LOAD_GLOB: - case Opcodes.SLEEP_OP: - case Opcodes.ALARM_OP: - case Opcodes.DEREF_GLOB: - case Opcodes.DEREF_GLOB_NONSTRICT: - case Opcodes.LOAD_GLOB_DYNAMIC: - case Opcodes.DEREF_SCALAR_STRICT: - case Opcodes.DEREF_SCALAR_NONSTRICT: + } + case Opcodes.EVAL_STRING, Opcodes.SELECT_OP, Opcodes.LOAD_GLOB, Opcodes.SLEEP_OP, Opcodes.ALARM_OP, Opcodes.DEREF_GLOB, Opcodes.DEREF_GLOB_NONSTRICT, Opcodes.LOAD_GLOB_DYNAMIC, Opcodes.DEREF_SCALAR_STRICT, Opcodes.DEREF_SCALAR_NONSTRICT -> { pc = executeSpecialIO(opcode, bytecode, pc, registers, code); - break; + } // ================================================================= // SLOW OPERATIONS (DEPRECATED) @@ -2161,315 +1384,120 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // GENERATED_HANDLERS_START // scalar_binary - case Opcodes.ATAN2: - case Opcodes.BINARY_AND: - case Opcodes.BINARY_OR: - case Opcodes.BINARY_XOR: - case Opcodes.EQ: - case Opcodes.NE: - case Opcodes.LT: - case Opcodes.LE: - case Opcodes.GT: - case Opcodes.GE: - case Opcodes.CMP: - case Opcodes.X: + case Opcodes.ATAN2, Opcodes.BINARY_AND, Opcodes.BINARY_OR, Opcodes.BINARY_XOR, Opcodes.EQ, Opcodes.NE, Opcodes.LT, Opcodes.LE, Opcodes.GT, Opcodes.GE, Opcodes.CMP, Opcodes.X -> { pc = ScalarBinaryOpcodeHandler.execute(opcode, bytecode, pc, registers); - break; + } // scalar_unary - case Opcodes.INT: - case Opcodes.LOG: - case Opcodes.SQRT: - case Opcodes.COS: - case Opcodes.SIN: - case Opcodes.EXP: - case Opcodes.ABS: - case Opcodes.BINARY_NOT: - case Opcodes.INTEGER_BITWISE_NOT: - case Opcodes.ORD: - case Opcodes.ORD_BYTES: - case Opcodes.OCT: - case Opcodes.HEX: - case Opcodes.SRAND: - case Opcodes.CHR: - case Opcodes.CHR_BYTES: - case Opcodes.LENGTH_BYTES: - case Opcodes.QUOTEMETA: - case Opcodes.FC: - case Opcodes.LC: - case Opcodes.LCFIRST: - case Opcodes.UC: - case Opcodes.UCFIRST: - case Opcodes.SLEEP: - case Opcodes.TELL: - case Opcodes.RMDIR: - case Opcodes.CLOSEDIR: - case Opcodes.REWINDDIR: - case Opcodes.TELLDIR: - case Opcodes.CHDIR: - case Opcodes.EXIT: + case Opcodes.INT, Opcodes.LOG, Opcodes.SQRT, Opcodes.COS, Opcodes.SIN, Opcodes.EXP, Opcodes.ABS, Opcodes.BINARY_NOT, Opcodes.INTEGER_BITWISE_NOT, Opcodes.ORD, Opcodes.ORD_BYTES, Opcodes.OCT, Opcodes.HEX, Opcodes.SRAND, Opcodes.CHR, Opcodes.CHR_BYTES, Opcodes.LENGTH_BYTES, Opcodes.QUOTEMETA, Opcodes.FC, Opcodes.LC, Opcodes.LCFIRST, Opcodes.UC, Opcodes.UCFIRST, Opcodes.SLEEP, Opcodes.TELL, Opcodes.RMDIR, Opcodes.CLOSEDIR, Opcodes.REWINDDIR, Opcodes.TELLDIR, Opcodes.CHDIR, Opcodes.EXIT -> { pc = ScalarUnaryOpcodeHandler.execute(opcode, bytecode, pc, registers); - break; + } // GENERATED_HANDLERS_END - case Opcodes.TR_TRANSLITERATE: + case Opcodes.TR_TRANSLITERATE -> { pc = SlowOpcodeHandler.executeTransliterate(bytecode, pc, registers); - break; - - case Opcodes.STORE_SYMBOLIC_SCALAR: { - // Store via symbolic reference: GlobalVariable.getGlobalVariable(nameReg.toString()).set(valueReg) - // Format: STORE_SYMBOLIC_SCALAR nameReg valueReg - int nameReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; - - // Get the variable name from the name register - RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg]; - String varName = nameScalar.toString(); - - // Normalize the variable name to include package prefix if needed - // This is important for ${label:var} cases where "colon" becomes "main::colon" - String normalizedName = NameNormalizer.normalizeVariableName( - varName, - "main" // Use main package as default for symbolic references - ); - - // Get the global variable and set its value - RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName); - RuntimeBase value = registers[valueReg]; - globalVar.set(value); - break; } - case Opcodes.LOAD_SYMBOLIC_SCALAR: { - // Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get() - // OR dereference if nameReg contains a scalar reference - // Format: LOAD_SYMBOLIC_SCALAR rd nameReg - int rd = bytecode[pc++]; - int nameReg = bytecode[pc++]; - - // Get the value from the name register - RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg]; + case Opcodes.STORE_SYMBOLIC_SCALAR -> { + pc = InlineOpcodeHandler.executeStoreSymbolicScalar(bytecode, pc, registers); + } - // Check if it's a scalar reference - if so, dereference it - if (nameScalar.type == RuntimeScalarType.REFERENCE) { - // This is ${\ref} - dereference the reference - registers[rd] = nameScalar.scalarDeref(); - } else { - // This is ${"varname"} - symbolic reference to variable - String varName = nameScalar.toString(); - - // Normalize the variable name to include package prefix if needed - // This is important for ${label:var} cases where "colon" becomes "main::colon" - String normalizedName = NameNormalizer.normalizeVariableName( - varName, - "main" // Use main package as default for symbolic references - ); - - // Get the global variable and load its value - RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName); - registers[rd] = globalVar; - } - break; + case Opcodes.LOAD_SYMBOLIC_SCALAR -> { + pc = InlineOpcodeHandler.executeLoadSymbolicScalar(bytecode, pc, registers); } - case Opcodes.FILETEST_LASTHANDLE: { + case Opcodes.FILETEST_LASTHANDLE -> { // File test on cached handle '_': rd = FileTestOperator.fileTestLastHandle(operator) // Format: FILETEST_LASTHANDLE rd operator_string_idx pc = SlowOpcodeHandler.executeFiletestLastHandle(bytecode, pc, registers, code); - break; } - case Opcodes.GLOB_SLOT_GET: { + case Opcodes.GLOB_SLOT_GET -> { // Glob slot access: rd = glob.hashDerefGetNonStrict(key, pkg) // Format: GLOB_SLOT_GET rd globReg keyReg pc = SlowOpcodeHandler.executeGlobSlotGet(bytecode, pc, registers); - break; } - case Opcodes.SPRINTF: + 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: + 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: + 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: + 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; + } - case Opcodes.TIE: { - // tie($var, $classname, @args): rd = TieOperators.tie(ctx, argsListReg) - // Format: TIE rd argsListReg context - int rd = bytecode[pc++]; - int argsReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - RuntimeList tieArgs = (RuntimeList) registers[argsReg]; - RuntimeScalar result = TieOperators.tie( - ctx, - tieArgs.elements.toArray(new RuntimeBase[0]) - ); - registers[rd] = result; - break; + case Opcodes.TIE -> { + pc = InlineOpcodeHandler.executeTie(bytecode, pc, registers); } - case Opcodes.UNTIE: { - // untie($var): rd = TieOperators.untie(ctx, argsListReg) - // Format: UNTIE rd argsListReg context - int rd = bytecode[pc++]; - int argsReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - RuntimeList untieArgs = (RuntimeList) registers[argsReg]; - RuntimeScalar result = TieOperators.untie( - ctx, - untieArgs.elements.toArray(new RuntimeBase[0]) - ); - registers[rd] = result; - break; + case Opcodes.UNTIE -> { + pc = InlineOpcodeHandler.executeUntie(bytecode, pc, registers); } - case Opcodes.TIED: { - // tied($var): rd = TieOperators.tied(ctx, argsListReg) - // Format: TIED rd argsListReg context - int rd = bytecode[pc++]; - int argsReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - RuntimeList tiedArgs = (RuntimeList) registers[argsReg]; - RuntimeScalar result = TieOperators.tied( - ctx, - tiedArgs.elements.toArray(new RuntimeBase[0]) - ); - registers[rd] = result; - break; + case Opcodes.TIED -> { + pc = InlineOpcodeHandler.executeTied(bytecode, pc, registers); } // Miscellaneous operators with context-sensitive signatures - case Opcodes.CHMOD: - case Opcodes.UNLINK: - case Opcodes.UTIME: - case Opcodes.RENAME: - case Opcodes.LINK: - case Opcodes.READLINK: - case Opcodes.UMASK: - case Opcodes.GETC: - case Opcodes.FILENO: - case Opcodes.QX: - case Opcodes.SYSTEM: - case Opcodes.CALLER: - case Opcodes.EACH: - case Opcodes.PACK: - case Opcodes.UNPACK: - case Opcodes.VEC: - case Opcodes.LOCALTIME: - case Opcodes.GMTIME: - case Opcodes.RESET: - case Opcodes.CRYPT: - case Opcodes.CLOSE: - case Opcodes.BINMODE: - case Opcodes.SEEK: - case Opcodes.EOF_OP: - case Opcodes.SYSREAD: - case Opcodes.SYSWRITE: - case Opcodes.SYSOPEN: - case Opcodes.SOCKET: - case Opcodes.BIND: - case Opcodes.CONNECT: - case Opcodes.LISTEN: - case Opcodes.WRITE: - case Opcodes.FORMLINE: - case Opcodes.PRINTF: - case Opcodes.ACCEPT: - case Opcodes.SYSSEEK: - case Opcodes.TRUNCATE: - case Opcodes.READ: - case Opcodes.OPENDIR: - case Opcodes.READDIR: - case Opcodes.SEEKDIR: + case Opcodes.CHMOD, Opcodes.UNLINK, Opcodes.UTIME, Opcodes.RENAME, Opcodes.LINK, Opcodes.READLINK, Opcodes.UMASK, Opcodes.GETC, Opcodes.FILENO, Opcodes.QX, Opcodes.SYSTEM, Opcodes.CALLER, Opcodes.EACH, Opcodes.PACK, Opcodes.UNPACK, Opcodes.VEC, Opcodes.LOCALTIME, Opcodes.GMTIME, Opcodes.RESET, Opcodes.CRYPT, Opcodes.CLOSE, Opcodes.BINMODE, Opcodes.SEEK, Opcodes.EOF_OP, Opcodes.SYSREAD, Opcodes.SYSWRITE, Opcodes.SYSOPEN, Opcodes.SOCKET, Opcodes.BIND, Opcodes.CONNECT, Opcodes.LISTEN, Opcodes.WRITE, Opcodes.FORMLINE, Opcodes.PRINTF, Opcodes.ACCEPT, Opcodes.SYSSEEK, Opcodes.TRUNCATE, Opcodes.READ, Opcodes.OPENDIR, Opcodes.READDIR, Opcodes.SEEKDIR -> { pc = MiscOpcodeHandler.execute(opcode, bytecode, pc, registers); - break; + } - case Opcodes.SET_PACKAGE: { + case Opcodes.SET_PACKAGE -> { // Non-scoped package declaration: package Foo; // Update the runtime current-package tracker so caller() returns the right package. int nameIdx = bytecode[pc++]; InterpreterState.currentPackage.get().set(code.stringPool[nameIdx]); - break; } - case Opcodes.PUSH_PACKAGE: { - // Scoped package block entry: package Foo { ... - // Save current package via DynamicVariableManager so it is restored - // automatically when the scope exits via POP_LOCAL_LEVEL. - int nameIdx = bytecode[pc++]; - DynamicVariableManager.pushLocalVariable(InterpreterState.currentPackage.get()); - InterpreterState.currentPackage.get().set(code.stringPool[nameIdx]); - break; + case Opcodes.PUSH_PACKAGE -> { + pc = InlineOpcodeHandler.executePushPackage(bytecode, pc, registers, code); } - case Opcodes.FLIP_FLOP: { - // Flip-flop operator: rd = ScalarFlipFlopOperator.evaluate(id, left, right) - int rd = bytecode[pc++]; - int flipFlopId = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = ScalarFlipFlopOperator.evaluate( - flipFlopId, - ((RuntimeBase) registers[rs1]).scalar(), - ((RuntimeBase) registers[rs2]).scalar()); - break; - } - - case Opcodes.LOCAL_GLOB: { - // Localize a typeglob: save state, return glob - int rd = bytecode[pc++]; - int nameIdx = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - RuntimeGlob glob = GlobalVariable.getGlobalIO(name); - DynamicVariableManager.pushLocalVariable(glob); - registers[rd] = glob; - break; + case Opcodes.FLIP_FLOP -> { + pc = InlineOpcodeHandler.executeFlipFlop(bytecode, pc, registers); } - case Opcodes.GET_LOCAL_LEVEL: { - // Save DynamicVariableManager local level into register rd - int rd = bytecode[pc++]; - registers[rd] = new RuntimeScalar(DynamicVariableManager.getLocalLevel()); - break; + case Opcodes.LOCAL_GLOB -> { + pc = InlineOpcodeHandler.executeLocalGlob(bytecode, pc, registers, code); + } + + case Opcodes.GET_LOCAL_LEVEL -> { + pc = InlineOpcodeHandler.executeGetLocalLevel(bytecode, pc, registers); } - case Opcodes.POP_PACKAGE: + case Opcodes.POP_PACKAGE -> { // Scoped package block exit — restore handled by POP_LOCAL_LEVEL. - break; + } - case Opcodes.DO_FILE: { - int rd = bytecode[pc++]; - int fileReg = bytecode[pc++]; - int ctx = bytecode[pc++]; - RuntimeScalar file = ((RuntimeBase) registers[fileReg]).scalar(); - registers[rd] = ModuleOperators.doFile(file, ctx); - break; + case Opcodes.DO_FILE -> { + pc = InlineOpcodeHandler.executeDoFile(bytecode, pc, registers); } - default: - // Unknown opcode + default -> { int opcodeInt = opcode; throw new RuntimeException( "Unknown opcode: " + opcodeInt + " at pc=" + (pc - 1) + " in " + code.sourceName + ":" + code.sourceLine ); + } } } @@ -2548,340 +1576,16 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c String errorMessage = formatInterpreterError(code, pc, e) + opcodeInfo; throw new RuntimeException(errorMessage, e); } - } // end outer while + } // end outer while (eval/die retry loop) } finally { + // Outer finally: restore interpreter state saved at method entry. + // Unwinds all `local` variables pushed during this frame, restores + // the current package, and pops the InterpreterState call stack. DynamicVariableManager.popToLocalLevel(savedLocalLevel); InterpreterState.currentPackage.get().set(savedPackage); InterpreterState.pop(); } } - - /** - * Separated to keep main execute() under JIT compilation limit. - * - * @return Updated program counter - */ - private static int executeArithmetic(int opcode, int[] bytecode, int pc, - RuntimeBase[] registers) { - switch (opcode) { - case Opcodes.MUL_SCALAR: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.multiply(s1, s2); - return pc; - } - - case Opcodes.DIV_SCALAR: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.divide(s1, s2); - return pc; - } - - case Opcodes.MOD_SCALAR: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.modulus(s1, s2); - return pc; - } - - case Opcodes.POW_SCALAR: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.pow(s1, s2); - return pc; - } - - case Opcodes.NEG_SCALAR: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = MathOperators.unaryMinus((RuntimeScalar) registers[rs]); - return pc; - } - - case Opcodes.ADD_SCALAR_INT: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - int immediate = readInt(bytecode, pc); - pc += 1; - registers[rd] = MathOperators.add( - (RuntimeScalar) registers[rs], - immediate - ); - return pc; - } - - case Opcodes.CONCAT: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - registers[rd] = StringOperators.stringConcat( - (RuntimeScalar) registers[rs1], - (RuntimeScalar) registers[rs2] - ); - return pc; - } - - case Opcodes.REPEAT: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - int repeatCtx = (registers[rs1] instanceof RuntimeScalar) - ? RuntimeContextType.SCALAR : RuntimeContextType.LIST; - registers[rd] = Operator.repeat( - registers[rs1], - (RuntimeScalar) registers[rs2], - repeatCtx - ); - return pc; - } - - case Opcodes.LENGTH: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = StringOperators.length((RuntimeScalar) registers[rs]); - return pc; - } - - case Opcodes.SUBTRACT_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - if (isImmutableProxy(registers[rd])) { - registers[rd] = ensureMutableScalar(registers[rd]); - } - 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; - } - - case Opcodes.MULTIPLY_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - if (isImmutableProxy(registers[rd])) { - registers[rd] = ensureMutableScalar(registers[rd]); - } - 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; - } - - case Opcodes.DIVIDE_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - if (isImmutableProxy(registers[rd])) { - registers[rd] = ensureMutableScalar(registers[rd]); - } - 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; - } - - case Opcodes.MODULUS_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - if (isImmutableProxy(registers[rd])) { - registers[rd] = ensureMutableScalar(registers[rd]); - } - 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; - } - - case Opcodes.REPEAT_ASSIGN: { - 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; - } - - case Opcodes.POW_ASSIGN: { - 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; - } - - case Opcodes.LEFT_SHIFT_ASSIGN: { - 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; - } - - case Opcodes.RIGHT_SHIFT_ASSIGN: { - 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; - } - - case Opcodes.INTEGER_LEFT_SHIFT_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - s1.set(BitwiseOperators.integerShiftLeft(s1, s2)); - return pc; - } - - case Opcodes.INTEGER_RIGHT_SHIFT_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - s1.set(BitwiseOperators.integerShiftRight(s1, s2)); - return pc; - } - - case Opcodes.INTEGER_DIV_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - s1.set(MathOperators.integerDivide(s1, s2)); - return pc; - } - - case Opcodes.INTEGER_MOD_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rd]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs]; - s1.set(MathOperators.integerModulus(s1, s2)); - return pc; - } - - case Opcodes.LOGICAL_AND_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); - if (!s1.getBoolean()) { - return pc; - } - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); - ((RuntimeScalar) registers[rd]).set(s2); - return pc; - } - - case Opcodes.LOGICAL_OR_ASSIGN: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); - if (s1.getBoolean()) { - return pc; - } - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); - ((RuntimeScalar) registers[rd]).set(s2); - return pc; - } - - case Opcodes.LEFT_SHIFT: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rs1]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs2]; - registers[rd] = BitwiseOperators.shiftLeft(s1, s2); - return pc; - } - - case Opcodes.RIGHT_SHIFT: { - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeScalar s1 = (RuntimeScalar) registers[rs1]; - RuntimeScalar s2 = (RuntimeScalar) registers[rs2]; - registers[rd] = BitwiseOperators.shiftRight(s1, s2); - return pc; - } - - // Phase 3: Promoted OperatorHandler operations (400+) - case Opcodes.OP_POW: { - // Power: rd = rs1 ** rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - RuntimeBase val1 = registers[rs1]; - RuntimeBase val2 = registers[rs2]; - RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); - RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); - registers[rd] = MathOperators.pow(s1, s2); - return pc; - } - - case Opcodes.OP_ABS: { - // Absolute value: rd = abs(rs) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase val = registers[rs]; - RuntimeScalar s = (val instanceof RuntimeScalar) ? (RuntimeScalar) val : val.scalar(); - registers[rd] = MathOperators.abs(s); - return pc; - } - - case Opcodes.OP_INT: { - // Integer conversion: rd = int(rs) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase val = registers[rs]; - RuntimeScalar s = (val instanceof RuntimeScalar) ? (RuntimeScalar) val : val.scalar(); - registers[rd] = MathOperators.integer(s); - return pc; - } - - default: - throw new RuntimeException("Unknown arithmetic opcode: " + opcode); - } - } - /** * Handle comparison and logical operations (opcodes 31-41). * Separated to keep main execute() under JIT compilation limit. @@ -2891,7 +1595,7 @@ private static int executeArithmetic(int opcode, int[] bytecode, int pc, private static int executeComparisons(int opcode, int[] bytecode, int pc, RuntimeBase[] registers) { switch (opcode) { - case Opcodes.COMPARE_NUM: { + case Opcodes.COMPARE_NUM -> { // Numeric comparison: rd = rs1 <=> rs2 int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2904,7 +1608,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.COMPARE_STR: { + case Opcodes.COMPARE_STR -> { // String comparison: rd = rs1 cmp rs2 int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2917,7 +1621,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.EQ_NUM: { + case Opcodes.EQ_NUM -> { // Numeric equality: rd = (rs1 == rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2930,7 +1634,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.LT_NUM: { + case Opcodes.LT_NUM -> { // Less than: rd = (rs1 < rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2943,7 +1647,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.GT_NUM: { + case Opcodes.GT_NUM -> { // Greater than: rd = (rs1 > rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2956,7 +1660,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.LE_NUM: { + case Opcodes.LE_NUM -> { // Less than or equal: rd = (rs1 <= rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2969,7 +1673,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.GE_NUM: { + case Opcodes.GE_NUM -> { // Greater than or equal: rd = (rs1 >= rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2982,7 +1686,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.NE_NUM: { + case Opcodes.NE_NUM -> { // Not equal: rd = (rs1 != rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -2995,7 +1699,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.EQ_STR: { + case Opcodes.EQ_STR -> { // String equality: rd = (rs1 eq rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -3010,7 +1714,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.NE_STR: { + case Opcodes.NE_STR -> { // String inequality: rd = (rs1 ne rs2) int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; @@ -3025,7 +1729,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.NOT: { + case Opcodes.NOT -> { int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeScalar val = (registers[rs] instanceof RuntimeScalar) @@ -3036,7 +1740,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.AND: { + case Opcodes.AND -> { // AND is short-circuit and handled in compiler typically // If we get here, just do boolean and int rd = bytecode[pc++]; @@ -3049,7 +1753,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - case Opcodes.OR: { + case Opcodes.OR -> { // OR is short-circuit and handled in compiler typically // If we get here, just do boolean or int rd = bytecode[pc++]; @@ -3062,7 +1766,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, return pc; } - default: + default -> throw new RuntimeException("Unknown comparison opcode: " + opcode); } } @@ -3074,7 +1778,7 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, private static int executeTypeOps(int opcode, int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { switch (opcode) { - case Opcodes.DEFINED: { + case Opcodes.DEFINED -> { int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeBase v = registers[rs]; @@ -3082,13 +1786,13 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, registers[rd] = defined ? RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; return pc; } - case Opcodes.REF: { + case Opcodes.REF -> { int rd = bytecode[pc++]; int rs = bytecode[pc++]; registers[rd] = ReferenceOperators.ref(registers[rs].scalar()); return pc; } - case Opcodes.BLESS: { + case Opcodes.BLESS -> { int rd = bytecode[pc++]; int refReg = bytecode[pc++]; int pkgReg = bytecode[pc++]; @@ -3097,7 +1801,7 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, registers[rd] = ReferenceOperators.bless(ref, pkg); return pc; } - case Opcodes.ISA: { + case Opcodes.ISA -> { int rd = bytecode[pc++]; int objReg = bytecode[pc++]; int pkgReg = bytecode[pc++]; @@ -3106,7 +1810,7 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, registers[rd] = ReferenceOperators.isa(obj, pkg); return pc; } - case Opcodes.PROTOTYPE: { + case Opcodes.PROTOTYPE -> { int rd = bytecode[pc++]; int rs = bytecode[pc++]; int packageIdx = readInt(bytecode, pc); @@ -3117,14 +1821,14 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, registers[rd] = RuntimeCode.prototype(registers[rs].scalar(), packageName); return pc; } - case Opcodes.QUOTE_REGEX: { + case Opcodes.QUOTE_REGEX -> { int rd = bytecode[pc++]; int patternReg = bytecode[pc++]; int flagsReg = bytecode[pc++]; registers[rd] = RuntimeRegex.getQuotedRegex(registers[patternReg].scalar(), registers[flagsReg].scalar()); return pc; } - default: + default -> throw new RuntimeException("Unknown type opcode: " + opcode); } } @@ -3138,29 +1842,18 @@ private static int executeSliceOps(int opcode, int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { // Direct method calls - no SLOWOP_* constants needed! switch (opcode) { - case Opcodes.DEREF_ARRAY: - return SlowOpcodeHandler.executeDerefArray(bytecode, pc, registers); - case Opcodes.DEREF_HASH: - return SlowOpcodeHandler.executeDerefHash(bytecode, pc, registers); - case Opcodes.DEREF_HASH_NONSTRICT: - return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code); - case Opcodes.DEREF_ARRAY_NONSTRICT: - return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code); - case Opcodes.ARRAY_SLICE: - return SlowOpcodeHandler.executeArraySlice(bytecode, pc, registers); - case Opcodes.ARRAY_SLICE_SET: - return SlowOpcodeHandler.executeArraySliceSet(bytecode, pc, registers); - case Opcodes.HASH_SLICE: - return SlowOpcodeHandler.executeHashSlice(bytecode, pc, registers); - case Opcodes.HASH_SLICE_SET: - return SlowOpcodeHandler.executeHashSliceSet(bytecode, pc, registers); - case Opcodes.HASH_SLICE_DELETE: - return SlowOpcodeHandler.executeHashSliceDelete(bytecode, pc, registers); - case Opcodes.HASH_KEYVALUE_SLICE: - return SlowOpcodeHandler.executeHashKeyValueSlice(bytecode, pc, registers); - case Opcodes.LIST_SLICE_FROM: - return SlowOpcodeHandler.executeListSliceFrom(bytecode, pc, registers); - default: + case Opcodes.DEREF_ARRAY -> { return SlowOpcodeHandler.executeDerefArray(bytecode, pc, registers); } + case Opcodes.DEREF_HASH -> { return SlowOpcodeHandler.executeDerefHash(bytecode, pc, registers); } + case Opcodes.DEREF_HASH_NONSTRICT -> { return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code); } + case Opcodes.DEREF_ARRAY_NONSTRICT -> { return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code); } + case Opcodes.ARRAY_SLICE -> { return SlowOpcodeHandler.executeArraySlice(bytecode, pc, registers); } + case Opcodes.ARRAY_SLICE_SET -> { return SlowOpcodeHandler.executeArraySliceSet(bytecode, pc, registers); } + case Opcodes.HASH_SLICE -> { return SlowOpcodeHandler.executeHashSlice(bytecode, pc, registers); } + case Opcodes.HASH_SLICE_SET -> { return SlowOpcodeHandler.executeHashSliceSet(bytecode, pc, registers); } + case Opcodes.HASH_SLICE_DELETE -> { return SlowOpcodeHandler.executeHashSliceDelete(bytecode, pc, registers); } + case Opcodes.HASH_KEYVALUE_SLICE -> { return SlowOpcodeHandler.executeHashKeyValueSlice(bytecode, pc, registers); } + case Opcodes.LIST_SLICE_FROM -> { return SlowOpcodeHandler.executeListSliceFrom(bytecode, pc, registers); } + default -> throw new RuntimeException("Unknown slice opcode: " + opcode); } } @@ -3172,19 +1865,13 @@ private static int executeSliceOps(int opcode, int[] bytecode, int pc, private static int executeArrayStringOps(int opcode, int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { switch (opcode) { - case Opcodes.SPLICE: - return SlowOpcodeHandler.executeSplice(bytecode, pc, registers); - case Opcodes.REVERSE: - return SlowOpcodeHandler.executeReverse(bytecode, pc, registers); - case Opcodes.SPLIT: - return SlowOpcodeHandler.executeSplit(bytecode, pc, registers); - case Opcodes.LENGTH_OP: - return SlowOpcodeHandler.executeLength(bytecode, pc, registers); - case Opcodes.EXISTS: - return SlowOpcodeHandler.executeExists(bytecode, pc, registers); - case Opcodes.DELETE: - return SlowOpcodeHandler.executeDelete(bytecode, pc, registers); - default: + case Opcodes.SPLICE -> { return SlowOpcodeHandler.executeSplice(bytecode, pc, registers); } + case Opcodes.REVERSE -> { return SlowOpcodeHandler.executeReverse(bytecode, pc, registers); } + case Opcodes.SPLIT -> { return SlowOpcodeHandler.executeSplit(bytecode, pc, registers); } + case Opcodes.LENGTH_OP -> { return SlowOpcodeHandler.executeLength(bytecode, pc, registers); } + case Opcodes.EXISTS -> { return SlowOpcodeHandler.executeExists(bytecode, pc, registers); } + case Opcodes.DELETE -> { return SlowOpcodeHandler.executeDelete(bytecode, pc, registers); } + default -> throw new RuntimeException("Unknown array/string opcode: " + opcode); } } @@ -3196,15 +1883,11 @@ private static int executeArrayStringOps(int opcode, int[] bytecode, int pc, private static int executeScopeOps(int opcode, int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { switch (opcode) { - case Opcodes.RETRIEVE_BEGIN_SCALAR: - return SlowOpcodeHandler.executeRetrieveBeginScalar(bytecode, pc, registers, code); - case Opcodes.RETRIEVE_BEGIN_ARRAY: - return SlowOpcodeHandler.executeRetrieveBeginArray(bytecode, pc, registers, code); - case Opcodes.RETRIEVE_BEGIN_HASH: - return SlowOpcodeHandler.executeRetrieveBeginHash(bytecode, pc, registers, code); - case Opcodes.LOCAL_SCALAR: - return SlowOpcodeHandler.executeLocalScalar(bytecode, pc, registers, code); - case Opcodes.LOCAL_ARRAY: { + case Opcodes.RETRIEVE_BEGIN_SCALAR -> { return SlowOpcodeHandler.executeRetrieveBeginScalar(bytecode, pc, registers, code); } + case Opcodes.RETRIEVE_BEGIN_ARRAY -> { return SlowOpcodeHandler.executeRetrieveBeginArray(bytecode, pc, registers, code); } + case Opcodes.RETRIEVE_BEGIN_HASH -> { return SlowOpcodeHandler.executeRetrieveBeginHash(bytecode, pc, registers, code); } + case Opcodes.LOCAL_SCALAR -> { return SlowOpcodeHandler.executeLocalScalar(bytecode, pc, registers, code); } + case Opcodes.LOCAL_ARRAY -> { int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String fullName = code.stringPool[nameIdx]; @@ -3214,7 +1897,7 @@ private static int executeScopeOps(int opcode, int[] bytecode, int pc, registers[rd] = GlobalVariable.getGlobalArray(fullName); return pc; } - case Opcodes.LOCAL_HASH: { + case Opcodes.LOCAL_HASH -> { int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String fullName = code.stringPool[nameIdx]; @@ -3224,7 +1907,7 @@ private static int executeScopeOps(int opcode, int[] bytecode, int pc, registers[rd] = GlobalVariable.getGlobalHash(fullName); return pc; } - default: + default -> throw new RuntimeException("Unknown scope opcode: " + opcode); } } @@ -3237,45 +1920,26 @@ private static int executeScopeOps(int opcode, int[] bytecode, int pc, private static int executeSystemOps(int opcode, int[] bytecode, int pc, RuntimeBase[] registers) { switch (opcode) { - case Opcodes.CHOWN: - return MiscOpcodeHandler.execute(Opcodes.CHOWN, bytecode, pc, registers); - case Opcodes.WAITPID: - return MiscOpcodeHandler.execute(Opcodes.WAITPID, bytecode, pc, registers); - case Opcodes.FORK: - return SlowOpcodeHandler.executeFork(bytecode, pc, registers); - case Opcodes.GETPPID: - return SlowOpcodeHandler.executeGetppid(bytecode, pc, registers); - case Opcodes.GETPGRP: - return MiscOpcodeHandler.execute(Opcodes.GETPGRP, bytecode, pc, registers); - case Opcodes.SETPGRP: - return MiscOpcodeHandler.execute(Opcodes.SETPGRP, bytecode, pc, registers); - case Opcodes.GETPRIORITY: - return MiscOpcodeHandler.execute(Opcodes.GETPRIORITY, bytecode, pc, registers); - case Opcodes.SETPRIORITY: - return MiscOpcodeHandler.execute(Opcodes.SETPRIORITY, bytecode, pc, registers); - case Opcodes.GETSOCKOPT: - return MiscOpcodeHandler.execute(Opcodes.GETSOCKOPT, bytecode, pc, registers); - case Opcodes.SETSOCKOPT: - return MiscOpcodeHandler.execute(Opcodes.SETSOCKOPT, bytecode, pc, registers); - case Opcodes.SYSCALL: - return SlowOpcodeHandler.executeSyscall(bytecode, pc, registers); - case Opcodes.SEMGET: - return SlowOpcodeHandler.executeSemget(bytecode, pc, registers); - case Opcodes.SEMOP: - return SlowOpcodeHandler.executeSemop(bytecode, pc, registers); - case Opcodes.MSGGET: - return SlowOpcodeHandler.executeMsgget(bytecode, pc, registers); - case Opcodes.MSGSND: - return SlowOpcodeHandler.executeMsgsnd(bytecode, pc, registers); - case Opcodes.MSGRCV: - return SlowOpcodeHandler.executeMsgrcv(bytecode, pc, registers); - case Opcodes.SHMGET: - return SlowOpcodeHandler.executeShmget(bytecode, pc, registers); - case Opcodes.SHMREAD: - return SlowOpcodeHandler.executeShmread(bytecode, pc, registers); - case Opcodes.SHMWRITE: - return SlowOpcodeHandler.executeShmwrite(bytecode, pc, registers); - default: + case Opcodes.CHOWN -> { return MiscOpcodeHandler.execute(Opcodes.CHOWN, bytecode, pc, registers); } + case Opcodes.WAITPID -> { return MiscOpcodeHandler.execute(Opcodes.WAITPID, bytecode, pc, registers); } + case Opcodes.FORK -> { return SlowOpcodeHandler.executeFork(bytecode, pc, registers); } + case Opcodes.GETPPID -> { return SlowOpcodeHandler.executeGetppid(bytecode, pc, registers); } + case Opcodes.GETPGRP -> { return MiscOpcodeHandler.execute(Opcodes.GETPGRP, bytecode, pc, registers); } + case Opcodes.SETPGRP -> { return MiscOpcodeHandler.execute(Opcodes.SETPGRP, bytecode, pc, registers); } + case Opcodes.GETPRIORITY -> { return MiscOpcodeHandler.execute(Opcodes.GETPRIORITY, bytecode, pc, registers); } + case Opcodes.SETPRIORITY -> { return MiscOpcodeHandler.execute(Opcodes.SETPRIORITY, bytecode, pc, registers); } + case Opcodes.GETSOCKOPT -> { return MiscOpcodeHandler.execute(Opcodes.GETSOCKOPT, bytecode, pc, registers); } + case Opcodes.SETSOCKOPT -> { return MiscOpcodeHandler.execute(Opcodes.SETSOCKOPT, bytecode, pc, registers); } + case Opcodes.SYSCALL -> { return SlowOpcodeHandler.executeSyscall(bytecode, pc, registers); } + case Opcodes.SEMGET -> { return SlowOpcodeHandler.executeSemget(bytecode, pc, registers); } + case Opcodes.SEMOP -> { return SlowOpcodeHandler.executeSemop(bytecode, pc, registers); } + case Opcodes.MSGGET -> { return SlowOpcodeHandler.executeMsgget(bytecode, pc, registers); } + case Opcodes.MSGSND -> { return SlowOpcodeHandler.executeMsgsnd(bytecode, pc, registers); } + case Opcodes.MSGRCV -> { return SlowOpcodeHandler.executeMsgrcv(bytecode, pc, registers); } + case Opcodes.SHMGET -> { return SlowOpcodeHandler.executeShmget(bytecode, pc, registers); } + case Opcodes.SHMREAD -> { return SlowOpcodeHandler.executeShmread(bytecode, pc, registers); } + case Opcodes.SHMWRITE -> { return SlowOpcodeHandler.executeShmwrite(bytecode, pc, registers); } + default -> throw new RuntimeException("Unknown system opcode: " + opcode); } } @@ -3288,27 +1952,17 @@ private static int executeSystemOps(int opcode, int[] bytecode, int pc, private static int executeSpecialIO(int opcode, int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { switch (opcode) { - case Opcodes.EVAL_STRING: - return SlowOpcodeHandler.executeEvalString(bytecode, pc, registers, code); - case Opcodes.SELECT_OP: - return SlowOpcodeHandler.executeSelect(bytecode, pc, registers); - case Opcodes.LOAD_GLOB: - return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code); - case Opcodes.SLEEP_OP: - return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); - case Opcodes.ALARM_OP: - return SlowOpcodeHandler.executeAlarm(bytecode, pc, registers); - case Opcodes.DEREF_GLOB: - return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code); - case Opcodes.DEREF_GLOB_NONSTRICT: - return SlowOpcodeHandler.executeDerefGlobNonStrict(bytecode, pc, registers, code); - case Opcodes.LOAD_GLOB_DYNAMIC: - return SlowOpcodeHandler.executeLoadGlobDynamic(bytecode, pc, registers, code); - case Opcodes.DEREF_SCALAR_STRICT: - return SlowOpcodeHandler.executeDerefScalarStrict(bytecode, pc, registers); - case Opcodes.DEREF_SCALAR_NONSTRICT: - return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code); - default: + case Opcodes.EVAL_STRING -> { return SlowOpcodeHandler.executeEvalString(bytecode, pc, registers, code); } + case Opcodes.SELECT_OP -> { return SlowOpcodeHandler.executeSelect(bytecode, pc, registers); } + case Opcodes.LOAD_GLOB -> { return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code); } + case Opcodes.SLEEP_OP -> { return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); } + case Opcodes.ALARM_OP -> { return SlowOpcodeHandler.executeAlarm(bytecode, pc, registers); } + case Opcodes.DEREF_GLOB -> { return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code); } + case Opcodes.DEREF_GLOB_NONSTRICT -> { return SlowOpcodeHandler.executeDerefGlobNonStrict(bytecode, pc, registers, code); } + case Opcodes.LOAD_GLOB_DYNAMIC -> { return SlowOpcodeHandler.executeLoadGlobDynamic(bytecode, pc, registers, code); } + case Opcodes.DEREF_SCALAR_STRICT -> { return SlowOpcodeHandler.executeDerefScalarStrict(bytecode, pc, registers); } + case Opcodes.DEREF_SCALAR_NONSTRICT -> { return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code); } + default -> throw new RuntimeException("Unknown special I/O opcode: " + opcode); } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java new file mode 100644 index 000000000..46a1c5411 --- /dev/null +++ b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java @@ -0,0 +1,1246 @@ +package org.perlonjava.backend.bytecode; + +import org.perlonjava.runtime.operators.*; +import org.perlonjava.runtime.runtimetypes.*; + +/** + * Inline opcode handlers for arithmetic, shift, collection, and list operations. + * + * Extracted from BytecodeInterpreter.execute() to reduce method size + * and keep it under the 8KB JIT compilation limit. + * + * Handles: arithmetic ops, shift ops, integer assign ops, array/hash operations, + * and list operations (CREATE_LIST, JOIN, SELECT, RANGE, MAP, GREP, SORT, etc.) + */ +public class InlineOpcodeHandler { + + // ========================================================================= + // Helper methods (duplicated from BytecodeInterpreter) + // ========================================================================= + + /** + * Check if a value is an immutable proxy (RuntimeScalarReadOnly or ScalarSpecialVariable). + * These cannot be mutated in place. + */ + static boolean isImmutableProxy(RuntimeBase val) { + return val instanceof RuntimeScalarReadOnly || val instanceof ScalarSpecialVariable; + } + + /** + * Create a mutable copy of a value if it is an immutable proxy. + * For RuntimeScalarReadOnly: copies type and value into a fresh RuntimeScalar. + * For ScalarSpecialVariable: resolves via getValueAsScalar(), then copies. + * For anything else: casts directly to RuntimeScalar. + */ + static RuntimeScalar ensureMutableScalar(RuntimeBase val) { + if (val instanceof RuntimeScalarReadOnly ro) { + RuntimeScalar copy = new RuntimeScalar(); + copy.type = ro.type; + copy.value = ro.value; + return copy; + } + if (val instanceof ScalarSpecialVariable sv) { + RuntimeScalar src = sv.getValueAsScalar(); + RuntimeScalar copy = new RuntimeScalar(); + copy.type = src.type; + copy.value = src.value; + return copy; + } + return (RuntimeScalar) val; + } + + // ========================================================================= + // ARITHMETIC OPERATORS + // ========================================================================= + + /** + * Addition: rd = rs1 + rs2 + * Format: ADD_SCALAR rd rs1 rs2 + */ + public static int executeAddScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + + RuntimeBase val1 = registers[rs1]; + RuntimeBase val2 = registers[rs2]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.add(s1, s2); + return pc; + } + + /** + * Subtraction: rd = rs1 - rs2 + * Format: SUB_SCALAR rd rs1 rs2 + */ + public static int executeSubScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + + RuntimeBase val1 = registers[rs1]; + RuntimeBase val2 = registers[rs2]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.subtract(s1, s2); + return pc; + } + + /** + * Multiplication: rd = rs1 * rs2 + * Format: MUL_SCALAR rd rs1 rs2 + */ + public static int executeMulScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + + RuntimeBase val1 = registers[rs1]; + RuntimeBase val2 = registers[rs2]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.multiply(s1, s2); + return pc; + } + + /** + * Division: rd = rs1 / rs2 + * Format: DIV_SCALAR rd rs1 rs2 + */ + public static int executeDivScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + + RuntimeBase val1 = registers[rs1]; + RuntimeBase val2 = registers[rs2]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.divide(s1, s2); + return pc; + } + + /** + * Modulus: rd = rs1 % rs2 + * Format: MOD_SCALAR rd rs1 rs2 + */ + public static int executeModScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + + RuntimeBase val1 = registers[rs1]; + RuntimeBase val2 = registers[rs2]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.modulus(s1, s2); + return pc; + } + + /** + * Exponentiation: rd = rs1 ** rs2 + * Format: POW_SCALAR rd rs1 rs2 + */ + public static int executePowScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + + RuntimeBase val1 = registers[rs1]; + RuntimeBase val2 = registers[rs2]; + RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar(); + RuntimeScalar s2 = (val2 instanceof RuntimeScalar) ? (RuntimeScalar) val2 : val2.scalar(); + + registers[rd] = MathOperators.pow(s1, s2); + return pc; + } + + /** + * Negation: rd = -rs + * Format: NEG_SCALAR rd rs + */ + public static int executeNegScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = MathOperators.unaryMinus((RuntimeScalar) registers[rs]); + return pc; + } + + /** + * Addition with immediate: rd = rs + immediate + * Format: ADD_SCALAR_INT rd rs immediate + */ + public static int executeAddScalarInt(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + int immediate = bytecode[pc]; + pc += 1; + registers[rd] = MathOperators.add( + (RuntimeScalar) registers[rs], + immediate + ); + return pc; + } + + /** + * String concatenation: rd = rs1 . rs2 + * Format: CONCAT rd rs1 rs2 + */ + public static int executeConcat(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeBase concatLeft = registers[rs1]; + RuntimeBase concatRight = registers[rs2]; + registers[rd] = StringOperators.stringConcat( + concatLeft instanceof RuntimeScalar ? (RuntimeScalar) concatLeft : concatLeft.scalar(), + concatRight instanceof RuntimeScalar ? (RuntimeScalar) concatRight : concatRight.scalar() + ); + return pc; + } + + /** + * String/list repetition: rd = rs1 x rs2 + * Format: REPEAT rd rs1 rs2 + */ + public static int executeRepeat(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeBase countVal = registers[rs2]; + RuntimeScalar count = (countVal instanceof RuntimeScalar) + ? (RuntimeScalar) countVal + : ((RuntimeList) countVal).scalar(); + int repeatCtx = (registers[rs1] instanceof RuntimeScalar) + ? RuntimeContextType.SCALAR : RuntimeContextType.LIST; + registers[rd] = Operator.repeat(registers[rs1], count, repeatCtx); + return pc; + } + + /** + * String length: rd = length(rs) + * Format: LENGTH rd rs + */ + public static int executeLength(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = StringOperators.length((RuntimeScalar) registers[rs]); + return pc; + } + + // ========================================================================= + // SHIFT OPERATIONS + // ========================================================================= + + /** + * Left shift: rd = rs1 << rs2 + * Format: LEFT_SHIFT rd rs1 rs2 + */ + public static int executeLeftShift(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rs1]; + RuntimeScalar s2 = (RuntimeScalar) registers[rs2]; + registers[rd] = BitwiseOperators.shiftLeft(s1, s2); + return pc; + } + + /** + * Right shift: rd = rs1 >> rs2 + * Format: RIGHT_SHIFT rd rs1 rs2 + */ + public static int executeRightShift(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rs1]; + RuntimeScalar s2 = (RuntimeScalar) registers[rs2]; + registers[rd] = BitwiseOperators.shiftRight(s1, s2); + return pc; + } + + /** + * Integer left shift: rd = rs1 << rs2 (integer semantics) + * Format: INTEGER_LEFT_SHIFT rd rs1 rs2 + */ + public static int executeIntegerLeftShift(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); + RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); + registers[rd] = BitwiseOperators.integerShiftLeft(s1, s2); + return pc; + } + + /** + * Integer right shift: rd = rs1 >> rs2 (integer semantics) + * Format: INTEGER_RIGHT_SHIFT rd rs1 rs2 + */ + public static int executeIntegerRightShift(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); + RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); + registers[rd] = BitwiseOperators.integerShiftRight(s1, s2); + return pc; + } + + /** + * Integer division: rd = rs1 / rs2 (integer semantics) + * Format: INTEGER_DIV rd rs1 rs2 + */ + public static int executeIntegerDiv(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); + RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); + registers[rd] = MathOperators.integerDivide(s1, s2); + return pc; + } + + /** + * Integer modulus: rd = rs1 % rs2 (integer semantics) + * Format: INTEGER_MOD rd rs1 rs2 + */ + public static int executeIntegerMod(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + RuntimeScalar s1 = (registers[rs1] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs1] : registers[rs1].scalar(); + RuntimeScalar s2 = (registers[rs2] instanceof RuntimeScalar) ? (RuntimeScalar) registers[rs2] : registers[rs2].scalar(); + registers[rd] = MathOperators.integerModulus(s1, s2); + return pc; + } + + // ========================================================================= + // INTEGER ASSIGN OPERATIONS + // ========================================================================= + + /** + * Integer left shift assign: rd <<= rs (integer semantics, in-place) + * Format: INTEGER_LEFT_SHIFT_ASSIGN rd rs + */ + public static int executeIntegerLeftShiftAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + s1.set(BitwiseOperators.integerShiftLeft(s1, (RuntimeScalar) registers[rs])); + return pc; + } + + /** + * Integer right shift assign: rd >>= rs (integer semantics, in-place) + * Format: INTEGER_RIGHT_SHIFT_ASSIGN rd rs + */ + public static int executeIntegerRightShiftAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + s1.set(BitwiseOperators.integerShiftRight(s1, (RuntimeScalar) registers[rs])); + return pc; + } + + /** + * Integer division assign: rd /= rs (integer semantics, in-place) + * Format: INTEGER_DIV_ASSIGN rd rs + */ + public static int executeIntegerDivAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + s1.set(MathOperators.integerDivide(s1, (RuntimeScalar) registers[rs])); + return pc; + } + + /** + * Integer modulus assign: rd %= rs (integer semantics, in-place) + * Format: INTEGER_MOD_ASSIGN rd rs + */ + public static int executeIntegerModAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar s1 = (RuntimeScalar) registers[rd]; + s1.set(MathOperators.integerModulus(s1, (RuntimeScalar) registers[rs])); + return pc; + } + + // ========================================================================= + // ARRAY OPERATIONS + // ========================================================================= + + /** + * Array element access: rd = array[index] + * Format: ARRAY_GET rd arrayReg indexReg + */ + public static int executeArrayGet(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int arrayReg = bytecode[pc++]; + int indexReg = bytecode[pc++]; + + RuntimeBase arrayBase = registers[arrayReg]; + RuntimeScalar idx = (RuntimeScalar) registers[indexReg]; + + if (arrayBase instanceof RuntimeArray) { + RuntimeArray arr = (RuntimeArray) arrayBase; + registers[rd] = arr.get(idx.getInt()); + } else if (arrayBase instanceof RuntimeList) { + RuntimeList list = (RuntimeList) arrayBase; + int index = idx.getInt(); + if (index < 0) index = list.elements.size() + index; + registers[rd] = (index >= 0 && index < list.elements.size()) + ? list.elements.get(index) + : new RuntimeScalar(); + } else { + throw new RuntimeException("ARRAY_GET: register " + arrayReg + " contains " + + (arrayBase == null ? "null" : arrayBase.getClass().getName()) + + " instead of RuntimeArray or RuntimeList"); + } + return pc; + } + + /** + * Array element store: array[index] = value + * Format: ARRAY_SET arrayReg indexReg valueReg + */ + public static int executeArraySet(int[] bytecode, int pc, RuntimeBase[] registers) { + int arrayReg = bytecode[pc++]; + int indexReg = bytecode[pc++]; + int valueReg = bytecode[pc++]; + RuntimeArray arr = (RuntimeArray) registers[arrayReg]; + RuntimeScalar idx = (RuntimeScalar) registers[indexReg]; + RuntimeBase valueBase = registers[valueReg]; + RuntimeScalar val = (valueBase instanceof RuntimeScalar) + ? (RuntimeScalar) valueBase : valueBase.scalar(); + arr.get(idx.getInt()).set(val); + return pc; + } + + /** + * Array push: push(@array, value) + * Format: ARRAY_PUSH arrayReg valueReg + */ + public static int executeArrayPush(int[] bytecode, int pc, RuntimeBase[] registers) { + int arrayReg = bytecode[pc++]; + int valueReg = bytecode[pc++]; + RuntimeArray arr = (RuntimeArray) registers[arrayReg]; + RuntimeBase val = registers[valueReg]; + arr.push(val); + return pc; + } + + /** + * Array pop: rd = pop(@array) + * Format: ARRAY_POP rd arrayReg + */ + public static int executeArrayPop(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int arrayReg = bytecode[pc++]; + RuntimeArray arr = (RuntimeArray) registers[arrayReg]; + registers[rd] = RuntimeArray.pop(arr); + return pc; + } + + /** + * Array shift: rd = shift(@array) + * Format: ARRAY_SHIFT rd arrayReg + */ + public static int executeArrayShift(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int arrayReg = bytecode[pc++]; + RuntimeArray arr = (RuntimeArray) registers[arrayReg]; + registers[rd] = RuntimeArray.shift(arr); + return pc; + } + + /** + * Array unshift: unshift(@array, value) + * Format: ARRAY_UNSHIFT arrayReg valueReg + */ + public static int executeArrayUnshift(int[] bytecode, int pc, RuntimeBase[] registers) { + int arrayReg = bytecode[pc++]; + int valueReg = bytecode[pc++]; + RuntimeArray arr = (RuntimeArray) registers[arrayReg]; + RuntimeBase val = registers[valueReg]; + RuntimeArray.unshift(arr, val); + return pc; + } + + /** + * Array size: rd = scalar(@array) or scalar(value) + * Special case for RuntimeList: return size, not last element. + * Format: ARRAY_SIZE rd operandReg + */ + public static int executeArraySize(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int operandReg = bytecode[pc++]; + RuntimeBase operand = registers[operandReg]; + if (operand instanceof RuntimeList) { + registers[rd] = new RuntimeScalar(((RuntimeList) operand).size()); + } else { + registers[rd] = operand.scalar(); + } + return pc; + } + + /** + * Set array last index: $#array = value + * Format: SET_ARRAY_LAST_INDEX arrayReg valueReg + */ + public static int executeSetArrayLastIndex(int[] bytecode, int pc, RuntimeBase[] registers) { + int arrayReg = bytecode[pc++]; + int valueReg = bytecode[pc++]; + RuntimeArray.indexLastElem((RuntimeArray) registers[arrayReg]) + .set(((RuntimeScalar) registers[valueReg])); + return pc; + } + + /** + * Create array reference from list: rd = new RuntimeArray(rs_list).createReference() + * Array literals always return references in Perl. + * Format: CREATE_ARRAY rd listReg + */ + public static int executeCreateArray(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int listReg = bytecode[pc++]; + + RuntimeBase source = registers[listReg]; + RuntimeArray array; + if (source instanceof RuntimeArray) { + array = (RuntimeArray) source; + } else { + RuntimeList list = source.getList(); + array = new RuntimeArray(list); + } + + registers[rd] = array.createReference(); + return pc; + } + + // ========================================================================= + // HASH OPERATIONS + // ========================================================================= + + /** + * Hash element access: rd = hash{key} + * Format: HASH_GET rd hashReg keyReg + */ + public static int executeHashGet(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int hashReg = bytecode[pc++]; + int keyReg = bytecode[pc++]; + RuntimeHash hash = (RuntimeHash) registers[hashReg]; + RuntimeScalar key = (RuntimeScalar) registers[keyReg]; + registers[rd] = hash.get(key); + return pc; + } + + /** + * Hash element store: hash{key} = value + * Creates a fresh copy to prevent aliasing bugs. + * Uses addToScalar to resolve special variables ($1, $2, etc.) + * Format: HASH_SET hashReg keyReg valueReg + */ + public static int executeHashSet(int[] bytecode, int pc, RuntimeBase[] registers) { + int hashReg = bytecode[pc++]; + int keyReg = bytecode[pc++]; + int valueReg = bytecode[pc++]; + RuntimeHash hash = (RuntimeHash) registers[hashReg]; + RuntimeScalar key = (RuntimeScalar) registers[keyReg]; + RuntimeBase valBase = registers[valueReg]; + RuntimeScalar val = (valBase instanceof RuntimeScalar) ? (RuntimeScalar) valBase : valBase.scalar(); + RuntimeScalar copy = new RuntimeScalar(); + val.addToScalar(copy); + hash.put(key.toString(), copy); + return pc; + } + + /** + * Check if hash key exists: rd = exists $hash{key} + * Format: HASH_EXISTS rd hashReg keyReg + */ + public static int executeHashExists(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int hashReg = bytecode[pc++]; + int keyReg = bytecode[pc++]; + RuntimeHash hash = (RuntimeHash) registers[hashReg]; + RuntimeScalar key = (RuntimeScalar) registers[keyReg]; + registers[rd] = hash.exists(key); + return pc; + } + + /** + * Delete hash key: rd = delete $hash{key} + * Format: HASH_DELETE rd hashReg keyReg + */ + public static int executeHashDelete(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int hashReg = bytecode[pc++]; + int keyReg = bytecode[pc++]; + RuntimeHash hash = (RuntimeHash) registers[hashReg]; + RuntimeScalar key = (RuntimeScalar) registers[keyReg]; + registers[rd] = hash.delete(key); + return pc; + } + + /** + * Check if array index exists: rd = exists $array[index] + * Format: ARRAY_EXISTS rd arrayReg indexReg + */ + public static int executeArrayExists(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int arrayReg = bytecode[pc++]; + int indexReg = bytecode[pc++]; + RuntimeArray array = (RuntimeArray) registers[arrayReg]; + RuntimeScalar index = (RuntimeScalar) registers[indexReg]; + registers[rd] = array.exists(index); + return pc; + } + + /** + * Delete array element: rd = delete $array[index] + * Format: ARRAY_DELETE rd arrayReg indexReg + */ + public static int executeArrayDelete(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int arrayReg = bytecode[pc++]; + int indexReg = bytecode[pc++]; + RuntimeArray array = (RuntimeArray) registers[arrayReg]; + RuntimeScalar index = (RuntimeScalar) registers[indexReg]; + registers[rd] = array.delete(index); + return pc; + } + + /** + * Get hash keys: rd = keys %hash + * Calls .keys() on RuntimeBase for proper error handling on non-hash types. + * Format: HASH_KEYS rd hashReg + */ + public static int executeHashKeys(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int hashReg = bytecode[pc++]; + registers[rd] = registers[hashReg].keys(); + return pc; + } + + /** + * Get hash values: rd = values %hash + * Calls .values() on RuntimeBase for proper error handling on non-hash types. + * Format: HASH_VALUES rd hashReg + */ + public static int executeHashValues(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int hashReg = bytecode[pc++]; + registers[rd] = registers[hashReg].values(); + return pc; + } + + // ========================================================================= + // LIST OPERATIONS + // ========================================================================= + + /** + * Convert list/array to its count in scalar context (e.g. @arr used as number). + * Format: LIST_TO_COUNT rd rs + */ + public static int executeListToCount(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase val = registers[rs]; + if (val instanceof RuntimeList) { + registers[rd] = new RuntimeScalar(((RuntimeList) val).elements.size()); + } else if (val instanceof RuntimeArray) { + registers[rd] = new RuntimeScalar(((RuntimeArray) val).size()); + } else { + registers[rd] = val.scalar(); + } + return pc; + } + + /** + * Convert list to scalar context: returns last element (Perl list-in-scalar semantics). + * Format: LIST_TO_SCALAR rd rs + */ + public static int executeListToScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = registers[rs].scalar(); + return pc; + } + + /** + * Convert value to RuntimeList, preserving aggregate types (PerlRange, RuntimeArray) + * so that consumers like Pack.pack() can iterate them via RuntimeList's iterator. + * Format: SCALAR_TO_LIST rd rs + */ + public static int executeScalarToList(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase val = registers[rs]; + if (val instanceof RuntimeList) { + registers[rd] = val; + } else if (val instanceof RuntimeScalar) { + RuntimeList list = new RuntimeList(); + list.elements.add(val); + registers[rd] = list; + } else { + // RuntimeArray, PerlRange, etc. - wrap in list, preserving type + RuntimeList list = new RuntimeList(); + list.elements.add(val); + registers[rd] = list; + } + return pc; + } + + /** + * Create RuntimeList from registers. + * Format: CREATE_LIST rd count rs1 rs2 ... rsN + */ + public static int executeCreateList(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int count = bytecode[pc++]; + + if (count == 0) { + // Empty list - fastest path + registers[rd] = new RuntimeList(); + } else if (count == 1) { + // Single element - avoid loop overhead + int rs = bytecode[pc++]; + RuntimeList list = new RuntimeList(); + list.add(registers[rs]); + registers[rd] = list; + } else { + // Multiple elements - preallocate and populate + RuntimeList list = new RuntimeList(); + + for (int i = 0; i < count; i++) { + int rs = bytecode[pc++]; + list.add(registers[rs]); + } + + registers[rd] = list; + } + return pc; + } + + /** + * String join: rd = join(separator, list) + * Format: JOIN rd separatorReg listReg + */ + public static int executeJoin(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int separatorReg = bytecode[pc++]; + int listReg = bytecode[pc++]; + + RuntimeBase separatorBase = registers[separatorReg]; + RuntimeScalar separator = (separatorBase instanceof RuntimeScalar) + ? (RuntimeScalar) separatorBase + : separatorBase.scalar(); + + RuntimeBase list = registers[listReg]; + + registers[rd] = StringOperators.joinForInterpolation(separator, list); + return pc; + } + + /** + * Select default output filehandle: rd = IOOperator.select(list, SCALAR) + * Format: SELECT rd listReg + */ + public static int executeSelect(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int listReg = bytecode[pc++]; + + RuntimeBase listBase = registers[listReg]; + RuntimeList list = (listBase instanceof RuntimeList rl) + ? rl : listBase.getList(); + RuntimeScalar result = IOOperator.select(list, RuntimeContextType.SCALAR); + registers[rd] = result; + return pc; + } + + /** + * Create range: rd = PerlRange.createRange(rs_start, rs_end) + * Format: RANGE rd startReg endReg + */ + public static int executeRange(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int startReg = bytecode[pc++]; + int endReg = bytecode[pc++]; + + RuntimeBase startBase = registers[startReg]; + RuntimeBase endBase = registers[endReg]; + + RuntimeScalar start = (startBase instanceof RuntimeScalar) ? (RuntimeScalar) startBase : + (startBase == null) ? new RuntimeScalar() : startBase.scalar(); + RuntimeScalar end = (endBase instanceof RuntimeScalar) ? (RuntimeScalar) endBase : + (endBase == null) ? new RuntimeScalar() : endBase.scalar(); + + PerlRange range = PerlRange.createRange(start, end); + registers[rd] = range; + return pc; + } + + /** + * Create hash reference from list: rd = RuntimeHash.createHash(rs_list).createReference() + * Hash literals always return references in Perl. + * Format: CREATE_HASH rd listReg + */ + public static int executeCreateHash(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int listReg = bytecode[pc++]; + + RuntimeBase list = registers[listReg]; + RuntimeHash hash = RuntimeHash.createHash(list); + + registers[rd] = hash.createReference(); + return pc; + } + + /** + * Random number: rd = Random.rand(max) + * Format: RAND rd maxReg + */ + public static int executeRand(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int maxReg = bytecode[pc++]; + + RuntimeScalar max = (RuntimeScalar) registers[maxReg]; + registers[rd] = Random.rand(max); + return pc; + } + + /** + * Map operator: rd = ListOperators.map(list, closure, ctx) + * Format: MAP rd listReg closureReg ctx + */ + public static int executeMap(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int listReg = bytecode[pc++]; + int closureReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + + RuntimeBase listBase = registers[listReg]; + RuntimeList list = listBase.getList(); + RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; + RuntimeList result = ListOperators.map(list, closure, ctx); + registers[rd] = result; + return pc; + } + + /** + * Grep operator: rd = ListOperators.grep(list, closure, ctx) + * Format: GREP rd listReg closureReg ctx + */ + public static int executeGrep(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int listReg = bytecode[pc++]; + int closureReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + + RuntimeBase listBase = registers[listReg]; + RuntimeList list = listBase.getList(); + RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; + RuntimeList result = ListOperators.grep(list, closure, ctx); + registers[rd] = result; + return pc; + } + + /** + * Sort operator: rd = ListOperators.sort(list, closure, package) + * Needs InterpretedCode for string pool access. + * Format: SORT rd listReg closureReg packageIdx(int) + */ + public static int executeSort(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int listReg = bytecode[pc++]; + int closureReg = bytecode[pc++]; + int packageIdx = bytecode[pc]; + pc += 1; + + RuntimeBase listBase = registers[listReg]; + RuntimeList list = listBase.getList(); + RuntimeScalar closure = (RuntimeScalar) registers[closureReg]; + String packageName = code.stringPool[packageIdx]; + RuntimeList result = ListOperators.sort(list, closure, packageName); + registers[rd] = result; + return pc; + } + + /** + * Create empty array: rd = new RuntimeArray() + * Format: NEW_ARRAY rd + */ + public static int executeNewArray(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + registers[rd] = new RuntimeArray(); + return pc; + } + + /** + * Create empty hash: rd = new RuntimeHash() + * Format: NEW_HASH rd + */ + public static int executeNewHash(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + registers[rd] = new RuntimeHash(); + return pc; + } + + /** + * Set array content from list: array_reg.setFromList(list_reg) + * Format: ARRAY_SET_FROM_LIST arrayReg listReg + */ + public static int executeArraySetFromList(int[] bytecode, int pc, RuntimeBase[] registers) { + int arrayReg = bytecode[pc++]; + int listReg = bytecode[pc++]; + + RuntimeArray array = (RuntimeArray) registers[arrayReg]; + RuntimeBase listBase = registers[listReg]; + RuntimeList list = listBase.getList(); + + array.setFromList(list); + return pc; + } + + /** + * List assignment: rd = lhsList.setFromList(rhsList) + * Format: SET_FROM_LIST rd lhsReg rhsReg + */ + public static int executeSetFromList(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int lhsReg = bytecode[pc++]; + int rhsReg = bytecode[pc++]; + RuntimeList lhsList = (RuntimeList) registers[lhsReg]; + RuntimeBase rhsBase = registers[rhsReg]; + RuntimeList rhsList = (rhsBase instanceof RuntimeList rl) ? rl : rhsBase.getList(); + RuntimeArray result = lhsList.setFromList(rhsList); + registers[rd] = result; + return pc; + } + + /** + * Set hash content from list: hash_reg = RuntimeHash.createHash(list_reg) + * Format: HASH_SET_FROM_LIST hashReg listReg + */ + public static int executeHashSetFromList(int[] bytecode, int pc, RuntimeBase[] registers) { + int hashReg = bytecode[pc++]; + int listReg = bytecode[pc++]; + + RuntimeHash existingHash = (RuntimeHash) registers[hashReg]; + RuntimeBase listBase = registers[listReg]; + + RuntimeHash newHash = RuntimeHash.createHash(listBase); + existingHash.elements = newHash.elements; + return pc; + } + + // ========================================================================= + // CONTROL FLOW - SPECIAL (RuntimeControlFlowList) + // ========================================================================= + + public static int executeCreateLast(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int labelIdx = bytecode[pc++]; + String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; + registers[rd] = new RuntimeControlFlowList(ControlFlowType.LAST, label, code.sourceName, code.sourceLine); + return pc; + } + + public static int executeCreateNext(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int labelIdx = bytecode[pc++]; + String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; + registers[rd] = new RuntimeControlFlowList(ControlFlowType.NEXT, label, code.sourceName, code.sourceLine); + return pc; + } + + public static int executeCreateRedo(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int labelIdx = bytecode[pc++]; + String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; + registers[rd] = new RuntimeControlFlowList(ControlFlowType.REDO, label, code.sourceName, code.sourceLine); + return pc; + } + + public static int executeCreateGoto(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int labelIdx = bytecode[pc++]; + String label = labelIdx == 255 ? null : code.stringPool[labelIdx]; + registers[rd] = new RuntimeControlFlowList(ControlFlowType.GOTO, label, code.sourceName, code.sourceLine); + return pc; + } + + public static int executeIsControlFlow(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = (registers[rs] instanceof RuntimeControlFlowList) ? + RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + return pc; + } + + // ========================================================================= + // SUPERINSTRUCTIONS + // ========================================================================= + + public static int executeIncReg(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + RuntimeBase incResult = MathOperators.add((RuntimeScalar) registers[rd], 1); + registers[rd] = isImmutableProxy(incResult) ? ensureMutableScalar(incResult) : incResult; + return pc; + } + + public static int executeDecReg(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + RuntimeBase decResult = MathOperators.subtract((RuntimeScalar) registers[rd], 1); + registers[rd] = isImmutableProxy(decResult) ? ensureMutableScalar(decResult) : decResult; + return pc; + } + + public static int executeAddAssign(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + if (isImmutableProxy(registers[rd])) { + registers[rd] = ensureMutableScalar(registers[rd]); + } + MathOperators.addAssign((RuntimeScalar) registers[rd], (RuntimeScalar) registers[rs]); + return pc; + } + + public static int executeAddAssignInt(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int immediate = bytecode[pc]; + pc += 1; + if (isImmutableProxy(registers[rd])) { + registers[rd] = ensureMutableScalar(registers[rd]); + } + RuntimeScalar result = MathOperators.add((RuntimeScalar) registers[rd], immediate); + ((RuntimeScalar) registers[rd]).set(result); + return pc; + } + + public static int executeXorLogical(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = Operator.xor((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); + return pc; + } + + // ========================================================================= + // ERROR HANDLING + // ========================================================================= + + public static int executeDie(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int msgReg = bytecode[pc++]; + int locationReg = bytecode[pc++]; + RuntimeBase message = registers[msgReg]; + RuntimeScalar where = (RuntimeScalar) registers[locationReg]; + WarnDie.die(message, where, code.sourceName, code.sourceLine); + throw new RuntimeException("die() did not throw exception"); + } + + public static int executeWarn(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int msgReg = bytecode[pc++]; + int locationReg = bytecode[pc++]; + RuntimeBase message = registers[msgReg]; + RuntimeScalar where = (RuntimeScalar) registers[locationReg]; + WarnDie.warn(message, where, code.sourceName, code.sourceLine); + return pc; + } + + // ========================================================================= + // REFERENCE OPERATIONS + // ========================================================================= + + public static int executeCreateRef(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase value = registers[rs]; + if (value == null) { + registers[rd] = RuntimeScalarCache.scalarUndef; + } else if (value instanceof RuntimeList list) { + if (list.size() == 1) { + registers[rd] = list.getFirst().createReference(); + } else { + registers[rd] = list.createListReference(); + } + } else { + registers[rd] = value.createReference(); + } + return pc; + } + + public static int executeDeref(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase value = registers[rs]; + if (value instanceof RuntimeScalar) { + RuntimeScalar scalar = (RuntimeScalar) value; + if (scalar.type == RuntimeScalarType.REFERENCE) { + registers[rd] = scalar.scalarDeref(); + } else { + registers[rd] = value; + } + } else { + registers[rd] = value; + } + return pc; + } + + public static int executeGetType(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + registers[rd] = new RuntimeScalar(((RuntimeScalar) registers[rs]).type); + return pc; + } + + // ========================================================================= + // SYMBOLIC REFERENCES + // ========================================================================= + + public static int executeStoreSymbolicScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int nameReg = bytecode[pc++]; + int valueReg = bytecode[pc++]; + RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg]; + String normalizedName = NameNormalizer.normalizeVariableName(nameScalar.toString(), "main"); + RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName); + globalVar.set(registers[valueReg]); + return pc; + } + + public static int executeLoadSymbolicScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int nameReg = bytecode[pc++]; + RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg]; + if (nameScalar.type == RuntimeScalarType.REFERENCE) { + registers[rd] = nameScalar.scalarDeref(); + } else { + String normalizedName = NameNormalizer.normalizeVariableName(nameScalar.toString(), "main"); + registers[rd] = GlobalVariable.getGlobalVariable(normalizedName); + } + return pc; + } + + // ========================================================================= + // TIE OPERATIONS + // ========================================================================= + + public static int executeTie(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int argsReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + RuntimeList tieArgs = (RuntimeList) registers[argsReg]; + registers[rd] = TieOperators.tie(ctx, tieArgs.elements.toArray(new RuntimeBase[0])); + return pc; + } + + public static int executeUntie(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int argsReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + RuntimeList untieArgs = (RuntimeList) registers[argsReg]; + registers[rd] = TieOperators.untie(ctx, untieArgs.elements.toArray(new RuntimeBase[0])); + return pc; + } + + public static int executeTied(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int argsReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + RuntimeList tiedArgs = (RuntimeList) registers[argsReg]; + registers[rd] = TieOperators.tied(ctx, tiedArgs.elements.toArray(new RuntimeBase[0])); + return pc; + } + + // ========================================================================= + // MISC COLD OPS + // ========================================================================= + + public static int executeStoreGlob(int[] bytecode, int pc, RuntimeBase[] registers) { + int globReg = bytecode[pc++]; + int valueReg = bytecode[pc++]; + Object val = registers[valueReg]; + RuntimeScalar scalarVal = (val instanceof RuntimeScalar) + ? (RuntimeScalar) val : ((RuntimeList) val).scalar(); + ((RuntimeGlob) registers[globReg]).set(scalarVal); + return pc; + } + + public static int executePushLocalVariable(int[] bytecode, int pc, RuntimeBase[] registers) { + int rs = bytecode[pc++]; + DynamicVariableManager.pushLocalVariable(registers[rs]); + return pc; + } + + public static int executeFlipFlop(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int flipFlopId = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = ScalarFlipFlopOperator.evaluate( + flipFlopId, + ((RuntimeBase) registers[rs1]).scalar(), + ((RuntimeBase) registers[rs2]).scalar()); + return pc; + } + + public static int executeLocalGlob(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + RuntimeGlob glob = GlobalVariable.getGlobalIO(name); + DynamicVariableManager.pushLocalVariable(glob); + registers[rd] = glob; + return pc; + } + + public static int executeGetLocalLevel(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + registers[rd] = new RuntimeScalar(DynamicVariableManager.getLocalLevel()); + return pc; + } + + public static int executeDoFile(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int fileReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + RuntimeScalar file = ((RuntimeBase) registers[fileReg]).scalar(); + registers[rd] = ModuleOperators.doFile(file, ctx); + return pc; + } + + public static int executePushPackage(int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { + int nameIdx = bytecode[pc++]; + DynamicVariableManager.pushLocalVariable(InterpreterState.currentPackage.get()); + InterpreterState.currentPackage.get().set(code.stringPool[nameIdx]); + return pc; + } + + public static int executeGlobOp(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int globId = bytecode[pc++]; + int patternReg = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = ScalarGlobOperator.evaluate(globId, (RuntimeScalar) registers[patternReg], ctx); + return pc; + } + + public static int executeUndefineScalar(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + if (isImmutableProxy(registers[rd])) { + registers[rd] = ensureMutableScalar(registers[rd]); + } + registers[rd].undefine(); + return pc; + } +} diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index 101585144..991f37513 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -1689,6 +1689,607 @@ public String disassemble() { case Opcodes.POP_LABELED_BLOCK: sb.append("POP_LABELED_BLOCK\n"); break; + + // ================================================================= + // CORE OPS (29-67) - String, comparison, logical, control flow + // ================================================================= + + case Opcodes.SUBSTR: { + // Substring: rd = substr(rs1, rs2, rs3) + // Format: SUBSTR rd strReg offsetReg lengthReg + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("SUBSTR r").append(rd).append(" = substr(r").append(rs1) + .append(", r").append(rs2).append(")\n"); + break; + } + case Opcodes.LENGTH: { + // String length: rd = length(rs) + // Format: LENGTH rd rs + rd = bytecode[pc++]; + int lenRs = bytecode[pc++]; + sb.append("LENGTH r").append(rd).append(" = length(r").append(lenRs).append(")\n"); + break; + } + case Opcodes.COMPARE_NUM: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("COMPARE_NUM r").append(rd).append(" = r").append(rs1).append(" <=> r").append(rs2).append("\n"); + break; + case Opcodes.COMPARE_STR: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("COMPARE_STR r").append(rd).append(" = r").append(rs1).append(" cmp r").append(rs2).append("\n"); + break; + case Opcodes.EQ_NUM: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("EQ_NUM r").append(rd).append(" = r").append(rs1).append(" == r").append(rs2).append("\n"); + break; + case Opcodes.EQ_STR: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("EQ_STR r").append(rd).append(" = r").append(rs1).append(" eq r").append(rs2).append("\n"); + break; + case Opcodes.NE_STR: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("NE_STR r").append(rd).append(" = r").append(rs1).append(" ne r").append(rs2).append("\n"); + break; + case Opcodes.AND: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("AND r").append(rd).append(" = r").append(rs1).append(" && r").append(rs2).append("\n"); + break; + case Opcodes.OR: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("OR r").append(rd).append(" = r").append(rs1).append(" || r").append(rs2).append("\n"); + break; + case Opcodes.CALL_BUILTIN: { + // Call builtin: rd = builtin(args, ctx) + // Format: CALL_BUILTIN rd builtinId argsReg ctx + rd = bytecode[pc++]; + int builtinId = bytecode[pc++]; + int builtinArgsReg = bytecode[pc++]; + int builtinCtx = bytecode[pc++]; + sb.append("CALL_BUILTIN r").append(rd).append(" = builtin(").append(builtinId) + .append(", r").append(builtinArgsReg).append(", ctx=").append(builtinCtx).append(")\n"); + break; + } + + // Control flow creation: rd = RuntimeControlFlowList(type, label) + // Format: CREATE_xxx rd labelIdx + case Opcodes.CREATE_LAST: { + rd = bytecode[pc++]; + int cfLabelIdx = bytecode[pc++]; + sb.append("CREATE_LAST r").append(rd).append(" label="); + if (cfLabelIdx == 255) { + sb.append(""); + } else if (stringPool != null && cfLabelIdx < stringPool.length) { + sb.append("\"").append(stringPool[cfLabelIdx]).append("\""); + } else { + sb.append(cfLabelIdx); + } + sb.append("\n"); + break; + } + case Opcodes.CREATE_NEXT: { + rd = bytecode[pc++]; + int cfLabelIdx = bytecode[pc++]; + sb.append("CREATE_NEXT r").append(rd).append(" label="); + if (cfLabelIdx == 255) { + sb.append(""); + } else if (stringPool != null && cfLabelIdx < stringPool.length) { + sb.append("\"").append(stringPool[cfLabelIdx]).append("\""); + } else { + sb.append(cfLabelIdx); + } + sb.append("\n"); + break; + } + case Opcodes.CREATE_REDO: { + rd = bytecode[pc++]; + int cfLabelIdx = bytecode[pc++]; + sb.append("CREATE_REDO r").append(rd).append(" label="); + if (cfLabelIdx == 255) { + sb.append(""); + } else if (stringPool != null && cfLabelIdx < stringPool.length) { + sb.append("\"").append(stringPool[cfLabelIdx]).append("\""); + } else { + sb.append(cfLabelIdx); + } + sb.append("\n"); + break; + } + case Opcodes.CREATE_GOTO: { + rd = bytecode[pc++]; + int cfLabelIdx = bytecode[pc++]; + sb.append("CREATE_GOTO r").append(rd).append(" label="); + if (cfLabelIdx == 255) { + sb.append(""); + } else if (stringPool != null && cfLabelIdx < stringPool.length) { + sb.append("\"").append(stringPool[cfLabelIdx]).append("\""); + } else { + sb.append(cfLabelIdx); + } + sb.append("\n"); + break; + } + case Opcodes.IS_CONTROL_FLOW: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + sb.append("IS_CONTROL_FLOW r").append(rd).append(" = (r").append(rs1).append(" instanceof ControlFlow)\n"); + break; + case Opcodes.GET_CONTROL_FLOW_TYPE: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + sb.append("GET_CONTROL_FLOW_TYPE r").append(rd).append(" = r").append(rs1).append(".getControlFlowType()\n"); + break; + + // ================================================================= + // SLICE/COLLECTION OPS (116-127) + // ================================================================= + + case Opcodes.ARRAY_SLICE: { + // Format: ARRAY_SLICE rd arrayReg indicesReg + rd = bytecode[pc++]; + int asArrayReg = bytecode[pc++]; + int asIndicesReg = bytecode[pc++]; + sb.append("ARRAY_SLICE r").append(rd).append(" = r").append(asArrayReg) + .append("[r").append(asIndicesReg).append("]\n"); + break; + } + case Opcodes.ARRAY_SLICE_SET: { + // Format: ARRAY_SLICE_SET arrayReg indicesReg valuesReg + int assArrayReg = bytecode[pc++]; + int assIndicesReg = bytecode[pc++]; + int assValuesReg = bytecode[pc++]; + sb.append("ARRAY_SLICE_SET r").append(assArrayReg) + .append("[r").append(assIndicesReg).append("] = r").append(assValuesReg).append("\n"); + break; + } + case Opcodes.HASH_SLICE: { + // Format: HASH_SLICE rd hashReg keysListReg + rd = bytecode[pc++]; + int hsHashReg = bytecode[pc++]; + int hsKeysReg = bytecode[pc++]; + sb.append("HASH_SLICE r").append(rd).append(" = r").append(hsHashReg) + .append("{r").append(hsKeysReg).append("}\n"); + break; + } + case Opcodes.HASH_SLICE_SET: { + // Format: HASH_SLICE_SET hashReg keysListReg valuesListReg + int hssHashReg = bytecode[pc++]; + int hssKeysReg = bytecode[pc++]; + int hssValuesReg = bytecode[pc++]; + sb.append("HASH_SLICE_SET r").append(hssHashReg) + .append("{r").append(hssKeysReg).append("} = r").append(hssValuesReg).append("\n"); + break; + } + case Opcodes.HASH_SLICE_DELETE: { + // Format: HASH_SLICE_DELETE rd hashReg keysListReg + rd = bytecode[pc++]; + int hsdHashReg = bytecode[pc++]; + int hsdKeysReg = bytecode[pc++]; + sb.append("HASH_SLICE_DELETE r").append(rd).append(" = delete r").append(hsdHashReg) + .append("{r").append(hsdKeysReg).append("}\n"); + break; + } + case Opcodes.LIST_SLICE_FROM: { + // Format: LIST_SLICE_FROM rd listReg startIndex + rd = bytecode[pc++]; + int lsfListReg = bytecode[pc++]; + int lsfStartIdx = bytecode[pc++]; + sb.append("LIST_SLICE_FROM r").append(rd).append(" = r").append(lsfListReg) + .append("[").append(lsfStartIdx).append("..]\n"); + break; + } + case Opcodes.SPLICE: { + // Format: SPLICE rd arrayReg argsReg context + rd = bytecode[pc++]; + int splArrayReg = bytecode[pc++]; + int splArgsReg = bytecode[pc++]; + int splCtx = bytecode[pc++]; + sb.append("SPLICE r").append(rd).append(" = splice(r").append(splArrayReg) + .append(", r").append(splArgsReg).append(", ctx=").append(splCtx).append(")\n"); + break; + } + case Opcodes.REVERSE: { + // Format: REVERSE rd argsReg ctx + rd = bytecode[pc++]; + int revArgsReg = bytecode[pc++]; + int revCtx = bytecode[pc++]; + sb.append("REVERSE r").append(rd).append(" = reverse(r").append(revArgsReg) + .append(", ctx=").append(revCtx).append(")\n"); + break; + } + case Opcodes.LENGTH_OP: { + // Format: LENGTH_OP rd stringReg + rd = bytecode[pc++]; + int lopRs = bytecode[pc++]; + sb.append("LENGTH_OP r").append(rd).append(" = length(r").append(lopRs).append(")\n"); + break; + } + case Opcodes.EXISTS: { + // Format: EXISTS rd operandReg + rd = bytecode[pc++]; + int exOperandReg = bytecode[pc++]; + sb.append("EXISTS r").append(rd).append(" = exists(r").append(exOperandReg).append(")\n"); + break; + } + case Opcodes.DELETE: { + // Format: DELETE rd operandReg + rd = bytecode[pc++]; + int delOperandReg = bytecode[pc++]; + sb.append("DELETE r").append(rd).append(" = delete(r").append(delOperandReg).append(")\n"); + break; + } + + // ================================================================= + // BEGIN RETRIEVAL (129-130) + // ================================================================= + + case Opcodes.RETRIEVE_BEGIN_ARRAY: { + // Format: RETRIEVE_BEGIN_ARRAY rd nameIdx beginId + rd = bytecode[pc++]; + nameIdx = bytecode[pc++]; + int rbaBeginId = bytecode[pc++]; + sb.append("RETRIEVE_BEGIN_ARRAY r").append(rd).append(" = BEGIN_").append(rbaBeginId) + .append("::").append(stringPool[nameIdx]).append("\n"); + break; + } + case Opcodes.RETRIEVE_BEGIN_HASH: { + // Format: RETRIEVE_BEGIN_HASH rd nameIdx beginId + rd = bytecode[pc++]; + nameIdx = bytecode[pc++]; + int rbhBeginId = bytecode[pc++]; + sb.append("RETRIEVE_BEGIN_HASH r").append(rd).append(" = BEGIN_").append(rbhBeginId) + .append("::").append(stringPool[nameIdx]).append("\n"); + break; + } + + // ================================================================= + // SYSTEM/IPC OPS (132-150) + // ================================================================= + + // Most system/IPC ops use MiscOpcodeHandler pattern: rd argsReg ctx → 3 operands + case Opcodes.CHOWN: + case Opcodes.WAITPID: + case Opcodes.GETPGRP: + case Opcodes.SETPGRP: + case Opcodes.GETPRIORITY: + case Opcodes.SETPRIORITY: + case Opcodes.GETSOCKOPT: + case Opcodes.SETSOCKOPT: { + rd = bytecode[pc++]; + int sysArgsReg = bytecode[pc++]; + int sysCtx = bytecode[pc++]; + String sysName = switch (opcode) { + case Opcodes.CHOWN -> "chown"; + case Opcodes.WAITPID -> "waitpid"; + case Opcodes.GETPGRP -> "getpgrp"; + case Opcodes.SETPGRP -> "setpgrp"; + case Opcodes.GETPRIORITY -> "getpriority"; + case Opcodes.SETPRIORITY -> "setpriority"; + case Opcodes.GETSOCKOPT -> "getsockopt"; + case Opcodes.SETSOCKOPT -> "setsockopt"; + default -> "sys_op_" + opcode; + }; + sb.append(sysName).append(" r").append(rd) + .append(" = ").append(sysName).append("(r").append(sysArgsReg) + .append(", ctx=").append(sysCtx).append(")\n"); + break; + } + case Opcodes.FORK: { + // Format: FORK rd + rd = bytecode[pc++]; + sb.append("FORK r").append(rd).append(" = fork()\n"); + break; + } + case Opcodes.GETPPID: { + // Format: GETPPID rd + rd = bytecode[pc++]; + sb.append("GETPPID r").append(rd).append(" = getppid()\n"); + break; + } + case Opcodes.SYSCALL: { + // Format: SYSCALL rd numberReg argCount [argRegs...] + rd = bytecode[pc++]; + int sysNumReg = bytecode[pc++]; + int sysArgCount = bytecode[pc++]; + sb.append("SYSCALL r").append(rd).append(" = syscall(r").append(sysNumReg).append(", ["); + for (int i = 0; i < sysArgCount; i++) { + if (i > 0) sb.append(", "); + sb.append("r").append(bytecode[pc++]); + } + sb.append("])\n"); + break; + } + case Opcodes.SEMGET: { + // Format: SEMGET rd keyReg nsemsReg flagsReg + rd = bytecode[pc++]; + int sgKeyReg = bytecode[pc++]; + int sgNsemsReg = bytecode[pc++]; + int sgFlagsReg = bytecode[pc++]; + sb.append("SEMGET r").append(rd).append(" = semget(r").append(sgKeyReg) + .append(", r").append(sgNsemsReg).append(", r").append(sgFlagsReg).append(")\n"); + break; + } + case Opcodes.SEMOP: { + // Format: SEMOP rd semidReg opstringReg + rd = bytecode[pc++]; + int soSemidReg = bytecode[pc++]; + int soOpstringReg = bytecode[pc++]; + sb.append("SEMOP r").append(rd).append(" = semop(r").append(soSemidReg) + .append(", r").append(soOpstringReg).append(")\n"); + break; + } + case Opcodes.MSGGET: { + // Format: MSGGET rd keyReg flagsReg + rd = bytecode[pc++]; + int mgKeyReg = bytecode[pc++]; + int mgFlagsReg = bytecode[pc++]; + sb.append("MSGGET r").append(rd).append(" = msgget(r").append(mgKeyReg) + .append(", r").append(mgFlagsReg).append(")\n"); + break; + } + case Opcodes.MSGSND: { + // Format: MSGSND rd idReg msgReg flagsReg + rd = bytecode[pc++]; + int msIdReg = bytecode[pc++]; + int msMsgReg = bytecode[pc++]; + int msFlagsReg = bytecode[pc++]; + sb.append("MSGSND r").append(rd).append(" = msgsnd(r").append(msIdReg) + .append(", r").append(msMsgReg).append(", r").append(msFlagsReg).append(")\n"); + break; + } + case Opcodes.MSGRCV: { + // Format: MSGRCV rd idReg sizeReg typeReg flagsReg + rd = bytecode[pc++]; + int mrIdReg = bytecode[pc++]; + int mrSizeReg = bytecode[pc++]; + int mrTypeReg = bytecode[pc++]; + int mrFlagsReg = bytecode[pc++]; + sb.append("MSGRCV r").append(rd).append(" = msgrcv(r").append(mrIdReg) + .append(", r").append(mrSizeReg).append(", r").append(mrTypeReg) + .append(", r").append(mrFlagsReg).append(")\n"); + break; + } + case Opcodes.SHMGET: { + // Format: SHMGET rd keyReg sizeReg flagsReg + rd = bytecode[pc++]; + int shgKeyReg = bytecode[pc++]; + int shgSizeReg = bytecode[pc++]; + int shgFlagsReg = bytecode[pc++]; + sb.append("SHMGET r").append(rd).append(" = shmget(r").append(shgKeyReg) + .append(", r").append(shgSizeReg).append(", r").append(shgFlagsReg).append(")\n"); + break; + } + case Opcodes.SHMREAD: { + // Format: SHMREAD rd idReg posReg sizeReg + rd = bytecode[pc++]; + int shrIdReg = bytecode[pc++]; + int shrPosReg = bytecode[pc++]; + int shrSizeReg = bytecode[pc++]; + sb.append("SHMREAD r").append(rd).append(" = shmread(r").append(shrIdReg) + .append(", r").append(shrPosReg).append(", r").append(shrSizeReg).append(")\n"); + break; + } + case Opcodes.SHMWRITE: { + // Format: SHMWRITE idReg posReg stringReg + int shwIdReg = bytecode[pc++]; + int shwPosReg = bytecode[pc++]; + int shwStringReg = bytecode[pc++]; + sb.append("SHMWRITE shmwrite(r").append(shwIdReg) + .append(", r").append(shwPosReg).append(", r").append(shwStringReg).append(")\n"); + break; + } + + // ================================================================= + // OPERATOR PROMOTIONS (156-157, 310) + // ================================================================= + + case Opcodes.OP_ABS: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + sb.append("OP_ABS r").append(rd).append(" = abs(r").append(rs1).append(")\n"); + break; + case Opcodes.OP_INT: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + sb.append("OP_INT r").append(rd).append(" = int(r").append(rs1).append(")\n"); + break; + case Opcodes.OP_POW: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("OP_POW r").append(rd).append(" = r").append(rs1).append(" ** r").append(rs2).append("\n"); + break; + + // ================================================================= + // MISC OPERATORS + // ================================================================= + + case Opcodes.TR_TRANSLITERATE: { + // Format: TR_TRANSLITERATE rd searchReg replaceReg modifiersReg targetReg context + rd = bytecode[pc++]; + int trSearchReg = bytecode[pc++]; + int trReplaceReg = bytecode[pc++]; + int trModifiersReg = bytecode[pc++]; + int trTargetReg = bytecode[pc++]; + int trCtx = bytecode[pc++]; + sb.append("TR_TRANSLITERATE r").append(rd).append(" = tr(r").append(trSearchReg) + .append(", r").append(trReplaceReg).append(", r").append(trModifiersReg) + .append(", r").append(trTargetReg).append(", ctx=").append(trCtx).append(")\n"); + break; + } + case Opcodes.STORE_SYMBOLIC_SCALAR: { + // Format: STORE_SYMBOLIC_SCALAR nameReg valueReg + int ssNameReg = bytecode[pc++]; + int ssValueReg = bytecode[pc++]; + sb.append("STORE_SYMBOLIC_SCALAR ${r").append(ssNameReg).append("} = r").append(ssValueReg).append("\n"); + break; + } + case Opcodes.LOAD_SYMBOLIC_SCALAR: { + // Format: LOAD_SYMBOLIC_SCALAR rd nameReg + rd = bytecode[pc++]; + int lsNameReg = bytecode[pc++]; + sb.append("LOAD_SYMBOLIC_SCALAR r").append(rd).append(" = ${r").append(lsNameReg).append("}\n"); + break; + } + case Opcodes.TIE: { + // Format: TIE rd argsReg ctx + rd = bytecode[pc++]; + int tieArgsReg = bytecode[pc++]; + int tieCtx = bytecode[pc++]; + sb.append("TIE r").append(rd).append(" = tie(r").append(tieArgsReg) + .append(", ctx=").append(tieCtx).append(")\n"); + break; + } + case Opcodes.UNTIE: { + // Format: UNTIE rd argsReg ctx + rd = bytecode[pc++]; + int untieArgsReg = bytecode[pc++]; + int untieCtx = bytecode[pc++]; + sb.append("UNTIE r").append(rd).append(" = untie(r").append(untieArgsReg) + .append(", ctx=").append(untieCtx).append(")\n"); + break; + } + case Opcodes.TIED: { + // Format: TIED rd argsReg ctx + rd = bytecode[pc++]; + int tiedArgsReg = bytecode[pc++]; + int tiedCtx = bytecode[pc++]; + sb.append("TIED r").append(rd).append(" = tied(r").append(tiedArgsReg) + .append(", ctx=").append(tiedCtx).append(")\n"); + break; + } + case Opcodes.QX: { + // Format: QX rd argsReg ctx (MiscOpcodeHandler pattern) + rd = bytecode[pc++]; + int qxArgsReg = bytecode[pc++]; + int qxCtx = bytecode[pc++]; + sb.append("QX r").append(rd).append(" = qx(r").append(qxArgsReg) + .append(", ctx=").append(qxCtx).append(")\n"); + break; + } + + // ================================================================= + // I/O OPERATIONS (309-330) + // ================================================================= + + case Opcodes.CLOSE: + case Opcodes.BINMODE: + case Opcodes.SEEK: + case Opcodes.EOF_OP: + case Opcodes.SYSREAD: + case Opcodes.SYSWRITE: + case Opcodes.SYSOPEN: + case Opcodes.SOCKET: + case Opcodes.BIND: + case Opcodes.CONNECT: + case Opcodes.LISTEN: + case Opcodes.WRITE: + case Opcodes.FORMLINE: + case Opcodes.PRINTF: + case Opcodes.ACCEPT: + case Opcodes.SYSSEEK: + case Opcodes.TRUNCATE: + case Opcodes.READ: + case Opcodes.OPENDIR: + case Opcodes.READDIR: + case Opcodes.SEEKDIR: { + // All use MiscOpcodeHandler pattern: rd argsReg ctx + rd = bytecode[pc++]; + int ioArgsReg = bytecode[pc++]; + int ioCtx = bytecode[pc++]; + String ioName = switch (opcode) { + case Opcodes.CLOSE -> "close"; + case Opcodes.BINMODE -> "binmode"; + case Opcodes.SEEK -> "seek"; + case Opcodes.EOF_OP -> "eof"; + case Opcodes.SYSREAD -> "sysread"; + case Opcodes.SYSWRITE -> "syswrite"; + case Opcodes.SYSOPEN -> "sysopen"; + case Opcodes.SOCKET -> "socket"; + case Opcodes.BIND -> "bind"; + case Opcodes.CONNECT -> "connect"; + case Opcodes.LISTEN -> "listen"; + case Opcodes.WRITE -> "write"; + case Opcodes.FORMLINE -> "formline"; + case Opcodes.PRINTF -> "printf"; + case Opcodes.ACCEPT -> "accept"; + case Opcodes.SYSSEEK -> "sysseek"; + case Opcodes.TRUNCATE -> "truncate"; + case Opcodes.READ -> "read"; + case Opcodes.OPENDIR -> "opendir"; + case Opcodes.READDIR -> "readdir"; + case Opcodes.SEEKDIR -> "seekdir"; + default -> "io_op_" + opcode; + }; + sb.append(ioName).append(" r").append(rd) + .append(" = ").append(ioName).append("(r").append(ioArgsReg) + .append(", ctx=").append(ioCtx).append(")\n"); + break; + } + + // ================================================================= + // OTHER MISSING OPS + // ================================================================= + + case Opcodes.LOAD_GLOB_DYNAMIC: { + // Format: LOAD_GLOB_DYNAMIC rd nameReg pkgIdx + rd = bytecode[pc++]; + int lgdNameReg = bytecode[pc++]; + int lgdPkgIdx = bytecode[pc++]; + sb.append("LOAD_GLOB_DYNAMIC r").append(rd).append(" = *{r").append(lgdNameReg) + .append("} pkg=").append(stringPool[lgdPkgIdx]).append("\n"); + break; + } + case Opcodes.SET_ARRAY_LAST_INDEX: { + // Format: SET_ARRAY_LAST_INDEX arrayReg valueReg + int saliArrayReg = bytecode[pc++]; + int saliValueReg = bytecode[pc++]; + sb.append("SET_ARRAY_LAST_INDEX $#r").append(saliArrayReg).append(" = r").append(saliValueReg).append("\n"); + break; + } + case Opcodes.XOR_LOGICAL: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + rs2 = bytecode[pc++]; + sb.append("XOR_LOGICAL r").append(rd).append(" = r").append(rs1).append(" xor r").append(rs2).append("\n"); + break; + case Opcodes.DEFINED_OR_ASSIGN: + rd = bytecode[pc++]; + rs1 = bytecode[pc++]; + sb.append("DEFINED_OR_ASSIGN r").append(rd).append(" //= r").append(rs1).append("\n"); + break; + case Opcodes.UNDEFINE_SCALAR: + rd = bytecode[pc++]; + sb.append("UNDEFINE_SCALAR r").append(rd).append("\n"); + break; + case Opcodes.SAVE_REGEX_STATE: { + // Format: SAVE_REGEX_STATE dummy + int srsDummy = bytecode[pc++]; + sb.append("SAVE_REGEX_STATE r").append(srsDummy).append("\n"); + break; + } + case Opcodes.RESTORE_REGEX_STATE: { + // Format: RESTORE_REGEX_STATE dummy + int rrsDummy = bytecode[pc++]; + sb.append("RESTORE_REGEX_STATE r").append(rrsDummy).append("\n"); + break; + } + default: sb.append("UNKNOWN(").append(opcode).append(")\n"); break; From f2ed94f8177cd836ad280244af10abb38becf6dc Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 13:23:18 +0100 Subject: [PATCH 3/5] Reformat BytecodeInterpreter.java Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../backend/bytecode/BytecodeInterpreter.java | 2740 +++++++++-------- 1 file changed, 1436 insertions(+), 1304 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index aa3587946..5c2a7d16a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1,13 +1,14 @@ package org.perlonjava.backend.bytecode; -import org.perlonjava.runtime.operators.*; -import org.perlonjava.runtime.perlmodule.Universal; +import org.perlonjava.runtime.operators.CompareOperators; +import org.perlonjava.runtime.operators.ReferenceOperators; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.regex.RuntimeRegex; import org.perlonjava.runtime.runtimetypes.*; /** * Bytecode interpreter with switch-based dispatch and pure register architecture. - * + *

* Key design principles: * 1. Pure register machine (NO expression stack) - required for control flow correctness * 2. 3-address code format: rd = rs1 op rs2 (explicit register operands) @@ -115,1468 +116,1505 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c // the bytecode dispatch at the eval's catch target PC. Without the while(true), // `continue` would have nowhere to go after the catch block. try { - outer: - while (true) { - try { - // Main dispatch loop - JVM JIT optimizes switch to tableswitch (O(1) jump) - while (pc < bytecode.length) { - // Update current PC for caller()/stack trace reporting. - // This allows ExceptionFormatter to map pc->tokenIndex->line using code.errorUtil, - // which also honors #line directives inside eval strings. - InterpreterState.setCurrentPc(pc); - int opcode = bytecode[pc++]; - - switch (opcode) { - // ================================================================= - // CONTROL FLOW - // ================================================================= - - case Opcodes.NOP -> { - // No operation - } + outer: + while (true) { + try { + // Main dispatch loop - JVM JIT optimizes switch to tableswitch (O(1) jump) + while (pc < bytecode.length) { + // Update current PC for caller()/stack trace reporting. + // This allows ExceptionFormatter to map pc->tokenIndex->line using code.errorUtil, + // which also honors #line directives inside eval strings. + InterpreterState.setCurrentPc(pc); + int opcode = bytecode[pc++]; + + switch (opcode) { + // ================================================================= + // CONTROL FLOW + // ================================================================= + + case Opcodes.NOP -> { + // No operation + } - case Opcodes.RETURN -> { - // Return from subroutine: return rd - int retReg = bytecode[pc++]; - RuntimeBase retVal = registers[retReg]; + case Opcodes.RETURN -> { + // Return from subroutine: return rd + int retReg = bytecode[pc++]; + RuntimeBase retVal = registers[retReg]; - if (retVal == null) { - return new RuntimeList(); - } - RuntimeList retList = retVal.getList(); - RuntimeCode.materializeSpecialVarsInResult(retList); - return retList; - } + if (retVal == null) { + return new RuntimeList(); + } + RuntimeList retList = retVal.getList(); + RuntimeCode.materializeSpecialVarsInResult(retList); + return retList; + } - case Opcodes.GOTO -> { - // Unconditional jump: pc = offset - int offset = readInt(bytecode, pc); - pc = offset; // Registers persist across jump (unlike stack-based!) - } + case Opcodes.GOTO -> { + // Unconditional jump: pc = offset + int offset = readInt(bytecode, pc); + pc = offset; // Registers persist across jump (unlike stack-based!) + } - case Opcodes.LAST, Opcodes.NEXT, Opcodes.REDO -> { - // Loop control: jump to target PC - // Format: opcode, target (absolute PC as int) - int target = readInt(bytecode, pc); - pc = target; - } + case Opcodes.LAST, Opcodes.NEXT, Opcodes.REDO -> { + // Loop control: jump to target PC + // Format: opcode, target (absolute PC as int) + int target = readInt(bytecode, pc); + pc = target; + } - case Opcodes.GOTO_IF_FALSE -> { - // Conditional jump: if (!rs) pc = offset - int condReg = bytecode[pc++]; - int target = readInt(bytecode, pc); - pc += 1; + case Opcodes.GOTO_IF_FALSE -> { + // Conditional jump: if (!rs) pc = offset + int condReg = bytecode[pc++]; + int target = readInt(bytecode, pc); + pc += 1; - // Convert to scalar if needed for boolean test - RuntimeBase condBase = registers[condReg]; - RuntimeScalar cond = (condBase instanceof RuntimeScalar) - ? (RuntimeScalar) condBase - : condBase.scalar(); + // Convert to scalar if needed for boolean test + RuntimeBase condBase = registers[condReg]; + RuntimeScalar cond = (condBase instanceof RuntimeScalar) + ? (RuntimeScalar) condBase + : condBase.scalar(); - if (!cond.getBoolean()) { - pc = target; // Jump - all registers stay valid! - } - } + if (!cond.getBoolean()) { + pc = target; // Jump - all registers stay valid! + } + } - case Opcodes.GOTO_IF_TRUE -> { - // Conditional jump: if (rs) pc = offset - int condReg = bytecode[pc++]; - int target = readInt(bytecode, pc); - pc += 1; + case Opcodes.GOTO_IF_TRUE -> { + // Conditional jump: if (rs) pc = offset + int condReg = bytecode[pc++]; + int target = readInt(bytecode, pc); + pc += 1; - // Convert to scalar if needed for boolean test - RuntimeBase condBase = registers[condReg]; - RuntimeScalar cond = (condBase instanceof RuntimeScalar) - ? (RuntimeScalar) condBase - : condBase.scalar(); + // Convert to scalar if needed for boolean test + RuntimeBase condBase = registers[condReg]; + RuntimeScalar cond = (condBase instanceof RuntimeScalar) + ? (RuntimeScalar) condBase + : condBase.scalar(); - if (cond.getBoolean()) { - pc = target; - } - } + if (cond.getBoolean()) { + pc = target; + } + } - // ================================================================= - // REGISTER OPERATIONS - // ================================================================= - - case Opcodes.ALIAS -> { - // Register alias: rd = rs (shares reference, does NOT copy value) - // Must unwrap RuntimeScalarReadOnly to prevent read-only values in variable registers - int dest = bytecode[pc++]; - int src = bytecode[pc++]; - RuntimeBase srcVal = registers[src]; - registers[dest] = isImmutableProxy(srcVal) ? ensureMutableScalar(srcVal) : srcVal; - } + // ================================================================= + // REGISTER OPERATIONS + // ================================================================= + + case Opcodes.ALIAS -> { + // Register alias: rd = rs (shares reference, does NOT copy value) + // Must unwrap RuntimeScalarReadOnly to prevent read-only values in variable registers + int dest = bytecode[pc++]; + int src = bytecode[pc++]; + RuntimeBase srcVal = registers[src]; + registers[dest] = isImmutableProxy(srcVal) ? ensureMutableScalar(srcVal) : srcVal; + } - case Opcodes.LOAD_CONST -> { - // Load from constant pool: rd = constants[index] - int rd = bytecode[pc++]; - int constIndex = bytecode[pc++]; - registers[rd] = (RuntimeBase) code.constants[constIndex]; - } + case Opcodes.LOAD_CONST -> { + // Load from constant pool: rd = constants[index] + int rd = bytecode[pc++]; + int constIndex = bytecode[pc++]; + registers[rd] = (RuntimeBase) code.constants[constIndex]; + } - case Opcodes.LOAD_INT -> { - // Load integer: rd = immediate (create NEW mutable scalar, not cached) - int rd = bytecode[pc++]; - int value = readInt(bytecode, pc); - pc += 1; - // Create NEW RuntimeScalar (mutable) instead of using cache - // This is needed for local variables that may be modified (++/--) - registers[rd] = new RuntimeScalar(value); - } + case Opcodes.LOAD_INT -> { + // Load integer: rd = immediate (create NEW mutable scalar, not cached) + int rd = bytecode[pc++]; + int value = readInt(bytecode, pc); + pc += 1; + // Create NEW RuntimeScalar (mutable) instead of using cache + // This is needed for local variables that may be modified (++/--) + registers[rd] = new RuntimeScalar(value); + } - case Opcodes.LOAD_STRING -> { - int rd = bytecode[pc++]; - int strIndex = bytecode[pc++]; - String s = code.stringPool[strIndex]; - RuntimeBase existing = registers[rd]; - if (!(existing instanceof RuntimeScalar rs - && rs.type == RuntimeScalarType.STRING - && s.equals(rs.value))) { - registers[rd] = new RuntimeScalar(s); - } - } + case Opcodes.LOAD_STRING -> { + int rd = bytecode[pc++]; + int strIndex = bytecode[pc++]; + String s = code.stringPool[strIndex]; + RuntimeBase existing = registers[rd]; + if (!(existing instanceof RuntimeScalar rs + && rs.type == RuntimeScalarType.STRING + && s.equals(rs.value))) { + registers[rd] = new RuntimeScalar(s); + } + } - case Opcodes.LOAD_BYTE_STRING -> { - int rd = bytecode[pc++]; - int strIndex = bytecode[pc++]; - String s = code.stringPool[strIndex]; - RuntimeBase existing = registers[rd]; - if (existing instanceof RuntimeScalar rs - && rs.type == RuntimeScalarType.BYTE_STRING - && s.equals(rs.value)) { - break; - } - RuntimeScalar bs = new RuntimeScalar(s); - bs.type = RuntimeScalarType.BYTE_STRING; - registers[rd] = bs; - } + case Opcodes.LOAD_BYTE_STRING -> { + int rd = bytecode[pc++]; + int strIndex = bytecode[pc++]; + String s = code.stringPool[strIndex]; + RuntimeBase existing = registers[rd]; + if (existing instanceof RuntimeScalar rs + && rs.type == RuntimeScalarType.BYTE_STRING + && s.equals(rs.value)) { + break; + } + RuntimeScalar bs = new RuntimeScalar(s); + bs.type = RuntimeScalarType.BYTE_STRING; + registers[rd] = bs; + } - case Opcodes.LOAD_VSTRING -> { - int rd = bytecode[pc++]; - int strIndex = bytecode[pc++]; - RuntimeScalar vs = new RuntimeScalar(code.stringPool[strIndex]); - vs.type = RuntimeScalarType.VSTRING; - registers[rd] = vs; - } + case Opcodes.LOAD_VSTRING -> { + int rd = bytecode[pc++]; + int strIndex = bytecode[pc++]; + RuntimeScalar vs = new RuntimeScalar(code.stringPool[strIndex]); + vs.type = RuntimeScalarType.VSTRING; + registers[rd] = vs; + } - case Opcodes.GLOB_OP -> { - pc = InlineOpcodeHandler.executeGlobOp(bytecode, pc, registers); - } + case Opcodes.GLOB_OP -> { + pc = InlineOpcodeHandler.executeGlobOp(bytecode, pc, registers); + } - case Opcodes.LOAD_UNDEF -> { - // Load undef: rd = new RuntimeScalar() - int rd = bytecode[pc++]; - registers[rd] = new RuntimeScalar(); - } + case Opcodes.LOAD_UNDEF -> { + // Load undef: rd = new RuntimeScalar() + int rd = bytecode[pc++]; + registers[rd] = new RuntimeScalar(); + } - case Opcodes.UNDEFINE_SCALAR -> { - pc = InlineOpcodeHandler.executeUndefineScalar(bytecode, pc, registers); - } + case Opcodes.UNDEFINE_SCALAR -> { + pc = InlineOpcodeHandler.executeUndefineScalar(bytecode, pc, registers); + } - case Opcodes.MY_SCALAR -> { - // Lexical scalar assignment: rd = new RuntimeScalar(); rd.set(rs) - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeScalar newScalar = new RuntimeScalar(); - registers[rs].addToScalar(newScalar); - registers[rd] = newScalar; - } + case Opcodes.MY_SCALAR -> { + // Lexical scalar assignment: rd = new RuntimeScalar(); rd.set(rs) + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeScalar newScalar = new RuntimeScalar(); + registers[rs].addToScalar(newScalar); + registers[rd] = newScalar; + } - // ================================================================= - // VARIABLE ACCESS - GLOBAL - // ================================================================= - - case Opcodes.LOAD_GLOBAL_SCALAR -> { - // Load global scalar: rd = GlobalVariable.getGlobalVariable(name) - int rd = bytecode[pc++]; - int nameIdx = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - // Uses SAME GlobalVariable as compiled code - registers[rd] = GlobalVariable.getGlobalVariable(name); - } + // ================================================================= + // VARIABLE ACCESS - GLOBAL + // ================================================================= + + case Opcodes.LOAD_GLOBAL_SCALAR -> { + // Load global scalar: rd = GlobalVariable.getGlobalVariable(name) + int rd = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + // Uses SAME GlobalVariable as compiled code + registers[rd] = GlobalVariable.getGlobalVariable(name); + } - case Opcodes.STORE_GLOBAL_SCALAR -> { - // Store global scalar: GlobalVariable.getGlobalVariable(name).set(rs) - int nameIdx = bytecode[pc++]; - int srcReg = bytecode[pc++]; - String name = code.stringPool[nameIdx]; + case Opcodes.STORE_GLOBAL_SCALAR -> { + // Store global scalar: GlobalVariable.getGlobalVariable(name).set(rs) + int nameIdx = bytecode[pc++]; + int srcReg = bytecode[pc++]; + String name = code.stringPool[nameIdx]; - // Convert to scalar if needed - RuntimeBase value = registers[srcReg]; - RuntimeScalar scalarValue = (value instanceof RuntimeScalar) - ? (RuntimeScalar) value - : value.scalar(); + // Convert to scalar if needed + RuntimeBase value = registers[srcReg]; + RuntimeScalar scalarValue = (value instanceof RuntimeScalar) + ? (RuntimeScalar) value + : value.scalar(); - GlobalVariable.getGlobalVariable(name).set(scalarValue); - } + GlobalVariable.getGlobalVariable(name).set(scalarValue); + } - case Opcodes.LOCAL_SCALAR_SAVE_LEVEL -> { - // Superinstruction: save dynamic level BEFORE makeLocal, then localize. - // Atomically: levelReg = getLocalLevel(), rd = makeLocal(name). - // The pre-push level in levelReg is used by POP_LOCAL_LEVEL after the loop. - int rd = bytecode[pc++]; - int levelReg = bytecode[pc++]; - int nameIdx = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - - registers[levelReg] = new RuntimeScalar(DynamicVariableManager.getLocalLevel()); - registers[rd] = GlobalRuntimeScalar.makeLocal(name); - } + case Opcodes.LOCAL_SCALAR_SAVE_LEVEL -> { + // Superinstruction: save dynamic level BEFORE makeLocal, then localize. + // Atomically: levelReg = getLocalLevel(), rd = makeLocal(name). + // The pre-push level in levelReg is used by POP_LOCAL_LEVEL after the loop. + int rd = bytecode[pc++]; + int levelReg = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + + registers[levelReg] = new RuntimeScalar(DynamicVariableManager.getLocalLevel()); + registers[rd] = GlobalRuntimeScalar.makeLocal(name); + } - case Opcodes.POP_LOCAL_LEVEL -> { - // Restore DynamicVariableManager to a previously saved local level. - // Matches JVM compiler's DynamicVariableManager.popToLocalLevel(savedLevel) call. - int rs = bytecode[pc++]; - int savedLevel = ((RuntimeScalar) registers[rs]).getInt(); - DynamicVariableManager.popToLocalLevel(savedLevel); - } + case Opcodes.POP_LOCAL_LEVEL -> { + // Restore DynamicVariableManager to a previously saved local level. + // Matches JVM compiler's DynamicVariableManager.popToLocalLevel(savedLevel) call. + int rs = bytecode[pc++]; + int savedLevel = ((RuntimeScalar) registers[rs]).getInt(); + DynamicVariableManager.popToLocalLevel(savedLevel); + } - case Opcodes.SAVE_REGEX_STATE -> { - pc++; - regexStateStack.push(new RegexState()); - } + case Opcodes.SAVE_REGEX_STATE -> { + pc++; + regexStateStack.push(new RegexState()); + } - case Opcodes.RESTORE_REGEX_STATE -> { - pc++; - if (!regexStateStack.isEmpty()) { - regexStateStack.pop().restore(); - } - } + case Opcodes.RESTORE_REGEX_STATE -> { + pc++; + if (!regexStateStack.isEmpty()) { + regexStateStack.pop().restore(); + } + } - case Opcodes.FOREACH_GLOBAL_NEXT_OR_EXIT -> { - // Superinstruction: foreach loop step for a global loop variable (e.g. $_). - // Combines: hasNext check, next() into varReg, aliasGlobalVariable, conditional jump. - // Do-while layout: if hasNext jump to bodyTarget, else fall through to exit. - int rd = bytecode[pc++]; - int iterReg = bytecode[pc++]; - int nameIdx = bytecode[pc++]; - int bodyTarget = readInt(bytecode, pc); - pc += 1; - - String name = code.stringPool[nameIdx]; - RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; - @SuppressWarnings("unchecked") - java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; - - if (iterator.hasNext()) { - RuntimeScalar element = iterator.next(); - if (isImmutableProxy(element)) { - element = ensureMutableScalar(element); - } - registers[rd] = element; - GlobalVariable.aliasGlobalVariable(name, element); - pc = bodyTarget; // ABSOLUTE jump back to body start - } else { - registers[rd] = new RuntimeScalar(); - } - } + case Opcodes.FOREACH_GLOBAL_NEXT_OR_EXIT -> { + // Superinstruction: foreach loop step for a global loop variable (e.g. $_). + // Combines: hasNext check, next() into varReg, aliasGlobalVariable, conditional jump. + // Do-while layout: if hasNext jump to bodyTarget, else fall through to exit. + int rd = bytecode[pc++]; + int iterReg = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + int bodyTarget = readInt(bytecode, pc); + pc += 1; + + String name = code.stringPool[nameIdx]; + RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; + @SuppressWarnings("unchecked") + java.util.Iterator iterator = + (java.util.Iterator) iterScalar.value; + + if (iterator.hasNext()) { + RuntimeScalar element = iterator.next(); + if (isImmutableProxy(element)) { + element = ensureMutableScalar(element); + } + registers[rd] = element; + GlobalVariable.aliasGlobalVariable(name, element); + pc = bodyTarget; // ABSOLUTE jump back to body start + } else { + registers[rd] = new RuntimeScalar(); + } + } - case Opcodes.STORE_GLOBAL_ARRAY -> { - // Store global array: GlobalVariable.getGlobalArray(name).setFromList(list) - int nameIdx = bytecode[pc++]; - int srcReg = bytecode[pc++]; - String name = code.stringPool[nameIdx]; + case Opcodes.STORE_GLOBAL_ARRAY -> { + // Store global array: GlobalVariable.getGlobalArray(name).setFromList(list) + int nameIdx = bytecode[pc++]; + int srcReg = bytecode[pc++]; + String name = code.stringPool[nameIdx]; - RuntimeArray globalArray = GlobalVariable.getGlobalArray(name); - RuntimeBase value = registers[srcReg]; + RuntimeArray globalArray = GlobalVariable.getGlobalArray(name); + RuntimeBase value = registers[srcReg]; - if (value == null) { - // Output disassembly around the error - String disasm = code.disassemble(); - throw new PerlCompilerException("STORE_GLOBAL_ARRAY: Register r" + srcReg + - " is null when storing to @" + name + " at pc=" + (pc-3) + "\n\nDisassembly:\n" + disasm); - } + if (value == null) { + // Output disassembly around the error + String disasm = code.disassemble(); + throw new PerlCompilerException("STORE_GLOBAL_ARRAY: Register r" + srcReg + + " is null when storing to @" + name + " at pc=" + (pc - 3) + "\n\nDisassembly:\n" + disasm); + } - // Clear and populate the global array from the source - if (value instanceof RuntimeArray) { - globalArray.elements.clear(); - globalArray.elements.addAll(((RuntimeArray) value).elements); - } else if (value instanceof RuntimeList) { - globalArray.setFromList((RuntimeList) value); - } else { - globalArray.setFromList(value.getList()); - } - } + // Clear and populate the global array from the source + if (value instanceof RuntimeArray) { + globalArray.elements.clear(); + globalArray.elements.addAll(((RuntimeArray) value).elements); + } else if (value instanceof RuntimeList) { + globalArray.setFromList((RuntimeList) value); + } else { + globalArray.setFromList(value.getList()); + } + } - case Opcodes.STORE_GLOBAL_HASH -> { - // Store global hash: GlobalVariable.getGlobalHash(name).setFromList(list) - int nameIdx = bytecode[pc++]; - int srcReg = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - - RuntimeHash globalHash = GlobalVariable.getGlobalHash(name); - RuntimeBase value = registers[srcReg]; - - // Clear and populate the global hash from the source - if (value instanceof RuntimeHash) { - globalHash.elements.clear(); - globalHash.elements.putAll(((RuntimeHash) value).elements); - } else if (value instanceof RuntimeList) { - globalHash.setFromList((RuntimeList) value); - } else { - globalHash.setFromList(value.getList()); - } - } + case Opcodes.STORE_GLOBAL_HASH -> { + // Store global hash: GlobalVariable.getGlobalHash(name).setFromList(list) + int nameIdx = bytecode[pc++]; + int srcReg = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + + RuntimeHash globalHash = GlobalVariable.getGlobalHash(name); + RuntimeBase value = registers[srcReg]; + + // Clear and populate the global hash from the source + if (value instanceof RuntimeHash) { + globalHash.elements.clear(); + globalHash.elements.putAll(((RuntimeHash) value).elements); + } else if (value instanceof RuntimeList) { + globalHash.setFromList((RuntimeList) value); + } else { + globalHash.setFromList(value.getList()); + } + } - case Opcodes.LOAD_GLOBAL_ARRAY -> { - // Load global array: rd = GlobalVariable.getGlobalArray(name) - int rd = bytecode[pc++]; - int nameIdx = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - registers[rd] = GlobalVariable.getGlobalArray(name); - } + case Opcodes.LOAD_GLOBAL_ARRAY -> { + // Load global array: rd = GlobalVariable.getGlobalArray(name) + int rd = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + registers[rd] = GlobalVariable.getGlobalArray(name); + } - case Opcodes.LOAD_GLOBAL_HASH -> { - // Load global hash: rd = GlobalVariable.getGlobalHash(name) - int rd = bytecode[pc++]; - int nameIdx = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - registers[rd] = GlobalVariable.getGlobalHash(name); - } + case Opcodes.LOAD_GLOBAL_HASH -> { + // Load global hash: rd = GlobalVariable.getGlobalHash(name) + int rd = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + registers[rd] = GlobalVariable.getGlobalHash(name); + } - case Opcodes.LOAD_GLOBAL_CODE -> { - // Load global code: rd = GlobalVariable.getGlobalCodeRef(name) - int rd = bytecode[pc++]; - int nameIdx = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - registers[rd] = GlobalVariable.getGlobalCodeRef(name); - } + case Opcodes.LOAD_GLOBAL_CODE -> { + // Load global code: rd = GlobalVariable.getGlobalCodeRef(name) + int rd = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + registers[rd] = GlobalVariable.getGlobalCodeRef(name); + } - case Opcodes.STORE_GLOBAL_CODE -> { - // Store global code: GlobalVariable.globalCodeRefs.put(name, codeRef) - int nameIdx = bytecode[pc++]; - int codeReg = bytecode[pc++]; - String name = code.stringPool[nameIdx]; - RuntimeScalar codeRef = (RuntimeScalar) registers[codeReg]; - // Store the code reference in the global namespace - GlobalVariable.globalCodeRefs.put(name, codeRef); - } + case Opcodes.STORE_GLOBAL_CODE -> { + // Store global code: GlobalVariable.globalCodeRefs.put(name, codeRef) + int nameIdx = bytecode[pc++]; + int codeReg = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + RuntimeScalar codeRef = (RuntimeScalar) registers[codeReg]; + // Store the code reference in the global namespace + GlobalVariable.globalCodeRefs.put(name, codeRef); + } - case Opcodes.CREATE_CLOSURE -> { - // Create closure with captured variables - // Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ... - pc = OpcodeHandlerExtended.executeCreateClosure(bytecode, pc, registers, code); - } + case Opcodes.CREATE_CLOSURE -> { + // Create closure with captured variables + // Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ... + pc = OpcodeHandlerExtended.executeCreateClosure(bytecode, pc, registers, code); + } - case Opcodes.SET_SCALAR -> { - // Set scalar value: registers[rd] = registers[rs] - // Use addToScalar which properly handles special variables like $& - // addToScalar calls getValueAsScalar() for ScalarSpecialVariable - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - RuntimeBase rdVal = registers[rd]; - RuntimeScalar rdScalar; - if (isImmutableProxy(rdVal)) { - rdScalar = new RuntimeScalar(); - registers[rd] = rdScalar; - } else if (rdVal instanceof RuntimeScalar) { - rdScalar = (RuntimeScalar) rdVal; - } else { - rdScalar = rdVal.scalar(); - } - registers[rs].addToScalar(rdScalar); - } + case Opcodes.SET_SCALAR -> { + // Set scalar value: registers[rd] = registers[rs] + // Use addToScalar which properly handles special variables like $& + // addToScalar calls getValueAsScalar() for ScalarSpecialVariable + int rd = bytecode[pc++]; + int rs = bytecode[pc++]; + RuntimeBase rdVal = registers[rd]; + RuntimeScalar rdScalar; + if (isImmutableProxy(rdVal)) { + rdScalar = new RuntimeScalar(); + registers[rd] = rdScalar; + } else if (rdVal instanceof RuntimeScalar) { + rdScalar = (RuntimeScalar) rdVal; + } else { + rdScalar = rdVal.scalar(); + } + registers[rs].addToScalar(rdScalar); + } - // ================================================================= - // ARITHMETIC OPERATORS - // ================================================================= + // ================================================================= + // ARITHMETIC OPERATORS + // ================================================================= - case Opcodes.ADD_SCALAR -> { - pc = InlineOpcodeHandler.executeAddScalar(bytecode, pc, registers); - } + case Opcodes.ADD_SCALAR -> { + pc = InlineOpcodeHandler.executeAddScalar(bytecode, pc, registers); + } - case Opcodes.SUB_SCALAR -> { - pc = InlineOpcodeHandler.executeSubScalar(bytecode, pc, registers); - } + case Opcodes.SUB_SCALAR -> { + pc = InlineOpcodeHandler.executeSubScalar(bytecode, pc, registers); + } - case Opcodes.MUL_SCALAR -> { - pc = InlineOpcodeHandler.executeMulScalar(bytecode, pc, registers); - } + case Opcodes.MUL_SCALAR -> { + pc = InlineOpcodeHandler.executeMulScalar(bytecode, pc, registers); + } - case Opcodes.DIV_SCALAR -> { - pc = InlineOpcodeHandler.executeDivScalar(bytecode, pc, registers); - } + case Opcodes.DIV_SCALAR -> { + pc = InlineOpcodeHandler.executeDivScalar(bytecode, pc, registers); + } - case Opcodes.MOD_SCALAR -> { - pc = InlineOpcodeHandler.executeModScalar(bytecode, pc, registers); - } + case Opcodes.MOD_SCALAR -> { + pc = InlineOpcodeHandler.executeModScalar(bytecode, pc, registers); + } - case Opcodes.POW_SCALAR -> { - pc = InlineOpcodeHandler.executePowScalar(bytecode, pc, registers); - } + case Opcodes.POW_SCALAR -> { + pc = InlineOpcodeHandler.executePowScalar(bytecode, pc, registers); + } - case Opcodes.NEG_SCALAR -> { - pc = InlineOpcodeHandler.executeNegScalar(bytecode, pc, registers); - } + case Opcodes.NEG_SCALAR -> { + pc = InlineOpcodeHandler.executeNegScalar(bytecode, pc, registers); + } - // Specialized unboxed operations (rare optimizations) - case Opcodes.ADD_SCALAR_INT -> { - pc = InlineOpcodeHandler.executeAddScalarInt(bytecode, pc, registers); - } + // Specialized unboxed operations (rare optimizations) + case Opcodes.ADD_SCALAR_INT -> { + pc = InlineOpcodeHandler.executeAddScalarInt(bytecode, pc, registers); + } - // ================================================================= - // STRING OPERATORS - // ================================================================= + // ================================================================= + // STRING OPERATORS + // ================================================================= - case Opcodes.CONCAT -> { - pc = InlineOpcodeHandler.executeConcat(bytecode, pc, registers); - } + case Opcodes.CONCAT -> { + pc = InlineOpcodeHandler.executeConcat(bytecode, pc, registers); + } - case Opcodes.REPEAT -> { - pc = InlineOpcodeHandler.executeRepeat(bytecode, pc, registers); - } + case Opcodes.REPEAT -> { + pc = InlineOpcodeHandler.executeRepeat(bytecode, pc, registers); + } - case Opcodes.LENGTH -> { - pc = InlineOpcodeHandler.executeLength(bytecode, pc, registers); - } + case Opcodes.LENGTH -> { + pc = InlineOpcodeHandler.executeLength(bytecode, pc, registers); + } - // ================================================================= - // COMPARISON AND LOGICAL OPERATORS (opcodes 31-39) - Delegated - // ================================================================= + // ================================================================= + // COMPARISON AND LOGICAL OPERATORS (opcodes 31-39) - Delegated + // ================================================================= - case Opcodes.COMPARE_NUM, Opcodes.COMPARE_STR, Opcodes.EQ_NUM, Opcodes.NE_NUM, Opcodes.LT_NUM, Opcodes.GT_NUM, Opcodes.LE_NUM, Opcodes.GE_NUM, Opcodes.EQ_STR, Opcodes.NE_STR, Opcodes.NOT -> { - pc = executeComparisons(opcode, bytecode, pc, registers); - } + case Opcodes.COMPARE_NUM, Opcodes.COMPARE_STR, Opcodes.EQ_NUM, Opcodes.NE_NUM, + Opcodes.LT_NUM, Opcodes.GT_NUM, Opcodes.LE_NUM, Opcodes.GE_NUM, Opcodes.EQ_STR, + Opcodes.NE_STR, Opcodes.NOT -> { + pc = executeComparisons(opcode, bytecode, pc, registers); + } - // ================================================================= - // TYPE AND REFERENCE OPERATORS (opcodes 102-105) - Delegated - // ================================================================= + // ================================================================= + // TYPE AND REFERENCE OPERATORS (opcodes 102-105) - Delegated + // ================================================================= - case Opcodes.DEFINED, Opcodes.REF, Opcodes.BLESS, Opcodes.ISA, Opcodes.PROTOTYPE, Opcodes.QUOTE_REGEX -> { - pc = executeTypeOps(opcode, bytecode, pc, registers, code); - } + case Opcodes.DEFINED, Opcodes.REF, Opcodes.BLESS, Opcodes.ISA, Opcodes.PROTOTYPE, + Opcodes.QUOTE_REGEX -> { + pc = executeTypeOps(opcode, bytecode, pc, registers, code); + } - // ================================================================= - // ITERATOR OPERATIONS - For efficient foreach loops - // ================================================================= + // ================================================================= + // ITERATOR OPERATIONS - For efficient foreach loops + // ================================================================= - case Opcodes.ITERATOR_CREATE -> { - // Create iterator: rd = rs.iterator() - // Format: ITERATOR_CREATE rd rs - pc = OpcodeHandlerExtended.executeIteratorCreate(bytecode, pc, registers); - } + case Opcodes.ITERATOR_CREATE -> { + // Create iterator: rd = rs.iterator() + // Format: ITERATOR_CREATE rd rs + pc = OpcodeHandlerExtended.executeIteratorCreate(bytecode, pc, registers); + } - case Opcodes.ITERATOR_HAS_NEXT -> { - // Check iterator: rd = iterator.hasNext() - // Format: ITERATOR_HAS_NEXT rd iterReg - pc = OpcodeHandlerExtended.executeIteratorHasNext(bytecode, pc, registers); - } + case Opcodes.ITERATOR_HAS_NEXT -> { + // Check iterator: rd = iterator.hasNext() + // Format: ITERATOR_HAS_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorHasNext(bytecode, pc, registers); + } - case Opcodes.ITERATOR_NEXT -> { - // Get next element: rd = iterator.next() - // Format: ITERATOR_NEXT rd iterReg - pc = OpcodeHandlerExtended.executeIteratorNext(bytecode, pc, registers); - } + case Opcodes.ITERATOR_NEXT -> { + // Get next element: rd = iterator.next() + // Format: ITERATOR_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorNext(bytecode, pc, registers); + } - case Opcodes.FOREACH_NEXT_OR_EXIT -> { - // Superinstruction for foreach loops (do-while layout). - // Combines: hasNext check, next() call, and conditional jump to body. - // Format: FOREACH_NEXT_OR_EXIT rd, iterReg, bodyTarget - // If hasNext: rd = iterator.next(), jump to bodyTarget (backward) - // Else: fall through to exit (iterator exhausted) - int rd = bytecode[pc++]; - int iterReg = bytecode[pc++]; - int bodyTarget = readInt(bytecode, pc); // Absolute target address - pc += 1; // Skip the int we just read - - RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; - @SuppressWarnings("unchecked") - java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; - - if (iterator.hasNext()) { - // Get next element and jump back to body - RuntimeScalar elem = iterator.next(); - registers[rd] = (isImmutableProxy(elem)) ? ensureMutableScalar(elem) : elem; - pc = bodyTarget; // ABSOLUTE jump back to body start - } else { - registers[rd] = new RuntimeScalar(); - } - } + case Opcodes.FOREACH_NEXT_OR_EXIT -> { + // Superinstruction for foreach loops (do-while layout). + // Combines: hasNext check, next() call, and conditional jump to body. + // Format: FOREACH_NEXT_OR_EXIT rd, iterReg, bodyTarget + // If hasNext: rd = iterator.next(), jump to bodyTarget (backward) + // Else: fall through to exit (iterator exhausted) + int rd = bytecode[pc++]; + int iterReg = bytecode[pc++]; + int bodyTarget = readInt(bytecode, pc); // Absolute target address + pc += 1; // Skip the int we just read + + RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; + @SuppressWarnings("unchecked") + java.util.Iterator iterator = + (java.util.Iterator) iterScalar.value; + + if (iterator.hasNext()) { + // Get next element and jump back to body + RuntimeScalar elem = iterator.next(); + registers[rd] = (isImmutableProxy(elem)) ? ensureMutableScalar(elem) : elem; + pc = bodyTarget; // ABSOLUTE jump back to body start + } else { + registers[rd] = new RuntimeScalar(); + } + } - // ================================================================= - // COMPOUND ASSIGNMENT OPERATORS (with overload support) - // ================================================================= + // ================================================================= + // COMPOUND ASSIGNMENT OPERATORS (with overload support) + // ================================================================= - case Opcodes.SUBTRACT_ASSIGN -> { - // Compound assignment: rd -= rs - // Format: SUBTRACT_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeSubtractAssign(bytecode, pc, registers); - } + case Opcodes.SUBTRACT_ASSIGN -> { + // Compound assignment: rd -= rs + // Format: SUBTRACT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeSubtractAssign(bytecode, pc, registers); + } - case Opcodes.MULTIPLY_ASSIGN -> { - // Compound assignment: rd *= rs - // Format: MULTIPLY_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeMultiplyAssign(bytecode, pc, registers); - } + case Opcodes.MULTIPLY_ASSIGN -> { + // Compound assignment: rd *= rs + // Format: MULTIPLY_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeMultiplyAssign(bytecode, pc, registers); + } - case Opcodes.DIVIDE_ASSIGN -> { - // Compound assignment: rd /= rs - // Format: DIVIDE_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeDivideAssign(bytecode, pc, registers); - } + case Opcodes.DIVIDE_ASSIGN -> { + // Compound assignment: rd /= rs + // Format: DIVIDE_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeDivideAssign(bytecode, pc, registers); + } - case Opcodes.MODULUS_ASSIGN -> { - // Compound assignment: rd %= rs - // Format: MODULUS_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeModulusAssign(bytecode, pc, registers); - } + case Opcodes.MODULUS_ASSIGN -> { + // Compound assignment: rd %= rs + // Format: MODULUS_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeModulusAssign(bytecode, pc, registers); + } - case Opcodes.REPEAT_ASSIGN -> { - // Compound assignment: rd x= rs - // Format: REPEAT_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeRepeatAssign(bytecode, pc, registers); - } + case Opcodes.REPEAT_ASSIGN -> { + // Compound assignment: rd x= rs + // Format: REPEAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeRepeatAssign(bytecode, pc, registers); + } - case Opcodes.POW_ASSIGN -> { - // Compound assignment: rd **= rs - // Format: POW_ASSIGN rd rs - pc = OpcodeHandlerExtended.executePowAssign(bytecode, pc, registers); - } + case Opcodes.POW_ASSIGN -> { + // Compound assignment: rd **= rs + // Format: POW_ASSIGN rd rs + pc = OpcodeHandlerExtended.executePowAssign(bytecode, pc, registers); + } - case Opcodes.LEFT_SHIFT_ASSIGN -> { - // Compound assignment: rd <<= rs - // Format: LEFT_SHIFT_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeLeftShiftAssign(bytecode, pc, registers); - } + case Opcodes.LEFT_SHIFT_ASSIGN -> { + // Compound assignment: rd <<= rs + // Format: LEFT_SHIFT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLeftShiftAssign(bytecode, pc, registers); + } - case Opcodes.RIGHT_SHIFT_ASSIGN -> { - pc = OpcodeHandlerExtended.executeRightShiftAssign(bytecode, pc, registers); - } + case Opcodes.RIGHT_SHIFT_ASSIGN -> { + pc = OpcodeHandlerExtended.executeRightShiftAssign(bytecode, pc, registers); + } - case Opcodes.INTEGER_LEFT_SHIFT_ASSIGN -> { - pc = InlineOpcodeHandler.executeIntegerLeftShiftAssign(bytecode, pc, registers); - } - case Opcodes.INTEGER_RIGHT_SHIFT_ASSIGN -> { - pc = InlineOpcodeHandler.executeIntegerRightShiftAssign(bytecode, pc, registers); - } - case Opcodes.INTEGER_DIV_ASSIGN -> { - pc = InlineOpcodeHandler.executeIntegerDivAssign(bytecode, pc, registers); - } - case Opcodes.INTEGER_MOD_ASSIGN -> { - pc = InlineOpcodeHandler.executeIntegerModAssign(bytecode, pc, registers); - } + case Opcodes.INTEGER_LEFT_SHIFT_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerLeftShiftAssign(bytecode, pc, registers); + } + case Opcodes.INTEGER_RIGHT_SHIFT_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerRightShiftAssign(bytecode, pc, registers); + } + case Opcodes.INTEGER_DIV_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerDivAssign(bytecode, pc, registers); + } + case Opcodes.INTEGER_MOD_ASSIGN -> { + pc = InlineOpcodeHandler.executeIntegerModAssign(bytecode, pc, registers); + } - case Opcodes.LOGICAL_AND_ASSIGN -> { - // Compound assignment: rd &&= rs (short-circuit) - // Format: LOGICAL_AND_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeLogicalAndAssign(bytecode, pc, registers); - } + case Opcodes.LOGICAL_AND_ASSIGN -> { + // Compound assignment: rd &&= rs (short-circuit) + // Format: LOGICAL_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalAndAssign(bytecode, pc, registers); + } - case Opcodes.LOGICAL_OR_ASSIGN -> { - // Compound assignment: rd ||= rs (short-circuit) - // Format: LOGICAL_OR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeLogicalOrAssign(bytecode, pc, registers); - } + case Opcodes.LOGICAL_OR_ASSIGN -> { + // Compound assignment: rd ||= rs (short-circuit) + // Format: LOGICAL_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalOrAssign(bytecode, pc, registers); + } - case Opcodes.DEFINED_OR_ASSIGN -> { - // Compound assignment: rd //= rs (short-circuit) - // Format: DEFINED_OR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeDefinedOrAssign(bytecode, pc, registers); - } + case Opcodes.DEFINED_OR_ASSIGN -> { + // Compound assignment: rd //= rs (short-circuit) + // Format: DEFINED_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeDefinedOrAssign(bytecode, pc, registers); + } - // ================================================================= - // SHIFT OPERATIONS - // ================================================================= + // ================================================================= + // SHIFT OPERATIONS + // ================================================================= - case Opcodes.LEFT_SHIFT -> { - pc = InlineOpcodeHandler.executeLeftShift(bytecode, pc, registers); - } + case Opcodes.LEFT_SHIFT -> { + pc = InlineOpcodeHandler.executeLeftShift(bytecode, pc, registers); + } - case Opcodes.RIGHT_SHIFT -> { - pc = InlineOpcodeHandler.executeRightShift(bytecode, pc, registers); - } + case Opcodes.RIGHT_SHIFT -> { + pc = InlineOpcodeHandler.executeRightShift(bytecode, pc, registers); + } - case Opcodes.INTEGER_LEFT_SHIFT -> { - pc = InlineOpcodeHandler.executeIntegerLeftShift(bytecode, pc, registers); - } + case Opcodes.INTEGER_LEFT_SHIFT -> { + pc = InlineOpcodeHandler.executeIntegerLeftShift(bytecode, pc, registers); + } - case Opcodes.INTEGER_RIGHT_SHIFT -> { - pc = InlineOpcodeHandler.executeIntegerRightShift(bytecode, pc, registers); - } + case Opcodes.INTEGER_RIGHT_SHIFT -> { + pc = InlineOpcodeHandler.executeIntegerRightShift(bytecode, pc, registers); + } - case Opcodes.INTEGER_DIV -> { - pc = InlineOpcodeHandler.executeIntegerDiv(bytecode, pc, registers); - } + case Opcodes.INTEGER_DIV -> { + pc = InlineOpcodeHandler.executeIntegerDiv(bytecode, pc, registers); + } - case Opcodes.INTEGER_MOD -> { - pc = InlineOpcodeHandler.executeIntegerMod(bytecode, pc, registers); - } + case Opcodes.INTEGER_MOD -> { + pc = InlineOpcodeHandler.executeIntegerMod(bytecode, pc, registers); + } - // ================================================================= - // ARRAY OPERATIONS - // ================================================================= + // ================================================================= + // ARRAY OPERATIONS + // ================================================================= - case Opcodes.ARRAY_GET -> { - pc = InlineOpcodeHandler.executeArrayGet(bytecode, pc, registers); - } + case Opcodes.ARRAY_GET -> { + pc = InlineOpcodeHandler.executeArrayGet(bytecode, pc, registers); + } - case Opcodes.ARRAY_SET -> { - pc = InlineOpcodeHandler.executeArraySet(bytecode, pc, registers); - } + case Opcodes.ARRAY_SET -> { + pc = InlineOpcodeHandler.executeArraySet(bytecode, pc, registers); + } - case Opcodes.ARRAY_PUSH -> { - pc = InlineOpcodeHandler.executeArrayPush(bytecode, pc, registers); - } + case Opcodes.ARRAY_PUSH -> { + pc = InlineOpcodeHandler.executeArrayPush(bytecode, pc, registers); + } - case Opcodes.ARRAY_POP -> { - pc = InlineOpcodeHandler.executeArrayPop(bytecode, pc, registers); - } + case Opcodes.ARRAY_POP -> { + pc = InlineOpcodeHandler.executeArrayPop(bytecode, pc, registers); + } - case Opcodes.ARRAY_SHIFT -> { - pc = InlineOpcodeHandler.executeArrayShift(bytecode, pc, registers); - } + case Opcodes.ARRAY_SHIFT -> { + pc = InlineOpcodeHandler.executeArrayShift(bytecode, pc, registers); + } - case Opcodes.ARRAY_UNSHIFT -> { - pc = InlineOpcodeHandler.executeArrayUnshift(bytecode, pc, registers); - } + case Opcodes.ARRAY_UNSHIFT -> { + pc = InlineOpcodeHandler.executeArrayUnshift(bytecode, pc, registers); + } - case Opcodes.ARRAY_SIZE -> { - pc = InlineOpcodeHandler.executeArraySize(bytecode, pc, registers); - } + case Opcodes.ARRAY_SIZE -> { + pc = InlineOpcodeHandler.executeArraySize(bytecode, pc, registers); + } - case Opcodes.SET_ARRAY_LAST_INDEX -> { - pc = InlineOpcodeHandler.executeSetArrayLastIndex(bytecode, pc, registers); - } + case Opcodes.SET_ARRAY_LAST_INDEX -> { + pc = InlineOpcodeHandler.executeSetArrayLastIndex(bytecode, pc, registers); + } - case Opcodes.CREATE_ARRAY -> { - pc = InlineOpcodeHandler.executeCreateArray(bytecode, pc, registers); - } + case Opcodes.CREATE_ARRAY -> { + pc = InlineOpcodeHandler.executeCreateArray(bytecode, pc, registers); + } - // ================================================================= - // HASH OPERATIONS - // ================================================================= + // ================================================================= + // HASH OPERATIONS + // ================================================================= - case Opcodes.HASH_GET -> { - pc = InlineOpcodeHandler.executeHashGet(bytecode, pc, registers); - } + case Opcodes.HASH_GET -> { + pc = InlineOpcodeHandler.executeHashGet(bytecode, pc, registers); + } - case Opcodes.HASH_SET -> { - pc = InlineOpcodeHandler.executeHashSet(bytecode, pc, registers); - } + case Opcodes.HASH_SET -> { + pc = InlineOpcodeHandler.executeHashSet(bytecode, pc, registers); + } - case Opcodes.HASH_EXISTS -> { - pc = InlineOpcodeHandler.executeHashExists(bytecode, pc, registers); - } + case Opcodes.HASH_EXISTS -> { + pc = InlineOpcodeHandler.executeHashExists(bytecode, pc, registers); + } - case Opcodes.HASH_DELETE -> { - pc = InlineOpcodeHandler.executeHashDelete(bytecode, pc, registers); - } + case Opcodes.HASH_DELETE -> { + pc = InlineOpcodeHandler.executeHashDelete(bytecode, pc, registers); + } - case Opcodes.ARRAY_EXISTS -> { - pc = InlineOpcodeHandler.executeArrayExists(bytecode, pc, registers); - } + case Opcodes.ARRAY_EXISTS -> { + pc = InlineOpcodeHandler.executeArrayExists(bytecode, pc, registers); + } - case Opcodes.ARRAY_DELETE -> { - pc = InlineOpcodeHandler.executeArrayDelete(bytecode, pc, registers); - } + case Opcodes.ARRAY_DELETE -> { + pc = InlineOpcodeHandler.executeArrayDelete(bytecode, pc, registers); + } - case Opcodes.HASH_KEYS -> { - pc = InlineOpcodeHandler.executeHashKeys(bytecode, pc, registers); - } + case Opcodes.HASH_KEYS -> { + pc = InlineOpcodeHandler.executeHashKeys(bytecode, pc, registers); + } - case Opcodes.HASH_VALUES -> { - pc = InlineOpcodeHandler.executeHashValues(bytecode, pc, registers); - } + case Opcodes.HASH_VALUES -> { + pc = InlineOpcodeHandler.executeHashValues(bytecode, pc, registers); + } - // ================================================================= - // SUBROUTINE CALLS - // ================================================================= - - case Opcodes.CALL_SUB -> { - // Call subroutine: rd = coderef->(args) - // May return RuntimeControlFlowList! - int rd = bytecode[pc++]; - int coderefReg = bytecode[pc++]; - int argsReg = bytecode[pc++]; - int context = bytecode[pc++]; - - // Auto-convert coderef to scalar if needed - RuntimeBase codeRefBase = registers[coderefReg]; - RuntimeScalar codeRef = (codeRefBase instanceof RuntimeScalar) - ? (RuntimeScalar) codeRefBase - : codeRefBase.scalar(); - RuntimeBase argsBase = registers[argsReg]; - - RuntimeArray callArgs; - if (argsBase instanceof RuntimeArray) { - callArgs = (RuntimeArray) argsBase; - } else if (argsBase instanceof RuntimeList) { - callArgs = new RuntimeArray(); - ((RuntimeList) argsBase).setArrayOfAlias(callArgs); - } else { - callArgs = new RuntimeArray((RuntimeScalar) argsBase); - } + // ================================================================= + // SUBROUTINE CALLS + // ================================================================= + + case Opcodes.CALL_SUB -> { + // Call subroutine: rd = coderef->(args) + // May return RuntimeControlFlowList! + int rd = bytecode[pc++]; + int coderefReg = bytecode[pc++]; + int argsReg = bytecode[pc++]; + int context = bytecode[pc++]; + + // Auto-convert coderef to scalar if needed + RuntimeBase codeRefBase = registers[coderefReg]; + RuntimeScalar codeRef = (codeRefBase instanceof RuntimeScalar) + ? (RuntimeScalar) codeRefBase + : codeRefBase.scalar(); + RuntimeBase argsBase = registers[argsReg]; + + RuntimeArray callArgs; + if (argsBase instanceof RuntimeArray) { + callArgs = (RuntimeArray) argsBase; + } else if (argsBase instanceof RuntimeList) { + callArgs = new RuntimeArray(); + argsBase.setArrayOfAlias(callArgs); + } else { + callArgs = new RuntimeArray((RuntimeScalar) argsBase); + } - RuntimeList result = RuntimeCode.apply(codeRef, "", callArgs, context); + RuntimeList result = RuntimeCode.apply(codeRef, "", callArgs, context); - // Convert to scalar if called in scalar context - if (context == RuntimeContextType.SCALAR) { - RuntimeBase scalarResult = result.scalar(); - registers[rd] = (isImmutableProxy(scalarResult)) ? ensureMutableScalar(scalarResult) : scalarResult; - } else { - registers[rd] = result; - } + // Convert to scalar if called in scalar context + if (context == RuntimeContextType.SCALAR) { + RuntimeBase scalarResult = result.scalar(); + registers[rd] = (isImmutableProxy(scalarResult)) ? ensureMutableScalar(scalarResult) : scalarResult; + } else { + registers[rd] = result; + } - // Check for control flow (last/next/redo/goto/tail-call) - if (result.isNonLocalGoto()) { - RuntimeControlFlowList flow = (RuntimeControlFlowList) result; - // Check labeled block stack for a matching label - boolean handled = false; - for (int i = labeledBlockStack.size() - 1; i >= 0; i--) { - int[] entry = labeledBlockStack.get(i); - String blockLabel = code.stringPool[entry[0]]; - if (flow.matchesLabel(blockLabel)) { - // Pop entries down to and including the match - while (labeledBlockStack.size() > i) { - labeledBlockStack.pop(); + // Check for control flow (last/next/redo/goto/tail-call) + if (result.isNonLocalGoto()) { + RuntimeControlFlowList flow = (RuntimeControlFlowList) result; + // Check labeled block stack for a matching label + boolean handled = false; + for (int i = labeledBlockStack.size() - 1; i >= 0; i--) { + int[] entry = labeledBlockStack.get(i); + String blockLabel = code.stringPool[entry[0]]; + if (flow.matchesLabel(blockLabel)) { + // Pop entries down to and including the match + while (labeledBlockStack.size() > i) { + labeledBlockStack.pop(); + } + pc = entry[1]; // jump to block exit + handled = true; + break; + } + } + if (!handled) { + return result; } - pc = entry[1]; // jump to block exit - handled = true; - break; } } - if (!handled) { - return result; - } - } - } - case Opcodes.CALL_METHOD -> { - // Call method: rd = RuntimeCode.call(invocant, method, currentSub, args, context) - // May return RuntimeControlFlowList! - int rd = bytecode[pc++]; - int invocantReg = bytecode[pc++]; - int methodReg = bytecode[pc++]; - int currentSubReg = bytecode[pc++]; - int argsReg = bytecode[pc++]; - int context = bytecode[pc++]; - - RuntimeScalar invocant = (RuntimeScalar) registers[invocantReg]; - RuntimeScalar method = (RuntimeScalar) registers[methodReg]; - RuntimeScalar currentSub = (RuntimeScalar) registers[currentSubReg]; - RuntimeBase argsBase = registers[argsReg]; - - RuntimeArray callArgs; - if (argsBase instanceof RuntimeArray) { - callArgs = (RuntimeArray) argsBase; - } else if (argsBase instanceof RuntimeList) { - callArgs = new RuntimeArray(); - ((RuntimeList) argsBase).setArrayOfAlias(callArgs); - } else { - callArgs = new RuntimeArray((RuntimeScalar) argsBase); - } + case Opcodes.CALL_METHOD -> { + // Call method: rd = RuntimeCode.call(invocant, method, currentSub, args, context) + // May return RuntimeControlFlowList! + int rd = bytecode[pc++]; + int invocantReg = bytecode[pc++]; + int methodReg = bytecode[pc++]; + int currentSubReg = bytecode[pc++]; + int argsReg = bytecode[pc++]; + int context = bytecode[pc++]; + + RuntimeScalar invocant = (RuntimeScalar) registers[invocantReg]; + RuntimeScalar method = (RuntimeScalar) registers[methodReg]; + RuntimeScalar currentSub = (RuntimeScalar) registers[currentSubReg]; + RuntimeBase argsBase = registers[argsReg]; + + RuntimeArray callArgs; + if (argsBase instanceof RuntimeArray) { + callArgs = (RuntimeArray) argsBase; + } else if (argsBase instanceof RuntimeList) { + callArgs = new RuntimeArray(); + argsBase.setArrayOfAlias(callArgs); + } else { + callArgs = new RuntimeArray((RuntimeScalar) argsBase); + } - RuntimeList result = RuntimeCode.call(invocant, method, currentSub, callArgs, context); + RuntimeList result = RuntimeCode.call(invocant, method, currentSub, callArgs, context); - // Convert to scalar if called in scalar context - if (context == RuntimeContextType.SCALAR) { - RuntimeBase scalarResult = result.scalar(); - registers[rd] = (isImmutableProxy(scalarResult)) ? ensureMutableScalar(scalarResult) : scalarResult; - } else { - registers[rd] = result; - } + // Convert to scalar if called in scalar context + if (context == RuntimeContextType.SCALAR) { + RuntimeBase scalarResult = result.scalar(); + registers[rd] = (isImmutableProxy(scalarResult)) ? ensureMutableScalar(scalarResult) : scalarResult; + } else { + registers[rd] = result; + } - // Check for control flow (last/next/redo/goto/tail-call) - if (result.isNonLocalGoto()) { - RuntimeControlFlowList flow = (RuntimeControlFlowList) result; - boolean handled = false; - for (int i = labeledBlockStack.size() - 1; i >= 0; i--) { - int[] entry = labeledBlockStack.get(i); - String blockLabel = code.stringPool[entry[0]]; - if (flow.matchesLabel(blockLabel)) { - while (labeledBlockStack.size() > i) { - labeledBlockStack.pop(); + // Check for control flow (last/next/redo/goto/tail-call) + if (result.isNonLocalGoto()) { + RuntimeControlFlowList flow = (RuntimeControlFlowList) result; + boolean handled = false; + for (int i = labeledBlockStack.size() - 1; i >= 0; i--) { + int[] entry = labeledBlockStack.get(i); + String blockLabel = code.stringPool[entry[0]]; + if (flow.matchesLabel(blockLabel)) { + while (labeledBlockStack.size() > i) { + labeledBlockStack.pop(); + } + pc = entry[1]; + handled = true; + break; + } + } + if (!handled) { + return result; } - pc = entry[1]; - handled = true; - break; } } - if (!handled) { - return result; - } - } - } - // ================================================================= - // CONTROL FLOW - SPECIAL (RuntimeControlFlowList) - // ================================================================= + // ================================================================= + // CONTROL FLOW - SPECIAL (RuntimeControlFlowList) + // ================================================================= - case Opcodes.CREATE_LAST -> { - pc = InlineOpcodeHandler.executeCreateLast(bytecode, pc, registers, code); - } - - case Opcodes.CREATE_NEXT -> { - pc = InlineOpcodeHandler.executeCreateNext(bytecode, pc, registers, code); - } + case Opcodes.CREATE_LAST -> { + pc = InlineOpcodeHandler.executeCreateLast(bytecode, pc, registers, code); + } - case Opcodes.CREATE_REDO -> { - pc = InlineOpcodeHandler.executeCreateRedo(bytecode, pc, registers, code); - } + case Opcodes.CREATE_NEXT -> { + pc = InlineOpcodeHandler.executeCreateNext(bytecode, pc, registers, code); + } - case Opcodes.CREATE_GOTO -> { - pc = InlineOpcodeHandler.executeCreateGoto(bytecode, pc, registers, code); - } + case Opcodes.CREATE_REDO -> { + pc = InlineOpcodeHandler.executeCreateRedo(bytecode, pc, registers, code); + } - case Opcodes.IS_CONTROL_FLOW -> { - pc = InlineOpcodeHandler.executeIsControlFlow(bytecode, pc, registers); - } + case Opcodes.CREATE_GOTO -> { + pc = InlineOpcodeHandler.executeCreateGoto(bytecode, pc, registers, code); + } - // ================================================================= - // MISCELLANEOUS - // ================================================================= + case Opcodes.IS_CONTROL_FLOW -> { + pc = InlineOpcodeHandler.executeIsControlFlow(bytecode, pc, registers); + } - case Opcodes.PRINT -> { - // Print to filehandle - // Format: PRINT contentReg filehandleReg - pc = OpcodeHandlerExtended.executePrint(bytecode, pc, registers); - } + // ================================================================= + // MISCELLANEOUS + // ================================================================= - case Opcodes.SAY -> { - // Say to filehandle - // Format: SAY contentReg filehandleReg - pc = OpcodeHandlerExtended.executeSay(bytecode, pc, registers); - } + case Opcodes.PRINT -> { + // Print to filehandle + // Format: PRINT contentReg filehandleReg + pc = OpcodeHandlerExtended.executePrint(bytecode, pc, registers); + } - // ================================================================= - // SUPERINSTRUCTIONS - Eliminate ALIAS overhead - // ================================================================= + case Opcodes.SAY -> { + // Say to filehandle + // Format: SAY contentReg filehandleReg + pc = OpcodeHandlerExtended.executeSay(bytecode, pc, registers); + } - case Opcodes.INC_REG -> { - pc = InlineOpcodeHandler.executeIncReg(bytecode, pc, registers); - } + // ================================================================= + // SUPERINSTRUCTIONS - Eliminate ALIAS overhead + // ================================================================= - case Opcodes.DEC_REG -> { - pc = InlineOpcodeHandler.executeDecReg(bytecode, pc, registers); - } + case Opcodes.INC_REG -> { + pc = InlineOpcodeHandler.executeIncReg(bytecode, pc, registers); + } - case Opcodes.ADD_ASSIGN -> { - pc = InlineOpcodeHandler.executeAddAssign(bytecode, pc, registers); - } + case Opcodes.DEC_REG -> { + pc = InlineOpcodeHandler.executeDecReg(bytecode, pc, registers); + } - case Opcodes.ADD_ASSIGN_INT -> { - pc = InlineOpcodeHandler.executeAddAssignInt(bytecode, pc, registers); - } + case Opcodes.ADD_ASSIGN -> { + pc = InlineOpcodeHandler.executeAddAssign(bytecode, pc, registers); + } - case Opcodes.STRING_CONCAT_ASSIGN -> { - // String concatenation and assign: rd .= rs - // Format: STRING_CONCAT_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeStringConcatAssign(bytecode, pc, registers); - } + case Opcodes.ADD_ASSIGN_INT -> { + pc = InlineOpcodeHandler.executeAddAssignInt(bytecode, pc, registers); + } - case Opcodes.BITWISE_AND_ASSIGN -> { - // Bitwise AND assignment: rd &= rs - // Format: BITWISE_AND_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeBitwiseAndAssign(bytecode, pc, registers); - } + case Opcodes.STRING_CONCAT_ASSIGN -> { + // String concatenation and assign: rd .= rs + // Format: STRING_CONCAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringConcatAssign(bytecode, pc, registers); + } - case Opcodes.BITWISE_OR_ASSIGN -> { - // Bitwise OR assignment: rd |= rs - // Format: BITWISE_OR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeBitwiseOrAssign(bytecode, pc, registers); - } + case Opcodes.BITWISE_AND_ASSIGN -> { + // Bitwise AND assignment: rd &= rs + // Format: BITWISE_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseAndAssign(bytecode, pc, registers); + } - case Opcodes.BITWISE_XOR_ASSIGN -> { - // Bitwise XOR assignment: rd ^= rs - // Format: BITWISE_XOR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeBitwiseXorAssign(bytecode, pc, registers); - } + case Opcodes.BITWISE_OR_ASSIGN -> { + // Bitwise OR assignment: rd |= rs + // Format: BITWISE_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseOrAssign(bytecode, pc, registers); + } - 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); - } + case Opcodes.BITWISE_XOR_ASSIGN -> { + // Bitwise XOR assignment: rd ^= rs + // Format: BITWISE_XOR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseXorAssign(bytecode, pc, registers); + } - 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); - } + 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); + } - 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); - } + 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); + } - case Opcodes.BITWISE_AND_BINARY -> { - // Numeric bitwise AND: rd = rs1 binary& rs2 - // Format: BITWISE_AND_BINARY rd rs1 rs2 - pc = OpcodeHandlerExtended.executeBitwiseAndBinary(bytecode, pc, registers); - } + 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); + } - case Opcodes.BITWISE_OR_BINARY -> { - // Numeric bitwise OR: rd = rs1 binary| rs2 - // Format: BITWISE_OR_BINARY rd rs1 rs2 - pc = OpcodeHandlerExtended.executeBitwiseOrBinary(bytecode, pc, registers); - } + case Opcodes.BITWISE_AND_BINARY -> { + // Numeric bitwise AND: rd = rs1 binary& rs2 + // Format: BITWISE_AND_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseAndBinary(bytecode, pc, registers); + } - case Opcodes.BITWISE_XOR_BINARY -> { - // Numeric bitwise XOR: rd = rs1 binary^ rs2 - // Format: BITWISE_XOR_BINARY rd rs1 rs2 - pc = OpcodeHandlerExtended.executeBitwiseXorBinary(bytecode, pc, registers); - } + case Opcodes.BITWISE_OR_BINARY -> { + // Numeric bitwise OR: rd = rs1 binary| rs2 + // Format: BITWISE_OR_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseOrBinary(bytecode, pc, registers); + } - case Opcodes.STRING_BITWISE_AND -> { - // String bitwise AND: rd = rs1 &. rs2 - // Format: STRING_BITWISE_AND rd rs1 rs2 - pc = OpcodeHandlerExtended.executeStringBitwiseAnd(bytecode, pc, registers); - } + case Opcodes.BITWISE_XOR_BINARY -> { + // Numeric bitwise XOR: rd = rs1 binary^ rs2 + // Format: BITWISE_XOR_BINARY rd rs1 rs2 + pc = OpcodeHandlerExtended.executeBitwiseXorBinary(bytecode, pc, registers); + } - case Opcodes.STRING_BITWISE_OR -> { - // String bitwise OR: rd = rs1 |. rs2 - // Format: STRING_BITWISE_OR rd rs1 rs2 - pc = OpcodeHandlerExtended.executeStringBitwiseOr(bytecode, pc, registers); - } + case Opcodes.STRING_BITWISE_AND -> { + // String bitwise AND: rd = rs1 &. rs2 + // Format: STRING_BITWISE_AND rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseAnd(bytecode, pc, registers); + } - case Opcodes.STRING_BITWISE_XOR -> { - // String bitwise XOR: rd = rs1 ^. rs2 - // Format: STRING_BITWISE_XOR rd rs1 rs2 - pc = OpcodeHandlerExtended.executeStringBitwiseXor(bytecode, pc, registers); - } + case Opcodes.STRING_BITWISE_OR -> { + // String bitwise OR: rd = rs1 |. rs2 + // Format: STRING_BITWISE_OR rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseOr(bytecode, pc, registers); + } - case Opcodes.XOR_LOGICAL -> { - pc = InlineOpcodeHandler.executeXorLogical(bytecode, pc, registers); - } + case Opcodes.STRING_BITWISE_XOR -> { + // String bitwise XOR: rd = rs1 ^. rs2 + // Format: STRING_BITWISE_XOR rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseXor(bytecode, pc, registers); + } - case Opcodes.BITWISE_NOT_BINARY -> { - // Numeric bitwise NOT: rd = binary~ rs - // Format: BITWISE_NOT_BINARY rd rs - pc = OpcodeHandlerExtended.executeBitwiseNotBinary(bytecode, pc, registers); - } + case Opcodes.XOR_LOGICAL -> { + pc = InlineOpcodeHandler.executeXorLogical(bytecode, pc, registers); + } - case Opcodes.BITWISE_NOT_STRING -> { - // String bitwise NOT: rd = ~. rs - // Format: BITWISE_NOT_STRING rd rs - pc = OpcodeHandlerExtended.executeBitwiseNotString(bytecode, pc, registers); - } + case Opcodes.BITWISE_NOT_BINARY -> { + // Numeric bitwise NOT: rd = binary~ rs + // Format: BITWISE_NOT_BINARY rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotBinary(bytecode, pc, registers); + } - // File test and stat operations - case Opcodes.STAT -> { - pc = OpcodeHandlerExtended.executeStat(bytecode, pc, registers); - } + case Opcodes.BITWISE_NOT_STRING -> { + // String bitwise NOT: rd = ~. rs + // Format: BITWISE_NOT_STRING rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotString(bytecode, pc, registers); + } - case Opcodes.LSTAT -> { - pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers); - } + // File test and stat operations + case Opcodes.STAT -> { + pc = OpcodeHandlerExtended.executeStat(bytecode, pc, registers); + } - case Opcodes.STAT_LASTHANDLE -> { - pc = OpcodeHandlerExtended.executeStatLastHandle(bytecode, pc, registers); - } + case Opcodes.LSTAT -> { + pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers); + } - case Opcodes.LSTAT_LASTHANDLE -> { - pc = OpcodeHandlerExtended.executeLstatLastHandle(bytecode, pc, registers); - } + case Opcodes.STAT_LASTHANDLE -> { + pc = OpcodeHandlerExtended.executeStatLastHandle(bytecode, pc, registers); + } - // File test operations (opcodes 190-216) - delegated to handler - case Opcodes.FILETEST_R, Opcodes.FILETEST_W, Opcodes.FILETEST_X, Opcodes.FILETEST_O, Opcodes.FILETEST_R_REAL, Opcodes.FILETEST_W_REAL, Opcodes.FILETEST_X_REAL, Opcodes.FILETEST_O_REAL, Opcodes.FILETEST_E, Opcodes.FILETEST_Z, Opcodes.FILETEST_S, Opcodes.FILETEST_F, Opcodes.FILETEST_D, Opcodes.FILETEST_L, Opcodes.FILETEST_P, Opcodes.FILETEST_S_UPPER, Opcodes.FILETEST_B, Opcodes.FILETEST_C, Opcodes.FILETEST_T, Opcodes.FILETEST_U, Opcodes.FILETEST_G, Opcodes.FILETEST_K, Opcodes.FILETEST_T_UPPER, Opcodes.FILETEST_B_UPPER, Opcodes.FILETEST_M, Opcodes.FILETEST_A, Opcodes.FILETEST_C_UPPER -> { - pc = OpcodeHandlerFileTest.executeFileTest(bytecode, pc, registers, opcode); - } + case Opcodes.LSTAT_LASTHANDLE -> { + pc = OpcodeHandlerExtended.executeLstatLastHandle(bytecode, pc, registers); + } - case Opcodes.PUSH_LOCAL_VARIABLE -> { - pc = InlineOpcodeHandler.executePushLocalVariable(bytecode, pc, registers); - } + // File test operations (opcodes 190-216) - delegated to handler + case Opcodes.FILETEST_R, Opcodes.FILETEST_W, Opcodes.FILETEST_X, Opcodes.FILETEST_O, + Opcodes.FILETEST_R_REAL, Opcodes.FILETEST_W_REAL, Opcodes.FILETEST_X_REAL, + Opcodes.FILETEST_O_REAL, Opcodes.FILETEST_E, Opcodes.FILETEST_Z, Opcodes.FILETEST_S, + Opcodes.FILETEST_F, Opcodes.FILETEST_D, Opcodes.FILETEST_L, Opcodes.FILETEST_P, + Opcodes.FILETEST_S_UPPER, Opcodes.FILETEST_B, Opcodes.FILETEST_C, Opcodes.FILETEST_T, + Opcodes.FILETEST_U, Opcodes.FILETEST_G, Opcodes.FILETEST_K, Opcodes.FILETEST_T_UPPER, + Opcodes.FILETEST_B_UPPER, Opcodes.FILETEST_M, Opcodes.FILETEST_A, + Opcodes.FILETEST_C_UPPER -> { + pc = OpcodeHandlerFileTest.executeFileTest(bytecode, pc, registers, opcode); + } - case Opcodes.STORE_GLOB -> { - pc = InlineOpcodeHandler.executeStoreGlob(bytecode, pc, registers); - } + case Opcodes.PUSH_LOCAL_VARIABLE -> { + pc = InlineOpcodeHandler.executePushLocalVariable(bytecode, pc, registers); + } - case Opcodes.OPEN -> { - // Open file: rd = IOOperator.open(ctx, args...) - // Format: OPEN rd ctx argsReg - pc = OpcodeHandlerExtended.executeOpen(bytecode, pc, registers); - } + case Opcodes.STORE_GLOB -> { + pc = InlineOpcodeHandler.executeStoreGlob(bytecode, pc, registers); + } - case Opcodes.READLINE -> { - // Read line from filehandle - // Format: READLINE rd fhReg ctx - pc = OpcodeHandlerExtended.executeReadline(bytecode, pc, registers); - } + case Opcodes.OPEN -> { + // Open file: rd = IOOperator.open(ctx, args...) + // Format: OPEN rd ctx argsReg + pc = OpcodeHandlerExtended.executeOpen(bytecode, pc, registers); + } - case Opcodes.MATCH_REGEX -> { - // Match regex - // Format: MATCH_REGEX rd stringReg regexReg ctx - pc = OpcodeHandlerExtended.executeMatchRegex(bytecode, pc, registers); - } + case Opcodes.READLINE -> { + // Read line from filehandle + // Format: READLINE rd fhReg ctx + pc = OpcodeHandlerExtended.executeReadline(bytecode, pc, registers); + } - case Opcodes.MATCH_REGEX_NOT -> { - // Negated regex match - // Format: MATCH_REGEX_NOT rd stringReg regexReg ctx - pc = OpcodeHandlerExtended.executeMatchRegexNot(bytecode, pc, registers); - } + case Opcodes.MATCH_REGEX -> { + // Match regex + // Format: MATCH_REGEX rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegex(bytecode, pc, registers); + } - case Opcodes.CHOMP -> { - // Chomp: rd = rs.chomp() - // Format: CHOMP rd rs - pc = OpcodeHandlerExtended.executeChomp(bytecode, pc, registers); - } + case Opcodes.MATCH_REGEX_NOT -> { + // Negated regex match + // Format: MATCH_REGEX_NOT rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegexNot(bytecode, pc, registers); + } - case Opcodes.WANTARRAY -> { - // Get wantarray context - // Format: WANTARRAY rd wantarrayReg - pc = OpcodeHandlerExtended.executeWantarray(bytecode, pc, registers); - } + case Opcodes.CHOMP -> { + // Chomp: rd = rs.chomp() + // Format: CHOMP rd rs + pc = OpcodeHandlerExtended.executeChomp(bytecode, pc, registers); + } - case Opcodes.REQUIRE -> { - // Require module or version - // Format: REQUIRE rd rs - pc = OpcodeHandlerExtended.executeRequire(bytecode, pc, registers); - } + case Opcodes.WANTARRAY -> { + // Get wantarray context + // Format: WANTARRAY rd wantarrayReg + pc = OpcodeHandlerExtended.executeWantarray(bytecode, pc, registers); + } - case Opcodes.POS -> { - // Get regex position - // Format: POS rd rs - pc = OpcodeHandlerExtended.executePos(bytecode, pc, registers); - } + case Opcodes.REQUIRE -> { + // Require module or version + // Format: REQUIRE rd rs + pc = OpcodeHandlerExtended.executeRequire(bytecode, pc, registers); + } - case Opcodes.INDEX -> { - // Find substring position - // Format: INDEX rd strReg substrReg posReg - pc = OpcodeHandlerExtended.executeIndex(bytecode, pc, registers); - } + case Opcodes.POS -> { + // Get regex position + // Format: POS rd rs + pc = OpcodeHandlerExtended.executePos(bytecode, pc, registers); + } - case Opcodes.RINDEX -> { - // Find substring position from end - // Format: RINDEX rd strReg substrReg posReg - pc = OpcodeHandlerExtended.executeRindex(bytecode, pc, registers); - } + case Opcodes.INDEX -> { + // Find substring position + // Format: INDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeIndex(bytecode, pc, registers); + } - case Opcodes.PRE_AUTOINCREMENT -> { - // Pre-increment: ++rd - // Format: PRE_AUTOINCREMENT rd - pc = OpcodeHandlerExtended.executePreAutoIncrement(bytecode, pc, registers); - } + case Opcodes.RINDEX -> { + // Find substring position from end + // Format: RINDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeRindex(bytecode, pc, registers); + } - case Opcodes.POST_AUTOINCREMENT -> { - // Post-increment: rd = rs++ - // Format: POST_AUTOINCREMENT rd rs - pc = OpcodeHandlerExtended.executePostAutoIncrement(bytecode, pc, registers); - } + case Opcodes.PRE_AUTOINCREMENT -> { + // Pre-increment: ++rd + // Format: PRE_AUTOINCREMENT rd + pc = OpcodeHandlerExtended.executePreAutoIncrement(bytecode, pc, registers); + } - case Opcodes.PRE_AUTODECREMENT -> { - // Pre-decrement: --rd - // Format: PRE_AUTODECREMENT rd - pc = OpcodeHandlerExtended.executePreAutoDecrement(bytecode, pc, registers); - } + case Opcodes.POST_AUTOINCREMENT -> { + // Post-increment: rd = rs++ + // Format: POST_AUTOINCREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoIncrement(bytecode, pc, registers); + } - case Opcodes.POST_AUTODECREMENT -> { - // Post-decrement: rd = rs-- - // Format: POST_AUTODECREMENT rd rs - pc = OpcodeHandlerExtended.executePostAutoDecrement(bytecode, pc, registers); - } + case Opcodes.PRE_AUTODECREMENT -> { + // Pre-decrement: --rd + // Format: PRE_AUTODECREMENT rd + pc = OpcodeHandlerExtended.executePreAutoDecrement(bytecode, pc, registers); + } - // ================================================================= - // ERROR HANDLING - // ================================================================= + case Opcodes.POST_AUTODECREMENT -> { + // Post-decrement: rd = rs-- + // Format: POST_AUTODECREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoDecrement(bytecode, pc, registers); + } - case Opcodes.DIE -> { - pc = InlineOpcodeHandler.executeDie(bytecode, pc, registers, code); - } + // ================================================================= + // ERROR HANDLING + // ================================================================= - case Opcodes.WARN -> { - pc = InlineOpcodeHandler.executeWarn(bytecode, pc, registers, code); - } + case Opcodes.DIE -> { + pc = InlineOpcodeHandler.executeDie(bytecode, pc, registers, code); + } - // ================================================================= - // REFERENCE OPERATIONS - // ================================================================= + case Opcodes.WARN -> { + pc = InlineOpcodeHandler.executeWarn(bytecode, pc, registers, code); + } - case Opcodes.CREATE_REF -> { - pc = InlineOpcodeHandler.executeCreateRef(bytecode, pc, registers); - } + // ================================================================= + // REFERENCE OPERATIONS + // ================================================================= - case Opcodes.DEREF -> { - pc = InlineOpcodeHandler.executeDeref(bytecode, pc, registers); - } + case Opcodes.CREATE_REF -> { + pc = InlineOpcodeHandler.executeCreateRef(bytecode, pc, registers); + } - case Opcodes.GET_TYPE -> { - pc = InlineOpcodeHandler.executeGetType(bytecode, pc, registers); - } + case Opcodes.DEREF -> { + pc = InlineOpcodeHandler.executeDeref(bytecode, pc, registers); + } - // ================================================================= - // EVAL BLOCK SUPPORT - // ================================================================= + case Opcodes.GET_TYPE -> { + pc = InlineOpcodeHandler.executeGetType(bytecode, pc, registers); + } - case Opcodes.EVAL_TRY -> { - // Start of eval block with exception handling - // Format: [EVAL_TRY] [catch_target_high] [catch_target_low] - // catch_target is absolute bytecode address (4 bytes) + // ================================================================= + // EVAL BLOCK SUPPORT + // ================================================================= - int catchPc = readInt(bytecode, pc); // Read 4-byte absolute address - pc += 1; // Skip the 2 shorts we just read + case Opcodes.EVAL_TRY -> { + // Start of eval block with exception handling + // Format: [EVAL_TRY] [catch_target_high] [catch_target_low] + // catch_target is absolute bytecode address (4 bytes) - // Push catch PC onto eval stack - evalCatchStack.push(catchPc); + int catchPc = readInt(bytecode, pc); // Read 4-byte absolute address + pc += 1; // Skip the 2 shorts we just read - // Clear $@ at start of eval block - GlobalVariable.setGlobalVariable("main::@", ""); + // Push catch PC onto eval stack + evalCatchStack.push(catchPc); - // Continue execution - if exception occurs, outer catch handler - // will check evalCatchStack and jump to catchPc - } + // Clear $@ at start of eval block + GlobalVariable.setGlobalVariable("main::@", ""); - case Opcodes.EVAL_END -> { - // End of successful eval block - clear $@ and pop catch stack - GlobalVariable.setGlobalVariable("main::@", ""); + // Continue execution - if exception occurs, outer catch handler + // will check evalCatchStack and jump to catchPc + } - // Pop the catch PC from eval stack (we didn't need it) - if (!evalCatchStack.isEmpty()) { - evalCatchStack.pop(); - } - } + case Opcodes.EVAL_END -> { + // End of successful eval block - clear $@ and pop catch stack + GlobalVariable.setGlobalVariable("main::@", ""); - case Opcodes.EVAL_CATCH -> { - // Exception handler for eval block - // Format: [EVAL_CATCH] [rd] - // This is only reached when an exception is caught + // Pop the catch PC from eval stack (we didn't need it) + if (!evalCatchStack.isEmpty()) { + evalCatchStack.pop(); + } + } - int rd = bytecode[pc++]; + case Opcodes.EVAL_CATCH -> { + // Exception handler for eval block + // Format: [EVAL_CATCH] [rd] + // This is only reached when an exception is caught - // WarnDie.catchEval() should have already been called to set $@ - // Just store undef as the eval result - registers[rd] = RuntimeScalarCache.scalarUndef; - } + int rd = bytecode[pc++]; - // ================================================================= - // LABELED BLOCK SUPPORT - // ================================================================= + // WarnDie.catchEval() should have already been called to set $@ + // Just store undef as the eval result + registers[rd] = RuntimeScalarCache.scalarUndef; + } - case Opcodes.PUSH_LABELED_BLOCK -> { - int labelIdx = bytecode[pc++]; - int exitPc = readInt(bytecode, pc); - pc += 1; - labeledBlockStack.push(new int[]{labelIdx, exitPc}); - } + // ================================================================= + // LABELED BLOCK SUPPORT + // ================================================================= - case Opcodes.POP_LABELED_BLOCK -> { - if (!labeledBlockStack.isEmpty()) { - labeledBlockStack.pop(); - } - } + case Opcodes.PUSH_LABELED_BLOCK -> { + int labelIdx = bytecode[pc++]; + int exitPc = readInt(bytecode, pc); + pc += 1; + labeledBlockStack.push(new int[]{labelIdx, exitPc}); + } - // ================================================================= - // LIST OPERATIONS - // ================================================================= + case Opcodes.POP_LABELED_BLOCK -> { + if (!labeledBlockStack.isEmpty()) { + labeledBlockStack.pop(); + } + } - case Opcodes.LIST_TO_COUNT -> { - pc = InlineOpcodeHandler.executeListToCount(bytecode, pc, registers); - } + // ================================================================= + // LIST OPERATIONS + // ================================================================= - case Opcodes.LIST_TO_SCALAR -> { - pc = InlineOpcodeHandler.executeListToScalar(bytecode, pc, registers); - } + case Opcodes.LIST_TO_COUNT -> { + pc = InlineOpcodeHandler.executeListToCount(bytecode, pc, registers); + } - case Opcodes.SCALAR_TO_LIST -> { - pc = InlineOpcodeHandler.executeScalarToList(bytecode, pc, registers); - } + case Opcodes.LIST_TO_SCALAR -> { + pc = InlineOpcodeHandler.executeListToScalar(bytecode, pc, registers); + } - case Opcodes.CREATE_LIST -> { - pc = InlineOpcodeHandler.executeCreateList(bytecode, pc, registers); - } + case Opcodes.SCALAR_TO_LIST -> { + pc = InlineOpcodeHandler.executeScalarToList(bytecode, pc, registers); + } - // ================================================================= - // STRING OPERATIONS - // ================================================================= + case Opcodes.CREATE_LIST -> { + pc = InlineOpcodeHandler.executeCreateList(bytecode, pc, registers); + } - case Opcodes.JOIN -> { - pc = InlineOpcodeHandler.executeJoin(bytecode, pc, registers); - } + // ================================================================= + // STRING OPERATIONS + // ================================================================= - case Opcodes.SELECT -> { - pc = InlineOpcodeHandler.executeSelect(bytecode, pc, registers); - } + case Opcodes.JOIN -> { + pc = InlineOpcodeHandler.executeJoin(bytecode, pc, registers); + } - case Opcodes.RANGE -> { - pc = InlineOpcodeHandler.executeRange(bytecode, pc, registers); - } + case Opcodes.SELECT -> { + pc = InlineOpcodeHandler.executeSelect(bytecode, pc, registers); + } - case Opcodes.CREATE_HASH -> { - pc = InlineOpcodeHandler.executeCreateHash(bytecode, pc, registers); - } + case Opcodes.RANGE -> { + pc = InlineOpcodeHandler.executeRange(bytecode, pc, registers); + } - case Opcodes.RAND -> { - pc = InlineOpcodeHandler.executeRand(bytecode, pc, registers); - } + case Opcodes.CREATE_HASH -> { + pc = InlineOpcodeHandler.executeCreateHash(bytecode, pc, registers); + } - case Opcodes.MAP -> { - pc = InlineOpcodeHandler.executeMap(bytecode, pc, registers); - } + case Opcodes.RAND -> { + pc = InlineOpcodeHandler.executeRand(bytecode, pc, registers); + } - case Opcodes.GREP -> { - pc = InlineOpcodeHandler.executeGrep(bytecode, pc, registers); - } + case Opcodes.MAP -> { + pc = InlineOpcodeHandler.executeMap(bytecode, pc, registers); + } - case Opcodes.SORT -> { - pc = InlineOpcodeHandler.executeSort(bytecode, pc, registers, code); - } + case Opcodes.GREP -> { + pc = InlineOpcodeHandler.executeGrep(bytecode, pc, registers); + } - case Opcodes.NEW_ARRAY -> { - pc = InlineOpcodeHandler.executeNewArray(bytecode, pc, registers); - } + case Opcodes.SORT -> { + pc = InlineOpcodeHandler.executeSort(bytecode, pc, registers, code); + } - case Opcodes.NEW_HASH -> { - pc = InlineOpcodeHandler.executeNewHash(bytecode, pc, registers); - } + case Opcodes.NEW_ARRAY -> { + pc = InlineOpcodeHandler.executeNewArray(bytecode, pc, registers); + } - case Opcodes.ARRAY_SET_FROM_LIST -> { - pc = InlineOpcodeHandler.executeArraySetFromList(bytecode, pc, registers); - } + case Opcodes.NEW_HASH -> { + pc = InlineOpcodeHandler.executeNewHash(bytecode, pc, registers); + } - case Opcodes.SET_FROM_LIST -> { - pc = InlineOpcodeHandler.executeSetFromList(bytecode, pc, registers); - } + case Opcodes.ARRAY_SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeArraySetFromList(bytecode, pc, registers); + } - case Opcodes.HASH_SET_FROM_LIST -> { - pc = InlineOpcodeHandler.executeHashSetFromList(bytecode, pc, registers); - } + case Opcodes.SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeSetFromList(bytecode, pc, registers); + } - // ================================================================= - // PHASE 2: DIRECT OPCODES (114-154) - Range delegation - // ================================================================= - // These operations were promoted from SLOW_OP for better performance. - // Organized in CONTIGUOUS groups for JVM tableswitch optimization. + case Opcodes.HASH_SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeHashSetFromList(bytecode, pc, registers); + } - // Group 1-2: Dereferencing and Slicing (114-121) - case Opcodes.DEREF_ARRAY, Opcodes.DEREF_HASH, Opcodes.DEREF_HASH_NONSTRICT, Opcodes.DEREF_ARRAY_NONSTRICT, Opcodes.ARRAY_SLICE, Opcodes.ARRAY_SLICE_SET, Opcodes.HASH_SLICE, Opcodes.HASH_SLICE_SET, Opcodes.HASH_SLICE_DELETE, Opcodes.HASH_KEYVALUE_SLICE, Opcodes.LIST_SLICE_FROM -> { - pc = executeSliceOps(opcode, bytecode, pc, registers, code); - } + // ================================================================= + // PHASE 2: DIRECT OPCODES (114-154) - Range delegation + // ================================================================= + // These operations were promoted from SLOW_OP for better performance. + // Organized in CONTIGUOUS groups for JVM tableswitch optimization. + + // Group 1-2: Dereferencing and Slicing (114-121) + case Opcodes.DEREF_ARRAY, Opcodes.DEREF_HASH, Opcodes.DEREF_HASH_NONSTRICT, + Opcodes.DEREF_ARRAY_NONSTRICT, Opcodes.ARRAY_SLICE, Opcodes.ARRAY_SLICE_SET, + Opcodes.HASH_SLICE, Opcodes.HASH_SLICE_SET, Opcodes.HASH_SLICE_DELETE, + Opcodes.HASH_KEYVALUE_SLICE, Opcodes.LIST_SLICE_FROM -> { + pc = executeSliceOps(opcode, bytecode, pc, registers, code); + } - // Group 3-4: Array/String/Exists/Delete (122-127) - case Opcodes.SPLICE, Opcodes.REVERSE, Opcodes.SPLIT, Opcodes.LENGTH_OP, Opcodes.EXISTS, Opcodes.DELETE -> { - pc = executeArrayStringOps(opcode, bytecode, pc, registers, code); - } + // Group 3-4: Array/String/Exists/Delete (122-127) + case Opcodes.SPLICE, Opcodes.REVERSE, Opcodes.SPLIT, Opcodes.LENGTH_OP, Opcodes.EXISTS, + Opcodes.DELETE -> { + pc = executeArrayStringOps(opcode, bytecode, pc, registers, code); + } - // Group 5: Closure/Scope (128-131) - case Opcodes.RETRIEVE_BEGIN_SCALAR, Opcodes.RETRIEVE_BEGIN_ARRAY, Opcodes.RETRIEVE_BEGIN_HASH, Opcodes.LOCAL_SCALAR, Opcodes.LOCAL_ARRAY, Opcodes.LOCAL_HASH -> { - pc = executeScopeOps(opcode, bytecode, pc, registers, code); - } + // Group 5: Closure/Scope (128-131) + case Opcodes.RETRIEVE_BEGIN_SCALAR, Opcodes.RETRIEVE_BEGIN_ARRAY, + Opcodes.RETRIEVE_BEGIN_HASH, Opcodes.LOCAL_SCALAR, Opcodes.LOCAL_ARRAY, + Opcodes.LOCAL_HASH -> { + pc = executeScopeOps(opcode, bytecode, pc, registers, code); + } - // Group 6-8: System Calls and IPC (132-150) - case Opcodes.CHOWN, Opcodes.WAITPID, Opcodes.FORK, Opcodes.GETPPID, Opcodes.GETPGRP, Opcodes.SETPGRP, Opcodes.GETPRIORITY, Opcodes.SETPRIORITY, Opcodes.GETSOCKOPT, Opcodes.SETSOCKOPT, Opcodes.SYSCALL, Opcodes.SEMGET, Opcodes.SEMOP, Opcodes.MSGGET, Opcodes.MSGSND, Opcodes.MSGRCV, Opcodes.SHMGET, Opcodes.SHMREAD, Opcodes.SHMWRITE -> { - pc = executeSystemOps(opcode, bytecode, pc, registers); - } + // Group 6-8: System Calls and IPC (132-150) + case Opcodes.CHOWN, Opcodes.WAITPID, Opcodes.FORK, Opcodes.GETPPID, Opcodes.GETPGRP, + Opcodes.SETPGRP, Opcodes.GETPRIORITY, Opcodes.SETPRIORITY, Opcodes.GETSOCKOPT, + Opcodes.SETSOCKOPT, Opcodes.SYSCALL, Opcodes.SEMGET, Opcodes.SEMOP, Opcodes.MSGGET, + Opcodes.MSGSND, Opcodes.MSGRCV, Opcodes.SHMGET, Opcodes.SHMREAD, Opcodes.SHMWRITE -> { + pc = executeSystemOps(opcode, bytecode, pc, registers); + } - // Group 9: Special I/O (151-154), glob ops, strict deref - case Opcodes.TIME_OP -> { - int rd = bytecode[pc++]; - registers[rd] = org.perlonjava.runtime.operators.Time.time(); - } - case Opcodes.EVAL_STRING, Opcodes.SELECT_OP, Opcodes.LOAD_GLOB, Opcodes.SLEEP_OP, Opcodes.ALARM_OP, Opcodes.DEREF_GLOB, Opcodes.DEREF_GLOB_NONSTRICT, Opcodes.LOAD_GLOB_DYNAMIC, Opcodes.DEREF_SCALAR_STRICT, Opcodes.DEREF_SCALAR_NONSTRICT -> { - pc = executeSpecialIO(opcode, bytecode, pc, registers, code); - } + // Group 9: Special I/O (151-154), glob ops, strict deref + case Opcodes.TIME_OP -> { + int rd = bytecode[pc++]; + registers[rd] = org.perlonjava.runtime.operators.Time.time(); + } + case Opcodes.EVAL_STRING, Opcodes.SELECT_OP, Opcodes.LOAD_GLOB, Opcodes.SLEEP_OP, + Opcodes.ALARM_OP, Opcodes.DEREF_GLOB, Opcodes.DEREF_GLOB_NONSTRICT, + Opcodes.LOAD_GLOB_DYNAMIC, Opcodes.DEREF_SCALAR_STRICT, + Opcodes.DEREF_SCALAR_NONSTRICT -> { + pc = executeSpecialIO(opcode, bytecode, pc, registers, code); + } - // ================================================================= - // SLOW OPERATIONS (DEPRECATED) - // ================================================================= + // ================================================================= + // SLOW OPERATIONS (DEPRECATED) + // ================================================================= - // DEPRECATED: SLOW_OP removed - all operations now use direct opcodes (114-154) + // DEPRECATED: SLOW_OP removed - all operations now use direct opcodes (114-154) - // ================================================================= - // GENERATED BUILT-IN FUNCTION HANDLERS - // ================================================================= - // Generated by dev/tools/generate_opcode_handlers.pl - // DO NOT EDIT MANUALLY - regenerate using the tool + // ================================================================= + // GENERATED BUILT-IN FUNCTION HANDLERS + // ================================================================= + // Generated by dev/tools/generate_opcode_handlers.pl + // DO NOT EDIT MANUALLY - regenerate using the tool - // GENERATED_HANDLERS_START + // GENERATED_HANDLERS_START - // scalar_binary - case Opcodes.ATAN2, Opcodes.BINARY_AND, Opcodes.BINARY_OR, Opcodes.BINARY_XOR, Opcodes.EQ, Opcodes.NE, Opcodes.LT, Opcodes.LE, Opcodes.GT, Opcodes.GE, Opcodes.CMP, Opcodes.X -> { - pc = ScalarBinaryOpcodeHandler.execute(opcode, bytecode, pc, registers); - } + // scalar_binary + case Opcodes.ATAN2, Opcodes.BINARY_AND, Opcodes.BINARY_OR, Opcodes.BINARY_XOR, Opcodes.EQ, + Opcodes.NE, Opcodes.LT, Opcodes.LE, Opcodes.GT, Opcodes.GE, Opcodes.CMP, Opcodes.X -> { + pc = ScalarBinaryOpcodeHandler.execute(opcode, bytecode, pc, registers); + } - // scalar_unary - case Opcodes.INT, Opcodes.LOG, Opcodes.SQRT, Opcodes.COS, Opcodes.SIN, Opcodes.EXP, Opcodes.ABS, Opcodes.BINARY_NOT, Opcodes.INTEGER_BITWISE_NOT, Opcodes.ORD, Opcodes.ORD_BYTES, Opcodes.OCT, Opcodes.HEX, Opcodes.SRAND, Opcodes.CHR, Opcodes.CHR_BYTES, Opcodes.LENGTH_BYTES, Opcodes.QUOTEMETA, Opcodes.FC, Opcodes.LC, Opcodes.LCFIRST, Opcodes.UC, Opcodes.UCFIRST, Opcodes.SLEEP, Opcodes.TELL, Opcodes.RMDIR, Opcodes.CLOSEDIR, Opcodes.REWINDDIR, Opcodes.TELLDIR, Opcodes.CHDIR, Opcodes.EXIT -> { - pc = ScalarUnaryOpcodeHandler.execute(opcode, bytecode, pc, registers); - } - // GENERATED_HANDLERS_END + // scalar_unary + case Opcodes.INT, Opcodes.LOG, Opcodes.SQRT, Opcodes.COS, Opcodes.SIN, Opcodes.EXP, + Opcodes.ABS, Opcodes.BINARY_NOT, Opcodes.INTEGER_BITWISE_NOT, Opcodes.ORD, + Opcodes.ORD_BYTES, Opcodes.OCT, Opcodes.HEX, Opcodes.SRAND, Opcodes.CHR, + Opcodes.CHR_BYTES, Opcodes.LENGTH_BYTES, Opcodes.QUOTEMETA, Opcodes.FC, Opcodes.LC, + Opcodes.LCFIRST, Opcodes.UC, Opcodes.UCFIRST, Opcodes.SLEEP, Opcodes.TELL, + Opcodes.RMDIR, Opcodes.CLOSEDIR, Opcodes.REWINDDIR, Opcodes.TELLDIR, Opcodes.CHDIR, + Opcodes.EXIT -> { + pc = ScalarUnaryOpcodeHandler.execute(opcode, bytecode, pc, registers); + } + // GENERATED_HANDLERS_END - case Opcodes.TR_TRANSLITERATE -> { - pc = SlowOpcodeHandler.executeTransliterate(bytecode, pc, registers); - } + case Opcodes.TR_TRANSLITERATE -> { + pc = SlowOpcodeHandler.executeTransliterate(bytecode, pc, registers); + } - case Opcodes.STORE_SYMBOLIC_SCALAR -> { - pc = InlineOpcodeHandler.executeStoreSymbolicScalar(bytecode, pc, registers); - } + case Opcodes.STORE_SYMBOLIC_SCALAR -> { + pc = InlineOpcodeHandler.executeStoreSymbolicScalar(bytecode, pc, registers); + } - case Opcodes.LOAD_SYMBOLIC_SCALAR -> { - pc = InlineOpcodeHandler.executeLoadSymbolicScalar(bytecode, pc, registers); - } + case Opcodes.LOAD_SYMBOLIC_SCALAR -> { + pc = InlineOpcodeHandler.executeLoadSymbolicScalar(bytecode, pc, registers); + } - case Opcodes.FILETEST_LASTHANDLE -> { - // File test on cached handle '_': rd = FileTestOperator.fileTestLastHandle(operator) - // Format: FILETEST_LASTHANDLE rd operator_string_idx - pc = SlowOpcodeHandler.executeFiletestLastHandle(bytecode, pc, registers, code); - } + case Opcodes.FILETEST_LASTHANDLE -> { + // File test on cached handle '_': rd = FileTestOperator.fileTestLastHandle(operator) + // Format: FILETEST_LASTHANDLE rd operator_string_idx + pc = SlowOpcodeHandler.executeFiletestLastHandle(bytecode, pc, registers, code); + } - case Opcodes.GLOB_SLOT_GET -> { - // Glob slot access: rd = glob.hashDerefGetNonStrict(key, pkg) - // Format: GLOB_SLOT_GET rd globReg keyReg - pc = SlowOpcodeHandler.executeGlobSlotGet(bytecode, pc, registers); - } + case Opcodes.GLOB_SLOT_GET -> { + // Glob slot access: rd = glob.hashDerefGetNonStrict(key, pkg) + // Format: GLOB_SLOT_GET rd globReg keyReg + pc = SlowOpcodeHandler.executeGlobSlotGet(bytecode, pc, registers); + } - case Opcodes.SPRINTF -> { - // sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) - // Format: SPRINTF rd formatReg argsListReg - pc = OpcodeHandlerExtended.executeSprintf(bytecode, pc, registers); - } + case Opcodes.SPRINTF -> { + // sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) + // Format: SPRINTF rd formatReg argsListReg + pc = OpcodeHandlerExtended.executeSprintf(bytecode, pc, registers); + } - case Opcodes.CHOP -> { - // chop($x): rd = StringOperators.chopScalar(scalarReg) - // Format: CHOP rd scalarReg - pc = OpcodeHandlerExtended.executeChop(bytecode, pc, registers); - } + case Opcodes.CHOP -> { + // chop($x): rd = StringOperators.chopScalar(scalarReg) + // Format: CHOP rd scalarReg + pc = OpcodeHandlerExtended.executeChop(bytecode, pc, registers); + } - 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); - } + 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); + } - 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); - } + 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); + } - case Opcodes.TIE -> { - pc = InlineOpcodeHandler.executeTie(bytecode, pc, registers); - } + case Opcodes.TIE -> { + pc = InlineOpcodeHandler.executeTie(bytecode, pc, registers); + } - case Opcodes.UNTIE -> { - pc = InlineOpcodeHandler.executeUntie(bytecode, pc, registers); - } + case Opcodes.UNTIE -> { + pc = InlineOpcodeHandler.executeUntie(bytecode, pc, registers); + } - case Opcodes.TIED -> { - pc = InlineOpcodeHandler.executeTied(bytecode, pc, registers); - } + case Opcodes.TIED -> { + pc = InlineOpcodeHandler.executeTied(bytecode, pc, registers); + } - // Miscellaneous operators with context-sensitive signatures - case Opcodes.CHMOD, Opcodes.UNLINK, Opcodes.UTIME, Opcodes.RENAME, Opcodes.LINK, Opcodes.READLINK, Opcodes.UMASK, Opcodes.GETC, Opcodes.FILENO, Opcodes.QX, Opcodes.SYSTEM, Opcodes.CALLER, Opcodes.EACH, Opcodes.PACK, Opcodes.UNPACK, Opcodes.VEC, Opcodes.LOCALTIME, Opcodes.GMTIME, Opcodes.RESET, Opcodes.CRYPT, Opcodes.CLOSE, Opcodes.BINMODE, Opcodes.SEEK, Opcodes.EOF_OP, Opcodes.SYSREAD, Opcodes.SYSWRITE, Opcodes.SYSOPEN, Opcodes.SOCKET, Opcodes.BIND, Opcodes.CONNECT, Opcodes.LISTEN, Opcodes.WRITE, Opcodes.FORMLINE, Opcodes.PRINTF, Opcodes.ACCEPT, Opcodes.SYSSEEK, Opcodes.TRUNCATE, Opcodes.READ, Opcodes.OPENDIR, Opcodes.READDIR, Opcodes.SEEKDIR -> { - pc = MiscOpcodeHandler.execute(opcode, bytecode, pc, registers); - } + // Miscellaneous operators with context-sensitive signatures + case Opcodes.CHMOD, Opcodes.UNLINK, Opcodes.UTIME, Opcodes.RENAME, Opcodes.LINK, + Opcodes.READLINK, Opcodes.UMASK, Opcodes.GETC, Opcodes.FILENO, Opcodes.QX, + Opcodes.SYSTEM, Opcodes.CALLER, Opcodes.EACH, Opcodes.PACK, Opcodes.UNPACK, + Opcodes.VEC, Opcodes.LOCALTIME, Opcodes.GMTIME, Opcodes.RESET, Opcodes.CRYPT, + Opcodes.CLOSE, Opcodes.BINMODE, Opcodes.SEEK, Opcodes.EOF_OP, Opcodes.SYSREAD, + Opcodes.SYSWRITE, Opcodes.SYSOPEN, Opcodes.SOCKET, Opcodes.BIND, Opcodes.CONNECT, + Opcodes.LISTEN, Opcodes.WRITE, Opcodes.FORMLINE, Opcodes.PRINTF, Opcodes.ACCEPT, + Opcodes.SYSSEEK, Opcodes.TRUNCATE, Opcodes.READ, Opcodes.OPENDIR, Opcodes.READDIR, + Opcodes.SEEKDIR -> { + pc = MiscOpcodeHandler.execute(opcode, bytecode, pc, registers); + } - case Opcodes.SET_PACKAGE -> { - // Non-scoped package declaration: package Foo; - // Update the runtime current-package tracker so caller() returns the right package. - int nameIdx = bytecode[pc++]; - InterpreterState.currentPackage.get().set(code.stringPool[nameIdx]); - } + case Opcodes.SET_PACKAGE -> { + // Non-scoped package declaration: package Foo; + // Update the runtime current-package tracker so caller() returns the right package. + int nameIdx = bytecode[pc++]; + InterpreterState.currentPackage.get().set(code.stringPool[nameIdx]); + } - case Opcodes.PUSH_PACKAGE -> { - pc = InlineOpcodeHandler.executePushPackage(bytecode, pc, registers, code); - } + case Opcodes.PUSH_PACKAGE -> { + pc = InlineOpcodeHandler.executePushPackage(bytecode, pc, registers, code); + } - case Opcodes.FLIP_FLOP -> { - pc = InlineOpcodeHandler.executeFlipFlop(bytecode, pc, registers); - } + case Opcodes.FLIP_FLOP -> { + pc = InlineOpcodeHandler.executeFlipFlop(bytecode, pc, registers); + } - case Opcodes.LOCAL_GLOB -> { - pc = InlineOpcodeHandler.executeLocalGlob(bytecode, pc, registers, code); - } + case Opcodes.LOCAL_GLOB -> { + pc = InlineOpcodeHandler.executeLocalGlob(bytecode, pc, registers, code); + } - case Opcodes.GET_LOCAL_LEVEL -> { - pc = InlineOpcodeHandler.executeGetLocalLevel(bytecode, pc, registers); - } + case Opcodes.GET_LOCAL_LEVEL -> { + pc = InlineOpcodeHandler.executeGetLocalLevel(bytecode, pc, registers); + } - case Opcodes.POP_PACKAGE -> { - // Scoped package block exit — restore handled by POP_LOCAL_LEVEL. - } + case Opcodes.POP_PACKAGE -> { + // Scoped package block exit — restore handled by POP_LOCAL_LEVEL. + } - case Opcodes.DO_FILE -> { - pc = InlineOpcodeHandler.executeDoFile(bytecode, pc, registers); - } + case Opcodes.DO_FILE -> { + pc = InlineOpcodeHandler.executeDoFile(bytecode, pc, registers); + } - default -> { - int opcodeInt = opcode; - throw new RuntimeException( - "Unknown opcode: " + opcodeInt + - " at pc=" + (pc - 1) + - " in " + code.sourceName + ":" + code.sourceLine - ); + default -> { + int opcodeInt = opcode; + throw new RuntimeException( + "Unknown opcode: " + opcodeInt + + " at pc=" + (pc - 1) + + " in " + code.sourceName + ":" + code.sourceLine + ); + } + } } - } - } - // Fell through end of bytecode - return empty list - return new RuntimeList(); + // Fell through end of bytecode - return empty list + return new RuntimeList(); - } catch (ClassCastException e) { - // Special handling for ClassCastException to show which opcode is failing - // Check if we're inside an eval block first - if (!evalCatchStack.isEmpty()) { - int catchPc = evalCatchStack.pop(); - WarnDie.catchEval(e); - pc = catchPc; - continue outer; - } + } catch (ClassCastException e) { + // Special handling for ClassCastException to show which opcode is failing + // Check if we're inside an eval block first + if (!evalCatchStack.isEmpty()) { + int catchPc = evalCatchStack.pop(); + WarnDie.catchEval(e); + pc = catchPc; + continue outer; + } - // Not in eval - show detailed error with bytecode context - int errorPc = Math.max(0, pc - 1); + // Not in eval - show detailed error with bytecode context + int errorPc = Math.max(0, pc - 1); - // Show bytecode context (10 bytes before errorPc) - StringBuilder bcContext = new StringBuilder(); - bcContext.append("\nBytecode context: ["); - for (int i = Math.max(0, errorPc - 10); i < Math.min(bytecode.length, errorPc + 5); i++) { - if (i == errorPc) { - bcContext.append(" >>>"); - } - bcContext.append(String.format(" %02X", bytecode[i] & 0xFF)); - if (i == errorPc) { - bcContext.append("<<<"); - } - } - bcContext.append(" ]"); + // Show bytecode context (10 bytes before errorPc) + StringBuilder bcContext = new StringBuilder(); + bcContext.append("\nBytecode context: ["); + for (int i = Math.max(0, errorPc - 10); i < Math.min(bytecode.length, errorPc + 5); i++) { + if (i == errorPc) { + bcContext.append(" >>>"); + } + bcContext.append(String.format(" %02X", bytecode[i] & 0xFF)); + if (i == errorPc) { + bcContext.append("<<<"); + } + } + bcContext.append(" ]"); - StackTraceElement[] st = e.getStackTrace(); - String javaLine = (st.length > 0) ? " [java:" + st[0].getFileName() + ":" + st[0].getLineNumber() + "]" : ""; - String errorMessage = "ClassCastException" + bcContext + ": " + e.getMessage() + javaLine; - throw new RuntimeException(formatInterpreterError(code, errorPc, new Exception(errorMessage)), e); - } catch (Throwable e) { - // Check if we're inside an eval block - if (!evalCatchStack.isEmpty()) { - // Inside eval block - catch the exception - int catchPc = evalCatchStack.pop(); // Pop the catch handler + StackTraceElement[] st = e.getStackTrace(); + String javaLine = (st.length > 0) ? " [java:" + st[0].getFileName() + ":" + st[0].getLineNumber() + "]" : ""; + String errorMessage = "ClassCastException" + bcContext + ": " + e.getMessage() + javaLine; + throw new RuntimeException(formatInterpreterError(code, errorPc, new Exception(errorMessage)), e); + } catch (Throwable e) { + // Check if we're inside an eval block + if (!evalCatchStack.isEmpty()) { + // Inside eval block - catch the exception + int catchPc = evalCatchStack.pop(); // Pop the catch handler - // Call WarnDie.catchEval() to set $@ - WarnDie.catchEval(e); + // Call WarnDie.catchEval() to set $@ + WarnDie.catchEval(e); - pc = catchPc; - continue outer; - } + pc = catchPc; + continue outer; + } - // Not in eval block - propagate exception - // If it's already a PerlDieException, re-throw as-is for proper formatting - if (e instanceof PerlDieException) { - throw (PerlDieException) e; - } + // Not in eval block - propagate exception + // If it's already a PerlDieException, re-throw as-is for proper formatting + if (e instanceof PerlDieException) { + throw (PerlDieException) e; + } - // Check if we're running inside an eval STRING context - // (sourceName starts with "(eval " when code is from eval STRING) - // In this case, don't wrap the exception - let the outer eval handler catch it - boolean insideEvalString = code.sourceName != null - && (code.sourceName.startsWith("(eval ") || code.sourceName.endsWith("(eval)")); - if (insideEvalString) { - // Re-throw as-is - will be caught by EvalStringHandler.evalString() - throw e; - } + // Check if we're running inside an eval STRING context + // (sourceName starts with "(eval " when code is from eval STRING) + // In this case, don't wrap the exception - let the outer eval handler catch it + boolean insideEvalString = code.sourceName != null + && (code.sourceName.startsWith("(eval ") || code.sourceName.endsWith("(eval)")); + if (insideEvalString) { + // Re-throw as-is - will be caught by EvalStringHandler.evalString() + throw e; + } - // Wrap other exceptions with interpreter context including bytecode context - int debugPc = Math.max(0, pc - 3); - String opcodeInfo = " [opcodes at pc-3..pc: "; - for (int di = debugPc; di <= Math.min(pc + 2, bytecode.length - 1); di++) { - if (di == pc) opcodeInfo += ">>>"; - opcodeInfo += bytecode[di] + " "; - if (di == pc) opcodeInfo += "<<< "; - } - opcodeInfo += "]"; - String errorMessage = formatInterpreterError(code, pc, e) + opcodeInfo; - throw new RuntimeException(errorMessage, e); - } - } // end outer while (eval/die retry loop) + // Wrap other exceptions with interpreter context including bytecode context + int debugPc = Math.max(0, pc - 3); + String opcodeInfo = " [opcodes at pc-3..pc: "; + for (int di = debugPc; di <= Math.min(pc + 2, bytecode.length - 1); di++) { + if (di == pc) opcodeInfo += ">>>"; + opcodeInfo += bytecode[di] + " "; + if (di == pc) opcodeInfo += "<<< "; + } + opcodeInfo += "]"; + String errorMessage = formatInterpreterError(code, pc, e) + opcodeInfo; + throw new RuntimeException(errorMessage, e); + } + } // end outer while (eval/die retry loop) } finally { // Outer finally: restore interpreter state saved at method entry. // Unwinds all `local` variables pushed during this frame, restores @@ -1586,6 +1624,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c InterpreterState.pop(); } } + /** * Handle comparison and logical operations (opcodes 31-41). * Separated to keep main execute() under JIT compilation limit. @@ -1733,10 +1772,10 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeScalar val = (registers[rs] instanceof RuntimeScalar) - ? (RuntimeScalar) registers[rs] - : ((RuntimeList) registers[rs]).scalar(); + ? (RuntimeScalar) registers[rs] + : registers[rs].scalar(); registers[rd] = val.getBoolean() ? - RuntimeScalarCache.scalarFalse : RuntimeScalarCache.scalarTrue; + RuntimeScalarCache.scalarFalse : RuntimeScalarCache.scalarTrue; return pc; } @@ -1746,10 +1785,10 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; - RuntimeScalar v1 = ((RuntimeBase) registers[rs1]).scalar(); - RuntimeScalar v2 = ((RuntimeBase) registers[rs2]).scalar(); + RuntimeScalar v1 = registers[rs1].scalar(); + RuntimeScalar v2 = registers[rs2].scalar(); registers[rd] = (v1.getBoolean() && v2.getBoolean()) ? - RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; return pc; } @@ -1759,15 +1798,14 @@ private static int executeComparisons(int opcode, int[] bytecode, int pc, int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; - RuntimeScalar v1 = ((RuntimeBase) registers[rs1]).scalar(); - RuntimeScalar v2 = ((RuntimeBase) registers[rs2]).scalar(); + RuntimeScalar v1 = registers[rs1].scalar(); + RuntimeScalar v2 = registers[rs2].scalar(); registers[rd] = (v1.getBoolean() || v2.getBoolean()) ? - RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; return pc; } - default -> - throw new RuntimeException("Unknown comparison opcode: " + opcode); + default -> throw new RuntimeException("Unknown comparison opcode: " + opcode); } } @@ -1828,8 +1866,7 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, registers[rd] = RuntimeRegex.getQuotedRegex(registers[patternReg].scalar(), registers[flagsReg].scalar()); return pc; } - default -> - throw new RuntimeException("Unknown type opcode: " + opcode); + default -> throw new RuntimeException("Unknown type opcode: " + opcode); } } @@ -1839,22 +1876,43 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, * Direct dispatch to SlowOpcodeHandler methods (Phase 2 complete). */ private static int executeSliceOps(int opcode, int[] bytecode, int pc, - RuntimeBase[] registers, InterpretedCode code) { + RuntimeBase[] registers, InterpretedCode code) { // Direct method calls - no SLOWOP_* constants needed! switch (opcode) { - case Opcodes.DEREF_ARRAY -> { return SlowOpcodeHandler.executeDerefArray(bytecode, pc, registers); } - case Opcodes.DEREF_HASH -> { return SlowOpcodeHandler.executeDerefHash(bytecode, pc, registers); } - case Opcodes.DEREF_HASH_NONSTRICT -> { return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code); } - case Opcodes.DEREF_ARRAY_NONSTRICT -> { return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code); } - case Opcodes.ARRAY_SLICE -> { return SlowOpcodeHandler.executeArraySlice(bytecode, pc, registers); } - case Opcodes.ARRAY_SLICE_SET -> { return SlowOpcodeHandler.executeArraySliceSet(bytecode, pc, registers); } - case Opcodes.HASH_SLICE -> { return SlowOpcodeHandler.executeHashSlice(bytecode, pc, registers); } - case Opcodes.HASH_SLICE_SET -> { return SlowOpcodeHandler.executeHashSliceSet(bytecode, pc, registers); } - case Opcodes.HASH_SLICE_DELETE -> { return SlowOpcodeHandler.executeHashSliceDelete(bytecode, pc, registers); } - case Opcodes.HASH_KEYVALUE_SLICE -> { return SlowOpcodeHandler.executeHashKeyValueSlice(bytecode, pc, registers); } - case Opcodes.LIST_SLICE_FROM -> { return SlowOpcodeHandler.executeListSliceFrom(bytecode, pc, registers); } - default -> - throw new RuntimeException("Unknown slice opcode: " + opcode); + case Opcodes.DEREF_ARRAY -> { + return SlowOpcodeHandler.executeDerefArray(bytecode, pc, registers); + } + case Opcodes.DEREF_HASH -> { + return SlowOpcodeHandler.executeDerefHash(bytecode, pc, registers); + } + case Opcodes.DEREF_HASH_NONSTRICT -> { + return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code); + } + case Opcodes.DEREF_ARRAY_NONSTRICT -> { + return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code); + } + case Opcodes.ARRAY_SLICE -> { + return SlowOpcodeHandler.executeArraySlice(bytecode, pc, registers); + } + case Opcodes.ARRAY_SLICE_SET -> { + return SlowOpcodeHandler.executeArraySliceSet(bytecode, pc, registers); + } + case Opcodes.HASH_SLICE -> { + return SlowOpcodeHandler.executeHashSlice(bytecode, pc, registers); + } + case Opcodes.HASH_SLICE_SET -> { + return SlowOpcodeHandler.executeHashSliceSet(bytecode, pc, registers); + } + case Opcodes.HASH_SLICE_DELETE -> { + return SlowOpcodeHandler.executeHashSliceDelete(bytecode, pc, registers); + } + case Opcodes.HASH_KEYVALUE_SLICE -> { + return SlowOpcodeHandler.executeHashKeyValueSlice(bytecode, pc, registers); + } + case Opcodes.LIST_SLICE_FROM -> { + return SlowOpcodeHandler.executeListSliceFrom(bytecode, pc, registers); + } + default -> throw new RuntimeException("Unknown slice opcode: " + opcode); } } @@ -1863,16 +1921,27 @@ private static int executeSliceOps(int opcode, int[] bytecode, int pc, * Handles: SPLICE, REVERSE, SPLIT, LENGTH_OP, EXISTS, DELETE */ private static int executeArrayStringOps(int opcode, int[] bytecode, int pc, - RuntimeBase[] registers, InterpretedCode code) { + RuntimeBase[] registers, InterpretedCode code) { switch (opcode) { - case Opcodes.SPLICE -> { return SlowOpcodeHandler.executeSplice(bytecode, pc, registers); } - case Opcodes.REVERSE -> { return SlowOpcodeHandler.executeReverse(bytecode, pc, registers); } - case Opcodes.SPLIT -> { return SlowOpcodeHandler.executeSplit(bytecode, pc, registers); } - case Opcodes.LENGTH_OP -> { return SlowOpcodeHandler.executeLength(bytecode, pc, registers); } - case Opcodes.EXISTS -> { return SlowOpcodeHandler.executeExists(bytecode, pc, registers); } - case Opcodes.DELETE -> { return SlowOpcodeHandler.executeDelete(bytecode, pc, registers); } - default -> - throw new RuntimeException("Unknown array/string opcode: " + opcode); + case Opcodes.SPLICE -> { + return SlowOpcodeHandler.executeSplice(bytecode, pc, registers); + } + case Opcodes.REVERSE -> { + return SlowOpcodeHandler.executeReverse(bytecode, pc, registers); + } + case Opcodes.SPLIT -> { + return SlowOpcodeHandler.executeSplit(bytecode, pc, registers); + } + case Opcodes.LENGTH_OP -> { + return SlowOpcodeHandler.executeLength(bytecode, pc, registers); + } + case Opcodes.EXISTS -> { + return SlowOpcodeHandler.executeExists(bytecode, pc, registers); + } + case Opcodes.DELETE -> { + return SlowOpcodeHandler.executeDelete(bytecode, pc, registers); + } + default -> throw new RuntimeException("Unknown array/string opcode: " + opcode); } } @@ -1881,12 +1950,20 @@ private static int executeArrayStringOps(int opcode, int[] bytecode, int pc, * Handles: RETRIEVE_BEGIN_*, LOCAL_SCALAR */ private static int executeScopeOps(int opcode, int[] bytecode, int pc, - RuntimeBase[] registers, InterpretedCode code) { + RuntimeBase[] registers, InterpretedCode code) { switch (opcode) { - case Opcodes.RETRIEVE_BEGIN_SCALAR -> { return SlowOpcodeHandler.executeRetrieveBeginScalar(bytecode, pc, registers, code); } - case Opcodes.RETRIEVE_BEGIN_ARRAY -> { return SlowOpcodeHandler.executeRetrieveBeginArray(bytecode, pc, registers, code); } - case Opcodes.RETRIEVE_BEGIN_HASH -> { return SlowOpcodeHandler.executeRetrieveBeginHash(bytecode, pc, registers, code); } - case Opcodes.LOCAL_SCALAR -> { return SlowOpcodeHandler.executeLocalScalar(bytecode, pc, registers, code); } + case Opcodes.RETRIEVE_BEGIN_SCALAR -> { + return SlowOpcodeHandler.executeRetrieveBeginScalar(bytecode, pc, registers, code); + } + case Opcodes.RETRIEVE_BEGIN_ARRAY -> { + return SlowOpcodeHandler.executeRetrieveBeginArray(bytecode, pc, registers, code); + } + case Opcodes.RETRIEVE_BEGIN_HASH -> { + return SlowOpcodeHandler.executeRetrieveBeginHash(bytecode, pc, registers, code); + } + case Opcodes.LOCAL_SCALAR -> { + return SlowOpcodeHandler.executeLocalScalar(bytecode, pc, registers, code); + } case Opcodes.LOCAL_ARRAY -> { int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; @@ -1907,40 +1984,76 @@ private static int executeScopeOps(int opcode, int[] bytecode, int pc, registers[rd] = GlobalVariable.getGlobalHash(fullName); return pc; } - default -> - throw new RuntimeException("Unknown scope opcode: " + opcode); + default -> throw new RuntimeException("Unknown scope opcode: " + opcode); } } /** * Execute system call and IPC operations (opcodes 132-150). * Handles: CHOWN, WAITPID, FORK, GETPPID, *PGRP, *PRIORITY, *SOCKOPT, - * SYSCALL, SEMGET, SEMOP, MSGGET, MSGSND, MSGRCV, SHMGET, SHMREAD, SHMWRITE + * SYSCALL, SEMGET, SEMOP, MSGGET, MSGSND, MSGRCV, SHMGET, SHMREAD, SHMWRITE */ private static int executeSystemOps(int opcode, int[] bytecode, int pc, - RuntimeBase[] registers) { + RuntimeBase[] registers) { switch (opcode) { - case Opcodes.CHOWN -> { return MiscOpcodeHandler.execute(Opcodes.CHOWN, bytecode, pc, registers); } - case Opcodes.WAITPID -> { return MiscOpcodeHandler.execute(Opcodes.WAITPID, bytecode, pc, registers); } - case Opcodes.FORK -> { return SlowOpcodeHandler.executeFork(bytecode, pc, registers); } - case Opcodes.GETPPID -> { return SlowOpcodeHandler.executeGetppid(bytecode, pc, registers); } - case Opcodes.GETPGRP -> { return MiscOpcodeHandler.execute(Opcodes.GETPGRP, bytecode, pc, registers); } - case Opcodes.SETPGRP -> { return MiscOpcodeHandler.execute(Opcodes.SETPGRP, bytecode, pc, registers); } - case Opcodes.GETPRIORITY -> { return MiscOpcodeHandler.execute(Opcodes.GETPRIORITY, bytecode, pc, registers); } - case Opcodes.SETPRIORITY -> { return MiscOpcodeHandler.execute(Opcodes.SETPRIORITY, bytecode, pc, registers); } - case Opcodes.GETSOCKOPT -> { return MiscOpcodeHandler.execute(Opcodes.GETSOCKOPT, bytecode, pc, registers); } - case Opcodes.SETSOCKOPT -> { return MiscOpcodeHandler.execute(Opcodes.SETSOCKOPT, bytecode, pc, registers); } - case Opcodes.SYSCALL -> { return SlowOpcodeHandler.executeSyscall(bytecode, pc, registers); } - case Opcodes.SEMGET -> { return SlowOpcodeHandler.executeSemget(bytecode, pc, registers); } - case Opcodes.SEMOP -> { return SlowOpcodeHandler.executeSemop(bytecode, pc, registers); } - case Opcodes.MSGGET -> { return SlowOpcodeHandler.executeMsgget(bytecode, pc, registers); } - case Opcodes.MSGSND -> { return SlowOpcodeHandler.executeMsgsnd(bytecode, pc, registers); } - case Opcodes.MSGRCV -> { return SlowOpcodeHandler.executeMsgrcv(bytecode, pc, registers); } - case Opcodes.SHMGET -> { return SlowOpcodeHandler.executeShmget(bytecode, pc, registers); } - case Opcodes.SHMREAD -> { return SlowOpcodeHandler.executeShmread(bytecode, pc, registers); } - case Opcodes.SHMWRITE -> { return SlowOpcodeHandler.executeShmwrite(bytecode, pc, registers); } - default -> - throw new RuntimeException("Unknown system opcode: " + opcode); + case Opcodes.CHOWN -> { + return MiscOpcodeHandler.execute(Opcodes.CHOWN, bytecode, pc, registers); + } + case Opcodes.WAITPID -> { + return MiscOpcodeHandler.execute(Opcodes.WAITPID, bytecode, pc, registers); + } + case Opcodes.FORK -> { + return SlowOpcodeHandler.executeFork(bytecode, pc, registers); + } + case Opcodes.GETPPID -> { + return SlowOpcodeHandler.executeGetppid(bytecode, pc, registers); + } + case Opcodes.GETPGRP -> { + return MiscOpcodeHandler.execute(Opcodes.GETPGRP, bytecode, pc, registers); + } + case Opcodes.SETPGRP -> { + return MiscOpcodeHandler.execute(Opcodes.SETPGRP, bytecode, pc, registers); + } + case Opcodes.GETPRIORITY -> { + return MiscOpcodeHandler.execute(Opcodes.GETPRIORITY, bytecode, pc, registers); + } + case Opcodes.SETPRIORITY -> { + return MiscOpcodeHandler.execute(Opcodes.SETPRIORITY, bytecode, pc, registers); + } + case Opcodes.GETSOCKOPT -> { + return MiscOpcodeHandler.execute(Opcodes.GETSOCKOPT, bytecode, pc, registers); + } + case Opcodes.SETSOCKOPT -> { + return MiscOpcodeHandler.execute(Opcodes.SETSOCKOPT, bytecode, pc, registers); + } + case Opcodes.SYSCALL -> { + return SlowOpcodeHandler.executeSyscall(bytecode, pc, registers); + } + case Opcodes.SEMGET -> { + return SlowOpcodeHandler.executeSemget(bytecode, pc, registers); + } + case Opcodes.SEMOP -> { + return SlowOpcodeHandler.executeSemop(bytecode, pc, registers); + } + case Opcodes.MSGGET -> { + return SlowOpcodeHandler.executeMsgget(bytecode, pc, registers); + } + case Opcodes.MSGSND -> { + return SlowOpcodeHandler.executeMsgsnd(bytecode, pc, registers); + } + case Opcodes.MSGRCV -> { + return SlowOpcodeHandler.executeMsgrcv(bytecode, pc, registers); + } + case Opcodes.SHMGET -> { + return SlowOpcodeHandler.executeShmget(bytecode, pc, registers); + } + case Opcodes.SHMREAD -> { + return SlowOpcodeHandler.executeShmread(bytecode, pc, registers); + } + case Opcodes.SHMWRITE -> { + return SlowOpcodeHandler.executeShmwrite(bytecode, pc, registers); + } + default -> throw new RuntimeException("Unknown system opcode: " + opcode); } } @@ -1950,20 +2063,39 @@ private static int executeSystemOps(int opcode, int[] bytecode, int pc, * DEREF_SCALAR_STRICT, DEREF_SCALAR_NONSTRICT */ private static int executeSpecialIO(int opcode, int[] bytecode, int pc, - RuntimeBase[] registers, InterpretedCode code) { + RuntimeBase[] registers, InterpretedCode code) { switch (opcode) { - case Opcodes.EVAL_STRING -> { return SlowOpcodeHandler.executeEvalString(bytecode, pc, registers, code); } - case Opcodes.SELECT_OP -> { return SlowOpcodeHandler.executeSelect(bytecode, pc, registers); } - case Opcodes.LOAD_GLOB -> { return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code); } - case Opcodes.SLEEP_OP -> { return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); } - case Opcodes.ALARM_OP -> { return SlowOpcodeHandler.executeAlarm(bytecode, pc, registers); } - case Opcodes.DEREF_GLOB -> { return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code); } - case Opcodes.DEREF_GLOB_NONSTRICT -> { return SlowOpcodeHandler.executeDerefGlobNonStrict(bytecode, pc, registers, code); } - case Opcodes.LOAD_GLOB_DYNAMIC -> { return SlowOpcodeHandler.executeLoadGlobDynamic(bytecode, pc, registers, code); } - case Opcodes.DEREF_SCALAR_STRICT -> { return SlowOpcodeHandler.executeDerefScalarStrict(bytecode, pc, registers); } - case Opcodes.DEREF_SCALAR_NONSTRICT -> { return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code); } - default -> - throw new RuntimeException("Unknown special I/O opcode: " + opcode); + case Opcodes.EVAL_STRING -> { + return SlowOpcodeHandler.executeEvalString(bytecode, pc, registers, code); + } + case Opcodes.SELECT_OP -> { + return SlowOpcodeHandler.executeSelect(bytecode, pc, registers); + } + case Opcodes.LOAD_GLOB -> { + return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code); + } + case Opcodes.SLEEP_OP -> { + return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); + } + case Opcodes.ALARM_OP -> { + return SlowOpcodeHandler.executeAlarm(bytecode, pc, registers); + } + case Opcodes.DEREF_GLOB -> { + return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code); + } + case Opcodes.DEREF_GLOB_NONSTRICT -> { + return SlowOpcodeHandler.executeDerefGlobNonStrict(bytecode, pc, registers, code); + } + case Opcodes.LOAD_GLOB_DYNAMIC -> { + return SlowOpcodeHandler.executeLoadGlobDynamic(bytecode, pc, registers, code); + } + case Opcodes.DEREF_SCALAR_STRICT -> { + return SlowOpcodeHandler.executeDerefScalarStrict(bytecode, pc, registers); + } + case Opcodes.DEREF_SCALAR_NONSTRICT -> { + return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code); + } + default -> throw new RuntimeException("Unknown special I/O opcode: " + opcode); } } @@ -1996,21 +2128,21 @@ private static String formatInterpreterError(InterpretedCode code, int errorPc, // We have token index and errorUtil - convert to line number int lineNumber = code.errorUtil.getLineNumber(tokenIndex); sb.append("Interpreter error in ").append(code.sourceName) - .append(" line ").append(lineNumber) - .append(" (pc=").append(errorPc).append("): ") - .append(e.getMessage()); + .append(" line ").append(lineNumber) + .append(" (pc=").append(errorPc).append("): ") + .append(e.getMessage()); } else if (tokenIndex != null) { // We have token index but no errorUtil sb.append("Interpreter error in ").append(code.sourceName) - .append(" at token ").append(tokenIndex) - .append(" (pc=").append(errorPc).append("): ") - .append(e.getMessage()); + .append(" at token ").append(tokenIndex) + .append(" (pc=").append(errorPc).append("): ") + .append(e.getMessage()); } else { // No token index available, use source line from code sb.append("Interpreter error in ").append(code.sourceName) - .append(" line ").append(code.sourceLine) - .append(" (pc=").append(errorPc).append("): ") - .append(e.getMessage()); + .append(" line ").append(code.sourceLine) + .append(" (pc=").append(errorPc).append("): ") + .append(e.getMessage()); } return sb.toString(); From e6c5a599bc4948d000b7a4e80406dfcb75e16e41 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 13:25:22 +0100 Subject: [PATCH 4/5] reformat code --- .../perlonjava/app/cli/CompilerOptions.java | 2 +- .../java/org/perlonjava/app/cli/Main.java | 2 +- .../app/scriptengine/PerlCompiledScript.java | 8 +- .../scriptengine/PerlLanguageProvider.java | 54 +- .../app/scriptengine/PerlScriptEngine.java | 2 +- .../backend/bytecode/BytecodeCompiler.java | 540 ++++--- .../backend/bytecode/CompileAssignment.java | 5 +- .../bytecode/CompileBinaryOperator.java | 10 +- .../bytecode/CompileBinaryOperatorHelper.java | 2 +- .../backend/bytecode/CompileOperator.java | 47 +- .../backend/bytecode/EvalStringHandler.java | 92 +- .../backend/bytecode/InlineOpcodeHandler.java | 44 +- .../backend/bytecode/InterpretedCode.java | 293 ++-- .../backend/bytecode/InterpreterState.java | 43 +- .../backend/bytecode/MiscOpcodeHandler.java | 16 +- .../bytecode/OpcodeHandlerExtended.java | 170 +- .../bytecode/OpcodeHandlerFileTest.java | 8 +- .../perlonjava/backend/bytecode/Opcodes.java | 1374 ++++++++++++----- .../bytecode/ScalarBinaryOpcodeHandler.java | 51 +- .../bytecode/ScalarUnaryOpcodeHandler.java | 36 +- .../backend/bytecode/SlowOpcodeHandler.java | 44 +- .../bytecode/VariableCaptureAnalyzer.java | 7 +- .../bytecode/VariableCollectorVisitor.java | 2 +- .../backend/jvm/ByteCodeSourceMapper.java | 30 +- .../perlonjava/backend/jvm/CompiledCode.java | 24 +- .../perlonjava/backend/jvm/Dereference.java | 26 +- .../backend/jvm/EmitBinaryOperator.java | 2 +- .../backend/jvm/EmitBinaryOperatorNode.java | 5 +- .../org/perlonjava/backend/jvm/EmitBlock.java | 19 +- .../backend/jvm/EmitControlFlow.java | 49 +- .../org/perlonjava/backend/jvm/EmitEval.java | 23 +- .../perlonjava/backend/jvm/EmitForeach.java | 133 +- .../perlonjava/backend/jvm/EmitFormat.java | 3 +- .../org/perlonjava/backend/jvm/EmitLabel.java | 2 +- .../backend/jvm/EmitLogicalOperator.java | 10 +- .../perlonjava/backend/jvm/EmitOperator.java | 21 +- .../backend/jvm/EmitOperatorChained.java | 2 +- .../backend/jvm/EmitOperatorFileTest.java | 2 +- .../backend/jvm/EmitOperatorLocal.java | 18 +- .../backend/jvm/EmitOperatorNode.java | 2 +- .../org/perlonjava/backend/jvm/EmitRegex.java | 8 +- .../perlonjava/backend/jvm/EmitStatement.java | 30 +- .../backend/jvm/EmitSubroutine.java | 80 +- .../perlonjava/backend/jvm/EmitVariable.java | 112 +- .../backend/jvm/EmitterContext.java | 2 +- .../backend/jvm/EmitterMethodCreator.java | 497 +++--- .../perlonjava/backend/jvm/GotoLabels.java | 4 +- .../perlonjava/backend/jvm/JavaClassInfo.java | 47 +- .../org/perlonjava/backend/jvm/Local.java | 2 +- .../perlonjava/backend/jvm/LoopLabels.java | 30 +- .../jvm/astrefactor/LargeBlockRefactorer.java | 8 +- .../jvm/astrefactor/LargeNodeRefactorer.java | 2 +- .../analysis/ControlFlowDetectorVisitor.java | 35 +- .../DepthFirstLiteralRefactorVisitor.java | 4 +- .../frontend/analysis/PrintVisitor.java | 2 +- .../frontend/analysis/RegexUsageDetector.java | 8 +- .../analysis/TempLocalCountVisitor.java | 17 +- .../frontend/astnode/AbstractNode.java | 9 +- .../frontend/astnode/ArrayLiteralNode.java | 2 +- .../frontend/astnode/BlockNode.java | 2 +- .../frontend/astnode/HashLiteralNode.java | 2 +- .../perlonjava/frontend/astnode/ListNode.java | 2 +- .../org/perlonjava/frontend/lexer/Lexer.java | 36 +- .../frontend/parser/ClassTransformer.java | 6 +- .../frontend/parser/CoreOperatorResolver.java | 3 +- .../frontend/parser/DataSection.java | 6 +- .../frontend/parser/IdentifierParser.java | 16 +- .../frontend/parser/ListParser.java | 24 +- .../frontend/parser/NumberParser.java | 2 +- .../frontend/parser/ParseBlock.java | 34 +- .../frontend/parser/ParseInfix.java | 16 +- .../frontend/parser/ParsePrimary.java | 24 +- .../perlonjava/frontend/parser/Parser.java | 22 +- .../frontend/parser/ParserTables.java | 14 +- .../frontend/parser/PrototypeArgs.java | 58 +- .../frontend/parser/SignatureParser.java | 52 +- .../frontend/parser/SpecialBlockParser.java | 4 +- .../frontend/parser/StatementParser.java | 54 +- .../frontend/parser/StatementResolver.java | 93 +- .../frontend/parser/StringDoubleQuoted.java | 3 +- .../frontend/parser/StringParser.java | 4 +- .../frontend/parser/StringSegmentParser.java | 33 +- .../frontend/parser/SubroutineParser.java | 150 +- .../perlonjava/frontend/parser/Variable.java | 56 +- .../frontend/semantic/ScopedSymbolTable.java | 4 +- .../perlonjava/runtime/io/DirectoryIO.java | 6 +- .../org/perlonjava/runtime/io/SocketIO.java | 5 +- .../runtime/mro/InheritanceResolver.java | 13 +- .../runtime/nativ/ExtendedNativeUtils.java | 13 +- .../runtime/operators/BitwiseOperators.java | 50 +- .../runtime/operators/CompareOperators.java | 10 +- .../runtime/operators/Directory.java | 8 +- .../runtime/operators/FileTestOperator.java | 65 +- .../operators/FormatModifierValidator.java | 39 +- .../runtime/operators/IOOperator.java | 2 +- .../runtime/operators/KillOperator.java | 22 +- .../runtime/operators/ListOperators.java | 2 +- .../runtime/operators/MathOperators.java | 9 +- .../runtime/operators/ModuleOperators.java | 178 +-- .../runtime/operators/Operator.java | 76 +- .../runtime/operators/OperatorHandler.java | 18 +- .../perlonjava/runtime/operators/Pack.java | 84 +- .../runtime/operators/Readline.java | 7 +- .../runtime/operators/ReferenceOperators.java | 42 +- .../operators/RuntimeTransliterate.java | 4 +- .../perlonjava/runtime/operators/Stat.java | 45 +- .../runtime/operators/StringOperators.java | 58 +- .../perlonjava/runtime/operators/Time.java | 1 - .../runtime/operators/UnpackState.java | 112 +- .../operators/pack/ControlPackHandler.java | 8 +- .../operators/pack/NumericPackHandler.java | 14 +- .../runtime/operators/pack/PackBuffer.java | 2 +- .../operators/pack/PackGroupHandler.java | 58 +- .../runtime/operators/pack/PackParser.java | 38 +- .../operators/pack/PointerPackHandler.java | 10 +- .../sprintf/SprintfValidationResult.java | 2 +- .../sprintf/SprintfVectorFormatter.java | 2 +- .../unpack/AtShriekFormatHandler.java | 4 +- .../operators/unpack/DotFormatHandler.java | 8 +- .../unpack/DotShriekFormatHandler.java | 6 +- .../unpack/NumericFormatHandler.java | 82 +- .../unpack/UnpackGroupProcessor.java | 4 +- .../operators/unpack/XFormatHandler.java | 1 + .../runtime/perlmodule/Builtin.java | 2 +- .../runtime/perlmodule/BytesPragma.java | 2 +- .../runtime/perlmodule/CompressZlib.java | 14 +- .../perlonjava/runtime/perlmodule/Cwd.java | 2 +- .../runtime/perlmodule/Exporter.java | 26 +- .../runtime/perlmodule/Feature.java | 2 +- .../runtime/perlmodule/FilterUtilCall.java | 146 +- .../runtime/perlmodule/IntegerPragma.java | 2 +- .../runtime/perlmodule/Internals.java | 2 +- .../runtime/perlmodule/MIMEBase64.java | 2 +- .../perlonjava/runtime/perlmodule/Mro.java | 4 +- .../perlonjava/runtime/perlmodule/PerlIO.java | 4 +- .../perlonjava/runtime/perlmodule/Strict.java | 2 +- .../perlonjava/runtime/perlmodule/Toml.java | 37 +- .../runtime/perlmodule/UnicodeNormalize.java | 50 +- .../runtime/perlmodule/UnicodeUCD.java | 153 +- .../runtime/perlmodule/Universal.java | 16 +- .../perlonjava/runtime/perlmodule/Utf8.java | 6 +- .../runtime/perlmodule/XSLoader.java | 2 +- .../runtime/regex/ExtendedCharClass.java | 48 +- .../runtime/regex/MultiCharFoldMapper.java | 44 +- .../perlonjava/runtime/regex/RegexFlags.java | 10 +- .../runtime/regex/RegexPreprocessor.java | 84 +- .../runtime/regex/RuntimeRegex.java | 242 +-- .../runtime/regex/UnicodeResolver.java | 60 +- .../runtime/runtimetypes/CallerStack.java | 2 +- .../runtimetypes/ControlFlowMarker.java | 72 +- .../runtime/runtimetypes/ControlFlowType.java | 28 +- .../runtime/runtimetypes/DualVar.java | 2 +- .../runtimetypes/ErrorMessageUtil.java | 7 +- .../runtimetypes/ExceptionFormatter.java | 8 +- .../runtime/runtimetypes/GlobalContext.java | 2 +- .../runtime/runtimetypes/GlobalVariable.java | 14 +- .../runtimetypes/HashSpecialVariable.java | 2 +- .../runtime/runtimetypes/NameNormalizer.java | 15 +- .../runtimetypes/OutputAutoFlushVariable.java | 6 +- .../runtime/runtimetypes/Overload.java | 24 +- .../runtime/runtimetypes/OverloadContext.java | 8 +- .../runtimetypes/PerlDieException.java | 2 +- .../runtime/runtimetypes/PerlRange.java | 4 +- .../runtime/runtimetypes/PerlSignalQueue.java | 4 +- .../runtime/runtimetypes/RuntimeArray.java | 46 +- .../runtime/runtimetypes/RuntimeCode.java | 821 +++++----- .../runtimetypes/RuntimeControlFlowList.java | 120 +- .../RuntimeControlFlowRegistry.java | 58 +- .../runtime/runtimetypes/RuntimeGlob.java | 10 +- .../runtime/runtimetypes/RuntimeHash.java | 38 +- .../runtime/runtimetypes/RuntimeIO.java | 75 +- .../runtime/runtimetypes/RuntimeList.java | 31 +- .../runtimetypes/RuntimePosLvalue.java | 74 +- .../runtime/runtimetypes/RuntimeScalar.java | 38 +- .../runtimetypes/RuntimeScalarCache.java | 2 +- .../runtime/runtimetypes/RuntimeStash.java | 14 +- .../runtimetypes/ScalarSpecialVariable.java | 31 +- .../perlonjava/runtime/util/Base64Util.java | 62 +- 178 files changed, 4542 insertions(+), 3990 deletions(-) diff --git a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java index b12529d14..447096908 100644 --- a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java +++ b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java @@ -77,8 +77,8 @@ public class CompilerOptions implements Cloneable { public boolean unicodeOutput = false; // -CO (same as stdout) public boolean unicodeArgs = false; // -CA public boolean unicodeLocale = false; // -CL - List moduleUseStatements = new ArrayList<>(); // For -m -M public RuntimeScalar incHook = null; // For storing @INC hook reference + List moduleUseStatements = new ArrayList<>(); // For -m -M @Override public CompilerOptions clone() { diff --git a/src/main/java/org/perlonjava/app/cli/Main.java b/src/main/java/org/perlonjava/app/cli/Main.java index 1674305aa..79da90829 100644 --- a/src/main/java/org/perlonjava/app/cli/Main.java +++ b/src/main/java/org/perlonjava/app/cli/Main.java @@ -1,9 +1,9 @@ package org.perlonjava.app.cli; +import org.perlonjava.app.scriptengine.PerlLanguageProvider; import org.perlonjava.runtime.runtimetypes.ErrorMessageUtil; import org.perlonjava.runtime.runtimetypes.GlobalVariable; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.app.scriptengine.PerlLanguageProvider; import java.util.Locale; diff --git a/src/main/java/org/perlonjava/app/scriptengine/PerlCompiledScript.java b/src/main/java/org/perlonjava/app/scriptengine/PerlCompiledScript.java index 6758a1826..92712e84b 100644 --- a/src/main/java/org/perlonjava/app/scriptengine/PerlCompiledScript.java +++ b/src/main/java/org/perlonjava/app/scriptengine/PerlCompiledScript.java @@ -1,9 +1,9 @@ package org.perlonjava.app.scriptengine; import org.perlonjava.runtime.runtimetypes.RuntimeArray; -import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; import org.perlonjava.runtime.runtimetypes.RuntimeCode; +import org.perlonjava.runtime.runtimetypes.RuntimeContextType; +import org.perlonjava.runtime.runtimetypes.RuntimeList; import javax.script.CompiledScript; import javax.script.ScriptContext; @@ -13,11 +13,11 @@ /** * PerlCompiledScript represents a pre-compiled Perl script that can be executed multiple times. - * + *

* This class implements the CompiledScript interface from JSR 223, allowing Perl code to be * compiled once and executed many times, which significantly improves performance when the same * script needs to be run repeatedly. - * + *

* The compiled code is stored as a generated class instance and invoked via MethodHandle to * avoid ClassLoader issues. */ diff --git a/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java b/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java index c6f30266a..ab9523fce 100644 --- a/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java +++ b/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java @@ -1,22 +1,22 @@ package org.perlonjava.app.scriptengine; import org.perlonjava.app.cli.CompilerOptions; -import org.perlonjava.frontend.astnode.Node; +import org.perlonjava.backend.bytecode.BytecodeCompiler; +import org.perlonjava.backend.bytecode.InterpretedCode; import org.perlonjava.backend.jvm.CompiledCode; import org.perlonjava.backend.jvm.EmitterContext; import org.perlonjava.backend.jvm.EmitterMethodCreator; import org.perlonjava.backend.jvm.JavaClassInfo; -import org.perlonjava.backend.bytecode.BytecodeCompiler; -import org.perlonjava.backend.bytecode.InterpretedCode; +import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.lexer.Lexer; import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.parser.DataSection; import org.perlonjava.frontend.parser.Parser; import org.perlonjava.frontend.parser.SpecialBlockParser; -import org.perlonjava.runtime.perlmodule.Strict; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.runtime.perlmodule.FilterUtilCall; +import org.perlonjava.runtime.perlmodule.Strict; import org.perlonjava.runtime.runtimetypes.*; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; @@ -318,9 +318,9 @@ private static RuntimeCode compileToExecutable(Node ast, EmitterContext ctx) thr // Interpreter path - returns InterpretedCode (extends RuntimeCode) ctx.logDebug("Compiling to bytecode interpreter"); BytecodeCompiler compiler = new BytecodeCompiler( - ctx.compilerOptions.fileName, - 1, // sourceLine (legacy parameter) - ctx.errorUtil // Pass errorUtil for proper error formatting with line numbers + ctx.compilerOptions.fileName, + 1, // sourceLine (legacy parameter) + ctx.errorUtil // Pass errorUtil for proper error formatting with line numbers ); InterpretedCode interpretedCode = compiler.compile(ast, ctx); @@ -337,28 +337,28 @@ private static RuntimeCode compileToExecutable(Node ast, EmitterContext ctx) thr ctx.logDebug("Compiling to JVM bytecode"); try { Class generatedClass = EmitterMethodCreator.createClassWithMethod( - ctx, - ast, - false // no try-catch + ctx, + ast, + false // no try-catch ); Constructor constructor = generatedClass.getConstructor(); Object instance = constructor.newInstance(); // Create MethodHandle for the apply() method MethodHandle methodHandle = RuntimeCode.lookup.findVirtual( - generatedClass, - "apply", - RuntimeCode.methodType + generatedClass, + "apply", + RuntimeCode.methodType ); // Wrap in CompiledCode for type safety and consistency // Main scripts don't have prototypes, so pass null CompiledCode compiled = new CompiledCode( - methodHandle, - instance, - null, // prototype (main scripts don't have one) - generatedClass, - ctx + methodHandle, + instance, + null, // prototype (main scripts don't have one) + generatedClass, + ctx ); return compiled; @@ -383,9 +383,9 @@ private static RuntimeCode compileToExecutable(Node ast, EmitterContext ctx) thr ctx.symbolTable.strictOptionsStack.push(0); } BytecodeCompiler compiler = new BytecodeCompiler( - ctx.compilerOptions.fileName, - 1, - ctx.errorUtil + ctx.compilerOptions.fileName, + 1, + ctx.errorUtil ); InterpretedCode interpretedCode = compiler.compile(ast, ctx); @@ -410,11 +410,11 @@ private static boolean needsInterpreterFallback(Throwable e) { String msg = t.getMessage(); if (msg != null && ( msg.contains("Method too large") || - msg.contains("Too many arguments in method signature") || - msg.contains("ASM frame computation failed") || - msg.contains("Unexpected runtime error during bytecode generation") || - msg.contains("dstFrame") || - msg.contains("requires interpreter fallback"))) { + msg.contains("Too many arguments in method signature") || + msg.contains("ASM frame computation failed") || + msg.contains("Unexpected runtime error during bytecode generation") || + msg.contains("dstFrame") || + msg.contains("requires interpreter fallback"))) { return true; } } diff --git a/src/main/java/org/perlonjava/app/scriptengine/PerlScriptEngine.java b/src/main/java/org/perlonjava/app/scriptengine/PerlScriptEngine.java index efdd3f1e5..c56715ffa 100644 --- a/src/main/java/org/perlonjava/app/scriptengine/PerlScriptEngine.java +++ b/src/main/java/org/perlonjava/app/scriptengine/PerlScriptEngine.java @@ -10,7 +10,7 @@ /** * The PerlScriptEngine class is a custom implementation of the AbstractScriptEngine * that supports the Compilable interface for improved performance. - * + *

* It allows the execution of Perl scripts within the Java environment using the Java Scripting API (JSR 223). *

* This class provides the necessary methods to evaluate Perl scripts, manage script contexts, and integrate diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index c0880d0a7..6cc79e053 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -1,25 +1,25 @@ package org.perlonjava.backend.bytecode; +import org.perlonjava.backend.jvm.EmitterContext; +import org.perlonjava.backend.jvm.EmitterMethodCreator; import org.perlonjava.frontend.analysis.FindDeclarationVisitor; import org.perlonjava.frontend.analysis.RegexUsageDetector; import org.perlonjava.frontend.analysis.Visitor; -import org.perlonjava.backend.jvm.EmitterMethodCreator; -import org.perlonjava.backend.jvm.EmitterContext; import org.perlonjava.frontend.astnode.*; -import org.perlonjava.runtime.perlmodule.Strict; -import org.perlonjava.runtime.runtimetypes.*; import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.frontend.semantic.SymbolTable; +import org.perlonjava.runtime.perlmodule.Strict; +import org.perlonjava.runtime.runtimetypes.*; import java.util.*; /** * BytecodeCompiler traverses the AST and generates interpreter bytecode. - * + *

* This is analogous to EmitterVisitor but generates custom bytecode * for the interpreter instead of JVM bytecode via ASM. - * + *

* Key responsibilities: * - Visit AST nodes and emit interpreter opcodes * - Allocate registers for variables and temporaries @@ -30,102 +30,64 @@ public class BytecodeCompiler implements Visitor { // Pre-allocate with reasonable initial capacity to reduce resizing // Typical small eval/subroutine needs 20-50 bytecodes, 5-10 constants, 3-8 strings final List bytecode = new ArrayList<>(64); - private final List constants = new ArrayList<>(16); - private final List stringPool = new ArrayList<>(16); - private final Map stringPoolIndex = new HashMap<>(16); // O(1) lookup - private final Map constantPoolIndex = new HashMap<>(16); // O(1) lookup - // Scoped symbol table for variable tracking, package/class tracking, and eval STRING support. // Replaces the old variableScopes Stack + allDeclaredVariables flat map. // Using ScopedSymbolTable gives us proper scope-aware variable lookups and // getAllVisibleVariables()/getVisibleVariableRegistry() for per-eval-site snapshots. final ScopedSymbolTable symbolTable = new ScopedSymbolTable(); - + // Goto label support: maps label names to their PC addresses for intra-function goto. + // pendingGotos tracks forward references (goto before label) needing patch-up. + final Map gotoLabelPcs = new HashMap<>(); + final List pendingGotos = new ArrayList<>(); // [patchPc(Integer), labelName(String)] + // Error reporting + final ErrorMessageUtil errorUtil; + // Per-eval-site variable registries: each eval STRING emission snapshots the + // currently visible variables so at runtime the correct registers are captured. + final List> evalSiteRegistries = new ArrayList<>(); + final List evalSitePragmaFlags = new ArrayList<>(); + // Variables captured by inner closures (named or anonymous subs compiled within this scope). + // Assignments to these variables must use SET_SCALAR alone (in-place modification) + // instead of LOAD_UNDEF + SET_SCALAR, to preserve the RuntimeScalar identity that + // closures share. + final Set closureCapturedVarNames = new HashSet<>(); + // Source information + final String sourceName; + final int sourceLine; + private final List constants = new ArrayList<>(16); + private final List stringPool = new ArrayList<>(16); + private final Map stringPoolIndex = new HashMap<>(16); // O(1) lookup + private final Map constantPoolIndex = new HashMap<>(16); // O(1) lookup // Scope index stack for proper ScopedSymbolTable.exitScope() calls private final Stack scopeIndices = new Stack<>(); - // Stack to save/restore register state when entering/exiting scopes private final Stack savedNextRegister = new Stack<>(); private final Stack savedBaseRegister = new Stack<>(); - // Loop label stack for last/next/redo control flow // Each entry tracks loop boundaries and optional label private final Stack loopStack = new Stack<>(); - - // Helper class to track loop boundaries - private static class LoopInfo { - final String label; // Loop label (null if unlabeled) - final int startPc; // PC for redo (start of loop body) - int continuePc; // PC for next (continue block or increment) - final List breakPcs; // PCs to patch for last (break) - final List nextPcs; // PCs to patch for next - final List redoPcs; // PCs to patch for redo - final boolean isTrueLoop; // True for for/while/foreach; false for do-while/bare blocks - - LoopInfo(String label, int startPc, boolean isTrueLoop) { - this.label = label; - this.startPc = startPc; - this.isTrueLoop = isTrueLoop; - this.continuePc = -1; // Will be set later - this.breakPcs = new ArrayList<>(); - this.nextPcs = new ArrayList<>(); - this.redoPcs = new ArrayList<>(); - } - } - - // Goto label support: maps label names to their PC addresses for intra-function goto. - // pendingGotos tracks forward references (goto before label) needing patch-up. - final Map gotoLabelPcs = new HashMap<>(); - final List pendingGotos = new ArrayList<>(); // [patchPc(Integer), labelName(String)] - // Token index tracking for error reporting private final TreeMap pcToTokenIndex = new TreeMap<>(); int currentTokenIndex = -1; // Track current token for error reporting + // Track last result register for expression chaining + int lastResultReg = -1; - // Error reporting - final ErrorMessageUtil errorUtil; - + // Track current calling context for subroutine calls + int currentCallContext = RuntimeContextType.LIST; // Default to LIST + Map capturedVarIndices; // Name → register index + // BEGIN support for named subroutine closures + int currentSubroutineBeginId = 0; // BEGIN ID for current named subroutine (0 = not in named sub) + Set currentSubroutineClosureVars = new HashSet<>(); // Variables captured from outer scope // EmitterContext for strict checks and other compile-time options private EmitterContext emitterContext; - // Register allocation private int nextRegister = 3; // 0=this, 1=@_, 2=wantarray private int baseRegisterForStatement = 3; // Reset point after each statement private int maxRegisterEverUsed = 2; // Track highest register ever allocated - - // Track last result register for expression chaining - int lastResultReg = -1; - - // Track current calling context for subroutine calls - int currentCallContext = RuntimeContextType.LIST; // Default to LIST - // True when this compiler was constructed for eval STRING (has parentRegistry) private boolean isEvalString; - // Closure support private RuntimeBase[] capturedVars; // Captured variable values private String[] capturedVarNames; // Parallel array of names - Map capturedVarIndices; // Name → register index - - // Per-eval-site variable registries: each eval STRING emission snapshots the - // currently visible variables so at runtime the correct registers are captured. - final List> evalSiteRegistries = new ArrayList<>(); - final List evalSitePragmaFlags = new ArrayList<>(); - - // BEGIN support for named subroutine closures - int currentSubroutineBeginId = 0; // BEGIN ID for current named subroutine (0 = not in named sub) - Set currentSubroutineClosureVars = new HashSet<>(); // Variables captured from outer scope - - // Variables captured by inner closures (named or anonymous subs compiled within this scope). - // Assignments to these variables must use SET_SCALAR alone (in-place modification) - // instead of LOAD_UNDEF + SET_SCALAR, to preserve the RuntimeScalar identity that - // closures share. - final Set closureCapturedVarNames = new HashSet<>(); - - // Source information - final String sourceName; - final int sourceLine; - public BytecodeCompiler(String sourceName, int sourceLine, ErrorMessageUtil errorUtil) { this.sourceName = sourceName; this.sourceLine = sourceLine; @@ -146,13 +108,13 @@ public BytecodeCompiler(String sourceName, int sourceLine) { * Constructor for eval STRING with parent scope variable registry. * Initializes symbolTable with variables from parent scope. * - * @param sourceName Source name for error messages - * @param sourceLine Source line for error messages - * @param errorUtil Error message utility + * @param sourceName Source name for error messages + * @param sourceLine Source line for error messages + * @param errorUtil Error message utility * @param parentRegistry Variable registry from parent scope (for eval STRING) */ public BytecodeCompiler(String sourceName, int sourceLine, ErrorMessageUtil errorUtil, - Map parentRegistry) { + Map parentRegistry) { this.sourceName = sourceName; this.sourceLine = sourceLine; this.errorUtil = errorUtil; @@ -197,6 +159,70 @@ public BytecodeCompiler(String sourceName, int sourceLine, ErrorMessageUtil erro } } + /** + * Helper: Check if a variable is a built-in special length-one variable. + * Single-character non-letter variables like $_, $0, $!, $; are always allowed under strict. + * Mirrors logic from EmitVariable.java. + */ + private static boolean isBuiltinSpecialLengthOneVar(String sigil, String name) { + if (!"$".equals(sigil) || name == null || name.length() != 1) { + return false; + } + char c = name.charAt(0); + // In Perl, many single-character non-identifier variables (punctuation/digits) + // are built-in special vars and are exempt from strict 'vars'. + return !Character.isLetter(c); + } + + /** + * Helper: Check if a variable is a built-in special scalar variable. + * Variables like ${^GLOBAL_PHASE}, $ARGV, $ENV are always allowed under strict. + * Mirrors logic from EmitVariable.java. + */ + private static boolean isBuiltinSpecialScalarVar(String sigil, String name) { + if (!"$".equals(sigil) || name == null || name.isEmpty()) { + return false; + } + // ${^FOO} variables are encoded as a leading ASCII control character. + // (e.g. ${^GLOBAL_PHASE} -> "\aLOBAL_PHASE"). These are built-in and strict-safe. + if (name.charAt(0) < 32) { + return true; + } + return name.equals("ARGV") + || name.equals("ARGVOUT") + || name.equals("ENV") + || name.equals("INC") + || name.equals("SIG") + || name.equals("STDIN") + || name.equals("STDOUT") + || name.equals("STDERR"); + } + + /** + * Helper: Check if a variable is a built-in special container variable. + * Variables like %ENV, @ARGV, @INC are always allowed under strict. + * Mirrors logic from EmitVariable.java. + */ + private static boolean isBuiltinSpecialContainerVar(String sigil, String name) { + if (name == null) { + return false; + } + if ("%".equals(sigil)) { + return name.equals("SIG") + || name.equals("ENV") + || name.equals("INC") + || name.equals("+") + || name.equals("-"); + } + if ("@".equals(sigil)) { + return name.equals("ARGV") + || name.equals("INC") + || name.equals("_") + || name.equals("F"); + } + return false; + } + boolean hasVariable(String name) { return symbolTable.getVariableIndex(name) != -1; } @@ -273,70 +299,6 @@ private String[] getVariableNames() { return symbolTable.getVariableNames(); } - /** - * Helper: Check if a variable is a built-in special length-one variable. - * Single-character non-letter variables like $_, $0, $!, $; are always allowed under strict. - * Mirrors logic from EmitVariable.java. - */ - private static boolean isBuiltinSpecialLengthOneVar(String sigil, String name) { - if (!"$".equals(sigil) || name == null || name.length() != 1) { - return false; - } - char c = name.charAt(0); - // In Perl, many single-character non-identifier variables (punctuation/digits) - // are built-in special vars and are exempt from strict 'vars'. - return !Character.isLetter(c); - } - - /** - * Helper: Check if a variable is a built-in special scalar variable. - * Variables like ${^GLOBAL_PHASE}, $ARGV, $ENV are always allowed under strict. - * Mirrors logic from EmitVariable.java. - */ - private static boolean isBuiltinSpecialScalarVar(String sigil, String name) { - if (!"$".equals(sigil) || name == null || name.isEmpty()) { - return false; - } - // ${^FOO} variables are encoded as a leading ASCII control character. - // (e.g. ${^GLOBAL_PHASE} -> "\aLOBAL_PHASE"). These are built-in and strict-safe. - if (name.charAt(0) < 32) { - return true; - } - return name.equals("ARGV") - || name.equals("ARGVOUT") - || name.equals("ENV") - || name.equals("INC") - || name.equals("SIG") - || name.equals("STDIN") - || name.equals("STDOUT") - || name.equals("STDERR"); - } - - /** - * Helper: Check if a variable is a built-in special container variable. - * Variables like %ENV, @ARGV, @INC are always allowed under strict. - * Mirrors logic from EmitVariable.java. - */ - private static boolean isBuiltinSpecialContainerVar(String sigil, String name) { - if (name == null) { - return false; - } - if ("%".equals(sigil)) { - return name.equals("SIG") - || name.equals("ENV") - || name.equals("INC") - || name.equals("+") - || name.equals("-"); - } - if ("@".equals(sigil)) { - return name.equals("ARGV") - || name.equals("INC") - || name.equals("_") - || name.equals("F"); - } - return false; - } - /** * Helper: Check if a non-ASCII length-1 scalar is allowed under 'no utf8'. * Under 'no utf8', Latin-1 characters (0x80-0xFF) in single-char variable names @@ -344,7 +306,7 @@ private static boolean isBuiltinSpecialContainerVar(String sigil, String name) { * Mirrors logic from EmitVariable.java lines 82-88. * * @param sigil The variable sigil - * @param name The bare variable name (without sigil) + * @param name The bare variable name (without sigil) * @return true if this is a non-ASCII length-1 scalar allowed under 'no utf8' */ private boolean isNonAsciiLengthOneScalarAllowedUnderNoUtf8(String sigil, String name) { @@ -357,6 +319,14 @@ private boolean isNonAsciiLengthOneScalarAllowedUnderNoUtf8(String sigil, String && !emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_UTF8); } + /** + * Returns true if strict refs is currently enabled in the symbol table. + */ + boolean isStrictRefsEnabled() { + return emitterContext != null && emitterContext.symbolTable != null + && emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_REFS); + } + /** * Helper: Check if strict vars should block access to this global variable. * Returns true if the variable should be BLOCKED (not allowed). @@ -365,11 +335,6 @@ private boolean isNonAsciiLengthOneScalarAllowedUnderNoUtf8(String sigil, String * @param varName The variable name with sigil (e.g., "$A", "@array") * @return true if access should be blocked under strict vars */ - /** Returns true if strict refs is currently enabled in the symbol table. */ - boolean isStrictRefsEnabled() { - return emitterContext != null && emitterContext.symbolTable != null - && emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_REFS); - } boolean isIntegerEnabled() { return emitterContext != null && emitterContext.symbolTable != null @@ -427,10 +392,7 @@ boolean shouldBlockGlobalUnderStrictVars(String varName) { // (e.g., created by `use vars` at parse time) // This mirrors the allowIfAlreadyExists logic in EmitVariable.java String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage()); - boolean allowIfAlreadyExists = false; - if (sigil.equals("$") && GlobalVariable.existsGlobalVariable(normalizedName)) { - allowIfAlreadyExists = true; - } + boolean allowIfAlreadyExists = sigil.equals("$") && GlobalVariable.existsGlobalVariable(normalizedName); if (sigil.equals("@") && GlobalVariable.existsGlobalArray(normalizedName)) { allowIfAlreadyExists = true; } @@ -452,19 +414,16 @@ boolean shouldBlockGlobalUnderStrictVars(String varName) { allowIfAlreadyExists = false; } - if (allowIfAlreadyExists) { - return false; - } + return !allowIfAlreadyExists; // BLOCK: Unqualified variable under strict vars - return true; } /** * Throw a compiler exception with proper error formatting. * Uses PerlCompilerException which formats with line numbers and code context. * - * @param message The error message + * @param message The error message * @param tokenIndex The token index where the error occurred */ void throwCompilerException(String message, int tokenIndex) { @@ -522,7 +481,7 @@ public InterpretedCode compile(Node node, EmitterContext ctx) { // See InterpreterState.currentPackage javadoc for details. if (ctx.symbolTable != null) { symbolTable.setCurrentPackage(ctx.symbolTable.getCurrentPackage(), - ctx.symbolTable.currentPackageIsClass()); + ctx.symbolTable.currentPackageIsClass()); symbolTable.strictOptionsStack.pop(); symbolTable.strictOptionsStack.push(ctx.symbolTable.strictOptionsStack.peek()); symbolTable.featureFlagsStack.pop(); @@ -585,29 +544,25 @@ public InterpretedCode compile(Node node, EmitterContext ctx) { // Build InterpretedCode return new InterpretedCode( - toShortArray(), - constants.toArray(), - stringPool.toArray(new String[0]), - maxRegisterEverUsed + 1, - capturedVars, - sourceName, - sourceLine, - pcToTokenIndex, - variableRegistry, - errorUtil, - strictOptions, - featureFlags, - warningFlags, - symbolTable.getCurrentPackage(), - evalSiteRegistries.isEmpty() ? null : evalSiteRegistries, - evalSitePragmaFlags.isEmpty() ? null : evalSitePragmaFlags + toShortArray(), + constants.toArray(), + stringPool.toArray(new String[0]), + maxRegisterEverUsed + 1, + capturedVars, + sourceName, + sourceLine, + pcToTokenIndex, + variableRegistry, + errorUtil, + strictOptions, + featureFlags, + warningFlags, + symbolTable.getCurrentPackage(), + evalSiteRegistries.isEmpty() ? null : evalSiteRegistries, + evalSitePragmaFlags.isEmpty() ? null : evalSitePragmaFlags ); } - // ========================================================================= - // CLOSURE DETECTION - // ========================================================================= - /** * Detect closure variables: variables referenced but not declared locally. * Populates capturedVars, capturedVarNames, and capturedVarIndices. @@ -627,7 +582,7 @@ private void detectClosureVariables(Node ast, EmitterContext ctx) { // Use getAllVisibleVariables() (TreeMap sorted by register index) with the same // filtering as SubroutineParser to ensure capturedVars ordering matches exactly. Map outerVars = - ctx.symbolTable.getAllVisibleVariables(); + ctx.symbolTable.getAllVisibleVariables(); int skipVariables = org.perlonjava.backend.jvm.EmitterMethodCreator.skipVariables; List outerVarNames = new ArrayList<>(); @@ -667,7 +622,7 @@ private void detectClosureVariables(Node ast, EmitterContext ctx) { Set localVars = getLocalVariableNames(ctx); referencedVars.removeAll(localVars); referencedVars.removeIf(name -> - name.equals("$_") || name.equals("$@") || name.equals("$!") + name.equals("$_") || name.equals("$@") || name.equals("$!") ); for (String varName : referencedVars) { if (!capturedVarIndices.containsKey(varName)) { @@ -684,6 +639,10 @@ private void detectClosureVariables(Node ast, EmitterContext ctx) { capturedVars = outerValues.toArray(new RuntimeBase[0]); } + // ========================================================================= + // CLOSURE DETECTION + // ========================================================================= + /** * Collect all variable references in AST. * @@ -745,10 +704,6 @@ private RuntimeBase getVariableValueFromContext(String varName, EmitterContext c return new RuntimeScalar(); } - // ========================================================================= - // VISITOR METHODS - // ========================================================================= - /** * Compiles a block node, creating a new lexical scope. * @@ -879,6 +834,10 @@ public void visit(BlockNode node) { lastResultReg = outerResultReg; } + // ========================================================================= + // VISITOR METHODS + // ========================================================================= + @Override public void visit(NumberNode node) { // Handle number literals with proper Perl semantics @@ -1066,7 +1025,7 @@ public void visit(IdentifierNode node) { if (!varName.startsWith("$") && !varName.startsWith("@") && !varName.startsWith("%")) { // This is a bareword (no sigil) if (emitterContext != null && emitterContext.symbolTable != null && - emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_SUBS)) { + emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_SUBS)) { throwCompilerException("Bareword \"" + varName + "\" not allowed while \"strict subs\" in use"); } // Not strict - treat bareword as string literal @@ -1100,10 +1059,6 @@ public void visit(IdentifierNode node) { } } - /** - * Handle hash slice operations: @hash{keys} or @$hashref{keys} - * Must be called before automatic operand compilation to avoid compiling @ operator - */ /** * Handle array element access: $array[index] * Example: $ARGV[0] or $array[$i] @@ -1132,8 +1087,8 @@ void handleArrayElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { } else { arrayReg = allocateRegister(); String globalArrayName = NameNormalizer.normalizeVariableName( - varName, - getCurrentPackage() + varName, + getCurrentPackage() ); int nameIdx = addToStringPool(globalArrayName); emit(Opcodes.LOAD_GLOBAL_ARRAY); @@ -1171,6 +1126,11 @@ void handleArrayElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { } } + /** + * Handle hash slice operations: @hash{keys} or @$hashref{keys} + * Must be called before automatic operand compilation to avoid compiling @ operator + */ + /** * Handle array slice: @array[indices] * Example: @array[0,2,4] or @array[1..5] @@ -1197,8 +1157,8 @@ void handleArraySlice(BinaryOperatorNode node, OperatorNode leftOp) { } else { arrayReg = allocateRegister(); String globalArrayName = NameNormalizer.normalizeVariableName( - varName, - getCurrentPackage() + varName, + getCurrentPackage() ); int nameIdx = addToStringPool(globalArrayName); emit(Opcodes.LOAD_GLOBAL_ARRAY); @@ -1298,8 +1258,8 @@ void handleHashElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { } else { hashReg = allocateRegister(); String globalHashName = NameNormalizer.normalizeVariableName( - varName, - getCurrentPackage() + varName, + getCurrentPackage() ); int nameIdx = addToStringPool(globalHashName); emit(Opcodes.LOAD_GLOBAL_HASH); @@ -1382,8 +1342,8 @@ void handleHashSlice(BinaryOperatorNode node, OperatorNode leftOp) { } else { hashReg = allocateRegister(); String globalHashName = NameNormalizer.normalizeVariableName( - varName, - getCurrentPackage() + varName, + getCurrentPackage() ); int nameIdx = addToStringPool(globalHashName); emit(Opcodes.LOAD_GLOBAL_HASH); @@ -1594,7 +1554,7 @@ void handleCompoundAssignment(BinaryOperatorNode node) { targetReg = allocateRegister(); // Strip sigil before normalizing (varName is "$x", need "x" for normalize) String normalizedName = NameNormalizer.normalizeVariableName( - varName.substring(1), getCurrentPackage()); + varName.substring(1), getCurrentPackage()); int nameIdx = addToStringPool(normalizedName); emit(Opcodes.LOAD_GLOBAL_SCALAR); emitReg(targetReg); @@ -1780,7 +1740,7 @@ void handleGeneralHashAccess(BinaryOperatorNode node) { // Check if this is a glob slot access: *X{key} // In this case, node.left is an OperatorNode with operator "*" boolean isGlobSlotAccess = (node.left instanceof OperatorNode) && - ((OperatorNode) node.left).operator.equals("*"); + ((OperatorNode) node.left).operator.equals("*"); if (isGlobSlotAccess) { // For glob slot access, call hashDerefGetNonStrict directly @@ -1853,8 +1813,8 @@ void handlePushUnshift(BinaryOperatorNode node) { // Global array - load it arrayReg = allocateRegister(); String globalArrayName = NameNormalizer.normalizeVariableName( - varName, - getCurrentPackage() + varName, + getCurrentPackage() ); int nameIdx = addToStringPool(globalArrayName); emit(Opcodes.LOAD_GLOBAL_ARRAY); @@ -2113,7 +2073,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Check if it's a double backslash first: my (\\$x) if (sigilOp.operand instanceof OperatorNode innerBackslash && - innerBackslash.operator.equals("\\")) { + innerBackslash.operator.equals("\\")) { // Double backslash: my (\\$x) // This creates a reference to a reference // We handle this completely here and add the final result to varRegs @@ -2147,7 +2107,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Process each element in the nested list as a declared reference for (Node nestedElement : nestedList.elements) { if (nestedElement instanceof OperatorNode nestedVarNode && - "$@%".contains(nestedVarNode.operator)) { + "$@%".contains(nestedVarNode.operator)) { // Get the variable name if (nestedVarNode.operand instanceof IdentifierNode idNode) { // Initialize based on original sigil. @@ -2185,7 +2145,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { } } } else if (sigilOp.operand instanceof OperatorNode varNode && - "$@%".contains(varNode.operator)) { + "$@%".contains(varNode.operator)) { // Single variable: my (\$x) or state (\$x) or my (\@x) or my (\%x) if (varNode.operand instanceof IdentifierNode idNode) { String originalSigil = varNode.operator; @@ -2309,7 +2269,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { emit(persistId); registerVariable(varName, reg); } - default -> throwCompilerException("Unsupported variable type in list declaration: " + sigil); + default -> + throwCompilerException("Unsupported variable type in list declaration: " + sigil); } varRegs.add(reg); @@ -2332,7 +2293,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { emit(Opcodes.NEW_HASH); emitReg(reg); } - default -> throwCompilerException("Unsupported variable type in list declaration: " + sigil); + default -> + throwCompilerException("Unsupported variable type in list declaration: " + sigil); } varRegs.add(reg); @@ -2503,8 +2465,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Load from global variable using normalized name String globalVarName = NameNormalizer.normalizeVariableName( - ((IdentifierNode) sigilOp.operand).name, - getCurrentPackage() + ((IdentifierNode) sigilOp.operand).name, + getCurrentPackage() ); int nameIdx = addToStringPool(globalVarName); @@ -2633,7 +2595,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { if (sigil.equals("\\")) { // Check if it's a double backslash first: our (\\$x) if (sigilOp.operand instanceof OperatorNode innerBackslash && - innerBackslash.operator.equals("\\")) { + innerBackslash.operator.equals("\\")) { // Double backslash: our (\\$x) // Recursively compile the inner part OperatorNode innerOur = new OperatorNode("our", innerBackslash, node.getIndex()); @@ -2661,7 +2623,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Process each element in the nested list as a declared reference for (Node nestedElement : nestedList.elements) { if (nestedElement instanceof OperatorNode nestedVarNode && - "$@%".contains(nestedVarNode.operator)) { + "$@%".contains(nestedVarNode.operator)) { if (nestedVarNode.operand instanceof IdentifierNode idNode) { String originalSigil = nestedVarNode.operator; String varName = originalSigil + idNode.name; @@ -2669,8 +2631,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Declare and load the package variable int reg = addVariable(varName, "our"); String globalVarName = NameNormalizer.normalizeVariableName( - idNode.name, - getCurrentPackage() + idNode.name, + getCurrentPackage() ); int nameIdx = addToStringPool(globalVarName); @@ -2702,7 +2664,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { } } } else if (sigilOp.operand instanceof OperatorNode varNode && - "$@%".contains(varNode.operator)) { + "$@%".contains(varNode.operator)) { // Single variable: our (\$x) or our (\@x) or our (\%x) if (varNode.operand instanceof IdentifierNode idNode) { String originalSigil = varNode.operator; @@ -2711,8 +2673,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Declare and load the package variable int reg = addVariable(varName, "our"); String globalVarName = NameNormalizer.normalizeVariableName( - idNode.name, - getCurrentPackage() + idNode.name, + getCurrentPackage() ); int nameIdx = addToStringPool(globalVarName); @@ -2759,8 +2721,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Load from global variable using normalized name String globalVarName = NameNormalizer.normalizeVariableName( - ((IdentifierNode) sigilOp.operand).name, - getCurrentPackage() + ((IdentifierNode) sigilOp.operand).name, + getCurrentPackage() ); int nameIdx = addToStringPool(globalVarName); @@ -2780,7 +2742,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { emitReg(reg); emit(nameIdx); } - default -> throwCompilerException("Unsupported variable type in list declaration: " + sigil); + default -> + throwCompilerException("Unsupported variable type in list declaration: " + sigil); } } @@ -2925,8 +2888,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { if (isDeclaredReference) { // Check if operand is a variable operator ($, @, %) if (sigilOp.operand instanceof OperatorNode innerOp && - "$@%".contains(innerOp.operator) && - innerOp.operand instanceof IdentifierNode idNode) { + "$@%".contains(innerOp.operator) && + innerOp.operand instanceof IdentifierNode idNode) { String originalSigil = innerOp.operator; String varName = originalSigil + idNode.name; @@ -3047,8 +3010,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Double backslash: local (\\$x) // Localize, create ref, create ref to ref if (sigilOp.operand instanceof OperatorNode varNode && - "$@%".contains(varNode.operator) && - varNode.operand instanceof IdentifierNode idNode) { + "$@%".contains(varNode.operator) && + varNode.operand instanceof IdentifierNode idNode) { String varName = varNode.operator + idNode.name; @@ -3098,8 +3061,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { if (sigilOp.operand instanceof ListNode nestedList) { for (Node nestedElement : nestedList.elements) { if (nestedElement instanceof OperatorNode nestedVarNode && - "$@%".contains(nestedVarNode.operator) && - nestedVarNode.operand instanceof IdentifierNode idNode) { + "$@%".contains(nestedVarNode.operator) && + nestedVarNode.operand instanceof IdentifierNode idNode) { String varName = nestedVarNode.operator + idNode.name; if (hasVariable(varName) && !isOurVariable(varName)) { @@ -3128,8 +3091,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { } } } else if (sigilOp.operand instanceof OperatorNode varNode && - "$@%".contains(varNode.operator) && - varNode.operand instanceof IdentifierNode idNode) { + "$@%".contains(varNode.operator) && + varNode.operand instanceof IdentifierNode idNode) { // Single variable: local (\$x), local (\@x), local (\%x) String originalSigil = varNode.operator; String varName = originalSigil + idNode.name; @@ -3276,8 +3239,8 @@ void compileVariableReference(OperatorNode node, String op) { // which must always be in the "main" package String globalVarName = varName.substring(1); // Remove $ sigil first globalVarName = NameNormalizer.normalizeVariableName( - globalVarName, - getCurrentPackage() + globalVarName, + getCurrentPackage() ); int rd = allocateRegister(); @@ -3673,10 +3636,6 @@ public void visit(OperatorNode node) { CompileOperator.visitOperator(this, node); } - // ========================================================================= - // HELPER METHODS - // ========================================================================= - int allocateRegister() { int reg = nextRegister++; if (reg > 65535) { @@ -3690,6 +3649,10 @@ int allocateRegister() { return reg; } + // ========================================================================= + // HELPER METHODS + // ========================================================================= + TreeMap collectVisiblePerlVariables() { TreeMap closureVarsByReg = new TreeMap<>(); Map visible = symbolTable.getAllVisibleVariables(); @@ -3734,13 +3697,13 @@ private int getHighestVariableRegister() { * Reset temporary registers after a statement completes. * This allows register reuse for the next statement, preventing register exhaustion * in large scripts. - * + *

* IMPORTANT: Only resets registers that are truly temporary. Preserves: * - 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 * to persist must be in a variable, not just a temporary register. @@ -3858,10 +3821,6 @@ private int[] toShortArray() { return result; } - // ========================================================================= - // UNIMPLEMENTED VISITOR METHODS (TODO) - // ========================================================================= - @Override public void visit(ArrayLiteralNode node) { // Array literal: [expr1, expr2, ...] @@ -3915,6 +3874,10 @@ public void visit(ArrayLiteralNode node) { lastResultReg = refReg; } + // ========================================================================= + // UNIMPLEMENTED VISITOR METHODS (TODO) + // ========================================================================= + @Override public void visit(HashLiteralNode node) { // Hash literal: {key1 => value1, key2 => value2, ...} @@ -3989,7 +3952,7 @@ public void visit(SubroutineNode node) { /** * Visit a named subroutine: sub foo { ... } - * + *

* NOTE: In practice, named subroutines are compiled by the parser using the JVM compiler, * not the interpreter. This method exists for completeness but may not be called for * typical named sub definitions. The parser creates compiled RuntimeCode objects that @@ -4060,13 +4023,13 @@ private void visitNamedSubroutine(SubroutineNode node) { } BytecodeCompiler subCompiler = new BytecodeCompiler( - this.sourceName, - node.getIndex(), - this.errorUtil, - packedRegistry + this.sourceName, + node.getIndex(), + this.errorUtil, + packedRegistry ); subCompiler.symbolTable.setCurrentPackage(getCurrentPackage(), - symbolTable.currentPackageIsClass()); + symbolTable.currentPackageIsClass()); // Set the BEGIN ID in the sub-compiler so it knows to use RETRIEVE_BEGIN opcodes subCompiler.currentSubroutineBeginId = beginId; @@ -4084,7 +4047,7 @@ private void visitNamedSubroutine(SubroutineNode node) { int codeReg = allocateRegister(); if (closureVarIndices.isEmpty()) { - RuntimeScalar codeScalar = new RuntimeScalar((RuntimeCode) subCode); + RuntimeScalar codeScalar = new RuntimeScalar(subCode); int constIdx = addToConstantPool(codeScalar); emit(Opcodes.LOAD_CONST); emitReg(codeReg); @@ -4117,15 +4080,7 @@ private void visitNamedSubroutine(SubroutineNode node) { /** * Visit an anonymous subroutine: sub { ... } - * - * Compiles the subroutine body to bytecode and creates an InterpretedCode instance. - * Handles closure variable capture if needed. - * - * The result is an InterpretedCode wrapped in RuntimeScalar, stored in lastResultReg. - */ - /** - * Visit an anonymous subroutine: sub { ... } - * + *

* Compiles the subroutine body to bytecode with closure support. * Anonymous subs capture lexical variables from the enclosing scope. */ @@ -4157,13 +4112,13 @@ private void visitAnonymousSubroutine(SubroutineNode node) { } BytecodeCompiler subCompiler = new BytecodeCompiler( - this.sourceName, - node.getIndex(), - this.errorUtil, - parentRegistry // Pass parent variable registry for nested closure support + this.sourceName, + node.getIndex(), + this.errorUtil, + parentRegistry // Pass parent variable registry for nested closure support ); subCompiler.symbolTable.setCurrentPackage(getCurrentPackage(), - symbolTable.currentPackageIsClass()); + symbolTable.currentPackageIsClass()); // Step 4: Compile the subroutine body // Sub-compiler will use parentRegistry to resolve captured variables @@ -4181,7 +4136,7 @@ private void visitAnonymousSubroutine(SubroutineNode node) { if (closureVarIndices.isEmpty()) { // No closures - just wrap the InterpretedCode - RuntimeScalar codeScalar = new RuntimeScalar((RuntimeCode) subCode); + RuntimeScalar codeScalar = new RuntimeScalar(subCode); int constIdx = addToConstantPool(codeScalar); emit(Opcodes.LOAD_CONST); emitReg(codeReg); @@ -4202,17 +4157,26 @@ private void visitAnonymousSubroutine(SubroutineNode node) { } /** - * Visit an eval block: eval { ... } + * Visit an anonymous subroutine: sub { ... } * - * Generates: - * EVAL_TRY catch_offset # Set up exception handler, clear $@ - * ... block bytecode ... - * EVAL_END # Clear $@ on success - * GOTO end - * LABEL catch: - * EVAL_CATCH rd # Set $@, store undef in rd - * LABEL end: + * Compiles the subroutine body to bytecode and creates an InterpretedCode instance. + * Handles closure variable capture if needed. * + * The result is an InterpretedCode wrapped in RuntimeScalar, stored in lastResultReg. + */ + + /** + * Visit an eval block: eval { ... } + *

+ * Generates: + * EVAL_TRY catch_offset # Set up exception handler, clear $@ + * ... block bytecode ... + * EVAL_END # Clear $@ on success + * GOTO end + * LABEL catch: + * EVAL_CATCH rd # Set $@, store undef in rd + * LABEL end: + *

* The result is stored in lastResultReg. */ private void visitEvalBlock(SubroutineNode node) { @@ -4889,10 +4853,6 @@ public void visit(ListNode node) { } } - // ================================================================= - // PACKAGE-LEVEL ACCESSORS FOR REFACTORED CLASSES - // ================================================================= - /** * Get the symbol table for package/strict/feature tracking. * Used by refactored compiler classes. @@ -4901,6 +4861,10 @@ ScopedSymbolTable getSymbolTable() { return symbolTable; } + // ================================================================= + // PACKAGE-LEVEL ACCESSORS FOR REFACTORED CLASSES + // ================================================================= + /** * Get the source name for error messages. * Used by refactored compiler classes. @@ -4929,7 +4893,6 @@ Map getCapturedVarIndices() { return capturedVarIndices; } - /** * Handle loop control operators: last, next, redo * Extracted for use by CompileOperator. @@ -4968,8 +4931,8 @@ void handleLoopControlOperator(OperatorNode node, String op) { // No matching loop found - non-local control flow // Emit CREATE_LAST/NEXT/REDO + RETURN to propagate via RuntimeControlFlowList short createOp = op.equals("last") ? Opcodes.CREATE_LAST - : op.equals("next") ? Opcodes.CREATE_NEXT - : Opcodes.CREATE_REDO; + : op.equals("next") ? Opcodes.CREATE_NEXT + : Opcodes.CREATE_REDO; int rd = allocateRegister(); emit(createOp); emitReg(rd); @@ -4987,8 +4950,8 @@ void handleLoopControlOperator(OperatorNode node, String op) { // Emit the opcode and record the PC to be patched later short opcode = op.equals("last") ? Opcodes.LAST - : op.equals("next") ? Opcodes.NEXT - : Opcodes.REDO; + : op.equals("next") ? Opcodes.NEXT + : Opcodes.REDO; emitWithToken(opcode, node.getIndex()); @@ -5005,4 +4968,25 @@ void handleLoopControlOperator(OperatorNode node, String op) { targetLoop.redoPcs.add(patchPc); } } + + // Helper class to track loop boundaries + private static class LoopInfo { + final String label; // Loop label (null if unlabeled) + final int startPc; // PC for redo (start of loop body) + final List breakPcs; // PCs to patch for last (break) + final List nextPcs; // PCs to patch for next + final List redoPcs; // PCs to patch for redo + final boolean isTrueLoop; // True for for/while/foreach; false for do-while/bare blocks + int continuePc; // PC for next (continue block or increment) + + LoopInfo(String label, int startPc, boolean isTrueLoop) { + this.label = label; + this.startPc = startPc; + this.isTrueLoop = isTrueLoop; + this.continuePc = -1; // Will be set later + this.breakPcs = new ArrayList<>(); + this.nextPcs = new ArrayList<>(); + this.redoPcs = new ArrayList<>(); + } + } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 3c7fc133a..9c3f34bb2 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -543,7 +543,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = localReg; } - default -> bytecodeCompiler.throwCompilerException("Unsupported variable type in local our: " + innerSigil); + default -> + bytecodeCompiler.throwCompilerException("Unsupported variable type in local our: " + innerSigil); } return; } @@ -1641,7 +1642,6 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rhsReg); bytecodeCompiler.lastResultReg = rhsReg; bytecodeCompiler.currentCallContext = savedContext; - return; } else if (node.left instanceof ListNode) { // List assignment: ($a, $b) = ... or () = ... // In scalar context, returns the number of elements on RHS @@ -1765,7 +1765,6 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } bytecodeCompiler.currentCallContext = savedContext; - return; } else { bytecodeCompiler.throwCompilerException("Assignment to non-identifier not yet supported: " + node.left.getClass().getSimpleName()); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java index d514f28a7..1ae0e0989 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java @@ -266,7 +266,7 @@ else if (node.right instanceof BinaryOperatorNode) { // Convert class name to string if needed: Class->method() if (invocantNode instanceof IdentifierNode) { String className = ((IdentifierNode) invocantNode).name; - invocantNode = new StringNode(className, ((IdentifierNode) invocantNode).getIndex()); + invocantNode = new StringNode(className, invocantNode.getIndex()); } // Convert method name to string if needed @@ -279,7 +279,7 @@ else if (node.right instanceof BinaryOperatorNode) { } if (methodNode instanceof IdentifierNode) { String methodName = ((IdentifierNode) methodNode).name; - methodNode = new StringNode(methodName, ((IdentifierNode) methodNode).getIndex()); + methodNode = new StringNode(methodName, methodNode.getIndex()); } // Compile invocant in scalar context @@ -807,11 +807,11 @@ static boolean isArrayLikeNode(Node node) { String o = op.operator; if (o.equals("@") || o.equals("%")) return true; if (o.equals("unpack") || o.equals("split") || o.equals("sort") || - o.equals("reverse") || o.equals("grep") || o.equals("map") || - o.equals("keys") || o.equals("values") || o.equals("each")) return true; + o.equals("reverse") || o.equals("grep") || o.equals("map") || + o.equals("keys") || o.equals("values") || o.equals("each")) return true; } if (node instanceof BinaryOperatorNode bin) { - if (bin.operator.equals("(") || bin.operator.equals("()")) return true; + return bin.operator.equals("(") || bin.operator.equals("()"); } return false; } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java index 2e4e4bd2a..03a9bd7dc 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java @@ -429,7 +429,7 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, // this case handles scalar-context flip-flop. int flipFlopId = org.perlonjava.runtime.operators.ScalarFlipFlopOperator.currentId++; org.perlonjava.runtime.operators.ScalarFlipFlopOperator op = - new org.perlonjava.runtime.operators.ScalarFlipFlopOperator(operator.equals("...")); + new org.perlonjava.runtime.operators.ScalarFlipFlopOperator(operator.equals("...")); org.perlonjava.runtime.operators.ScalarFlipFlopOperator.flipFlops.putIfAbsent(flipFlopId, op); bytecodeCompiler.emit(Opcodes.FLIP_FLOP); bytecodeCompiler.emitReg(rd); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 471938a19..c862d2df5 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -1,12 +1,8 @@ package org.perlonjava.backend.bytecode; import org.perlonjava.frontend.astnode.*; -import org.perlonjava.runtime.runtimetypes.ClassRegistry; -import org.perlonjava.runtime.runtimetypes.GlobalVariable; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.runtimetypes.NameNormalizer; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; import org.perlonjava.runtime.operators.ScalarGlobOperator; +import org.perlonjava.runtime.runtimetypes.*; import java.util.ArrayList; import java.util.List; @@ -113,7 +109,6 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else { bytecodeCompiler.throwCompilerException("scalar operator requires an operand"); } - return; } else if (op.equals("package") || op.equals("class")) { // Package/Class declaration: package Foo; or class Foo; // This updates the current package context for subsequent variable declarations @@ -921,10 +916,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Snapshot visible variables and pragma flags for this eval site int evalSiteIndex = bytecodeCompiler.evalSiteRegistries.size(); bytecodeCompiler.evalSiteRegistries.add( - bytecodeCompiler.symbolTable.getVisibleVariableRegistry()); + bytecodeCompiler.symbolTable.getVisibleVariableRegistry()); bytecodeCompiler.evalSitePragmaFlags.add(new int[]{ - bytecodeCompiler.symbolTable.strictOptionsStack.peek(), - bytecodeCompiler.symbolTable.featureFlagsStack.peek() + bytecodeCompiler.symbolTable.strictOptionsStack.peek(), + bytecodeCompiler.symbolTable.featureFlagsStack.peek() }); bytecodeCompiler.emitWithToken(Opcodes.EVAL_STRING, node.getIndex()); @@ -2630,23 +2625,23 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); bytecodeCompiler.lastResultReg = rd; } else if (op.equals("chmod") || op.equals("unlink") || op.equals("utime") || - op.equals("rename") || op.equals("link") || op.equals("readlink") || - op.equals("umask") || op.equals("system") || op.equals("pack") || - op.equals("unpack") || op.equals("vec") || op.equals("crypt") || - op.equals("localtime") || op.equals("gmtime") || op.equals("caller") || op.equals("reset") || - op.equals("fileno") || op.equals("getc") || op.equals("qx") || - op.equals("close") || - op.equals("binmode") || op.equals("seek") || - op.equals("eof") || op.equals("sysread") || op.equals("syswrite") || - op.equals("sysopen") || op.equals("socket") || op.equals("bind") || - op.equals("connect") || op.equals("listen") || op.equals("write") || - op.equals("formline") || op.equals("printf") || op.equals("accept") || - op.equals("sysseek") || op.equals("truncate") || op.equals("read") || - op.equals("chown") || op.equals("waitpid") || - op.equals("setsockopt") || op.equals("getsockopt") || - op.equals("getpgrp") || op.equals("setpgrp") || - op.equals("getpriority") || op.equals("setpriority") || - op.equals("opendir") || op.equals("readdir") || op.equals("seekdir")) { + op.equals("rename") || op.equals("link") || op.equals("readlink") || + op.equals("umask") || op.equals("system") || op.equals("pack") || + op.equals("unpack") || op.equals("vec") || op.equals("crypt") || + op.equals("localtime") || op.equals("gmtime") || op.equals("caller") || op.equals("reset") || + op.equals("fileno") || op.equals("getc") || op.equals("qx") || + op.equals("close") || + op.equals("binmode") || op.equals("seek") || + op.equals("eof") || op.equals("sysread") || op.equals("syswrite") || + op.equals("sysopen") || op.equals("socket") || op.equals("bind") || + op.equals("connect") || op.equals("listen") || op.equals("write") || + op.equals("formline") || op.equals("printf") || op.equals("accept") || + op.equals("sysseek") || op.equals("truncate") || op.equals("read") || + op.equals("chown") || op.equals("waitpid") || + op.equals("setsockopt") || op.equals("getsockopt") || + op.equals("getpgrp") || op.equals("setpgrp") || + op.equals("getpriority") || op.equals("setpriority") || + op.equals("opendir") || op.equals("readdir") || op.equals("seekdir")) { // Generic handler for operators that take arguments and call runtime methods // Format: OPCODE rd argsReg ctx // argsReg must be a RuntimeList diff --git a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java index 917d21b0c..f5625ebb4 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java @@ -1,25 +1,25 @@ package org.perlonjava.backend.bytecode; -import org.perlonjava.frontend.astnode.Node; +import org.perlonjava.app.cli.CompilerOptions; import org.perlonjava.backend.jvm.EmitterContext; import org.perlonjava.backend.jvm.JavaClassInfo; +import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.lexer.Lexer; import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.parser.Parser; -import org.perlonjava.runtime.runtimetypes.*; -import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.frontend.semantic.ScopedSymbolTable; -import org.perlonjava.app.cli.CompilerOptions; +import org.perlonjava.runtime.operators.WarnDie; +import org.perlonjava.runtime.runtimetypes.*; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.HashMap; -import java.util.ArrayList; /** * Handler for eval STRING operations in the interpreter. - * + *

* Implements dynamic code evaluation with proper variable capture and error handling: * - Parses Perl string to AST * - Compiles AST to interpreter bytecode @@ -39,7 +39,7 @@ private static void evalTrace(String msg) { /** * Evaluate a Perl string dynamically. - * + *

* This implements eval STRING semantics: * - Parse and compile the string * - Execute in the current scope context @@ -47,30 +47,30 @@ private static void evalTrace(String msg) { * - Return result or undef on error * - Set $@ on error * - * @param perlCode The Perl code string to evaluate - * @param currentCode The current InterpretedCode (for context) - * @param registers Current register array (for variable access) - * @param sourceName Source name for error messages - * @param sourceLine Source line for error messages - * @param callContext The calling context (VOID/SCALAR/LIST) for wantarray inside eval + * @param perlCode The Perl code string to evaluate + * @param currentCode The current InterpretedCode (for context) + * @param registers Current register array (for variable access) + * @param sourceName Source name for error messages + * @param sourceLine Source line for error messages + * @param callContext The calling context (VOID/SCALAR/LIST) for wantarray inside eval * @return RuntimeScalar result of evaluation (undef on error) */ public static RuntimeScalar evalString(String perlCode, - InterpretedCode currentCode, - RuntimeBase[] registers, - String sourceName, - int sourceLine, - int callContext) { + InterpretedCode currentCode, + RuntimeBase[] registers, + String sourceName, + int sourceLine, + int callContext) { return evalStringList(perlCode, currentCode, registers, sourceName, sourceLine, callContext, null).scalar(); } public static RuntimeScalar evalString(String perlCode, - InterpretedCode currentCode, - RuntimeBase[] registers, - String sourceName, - int sourceLine, - int callContext, - Map siteRegistry) { + InterpretedCode currentCode, + RuntimeBase[] registers, + String sourceName, + int sourceLine, + int callContext, + Map siteRegistry) { return evalStringList(perlCode, currentCode, registers, sourceName, sourceLine, callContext, siteRegistry).scalar(); } @@ -283,8 +283,8 @@ public static RuntimeList evalStringList(String perlCode, } evalTrace("EvalStringHandler exec ok ctx=" + callContext + " resultScalar=" + (result != null ? result.scalar().toString() : "null") + - " resultBool=" + (result != null && result.scalar() != null ? result.scalar().getBoolean() : false) + - " $@=" + GlobalVariable.getGlobalVariable("main::@").toString()); + " resultBool=" + (result != null && result.scalar() != null && result.scalar().getBoolean()) + + " $@=" + GlobalVariable.getGlobalVariable("main::@")); return result; } catch (Exception e) { evalTrace("EvalStringHandler exec exception ctx=" + callContext + " ex=" + e.getClass().getSimpleName() + " msg=" + e.getMessage()); @@ -295,19 +295,19 @@ public static RuntimeList evalStringList(String perlCode, /** * Evaluate a Perl string with explicit variable capture. - * + *

* This version allows passing specific captured variables for the eval context. * - * @param perlCode The Perl code string to evaluate - * @param capturedVars Variables to capture from outer scope - * @param sourceName Source name for error messages - * @param sourceLine Source line for error messages + * @param perlCode The Perl code string to evaluate + * @param capturedVars Variables to capture from outer scope + * @param sourceName Source name for error messages + * @param sourceLine Source line for error messages * @return RuntimeScalar result of evaluation (undef on error) */ public static RuntimeScalar evalString(String perlCode, - RuntimeBase[] capturedVars, - String sourceName, - int sourceLine) { + RuntimeBase[] capturedVars, + String sourceName, + int sourceLine) { try { // Clear $@ at start GlobalVariable.getGlobalVariable("main::@").set(""); @@ -321,14 +321,14 @@ public static RuntimeScalar evalString(String perlCode, ScopedSymbolTable symbolTable = new ScopedSymbolTable(); ErrorMessageUtil errorUtil = new ErrorMessageUtil(sourceName, tokens); EmitterContext ctx = new EmitterContext( - new JavaClassInfo(), - symbolTable, - null, null, - RuntimeContextType.SCALAR, - false, - errorUtil, - opts, - null + new JavaClassInfo(), + symbolTable, + null, null, + RuntimeContextType.SCALAR, + false, + errorUtil, + opts, + null ); Parser parser = new Parser(ctx, tokens); @@ -338,8 +338,8 @@ public static RuntimeScalar evalString(String perlCode, // IMPORTANT: Do NOT call compiler.setCompilePackage() here — same reason as the // first evalString overload above: it corrupts die/warn location baking. BytecodeCompiler compiler = new BytecodeCompiler( - sourceName + " (eval)", - sourceLine + sourceName + " (eval)", + sourceLine ); InterpretedCode evalCode = compiler.compile(ast, ctx); // Pass ctx for context propagation if (RuntimeCode.DISASSEMBLE) { @@ -379,7 +379,7 @@ public static RuntimeScalar evalString(String perlCode, /** * Detect which variables from outer scope are referenced in eval string. - * + *

* This is used for proper variable capture (similar to closure analysis). * TODO: Implement proper lexical variable detection from AST * diff --git a/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java index 46a1c5411..8cfaff9ee 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java @@ -5,10 +5,10 @@ /** * Inline opcode handlers for arithmetic, shift, collection, and list operations. - * + *

* Extracted from BytecodeInterpreter.execute() to reduce method size * and keep it under the 8KB JIT compilation limit. - * + *

* Handles: arithmetic ops, shift ops, integer assign ops, array/hash operations, * and list operations (CREATE_LIST, JOIN, SELECT, RANGE, MAP, GREP, SORT, etc.) */ @@ -182,8 +182,8 @@ public static int executeAddScalarInt(int[] bytecode, int pc, RuntimeBase[] regi int immediate = bytecode[pc]; pc += 1; registers[rd] = MathOperators.add( - (RuntimeScalar) registers[rs], - immediate + (RuntimeScalar) registers[rs], + immediate ); return pc; } @@ -199,8 +199,8 @@ public static int executeConcat(int[] bytecode, int pc, RuntimeBase[] registers) RuntimeBase concatLeft = registers[rs1]; RuntimeBase concatRight = registers[rs2]; registers[rd] = StringOperators.stringConcat( - concatLeft instanceof RuntimeScalar ? (RuntimeScalar) concatLeft : concatLeft.scalar(), - concatRight instanceof RuntimeScalar ? (RuntimeScalar) concatRight : concatRight.scalar() + concatLeft instanceof RuntimeScalar ? (RuntimeScalar) concatLeft : concatLeft.scalar(), + concatRight instanceof RuntimeScalar ? (RuntimeScalar) concatRight : concatRight.scalar() ); return pc; } @@ -215,10 +215,10 @@ public static int executeRepeat(int[] bytecode, int pc, RuntimeBase[] registers) int rs2 = bytecode[pc++]; RuntimeBase countVal = registers[rs2]; RuntimeScalar count = (countVal instanceof RuntimeScalar) - ? (RuntimeScalar) countVal - : ((RuntimeList) countVal).scalar(); + ? (RuntimeScalar) countVal + : countVal.scalar(); int repeatCtx = (registers[rs1] instanceof RuntimeScalar) - ? RuntimeContextType.SCALAR : RuntimeContextType.LIST; + ? RuntimeContextType.SCALAR : RuntimeContextType.LIST; registers[rd] = Operator.repeat(registers[rs1], count, repeatCtx); return pc; } @@ -398,12 +398,12 @@ public static int executeArrayGet(int[] bytecode, int pc, RuntimeBase[] register int index = idx.getInt(); if (index < 0) index = list.elements.size() + index; registers[rd] = (index >= 0 && index < list.elements.size()) - ? list.elements.get(index) - : new RuntimeScalar(); + ? list.elements.get(index) + : new RuntimeScalar(); } else { throw new RuntimeException("ARRAY_GET: register " + arrayReg + " contains " + - (arrayBase == null ? "null" : arrayBase.getClass().getName()) + - " instead of RuntimeArray or RuntimeList"); + (arrayBase == null ? "null" : arrayBase.getClass().getName()) + + " instead of RuntimeArray or RuntimeList"); } return pc; } @@ -743,8 +743,8 @@ public static int executeJoin(int[] bytecode, int pc, RuntimeBase[] registers) { RuntimeBase separatorBase = registers[separatorReg]; RuntimeScalar separator = (separatorBase instanceof RuntimeScalar) - ? (RuntimeScalar) separatorBase - : separatorBase.scalar(); + ? (RuntimeScalar) separatorBase + : separatorBase.scalar(); RuntimeBase list = registers[listReg]; @@ -781,9 +781,9 @@ public static int executeRange(int[] bytecode, int pc, RuntimeBase[] registers) RuntimeBase endBase = registers[endReg]; RuntimeScalar start = (startBase instanceof RuntimeScalar) ? (RuntimeScalar) startBase : - (startBase == null) ? new RuntimeScalar() : startBase.scalar(); + (startBase == null) ? new RuntimeScalar() : startBase.scalar(); RuntimeScalar end = (endBase instanceof RuntimeScalar) ? (RuntimeScalar) endBase : - (endBase == null) ? new RuntimeScalar() : endBase.scalar(); + (endBase == null) ? new RuntimeScalar() : endBase.scalar(); PerlRange range = PerlRange.createRange(start, end); registers[rd] = range; @@ -984,7 +984,7 @@ public static int executeIsControlFlow(int[] bytecode, int pc, RuntimeBase[] reg int rd = bytecode[pc++]; int rs = bytecode[pc++]; registers[rd] = (registers[rs] instanceof RuntimeControlFlowList) ? - RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; + RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; return pc; } @@ -1188,9 +1188,9 @@ public static int executeFlipFlop(int[] bytecode, int pc, RuntimeBase[] register int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; registers[rd] = ScalarFlipFlopOperator.evaluate( - flipFlopId, - ((RuntimeBase) registers[rs1]).scalar(), - ((RuntimeBase) registers[rs2]).scalar()); + flipFlopId, + registers[rs1].scalar(), + registers[rs2].scalar()); return pc; } @@ -1214,7 +1214,7 @@ public static int executeDoFile(int[] bytecode, int pc, RuntimeBase[] registers) int rd = bytecode[pc++]; int fileReg = bytecode[pc++]; int ctx = bytecode[pc++]; - RuntimeScalar file = ((RuntimeBase) registers[fileReg]).scalar(); + RuntimeScalar file = registers[fileReg].scalar(); registers[rd] = ModuleOperators.doFile(file, ctx); return pc; } diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index 991f37513..c871055c9 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -9,14 +9,14 @@ /** * Interpreted bytecode that extends RuntimeCode. - * + *

* This class represents Perl code that is interpreted rather than compiled to JVM bytecode. * It is COMPLETELY INDISTINGUISHABLE from compiled RuntimeCode to the rest of the system: * - Can be stored in global variables ($::func) * - Can be passed as code references * - Can capture variables (closures work both directions) * - Can be used in method dispatch, overload, @ISA, etc. - * + *

* The ONLY difference is the execution engine: * - Compiled RuntimeCode uses MethodHandle to invoke JVM bytecode * - InterpretedCode overrides apply() to dispatch to BytecodeInterpreter @@ -47,55 +47,55 @@ public class InterpretedCode extends RuntimeCode { /** * Constructor for InterpretedCode. * - * @param bytecode The bytecode instructions - * @param constants Constant pool (RuntimeBase objects) - * @param stringPool String constants (variable names, etc.) - * @param maxRegisters Number of registers needed for execution - * @param capturedVars Captured variables for closure support (may be null) - * @param sourceName Source file name for debugging - * @param sourceLine Source line number for debugging - * @param pcToTokenIndex Map from bytecode PC to AST tokenIndex for error reporting + * @param bytecode The bytecode instructions + * @param constants Constant pool (RuntimeBase objects) + * @param stringPool String constants (variable names, etc.) + * @param maxRegisters Number of registers needed for execution + * @param capturedVars Captured variables for closure support (may be null) + * @param sourceName Source file name for debugging + * @param sourceLine Source line number for debugging + * @param pcToTokenIndex Map from bytecode PC to AST tokenIndex for error reporting * @param variableRegistry Variable name → register index mapping (for eval STRING) - * @param errorUtil Error message utility for line number lookup - * @param strictOptions Strict flags at compile time (for eval STRING inheritance) - * @param featureFlags Feature flags at compile time (for eval STRING inheritance) - * @param warningFlags Warning flags at compile time (for eval STRING inheritance) + * @param errorUtil Error message utility for line number lookup + * @param strictOptions Strict flags at compile time (for eval STRING inheritance) + * @param featureFlags Feature flags at compile time (for eval STRING inheritance) + * @param warningFlags Warning flags at compile time (for eval STRING inheritance) */ public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool, - int maxRegisters, RuntimeBase[] capturedVars, - String sourceName, int sourceLine, - TreeMap pcToTokenIndex, - Map variableRegistry, - ErrorMessageUtil errorUtil, - int strictOptions, int featureFlags, BitSet warningFlags) { + int maxRegisters, RuntimeBase[] capturedVars, + String sourceName, int sourceLine, + TreeMap pcToTokenIndex, + Map variableRegistry, + ErrorMessageUtil errorUtil, + int strictOptions, int featureFlags, BitSet warningFlags) { this(bytecode, constants, stringPool, maxRegisters, capturedVars, - sourceName, sourceLine, pcToTokenIndex, variableRegistry, errorUtil, - strictOptions, featureFlags, warningFlags, "main", null, null); + sourceName, sourceLine, pcToTokenIndex, variableRegistry, errorUtil, + strictOptions, featureFlags, warningFlags, "main", null, null); } public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool, - int maxRegisters, RuntimeBase[] capturedVars, - String sourceName, int sourceLine, - TreeMap pcToTokenIndex, - Map variableRegistry, - ErrorMessageUtil errorUtil, - int strictOptions, int featureFlags, BitSet warningFlags, - String compilePackage) { + int maxRegisters, RuntimeBase[] capturedVars, + String sourceName, int sourceLine, + TreeMap pcToTokenIndex, + Map variableRegistry, + ErrorMessageUtil errorUtil, + int strictOptions, int featureFlags, BitSet warningFlags, + String compilePackage) { this(bytecode, constants, stringPool, maxRegisters, capturedVars, - sourceName, sourceLine, pcToTokenIndex, variableRegistry, errorUtil, - strictOptions, featureFlags, warningFlags, compilePackage, null, null); + sourceName, sourceLine, pcToTokenIndex, variableRegistry, errorUtil, + strictOptions, featureFlags, warningFlags, compilePackage, null, null); } public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool, - int maxRegisters, RuntimeBase[] capturedVars, - String sourceName, int sourceLine, - TreeMap pcToTokenIndex, - Map variableRegistry, - ErrorMessageUtil errorUtil, - int strictOptions, int featureFlags, BitSet warningFlags, - String compilePackage, - List> evalSiteRegistries, - List evalSitePragmaFlags) { + int maxRegisters, RuntimeBase[] capturedVars, + String sourceName, int sourceLine, + TreeMap pcToTokenIndex, + Map variableRegistry, + ErrorMessageUtil errorUtil, + int strictOptions, int featureFlags, BitSet warningFlags, + String compilePackage, + List> evalSiteRegistries, + List evalSitePragmaFlags) { super(null, new java.util.ArrayList<>()); this.bytecode = bytecode; this.constants = constants; @@ -120,25 +120,33 @@ public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool, // Legacy constructor for backward compatibility public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool, - int maxRegisters, RuntimeBase[] capturedVars, - String sourceName, int sourceLine, - java.util.Map pcToTokenIndex) { + int maxRegisters, RuntimeBase[] capturedVars, + String sourceName, int sourceLine, + java.util.Map pcToTokenIndex) { this(bytecode, constants, stringPool, maxRegisters, capturedVars, - sourceName, sourceLine, - pcToTokenIndex instanceof TreeMap ? (TreeMap)pcToTokenIndex : new TreeMap<>(pcToTokenIndex), - null, null, 0, 0, new BitSet()); + sourceName, sourceLine, + pcToTokenIndex instanceof TreeMap ? (TreeMap) pcToTokenIndex : new TreeMap<>(pcToTokenIndex), + null, null, 0, 0, new BitSet()); } // Legacy constructor with variableRegistry but no errorUtil public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool, - int maxRegisters, RuntimeBase[] capturedVars, - String sourceName, int sourceLine, - java.util.Map pcToTokenIndex, - Map variableRegistry) { + int maxRegisters, RuntimeBase[] capturedVars, + String sourceName, int sourceLine, + java.util.Map pcToTokenIndex, + Map variableRegistry) { this(bytecode, constants, stringPool, maxRegisters, capturedVars, - sourceName, sourceLine, - pcToTokenIndex instanceof TreeMap ? (TreeMap)pcToTokenIndex : new TreeMap<>(pcToTokenIndex), - variableRegistry, null, 0, 0, new BitSet()); + sourceName, sourceLine, + pcToTokenIndex instanceof TreeMap ? (TreeMap) pcToTokenIndex : new TreeMap<>(pcToTokenIndex), + variableRegistry, null, 0, 0, new BitSet()); + } + + /** + * Read a 32-bit integer from bytecode (stored as 1 int slot). + * With int[] storage a full int fits in a single slot. + */ + private static int readInt(int[] bytecode, int pc) { + return bytecode[pc]; } /** @@ -182,22 +190,22 @@ public boolean defined() { */ public InterpretedCode withCapturedVars(RuntimeBase[] capturedVars) { InterpretedCode copy = new InterpretedCode( - this.bytecode, - this.constants, - this.stringPool, - this.maxRegisters, - capturedVars, - this.sourceName, - this.sourceLine, - this.pcToTokenIndex, - this.variableRegistry, - this.errorUtil, - this.strictOptions, - this.featureFlags, - this.warningFlags, - this.compilePackage, - this.evalSiteRegistries, - this.evalSitePragmaFlags + this.bytecode, + this.constants, + this.stringPool, + this.maxRegisters, + capturedVars, + this.sourceName, + this.sourceLine, + this.pcToTokenIndex, + this.variableRegistry, + this.errorUtil, + this.strictOptions, + this.featureFlags, + this.warningFlags, + this.compilePackage, + this.evalSiteRegistries, + this.evalSitePragmaFlags ); copy.prototype = this.prototype; copy.attributes = this.attributes; @@ -238,12 +246,12 @@ public RuntimeScalar registerAsNamedSub(String name) { @Override public String toString() { return "InterpretedCode{" + - "sourceName='" + sourceName + '\'' + - ", sourceLine=" + sourceLine + - ", bytecode.length=" + bytecode.length + - ", maxRegisters=" + maxRegisters + - ", hasCapturedVars=" + (capturedVars != null && capturedVars.length > 0) + - '}'; + "sourceName='" + sourceName + '\'' + + ", sourceLine=" + sourceLine + + ", bytecode.length=" + bytecode.length + + ", maxRegisters=" + maxRegisters + + ", hasCapturedVars=" + (capturedVars != null && capturedVars.length > 0) + + '}'; } /** @@ -319,7 +327,7 @@ public String disassemble() { // Special handling for PerlRange to avoid expanding large ranges PerlRange range = (PerlRange) obj; sb.append("PerlRange{").append(range.getStart().toString()).append("..") - .append(range.getEnd().toString()).append("}"); + .append(range.getEnd().toString()).append("}"); } else { // For other objects, show class name and limit string length String objStr = obj.toString(); @@ -344,15 +352,15 @@ public String disassemble() { rd = bytecode[pc++]; int strIdx = bytecode[pc++]; sb.append(opcode == Opcodes.LOAD_BYTE_STRING ? "LOAD_BYTE_STRING r" : "LOAD_STRING r") - .append(rd).append(" = \""); + .append(rd).append(" = \""); if (stringPool != null && strIdx < stringPool.length) { String str = stringPool[strIdx]; // Escape special characters for readability str = str.replace("\\", "\\\\") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t") - .replace("\"", "\\\""); + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + .replace("\"", "\\\""); sb.append(str); } sb.append("\"\n"); @@ -372,7 +380,7 @@ public String disassemble() { int globPattern = bytecode[pc++]; int globCtx = bytecode[pc++]; sb.append("GLOB_OP r").append(globRd).append(" = glob(id=").append(globId) - .append(", r").append(globPattern).append(", ctx=").append(globCtx).append(")\n"); + .append(", r").append(globPattern).append(", ctx=").append(globCtx).append(")\n"); break; } case Opcodes.LOAD_UNDEF: @@ -1107,7 +1115,7 @@ public String disassemble() { int argsReg = bytecode[pc++]; int ctx = bytecode[pc++]; sb.append("CALL_SUB r").append(rd).append(" = r").append(coderefReg) - .append("->(r").append(argsReg).append(", ctx=").append(ctx).append(")\n"); + .append("->(r").append(argsReg).append(", ctx=").append(ctx).append(")\n"); break; case Opcodes.CALL_METHOD: rd = bytecode[pc++]; @@ -1117,16 +1125,16 @@ public String disassemble() { argsReg = bytecode[pc++]; ctx = bytecode[pc++]; sb.append("CALL_METHOD r").append(rd).append(" = r").append(invocantReg) - .append("->r").append(methodReg) - .append("(r").append(argsReg).append(", sub=r").append(currentSubReg) - .append(", ctx=").append(ctx).append(")\n"); + .append("->r").append(methodReg) + .append("(r").append(argsReg).append(", sub=r").append(currentSubReg) + .append(", ctx=").append(ctx).append(")\n"); break; case Opcodes.JOIN: rd = bytecode[pc++]; int separatorReg = bytecode[pc++]; int listReg = bytecode[pc++]; sb.append("JOIN r").append(rd).append(" = join(r").append(separatorReg) - .append(", r").append(listReg).append(")\n"); + .append(", r").append(listReg).append(")\n"); break; case Opcodes.SELECT: rd = bytecode[pc++]; @@ -1155,7 +1163,7 @@ public String disassemble() { rs2 = bytecode[pc++]; // closure register int mapCtx = bytecode[pc++]; // context sb.append("MAP r").append(rd).append(" = map(r").append(rs1) - .append(", r").append(rs2).append(", ctx=").append(mapCtx).append(")\n"); + .append(", r").append(rs2).append(", ctx=").append(mapCtx).append(")\n"); break; case Opcodes.GREP: rd = bytecode[pc++]; @@ -1163,7 +1171,7 @@ public String disassemble() { rs2 = bytecode[pc++]; // closure register int grepCtx = bytecode[pc++]; // context sb.append("GREP r").append(rd).append(" = grep(r").append(rs1) - .append(", r").append(rs2).append(", ctx=").append(grepCtx).append(")\n"); + .append(", r").append(rs2).append(", ctx=").append(grepCtx).append(")\n"); break; case Opcodes.SORT: rd = bytecode[pc++]; @@ -1172,7 +1180,7 @@ public String disassemble() { int pkgIdx = readInt(bytecode, pc); pc += 1; sb.append("SORT r").append(rd).append(" = sort(r").append(rs1) - .append(", r").append(rs2).append(", pkg=").append(stringPool[pkgIdx]).append(")\n"); + .append(", r").append(rs2).append(", pkg=").append(stringPool[pkgIdx]).append(")\n"); break; case Opcodes.NEW_ARRAY: rd = bytecode[pc++]; @@ -1240,14 +1248,14 @@ public String disassemble() { int refReg = bytecode[pc++]; int packageReg = bytecode[pc++]; sb.append("BLESS r").append(rd).append(" = bless(r").append(refReg) - .append(", r").append(packageReg).append(")\n"); + .append(", r").append(packageReg).append(")\n"); break; case Opcodes.ISA: rd = bytecode[pc++]; int objReg = bytecode[pc++]; int pkgReg = bytecode[pc++]; sb.append("ISA r").append(rd).append(" = isa(r").append(objReg) - .append(", r").append(pkgReg).append(")\n"); + .append(", r").append(pkgReg).append(")\n"); break; case Opcodes.PROTOTYPE: rd = bytecode[pc++]; @@ -1255,16 +1263,16 @@ public String disassemble() { int packageIdx = readInt(bytecode, pc); pc += 1; // readInt reads 2 shorts String packageName = (stringPool != null && packageIdx < stringPool.length) ? - stringPool[packageIdx] : ""; + stringPool[packageIdx] : ""; sb.append("PROTOTYPE r").append(rd).append(" = prototype(r").append(rs) - .append(", \"").append(packageName).append("\")\n"); + .append(", \"").append(packageName).append("\")\n"); break; case Opcodes.QUOTE_REGEX: rd = bytecode[pc++]; int patternReg = bytecode[pc++]; int flagsReg = bytecode[pc++]; sb.append("QUOTE_REGEX r").append(rd).append(" = qr{r").append(patternReg) - .append("}r").append(flagsReg).append("\n"); + .append("}r").append(flagsReg).append("\n"); break; case Opcodes.ITERATOR_CREATE: rd = bytecode[pc++]; @@ -1287,8 +1295,8 @@ public String disassemble() { int bodyTarget = readInt(bytecode, pc); // Absolute body address pc += 1; sb.append("FOREACH_NEXT_OR_EXIT r").append(rd) - .append(" = r").append(iterReg).append(".next() and goto ") - .append(bodyTarget).append("\n"); + .append(" = r").append(iterReg).append(".next() and goto ") + .append(bodyTarget).append("\n"); break; } case Opcodes.SUBTRACT_ASSIGN: @@ -1418,7 +1426,7 @@ public String disassemble() { int evalCtx = bytecode[pc++]; int evalSite = bytecode[pc++]; sb.append("EVAL_STRING r").append(rd).append(" = eval(r").append(rs) - .append(", ctx=").append(evalCtx).append(", site=").append(evalSite).append(")\n"); + .append(", ctx=").append(evalCtx).append(", site=").append(evalSite).append(")\n"); break; case Opcodes.SELECT_OP: rd = bytecode[pc++]; @@ -1494,7 +1502,7 @@ public String disassemble() { nameIdx = bytecode[pc++]; int beginId = bytecode[pc++]; sb.append("RETRIEVE_BEGIN_SCALAR r").append(rd).append(" = BEGIN_").append(beginId) - .append("::").append(stringPool[nameIdx]).append("\n"); + .append("::").append(stringPool[nameIdx]).append("\n"); break; case Opcodes.SPLIT: rd = bytecode[pc++]; @@ -1502,7 +1510,7 @@ public String disassemble() { int splitArgsReg = bytecode[pc++]; int splitCtx = bytecode[pc++]; sb.append("SPLIT r").append(rd).append(" = split(r").append(splitPatternReg) - .append(", r").append(splitArgsReg).append(", ctx=").append(splitCtx).append(")\n"); + .append(", r").append(splitArgsReg).append(", ctx=").append(splitCtx).append(")\n"); break; case Opcodes.LOCAL_SCALAR: rd = bytecode[pc++]; @@ -1524,7 +1532,7 @@ public String disassemble() { int levelReg = bytecode[pc++]; nameIdx = bytecode[pc++]; sb.append("LOCAL_SCALAR_SAVE_LEVEL r").append(rd).append(", level=r").append(levelReg) - .append(" = local $").append(stringPool[nameIdx]).append("\n"); + .append(" = local $").append(stringPool[nameIdx]).append("\n"); break; } case Opcodes.POP_LOCAL_LEVEL: @@ -1535,9 +1543,10 @@ public String disassemble() { rd = bytecode[pc++]; int fgIterReg = bytecode[pc++]; nameIdx = bytecode[pc++]; - int fgBody = readInt(bytecode, pc); pc += 1; + int fgBody = readInt(bytecode, pc); + pc += 1; sb.append("FOREACH_GLOBAL_NEXT_OR_EXIT r").append(rd).append(" = r").append(fgIterReg) - .append(".next(), alias $").append(stringPool[nameIdx]).append(" and goto ").append(fgBody).append("\n"); + .append(".next(), alias $").append(stringPool[nameIdx]).append(" and goto ").append(fgBody).append("\n"); break; } // Misc list operators: OPCODE rd argsReg ctx @@ -1586,8 +1595,8 @@ public String disassemble() { default -> "misc_op_" + opcode; }; sb.append(miscName).append(" r").append(rd) - .append(" = ").append(miscName).append("(r").append(miscArgsReg) - .append(", ctx=").append(miscCtx).append(")\n"); + .append(" = ").append(miscName).append("(r").append(miscArgsReg) + .append(", ctx=").append(miscCtx).append(")\n"); break; } @@ -1701,7 +1710,7 @@ public String disassemble() { rs1 = bytecode[pc++]; rs2 = bytecode[pc++]; sb.append("SUBSTR r").append(rd).append(" = substr(r").append(rs1) - .append(", r").append(rs2).append(")\n"); + .append(", r").append(rs2).append(")\n"); break; } case Opcodes.LENGTH: { @@ -1762,7 +1771,7 @@ public String disassemble() { int builtinArgsReg = bytecode[pc++]; int builtinCtx = bytecode[pc++]; sb.append("CALL_BUILTIN r").append(rd).append(" = builtin(").append(builtinId) - .append(", r").append(builtinArgsReg).append(", ctx=").append(builtinCtx).append(")\n"); + .append(", r").append(builtinArgsReg).append(", ctx=").append(builtinCtx).append(")\n"); break; } @@ -1845,7 +1854,7 @@ public String disassemble() { int asArrayReg = bytecode[pc++]; int asIndicesReg = bytecode[pc++]; sb.append("ARRAY_SLICE r").append(rd).append(" = r").append(asArrayReg) - .append("[r").append(asIndicesReg).append("]\n"); + .append("[r").append(asIndicesReg).append("]\n"); break; } case Opcodes.ARRAY_SLICE_SET: { @@ -1854,7 +1863,7 @@ public String disassemble() { int assIndicesReg = bytecode[pc++]; int assValuesReg = bytecode[pc++]; sb.append("ARRAY_SLICE_SET r").append(assArrayReg) - .append("[r").append(assIndicesReg).append("] = r").append(assValuesReg).append("\n"); + .append("[r").append(assIndicesReg).append("] = r").append(assValuesReg).append("\n"); break; } case Opcodes.HASH_SLICE: { @@ -1863,7 +1872,7 @@ public String disassemble() { int hsHashReg = bytecode[pc++]; int hsKeysReg = bytecode[pc++]; sb.append("HASH_SLICE r").append(rd).append(" = r").append(hsHashReg) - .append("{r").append(hsKeysReg).append("}\n"); + .append("{r").append(hsKeysReg).append("}\n"); break; } case Opcodes.HASH_SLICE_SET: { @@ -1872,7 +1881,7 @@ public String disassemble() { int hssKeysReg = bytecode[pc++]; int hssValuesReg = bytecode[pc++]; sb.append("HASH_SLICE_SET r").append(hssHashReg) - .append("{r").append(hssKeysReg).append("} = r").append(hssValuesReg).append("\n"); + .append("{r").append(hssKeysReg).append("} = r").append(hssValuesReg).append("\n"); break; } case Opcodes.HASH_SLICE_DELETE: { @@ -1881,7 +1890,7 @@ public String disassemble() { int hsdHashReg = bytecode[pc++]; int hsdKeysReg = bytecode[pc++]; sb.append("HASH_SLICE_DELETE r").append(rd).append(" = delete r").append(hsdHashReg) - .append("{r").append(hsdKeysReg).append("}\n"); + .append("{r").append(hsdKeysReg).append("}\n"); break; } case Opcodes.LIST_SLICE_FROM: { @@ -1890,7 +1899,7 @@ public String disassemble() { int lsfListReg = bytecode[pc++]; int lsfStartIdx = bytecode[pc++]; sb.append("LIST_SLICE_FROM r").append(rd).append(" = r").append(lsfListReg) - .append("[").append(lsfStartIdx).append("..]\n"); + .append("[").append(lsfStartIdx).append("..]\n"); break; } case Opcodes.SPLICE: { @@ -1900,7 +1909,7 @@ public String disassemble() { int splArgsReg = bytecode[pc++]; int splCtx = bytecode[pc++]; sb.append("SPLICE r").append(rd).append(" = splice(r").append(splArrayReg) - .append(", r").append(splArgsReg).append(", ctx=").append(splCtx).append(")\n"); + .append(", r").append(splArgsReg).append(", ctx=").append(splCtx).append(")\n"); break; } case Opcodes.REVERSE: { @@ -1909,7 +1918,7 @@ public String disassemble() { int revArgsReg = bytecode[pc++]; int revCtx = bytecode[pc++]; sb.append("REVERSE r").append(rd).append(" = reverse(r").append(revArgsReg) - .append(", ctx=").append(revCtx).append(")\n"); + .append(", ctx=").append(revCtx).append(")\n"); break; } case Opcodes.LENGTH_OP: { @@ -1944,7 +1953,7 @@ public String disassemble() { nameIdx = bytecode[pc++]; int rbaBeginId = bytecode[pc++]; sb.append("RETRIEVE_BEGIN_ARRAY r").append(rd).append(" = BEGIN_").append(rbaBeginId) - .append("::").append(stringPool[nameIdx]).append("\n"); + .append("::").append(stringPool[nameIdx]).append("\n"); break; } case Opcodes.RETRIEVE_BEGIN_HASH: { @@ -1953,7 +1962,7 @@ public String disassemble() { nameIdx = bytecode[pc++]; int rbhBeginId = bytecode[pc++]; sb.append("RETRIEVE_BEGIN_HASH r").append(rd).append(" = BEGIN_").append(rbhBeginId) - .append("::").append(stringPool[nameIdx]).append("\n"); + .append("::").append(stringPool[nameIdx]).append("\n"); break; } @@ -1985,8 +1994,8 @@ public String disassemble() { default -> "sys_op_" + opcode; }; sb.append(sysName).append(" r").append(rd) - .append(" = ").append(sysName).append("(r").append(sysArgsReg) - .append(", ctx=").append(sysCtx).append(")\n"); + .append(" = ").append(sysName).append("(r").append(sysArgsReg) + .append(", ctx=").append(sysCtx).append(")\n"); break; } case Opcodes.FORK: { @@ -2021,7 +2030,7 @@ public String disassemble() { int sgNsemsReg = bytecode[pc++]; int sgFlagsReg = bytecode[pc++]; sb.append("SEMGET r").append(rd).append(" = semget(r").append(sgKeyReg) - .append(", r").append(sgNsemsReg).append(", r").append(sgFlagsReg).append(")\n"); + .append(", r").append(sgNsemsReg).append(", r").append(sgFlagsReg).append(")\n"); break; } case Opcodes.SEMOP: { @@ -2030,7 +2039,7 @@ public String disassemble() { int soSemidReg = bytecode[pc++]; int soOpstringReg = bytecode[pc++]; sb.append("SEMOP r").append(rd).append(" = semop(r").append(soSemidReg) - .append(", r").append(soOpstringReg).append(")\n"); + .append(", r").append(soOpstringReg).append(")\n"); break; } case Opcodes.MSGGET: { @@ -2039,7 +2048,7 @@ public String disassemble() { int mgKeyReg = bytecode[pc++]; int mgFlagsReg = bytecode[pc++]; sb.append("MSGGET r").append(rd).append(" = msgget(r").append(mgKeyReg) - .append(", r").append(mgFlagsReg).append(")\n"); + .append(", r").append(mgFlagsReg).append(")\n"); break; } case Opcodes.MSGSND: { @@ -2049,7 +2058,7 @@ public String disassemble() { int msMsgReg = bytecode[pc++]; int msFlagsReg = bytecode[pc++]; sb.append("MSGSND r").append(rd).append(" = msgsnd(r").append(msIdReg) - .append(", r").append(msMsgReg).append(", r").append(msFlagsReg).append(")\n"); + .append(", r").append(msMsgReg).append(", r").append(msFlagsReg).append(")\n"); break; } case Opcodes.MSGRCV: { @@ -2060,8 +2069,8 @@ public String disassemble() { int mrTypeReg = bytecode[pc++]; int mrFlagsReg = bytecode[pc++]; sb.append("MSGRCV r").append(rd).append(" = msgrcv(r").append(mrIdReg) - .append(", r").append(mrSizeReg).append(", r").append(mrTypeReg) - .append(", r").append(mrFlagsReg).append(")\n"); + .append(", r").append(mrSizeReg).append(", r").append(mrTypeReg) + .append(", r").append(mrFlagsReg).append(")\n"); break; } case Opcodes.SHMGET: { @@ -2071,7 +2080,7 @@ public String disassemble() { int shgSizeReg = bytecode[pc++]; int shgFlagsReg = bytecode[pc++]; sb.append("SHMGET r").append(rd).append(" = shmget(r").append(shgKeyReg) - .append(", r").append(shgSizeReg).append(", r").append(shgFlagsReg).append(")\n"); + .append(", r").append(shgSizeReg).append(", r").append(shgFlagsReg).append(")\n"); break; } case Opcodes.SHMREAD: { @@ -2081,7 +2090,7 @@ public String disassemble() { int shrPosReg = bytecode[pc++]; int shrSizeReg = bytecode[pc++]; sb.append("SHMREAD r").append(rd).append(" = shmread(r").append(shrIdReg) - .append(", r").append(shrPosReg).append(", r").append(shrSizeReg).append(")\n"); + .append(", r").append(shrPosReg).append(", r").append(shrSizeReg).append(")\n"); break; } case Opcodes.SHMWRITE: { @@ -2090,7 +2099,7 @@ public String disassemble() { int shwPosReg = bytecode[pc++]; int shwStringReg = bytecode[pc++]; sb.append("SHMWRITE shmwrite(r").append(shwIdReg) - .append(", r").append(shwPosReg).append(", r").append(shwStringReg).append(")\n"); + .append(", r").append(shwPosReg).append(", r").append(shwStringReg).append(")\n"); break; } @@ -2128,8 +2137,8 @@ public String disassemble() { int trTargetReg = bytecode[pc++]; int trCtx = bytecode[pc++]; sb.append("TR_TRANSLITERATE r").append(rd).append(" = tr(r").append(trSearchReg) - .append(", r").append(trReplaceReg).append(", r").append(trModifiersReg) - .append(", r").append(trTargetReg).append(", ctx=").append(trCtx).append(")\n"); + .append(", r").append(trReplaceReg).append(", r").append(trModifiersReg) + .append(", r").append(trTargetReg).append(", ctx=").append(trCtx).append(")\n"); break; } case Opcodes.STORE_SYMBOLIC_SCALAR: { @@ -2152,7 +2161,7 @@ public String disassemble() { int tieArgsReg = bytecode[pc++]; int tieCtx = bytecode[pc++]; sb.append("TIE r").append(rd).append(" = tie(r").append(tieArgsReg) - .append(", ctx=").append(tieCtx).append(")\n"); + .append(", ctx=").append(tieCtx).append(")\n"); break; } case Opcodes.UNTIE: { @@ -2161,7 +2170,7 @@ public String disassemble() { int untieArgsReg = bytecode[pc++]; int untieCtx = bytecode[pc++]; sb.append("UNTIE r").append(rd).append(" = untie(r").append(untieArgsReg) - .append(", ctx=").append(untieCtx).append(")\n"); + .append(", ctx=").append(untieCtx).append(")\n"); break; } case Opcodes.TIED: { @@ -2170,7 +2179,7 @@ public String disassemble() { int tiedArgsReg = bytecode[pc++]; int tiedCtx = bytecode[pc++]; sb.append("TIED r").append(rd).append(" = tied(r").append(tiedArgsReg) - .append(", ctx=").append(tiedCtx).append(")\n"); + .append(", ctx=").append(tiedCtx).append(")\n"); break; } case Opcodes.QX: { @@ -2179,7 +2188,7 @@ public String disassemble() { int qxArgsReg = bytecode[pc++]; int qxCtx = bytecode[pc++]; sb.append("QX r").append(rd).append(" = qx(r").append(qxArgsReg) - .append(", ctx=").append(qxCtx).append(")\n"); + .append(", ctx=").append(qxCtx).append(")\n"); break; } @@ -2237,8 +2246,8 @@ public String disassemble() { default -> "io_op_" + opcode; }; sb.append(ioName).append(" r").append(rd) - .append(" = ").append(ioName).append("(r").append(ioArgsReg) - .append(", ctx=").append(ioCtx).append(")\n"); + .append(" = ").append(ioName).append("(r").append(ioArgsReg) + .append(", ctx=").append(ioCtx).append(")\n"); break; } @@ -2252,7 +2261,7 @@ public String disassemble() { int lgdNameReg = bytecode[pc++]; int lgdPkgIdx = bytecode[pc++]; sb.append("LOAD_GLOB_DYNAMIC r").append(rd).append(" = *{r").append(lgdNameReg) - .append("} pkg=").append(stringPool[lgdPkgIdx]).append("\n"); + .append("} pkg=").append(stringPool[lgdPkgIdx]).append("\n"); break; } case Opcodes.SET_ARRAY_LAST_INDEX: { @@ -2298,14 +2307,6 @@ public String disassemble() { return sb.toString(); } - /** - * Read a 32-bit integer from bytecode (stored as 1 int slot). - * With int[] storage a full int fits in a single slot. - */ - private static int readInt(int[] bytecode, int pc) { - return bytecode[pc]; - } - /** * Builder class for constructing InterpretedCode instances. */ @@ -2358,7 +2359,7 @@ public InterpretedCode build() { throw new IllegalStateException("Bytecode is required"); } return new InterpretedCode(bytecode, constants, stringPool, maxRegisters, - capturedVars, sourceName, sourceLine, null, null); + capturedVars, sourceName, sourceLine, null, null); } } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java b/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java index 2e1494914..92200a4df 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java @@ -1,6 +1,7 @@ package org.perlonjava.backend.bytecode; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -15,12 +16,6 @@ * Only tracks the call stack for stack trace generation, not PC updates. */ public class InterpreterState { - private static final ThreadLocal> frameStack = - ThreadLocal.withInitial(ArrayDeque::new); - - private static final ThreadLocal> pcStack = - ThreadLocal.withInitial(ArrayDeque::new); - /** * Thread-local RuntimeScalar holding the runtime current package name. * @@ -50,22 +45,10 @@ public class InterpreterState { */ public static final ThreadLocal currentPackage = ThreadLocal.withInitial(() -> new RuntimeScalar("main")); - - /** - * Represents a single interpreter call frame. - * Contains minimal information needed for stack trace formatting. - */ - public static class InterpreterFrame { - public final InterpretedCode code; - public final String packageName; - public final String subroutineName; - - public InterpreterFrame(InterpretedCode code, String packageName, String subroutineName) { - this.code = code; - this.packageName = packageName; - this.subroutineName = subroutineName; - } - } + private static final ThreadLocal> frameStack = + ThreadLocal.withInitial(ArrayDeque::new); + private static final ThreadLocal> pcStack = + ThreadLocal.withInitial(ArrayDeque::new); /** * Push a new interpreter frame onto the stack. @@ -129,4 +112,20 @@ public static List getPcStack() { return new ArrayList<>(pcStack.get()); } + /** + * Represents a single interpreter call frame. + * Contains minimal information needed for stack trace formatting. + */ + public static class InterpreterFrame { + public final InterpretedCode code; + public final String packageName; + public final String subroutineName; + + public InterpreterFrame(InterpretedCode code, String packageName, String subroutineName) { + this.code = code; + this.packageName = packageName; + this.subroutineName = subroutineName; + } + } + } diff --git a/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java index f5a939432..5a555022a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/MiscOpcodeHandler.java @@ -2,16 +2,7 @@ import org.perlonjava.runtime.nativ.NativeUtils; import org.perlonjava.runtime.operators.*; -import org.perlonjava.runtime.operators.ChownOperator; -import org.perlonjava.runtime.operators.Operator; -import org.perlonjava.runtime.operators.Directory; -import org.perlonjava.runtime.operators.WaitpidOperator; -import org.perlonjava.runtime.operators.Unpack; -import org.perlonjava.runtime.runtimetypes.RuntimeBase; -import org.perlonjava.runtime.runtimetypes.RuntimeCode; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; +import org.perlonjava.runtime.runtimetypes.*; /** * Handler for miscellaneous opcodes that call runtime operator methods. @@ -89,13 +80,14 @@ public static int execute(int opcode, int[] bytecode, int pc, RuntimeBase[] regi case Opcodes.GETPRIORITY -> Operator.getpriority(ctx, argsArray); case Opcodes.SETPRIORITY -> new RuntimeScalar(0); // stub - no native impl yet case Opcodes.OPENDIR -> Directory.opendir(args); - case Opcodes.READDIR -> Directory.readdir(args.elements.isEmpty() ? null : (RuntimeScalar) args.elements.get(0), ctx); + case Opcodes.READDIR -> + Directory.readdir(args.elements.isEmpty() ? null : (RuntimeScalar) args.elements.get(0), ctx); case Opcodes.SEEKDIR -> Directory.seekdir(args); default -> throw new IllegalStateException("Unknown opcode in MiscOpcodeHandler: " + opcode); }; if (ctx == RuntimeContextType.SCALAR && result instanceof RuntimeList) { - result = ((RuntimeList) result).scalar(); + result = result.scalar(); } registers[rd] = result; return pc; diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index 6033b4ec8..054abfc73 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -1,15 +1,15 @@ package org.perlonjava.backend.bytecode; -import org.perlonjava.runtime.regex.RuntimeRegex; import org.perlonjava.runtime.operators.*; +import org.perlonjava.runtime.regex.RuntimeRegex; import org.perlonjava.runtime.runtimetypes.*; /** * 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. */ @@ -19,8 +19,8 @@ public class OpcodeHandlerExtended { * Execute sprintf operation. * Format: SPRINTF rd formatReg argsListReg * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -40,8 +40,8 @@ public static int executeSprintf(int[] bytecode, int pc, RuntimeBase[] registers * Execute chop operation. * Format: CHOP rd scalarReg * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -57,8 +57,8 @@ public static int executeChop(int[] bytecode, int pc, RuntimeBase[] registers) { * 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 bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -80,8 +80,8 @@ public static int executeGetReplacementRegex(int[] bytecode, int pc, RuntimeBase * Execute substr with variable arguments. * Format: SUBSTR_VAR rd argsListReg ctx * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -101,8 +101,8 @@ public static int executeSubstrVar(int[] bytecode, int pc, RuntimeBase[] registe * Execute repeat assign operation. * Format: REPEAT_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -113,9 +113,9 @@ public static int executeRepeatAssign(int[] bytecode, int pc, RuntimeBase[] regi registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeBase result = Operator.repeat( - registers[rd], - (RuntimeScalar) registers[rs], - 1 // scalar context + registers[rd], + (RuntimeScalar) registers[rs], + 1 // scalar context ); ((RuntimeScalar) registers[rd]).set((RuntimeScalar) result); return pc; @@ -125,8 +125,8 @@ public static int executeRepeatAssign(int[] bytecode, int pc, RuntimeBase[] regi * Execute power assign operation. * Format: POW_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -149,8 +149,8 @@ public static int executePowAssign(int[] bytecode, int pc, RuntimeBase[] registe * Execute left shift assign operation. * Format: LEFT_SHIFT_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -171,8 +171,8 @@ public static int executeLeftShiftAssign(int[] bytecode, int pc, RuntimeBase[] r * Execute right shift assign operation. * Format: RIGHT_SHIFT_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -193,8 +193,8 @@ public static int executeRightShiftAssign(int[] bytecode, int pc, RuntimeBase[] * Execute logical AND assign operation. * Format: LOGICAL_AND_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -204,11 +204,11 @@ public static int executeLogicalAndAssign(int[] bytecode, int pc, RuntimeBase[] if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); + RuntimeScalar s1 = registers[rd].scalar(); if (!s1.getBoolean()) { return pc; } - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); + RuntimeScalar s2 = registers[rs].scalar(); ((RuntimeScalar) registers[rd]).set(s2); return pc; } @@ -217,8 +217,8 @@ public static int executeLogicalAndAssign(int[] bytecode, int pc, RuntimeBase[] * Execute logical OR assign operation. * Format: LOGICAL_OR_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -228,11 +228,11 @@ public static int executeLogicalOrAssign(int[] bytecode, int pc, RuntimeBase[] r if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); + RuntimeScalar s1 = registers[rd].scalar(); if (s1.getBoolean()) { return pc; } - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); + RuntimeScalar s2 = registers[rs].scalar(); ((RuntimeScalar) registers[rd]).set(s2); return pc; } @@ -243,11 +243,11 @@ public static int executeDefinedOrAssign(int[] bytecode, int pc, RuntimeBase[] r if (BytecodeInterpreter.isImmutableProxy(registers[rd])) { registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } - RuntimeScalar s1 = ((RuntimeBase) registers[rd]).scalar(); + RuntimeScalar s1 = registers[rd].scalar(); if (s1.getDefinedBoolean()) { return pc; } - RuntimeScalar s2 = ((RuntimeBase) registers[rs]).scalar(); + RuntimeScalar s2 = registers[rs].scalar(); ((RuntimeScalar) registers[rd]).set(s2); return pc; } @@ -256,8 +256,8 @@ public static int executeDefinedOrAssign(int[] bytecode, int pc, RuntimeBase[] r * Execute string concatenation assign operation. * Format: STRING_CONCAT_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -268,8 +268,8 @@ public static int executeStringConcatAssign(int[] bytecode, int pc, RuntimeBase[ registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeScalar result = StringOperators.stringConcat( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] ); ((RuntimeScalar) registers[rd]).set(result); return pc; @@ -279,8 +279,8 @@ public static int executeStringConcatAssign(int[] bytecode, int pc, RuntimeBase[ * Execute bitwise AND assign operation. * Format: BITWISE_AND_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -291,8 +291,8 @@ public static int executeBitwiseAndAssign(int[] bytecode, int pc, RuntimeBase[] registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeScalar result = BitwiseOperators.bitwiseAnd( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] ); ((RuntimeScalar) registers[rd]).set(result); return pc; @@ -302,8 +302,8 @@ public static int executeBitwiseAndAssign(int[] bytecode, int pc, RuntimeBase[] * Execute bitwise OR assign operation. * Format: BITWISE_OR_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -314,8 +314,8 @@ public static int executeBitwiseOrAssign(int[] bytecode, int pc, RuntimeBase[] r registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeScalar result = BitwiseOperators.bitwiseOrBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] ); ((RuntimeScalar) registers[rd]).set(result); return pc; @@ -325,8 +325,8 @@ public static int executeBitwiseOrAssign(int[] bytecode, int pc, RuntimeBase[] r * Execute bitwise XOR assign operation. * Format: BITWISE_XOR_ASSIGN rd rs * - * @param bytecode The bytecode array - * @param pc Current program counter + * @param bytecode The bytecode array + * @param pc Current program counter * @param registers Register file * @return Updated program counter */ @@ -337,8 +337,8 @@ public static int executeBitwiseXorAssign(int[] bytecode, int pc, RuntimeBase[] registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeScalar result = BitwiseOperators.bitwiseXorBinary( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] ); ((RuntimeScalar) registers[rd]).set(result); return pc; @@ -351,8 +351,8 @@ public static int executeStringBitwiseAndAssign(int[] bytecode, int pc, RuntimeB registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeScalar result = BitwiseOperators.bitwiseAndDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] ); ((RuntimeScalar) registers[rd]).set(result); return pc; @@ -365,8 +365,8 @@ public static int executeStringBitwiseOrAssign(int[] bytecode, int pc, RuntimeBa registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeScalar result = BitwiseOperators.bitwiseOrDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] ); ((RuntimeScalar) registers[rd]).set(result); return pc; @@ -379,8 +379,8 @@ public static int executeStringBitwiseXorAssign(int[] bytecode, int pc, RuntimeB registers[rd] = BytecodeInterpreter.ensureMutableScalar(registers[rd]); } RuntimeScalar result = BitwiseOperators.bitwiseXorDot( - (RuntimeScalar) registers[rd], - (RuntimeScalar) registers[rs] + (RuntimeScalar) registers[rd], + (RuntimeScalar) registers[rs] ); ((RuntimeScalar) registers[rd]).set(result); return pc; @@ -397,8 +397,8 @@ public static int executeBitwiseAndBinary(int[] bytecode, int pc, RuntimeBase[] int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; registers[rd] = BitwiseOperators.bitwiseAnd( - registers[rs1].scalar(), - registers[rs2].scalar() + registers[rs1].scalar(), + registers[rs2].scalar() ); return pc; } @@ -412,8 +412,8 @@ public static int executeBitwiseOrBinary(int[] bytecode, int pc, RuntimeBase[] r int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; registers[rd] = BitwiseOperators.bitwiseOr( - registers[rs1].scalar(), - registers[rs2].scalar() + registers[rs1].scalar(), + registers[rs2].scalar() ); return pc; } @@ -427,8 +427,8 @@ public static int executeBitwiseXorBinary(int[] bytecode, int pc, RuntimeBase[] int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; registers[rd] = BitwiseOperators.bitwiseXor( - registers[rs1].scalar(), - registers[rs2].scalar() + registers[rs1].scalar(), + registers[rs2].scalar() ); return pc; } @@ -442,8 +442,8 @@ public static int executeStringBitwiseAnd(int[] bytecode, int pc, RuntimeBase[] int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; registers[rd] = BitwiseOperators.bitwiseAndDot( - registers[rs1].scalar(), - registers[rs2].scalar() + registers[rs1].scalar(), + registers[rs2].scalar() ); return pc; } @@ -457,8 +457,8 @@ public static int executeStringBitwiseOr(int[] bytecode, int pc, RuntimeBase[] r int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; registers[rd] = BitwiseOperators.bitwiseOrDot( - registers[rs1].scalar(), - registers[rs2].scalar() + registers[rs1].scalar(), + registers[rs2].scalar() ); return pc; } @@ -472,8 +472,8 @@ public static int executeStringBitwiseXor(int[] bytecode, int pc, RuntimeBase[] int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; registers[rd] = BitwiseOperators.bitwiseXorDot( - registers[rs1].scalar(), - registers[rs2].scalar() + registers[rs1].scalar(), + registers[rs2].scalar() ); return pc; } @@ -551,8 +551,8 @@ public static int executePrint(int[] bytecode, int pc, RuntimeBase[] registers) // Filehandle should be scalar - convert if needed RuntimeBase fhBase = registers[filehandleReg]; RuntimeScalar fh = (fhBase instanceof RuntimeScalar) - ? (RuntimeScalar) fhBase - : fhBase.scalar(); + ? (RuntimeScalar) fhBase + : fhBase.scalar(); RuntimeList list; if (val instanceof RuntimeList) { @@ -589,8 +589,8 @@ public static int executeSay(int[] bytecode, int pc, RuntimeBase[] registers) { // Filehandle should be scalar - convert if needed RuntimeBase fhBase = registers[filehandleReg]; RuntimeScalar fh = (fhBase instanceof RuntimeScalar) - ? (RuntimeScalar) fhBase - : fhBase.scalar(); + ? (RuntimeScalar) fhBase + : fhBase.scalar(); RuntimeList list; if (val instanceof RuntimeList) { @@ -669,9 +669,9 @@ public static int executeIndex(int[] bytecode, int pc, RuntimeBase[] registers) int substrReg = bytecode[pc++]; int posReg = bytecode[pc++]; registers[rd] = StringOperators.index( - (RuntimeScalar) registers[strReg], - (RuntimeScalar) registers[substrReg], - (RuntimeScalar) registers[posReg] + (RuntimeScalar) registers[strReg], + (RuntimeScalar) registers[substrReg], + (RuntimeScalar) registers[posReg] ); return pc; } @@ -686,9 +686,9 @@ public static int executeRindex(int[] bytecode, int pc, RuntimeBase[] registers) int substrReg = bytecode[pc++]; int posReg = bytecode[pc++]; registers[rd] = StringOperators.rindex( - (RuntimeScalar) registers[strReg], - (RuntimeScalar) registers[substrReg], - (RuntimeScalar) registers[posReg] + (RuntimeScalar) registers[strReg], + (RuntimeScalar) registers[substrReg], + (RuntimeScalar) registers[posReg] ); return pc; } @@ -790,9 +790,9 @@ public static int executeMatchRegex(int[] bytecode, int pc, RuntimeBase[] regist int regexReg = bytecode[pc++]; int ctx = bytecode[pc++]; registers[rd] = RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], - (RuntimeScalar) registers[stringReg], - ctx + (RuntimeScalar) registers[regexReg], + (RuntimeScalar) registers[stringReg], + ctx ); return pc; } @@ -807,9 +807,9 @@ public static int executeMatchRegexNot(int[] bytecode, int pc, RuntimeBase[] reg int regexReg = bytecode[pc++]; int ctx = bytecode[pc++]; RuntimeBase matchResult = RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], - (RuntimeScalar) registers[stringReg], - ctx + (RuntimeScalar) registers[regexReg], + (RuntimeScalar) registers[stringReg], + ctx ); // Negate the boolean result registers[rd] = new RuntimeScalar(matchResult.scalar().getBoolean() ? 0 : 1); @@ -839,7 +839,7 @@ public static int executeCreateClosure(int[] bytecode, int pc, RuntimeBase[] reg InterpretedCode closureCode = template.withCapturedVars(capturedVars); // Wrap in RuntimeScalar - registers[rd] = new RuntimeScalar((RuntimeCode) closureCode); + registers[rd] = new RuntimeScalar(closureCode); return pc; } @@ -871,7 +871,7 @@ public static int executeIteratorHasNext(int[] bytecode, int pc, RuntimeBase[] r RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; @SuppressWarnings("unchecked") java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; + (java.util.Iterator) iterScalar.value; boolean hasNext = iterator.hasNext(); registers[rd] = hasNext ? RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse; @@ -889,7 +889,7 @@ public static int executeIteratorNext(int[] bytecode, int pc, RuntimeBase[] regi RuntimeScalar iterScalar = (RuntimeScalar) registers[iterReg]; @SuppressWarnings("unchecked") java.util.Iterator iterator = - (java.util.Iterator) iterScalar.value; + (java.util.Iterator) iterScalar.value; RuntimeScalar next = iterator.next(); registers[rd] = BytecodeInterpreter.isImmutableProxy(next) ? BytecodeInterpreter.ensureMutableScalar(next) : next; diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerFileTest.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerFileTest.java index 9e6b5971f..cfd5472d2 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerFileTest.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerFileTest.java @@ -6,7 +6,7 @@ /** * File test opcode handlers. - * + *

* Extracted from BytecodeInterpreter.execute() to reduce method size. * Handles all file test operations (opcodes 190-216). */ @@ -16,10 +16,10 @@ 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 bytecode The bytecode array + * @param pc Current program counter * @param registers Register file - * @param opcode The file test opcode (190-216) + * @param opcode The file test opcode (190-216) * @return Updated program counter */ public static int executeFileTest(int[] bytecode, int pc, RuntimeBase[] registers, int opcode) { diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 66ad07b18..cde25ef02 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -6,22 +6,22 @@ /** * Bytecode opcodes for the PerlOnJava interpreter. - * + *

* Design: Pure register machine with 3-address code format. * Uses SHORT opcodes (0-32767) to support unlimited operation space. - * + *

* CRITICAL: Keep opcodes CONTIGUOUS within functional groups for JVM * tableswitch optimization (O(1) vs O(log n) lookupswitch). - * + *

* Register architecture is REQUIRED for control flow correctness: * Perl's GOTO/last/next/redo would corrupt a stack-based architecture. - * + *

* Opcode Ranges: * - 0-113: Core operations (current) * - 114-199: Reserved for expansion * - 200-299: Reserved * - 300+: Future operator promotions (CONTIGUOUS blocks!) - * + *

* Infrastructure: Bytecode already uses short[] array, compiler already * emits short values. Only the opcode type definitions changed. */ @@ -30,286 +30,436 @@ public class Opcodes { // CONTROL FLOW (0-4) // ================================================================= - /** No operation (padding/alignment) */ + /** + * No operation (padding/alignment) + */ public static final short NOP = 0; - /** Return from subroutine: return rd - * May return RuntimeControlFlowList for last/next/redo/goto */ + /** + * Return from subroutine: return rd + * May return RuntimeControlFlowList for last/next/redo/goto + */ public static final short RETURN = 1; - /** Unconditional jump: pc = offset (absolute bytecode offset) */ + /** + * Unconditional jump: pc = offset (absolute bytecode offset) + */ public static final short GOTO = 2; - /** Conditional jump: if (!rs) pc = offset */ + /** + * Conditional jump: if (!rs) pc = offset + */ public static final short GOTO_IF_FALSE = 3; - /** Conditional jump: if (rs) pc = offset */ + /** + * Conditional jump: if (rs) pc = offset + */ public static final short GOTO_IF_TRUE = 4; // ================================================================= // REGISTER OPERATIONS (5-9) // ================================================================= - /** Register alias: rd = rs (shares reference, does NOT copy value) */ + /** + * Register alias: rd = rs (shares reference, does NOT copy value) + */ public static final short ALIAS = 5; - /** Load from constant pool: rd = constants[index] */ + /** + * Load from constant pool: rd = constants[index] + */ public static final short LOAD_CONST = 6; - /** Load cached integer: rd = RuntimeScalarCache.getScalarInt(immediate32) */ + /** + * Load cached integer: rd = RuntimeScalarCache.getScalarInt(immediate32) + */ public static final short LOAD_INT = 7; - /** Load string: rd = new RuntimeScalar(stringPool[index]) */ + /** + * Load string: rd = new RuntimeScalar(stringPool[index]) + */ public static final short LOAD_STRING = 8; - /** Load undef: rd = new RuntimeScalar() */ + /** + * Load undef: rd = new RuntimeScalar() + */ public static final short LOAD_UNDEF = 9; // ================================================================= // VARIABLE ACCESS - GLOBAL (10-16) // ================================================================= - /** Load global scalar: rd = GlobalVariable.getGlobalScalar(stringPool[index]) */ + /** + * Load global scalar: rd = GlobalVariable.getGlobalScalar(stringPool[index]) + */ public static final short LOAD_GLOBAL_SCALAR = 10; - /** Store global scalar: GlobalVariable.getGlobalScalar(stringPool[index]).set(rs) */ + /** + * Store global scalar: GlobalVariable.getGlobalScalar(stringPool[index]).set(rs) + */ public static final short STORE_GLOBAL_SCALAR = 11; - /** Load global array: rd = GlobalVariable.getGlobalArray(stringPool[index]) */ + /** + * Load global array: rd = GlobalVariable.getGlobalArray(stringPool[index]) + */ public static final short LOAD_GLOBAL_ARRAY = 12; - /** Store global array: GlobalVariable.getGlobalArray(stringPool[index]).elements = rs */ + /** + * Store global array: GlobalVariable.getGlobalArray(stringPool[index]).elements = rs + */ public static final short STORE_GLOBAL_ARRAY = 13; - /** Load global hash: rd = GlobalVariable.getGlobalHash(stringPool[index]) */ + /** + * Load global hash: rd = GlobalVariable.getGlobalHash(stringPool[index]) + */ public static final short LOAD_GLOBAL_HASH = 14; - /** Store global hash: GlobalVariable.getGlobalHash(stringPool[index]).elements = rs */ + /** + * Store global hash: GlobalVariable.getGlobalHash(stringPool[index]).elements = rs + */ public static final short STORE_GLOBAL_HASH = 15; - /** Load global code: rd = GlobalVariable.getGlobalCodeRef(stringPool[index]) */ + /** + * Load global code: rd = GlobalVariable.getGlobalCodeRef(stringPool[index]) + */ public static final short LOAD_GLOBAL_CODE = 16; // ================================================================= // ARITHMETIC OPERATORS (17-26) - call org.perlonjava.runtime.operators.MathOperators // ================================================================= - /** Addition: rd = MathOperators.add(rs1, rs2) */ + /** + * Addition: rd = MathOperators.add(rs1, rs2) + */ public static final short ADD_SCALAR = 17; - /** Subtraction: rd = MathOperators.subtract(rs1, rs2) */ + /** + * Subtraction: rd = MathOperators.subtract(rs1, rs2) + */ public static final short SUB_SCALAR = 18; - /** Multiplication: rd = MathOperators.multiply(rs1, rs2) */ + /** + * Multiplication: rd = MathOperators.multiply(rs1, rs2) + */ public static final short MUL_SCALAR = 19; - /** Division: rd = MathOperators.divide(rs1, rs2) */ + /** + * Division: rd = MathOperators.divide(rs1, rs2) + */ public static final short DIV_SCALAR = 20; - /** Modulus: rd = MathOperators.modulus(rs1, rs2) */ + /** + * Modulus: rd = MathOperators.modulus(rs1, rs2) + */ public static final short MOD_SCALAR = 21; - /** Exponentiation: rd = MathOperators.power(rs1, rs2) */ + /** + * Exponentiation: rd = MathOperators.power(rs1, rs2) + */ public static final short POW_SCALAR = 22; - /** Negation: rd = MathOperators.negate(rs) */ + /** + * Negation: rd = MathOperators.negate(rs) + */ public static final short NEG_SCALAR = 23; // Specialized unboxed operations (optimized for pure int math) - /** Addition with immediate: rd = rs + immediate32 (unboxed int fast path) */ + /** + * Addition with immediate: rd = rs + immediate32 (unboxed int fast path) + */ public static final short ADD_SCALAR_INT = 24; - /** Subtraction with immediate: rd = rs - immediate32 (unboxed int fast path) */ + /** + * Subtraction with immediate: rd = rs - immediate32 (unboxed int fast path) + */ public static final short SUB_SCALAR_INT = 25; - /** Multiplication with immediate: rd = rs * immediate32 (unboxed int fast path) */ + /** + * Multiplication with immediate: rd = rs * immediate32 (unboxed int fast path) + */ public static final short MUL_SCALAR_INT = 26; // ================================================================= // STRING OPERATORS (27-30) - call org.perlonjava.runtime.operators.StringOperators // ================================================================= - /** String concatenation: rd = StringOperators.concat(rs1, rs2) */ + /** + * String concatenation: rd = StringOperators.concat(rs1, rs2) + */ public static final short CONCAT = 27; - /** String repetition: rd = StringOperators.repeat(rs1, rs2) */ + /** + * String repetition: rd = StringOperators.repeat(rs1, rs2) + */ public static final short REPEAT = 28; - /** Substring: rd = StringOperators.substr(str_reg, offset_reg, length_reg) */ + /** + * Substring: rd = StringOperators.substr(str_reg, offset_reg, length_reg) + */ public static final short SUBSTR = 29; - /** String length: rd = StringOperators.length(rs) */ + /** + * String length: rd = StringOperators.length(rs) + */ public static final short LENGTH = 30; // ================================================================= // COMPARISON OPERATORS (31-38) - call org.perlonjava.runtime.operators.CompareOperators // ================================================================= - /** Numeric comparison: rd = CompareOperators.compareNum(rs1, rs2) */ + /** + * Numeric comparison: rd = CompareOperators.compareNum(rs1, rs2) + */ public static final short COMPARE_NUM = 31; - /** String comparison: rd = CompareOperators.compareStr(rs1, rs2) */ + /** + * String comparison: rd = CompareOperators.compareStr(rs1, rs2) + */ public static final short COMPARE_STR = 32; - /** Numeric equality: rd = CompareOperators.numericEqual(rs1, rs2) */ + /** + * Numeric equality: rd = CompareOperators.numericEqual(rs1, rs2) + */ public static final short EQ_NUM = 33; - /** Numeric inequality: rd = CompareOperators.numericNotEqual(rs1, rs2) */ + /** + * Numeric inequality: rd = CompareOperators.numericNotEqual(rs1, rs2) + */ public static final short NE_NUM = 34; - /** Less than: rd = CompareOperators.numericLessThan(rs1, rs2) */ + /** + * Less than: rd = CompareOperators.numericLessThan(rs1, rs2) + */ public static final short LT_NUM = 35; - /** Greater than: rd = CompareOperators.numericGreaterThan(rs1, rs2) */ + /** + * Greater than: rd = CompareOperators.numericGreaterThan(rs1, rs2) + */ public static final short GT_NUM = 36; - /** String equality: rd = CompareOperators.stringEqual(rs1, rs2) */ + /** + * String equality: rd = CompareOperators.stringEqual(rs1, rs2) + */ public static final short EQ_STR = 37; - /** String inequality: rd = CompareOperators.stringNotEqual(rs1, rs2) */ + /** + * String inequality: rd = CompareOperators.stringNotEqual(rs1, rs2) + */ public static final short NE_STR = 38; // ================================================================= // LOGICAL OPERATORS (39-41) // ================================================================= - /** Logical NOT: rd = !rs */ + /** + * Logical NOT: rd = !rs + */ public static final short NOT = 39; - /** Logical AND: rd = rs1 && rs2 (short-circuit handled in bytecode compiler) */ + /** + * Logical AND: rd = rs1 && rs2 (short-circuit handled in bytecode compiler) + */ public static final short AND = 40; - /** Logical OR: rd = rs1 || rs2 (short-circuit handled in bytecode compiler) */ + /** + * Logical OR: rd = rs1 || rs2 (short-circuit handled in bytecode compiler) + */ public static final short OR = 41; // ================================================================= // ARRAY OPERATIONS (42-49) - use RuntimeArray API // ================================================================= - /** Array element access: rd = array_reg.get(index_reg) */ + /** + * Array element access: rd = array_reg.get(index_reg) + */ public static final short ARRAY_GET = 42; - /** Array element store: array_reg.set(index_reg, value_reg) */ + /** + * Array element store: array_reg.set(index_reg, value_reg) + */ public static final short ARRAY_SET = 43; - /** Array push: array_reg.push(value_reg) */ + /** + * Array push: array_reg.push(value_reg) + */ public static final short ARRAY_PUSH = 44; - /** Array pop: rd = array_reg.pop() */ + /** + * Array pop: rd = array_reg.pop() + */ public static final short ARRAY_POP = 45; - /** Array shift: rd = array_reg.shift() */ + /** + * Array shift: rd = array_reg.shift() + */ public static final short ARRAY_SHIFT = 46; - /** Array unshift: array_reg.unshift(value_reg) */ + /** + * Array unshift: array_reg.unshift(value_reg) + */ public static final short ARRAY_UNSHIFT = 47; - /** Array size: rd = new RuntimeScalar(array_reg.size()) */ + /** + * Array size: rd = new RuntimeScalar(array_reg.size()) + */ public static final short ARRAY_SIZE = 48; - /** Create array: rd = new RuntimeArray() */ + /** + * Create array: rd = new RuntimeArray() + */ public static final short CREATE_ARRAY = 49; // ================================================================= // HASH OPERATIONS (50-56) - use RuntimeHash API // ================================================================= - /** Hash element access: rd = hash_reg.get(key_reg) */ + /** + * Hash element access: rd = hash_reg.get(key_reg) + */ public static final short HASH_GET = 50; - /** Hash element store: hash_reg.put(key_reg, value_reg) */ + /** + * Hash element store: hash_reg.put(key_reg, value_reg) + */ public static final short HASH_SET = 51; - /** Hash exists: rd = hash_reg.exists(key_reg) */ + /** + * Hash exists: rd = hash_reg.exists(key_reg) + */ public static final short HASH_EXISTS = 52; - /** Hash delete: rd = hash_reg.delete(key_reg) */ + /** + * Hash delete: rd = hash_reg.delete(key_reg) + */ public static final short HASH_DELETE = 53; - /** Hash keys: rd = hash_reg.keys() */ + /** + * Hash keys: rd = hash_reg.keys() + */ public static final short HASH_KEYS = 54; - /** Hash values: rd = hash_reg.values() */ + /** + * Hash values: rd = hash_reg.values() + */ public static final short HASH_VALUES = 55; - /** Create hash reference from list: rd = RuntimeHash.createHash(rs_list).createReference() */ + /** + * Create hash reference from list: rd = RuntimeHash.createHash(rs_list).createReference() + */ public static final short CREATE_HASH = 56; // ================================================================= // SUBROUTINE CALLS (57-59) - RuntimeCode.apply // ================================================================= - /** Call subroutine: rd = RuntimeCode.apply(coderef_reg, args_reg, context) - * May return RuntimeControlFlowList for last/next/redo/goto */ + /** + * Call subroutine: rd = RuntimeCode.apply(coderef_reg, args_reg, context) + * May return RuntimeControlFlowList for last/next/redo/goto + */ public static final short CALL_SUB = 57; - /** Call method: rd = RuntimeCode.call(obj_reg, method_name, args_reg, context) */ + /** + * Call method: rd = RuntimeCode.call(obj_reg, method_name, args_reg, context) + */ public static final short CALL_METHOD = 58; - /** Call builtin: rd = BuiltinRegistry.call(builtin_id, args_reg, context) */ + /** + * Call builtin: rd = BuiltinRegistry.call(builtin_id, args_reg, context) + */ public static final short CALL_BUILTIN = 59; // ================================================================= // CONTEXT OPERATIONS (60-61) // ================================================================= - /** Convert list/array to count in scalar context: rd = list.size() - * Format: LIST_TO_COUNT rd rs */ + /** + * Convert list/array to count in scalar context: rd = list.size() + * Format: LIST_TO_COUNT rd rs + */ public static final short LIST_TO_COUNT = 60; - /** Scalar to list: rd = new RuntimeList(scalar_reg) */ + /** + * Scalar to list: rd = new RuntimeList(scalar_reg) + */ public static final short SCALAR_TO_LIST = 61; // ================================================================= // CONTROL FLOW - SPECIAL (62-67) - RuntimeControlFlowList // ================================================================= - /** Create LAST control flow: rd = new RuntimeControlFlowList(LAST, label_index) */ + /** + * Create LAST control flow: rd = new RuntimeControlFlowList(LAST, label_index) + */ public static final short CREATE_LAST = 62; - /** Create NEXT control flow: rd = new RuntimeControlFlowList(NEXT, label_index) */ + /** + * Create NEXT control flow: rd = new RuntimeControlFlowList(NEXT, label_index) + */ public static final short CREATE_NEXT = 63; - /** Create REDO control flow: rd = new RuntimeControlFlowList(REDO, label_index) */ + /** + * Create REDO control flow: rd = new RuntimeControlFlowList(REDO, label_index) + */ public static final short CREATE_REDO = 64; - /** Create GOTO control flow: rd = new RuntimeControlFlowList(GOTO, label_index) */ + /** + * Create GOTO control flow: rd = new RuntimeControlFlowList(GOTO, label_index) + */ public static final short CREATE_GOTO = 65; - /** Check if return value is control flow: rd = (rs instanceof RuntimeControlFlowList) */ + /** + * Check if return value is control flow: rd = (rs instanceof RuntimeControlFlowList) + */ public static final short IS_CONTROL_FLOW = 66; - /** Get control flow type: rd = ((RuntimeControlFlowList)rs).getControlFlowType().ordinal() */ + /** + * Get control flow type: rd = ((RuntimeControlFlowList)rs).getControlFlowType().ordinal() + */ public static final short GET_CONTROL_FLOW_TYPE = 67; // ================================================================= // REFERENCE OPERATIONS (68-70) // ================================================================= - /** Create scalar reference: rd = new RuntimeScalar(rs) */ + /** + * Create scalar reference: rd = new RuntimeScalar(rs) + */ public static final short CREATE_REF = 68; - /** Dereference: rd = rs.dereference() */ + /** + * Dereference: rd = rs.dereference() + */ public static final short DEREF = 69; - /** Type check: rd = new RuntimeScalar(rs.type.name()) */ + /** + * Type check: rd = new RuntimeScalar(rs.type.name()) + */ public static final short GET_TYPE = 70; // ================================================================= // MISCELLANEOUS (71-74) // ================================================================= - /** Print to filehandle: print(rs_content, rs_filehandle) */ + /** + * Print to filehandle: print(rs_content, rs_filehandle) + */ public static final short PRINT = 71; - /** Say to filehandle: say(rs_content, rs_filehandle) */ + /** + * Say to filehandle: say(rs_content, rs_filehandle) + */ public static final short SAY = 72; - /** Die with message: die(rs) */ + /** + * Die with message: die(rs) + */ public static final short DIE = 73; - /** Warn with message: warn(rs) */ + /** + * Warn with message: warn(rs) + */ public static final short WARN = 74; // ================================================================= @@ -317,28 +467,44 @@ public class Opcodes { // These eliminate ALIAS overhead by doing operation + store in one step // ================================================================= - /** Increment register in-place: rd = rd + 1 (combines ADD_SCALAR_INT + ALIAS) */ + /** + * Increment register in-place: rd = rd + 1 (combines ADD_SCALAR_INT + ALIAS) + */ public static final short INC_REG = 75; - /** Decrement register in-place: rd = rd - 1 (combines SUB_SCALAR_INT + ALIAS) */ + /** + * Decrement register in-place: rd = rd - 1 (combines SUB_SCALAR_INT + ALIAS) + */ public static final short DEC_REG = 76; - /** Add and assign: rd = rd + rs (combines ADD_SCALAR + ALIAS when dest == src1) */ + /** + * Add and assign: rd = rd + rs (combines ADD_SCALAR + ALIAS when dest == src1) + */ public static final short ADD_ASSIGN = 77; - /** Add immediate and assign: rd = rd + imm (combines ADD_SCALAR_INT + ALIAS when dest == src) */ + /** + * Add immediate and assign: rd = rd + imm (combines ADD_SCALAR_INT + ALIAS when dest == src) + */ public static final short ADD_ASSIGN_INT = 78; - /** Pre-increment: ++rd (calls RuntimeScalar.preAutoIncrement) */ + /** + * Pre-increment: ++rd (calls RuntimeScalar.preAutoIncrement) + */ public static final short PRE_AUTOINCREMENT = 79; - /** Post-increment: rd++ (calls RuntimeScalar.postAutoIncrement) */ + /** + * Post-increment: rd++ (calls RuntimeScalar.postAutoIncrement) + */ public static final short POST_AUTOINCREMENT = 80; - /** Pre-decrement: --rd (calls RuntimeScalar.preAutoDecrement) */ + /** + * Pre-decrement: --rd (calls RuntimeScalar.preAutoDecrement) + */ public static final short PRE_AUTODECREMENT = 81; - /** Post-decrement: rd-- (calls RuntimeScalar.postAutoDecrement) */ + /** + * Post-decrement: rd-- (calls RuntimeScalar.postAutoDecrement) + */ public static final short POST_AUTODECREMENT = 82; // ================================================================= @@ -349,7 +515,7 @@ public class Opcodes { * EVAL_TRY: Mark start of eval block with exception handling * Format: [EVAL_TRY] [catch_offset_high] [catch_offset_low] * Effect: Sets up exception handler. If exception occurs, jump to catch_offset. - * At start: Set $@ = "" + * At start: Set $@ = "" */ public static final short EVAL_TRY = 83; @@ -357,7 +523,7 @@ public class Opcodes { * EVAL_CATCH: Mark start of catch block * Format: [EVAL_CATCH] [rd] * Effect: Exception object is captured, WarnDie.catchEval() is called to set $@, - * and undef is stored in rd as the eval result. + * and undef is stored in rd as the eval result. */ public static final short EVAL_CATCH = 84; @@ -372,12 +538,12 @@ public class Opcodes { * CREATE_LIST: Create RuntimeList from registers * Format: [CREATE_LIST] [rd] [count] [rs1] [rs2] ... [rsN] * Effect: rd = new RuntimeList(registers[rs1], registers[rs2], ..., registers[rsN]) - * + *

* Highly optimized for common cases: * - count=0: Creates empty RuntimeList * - count=1: Creates RuntimeList with single element * - count>1: Creates RuntimeList and adds all elements - * + *

* This is the most performance-critical opcode for list operations. */ public static final short CREATE_LIST = 86; @@ -390,15 +556,15 @@ public class Opcodes { * SLOW_OP: Dispatch to rarely-used operation handler * Format: [SLOW_OP] [slow_op_id] [operands...] * Effect: Dispatches to SlowOpcodeHandler based on slow_op_id - * + *

* This uses only ONE opcode number but supports 256 slow operations * via the slow_op_id byte parameter. Keeps main switch compact for * CPU i-cache optimization while allowing unlimited rare operations. - * + *

* Philosophy: * - Fast operations (0-90): Direct opcodes in main switch * - Slow operations (via SLOW_OP): Delegated to SlowOpcodeHandler - * + *

* Performance: Adds ~5ns overhead but keeps main loop ~10-15% faster. */ public static final short SLOW_OP = 87; @@ -407,84 +573,128 @@ public class Opcodes { // STRING OPERATIONS (88) // ================================================================= - /** Join list elements with separator: rd = join(rs_separator, rs_list) */ + /** + * Join list elements with separator: rd = join(rs_separator, rs_list) + */ public static final short JOIN = 88; // ================================================================= // I/O OPERATIONS (89) // ================================================================= - /** Select default output filehandle: rd = IOOperator.select(rs_list, SCALAR) */ + /** + * Select default output filehandle: rd = IOOperator.select(rs_list, SCALAR) + */ public static final short SELECT = 89; - /** Create range: rd = PerlRange.createRange(rs_start, rs_end) */ + /** + * Create range: rd = PerlRange.createRange(rs_start, rs_end) + */ public static final short RANGE = 90; - /** Random number: rd = Random.rand(rs_max) */ + /** + * Random number: rd = Random.rand(rs_max) + */ public static final short RAND = 91; - /** Map operator: rd = ListOperators.map(list_reg, closure_reg, context) */ + /** + * Map operator: rd = ListOperators.map(list_reg, closure_reg, context) + */ public static final short MAP = 92; - /** Create empty array: rd = new RuntimeArray() */ + /** + * Create empty array: rd = new RuntimeArray() + */ public static final short NEW_ARRAY = 93; - /** Create empty hash: rd = new RuntimeHash() */ + /** + * Create empty hash: rd = new RuntimeHash() + */ public static final short NEW_HASH = 94; - /** Set array from list: array_reg.setFromList(list_reg) */ + /** + * Set array from list: array_reg.setFromList(list_reg) + */ public static final short ARRAY_SET_FROM_LIST = 95; - /** Set hash from list: hash_reg = RuntimeHash.createHash(list_reg) then copy elements */ + /** + * Set hash from list: hash_reg = RuntimeHash.createHash(list_reg) then copy elements + */ public static final short HASH_SET_FROM_LIST = 96; - /** Store global code: GlobalVariable.getGlobalCodeRef().put(stringPool[nameIdx], codeRef) */ + /** + * Store global code: GlobalVariable.getGlobalCodeRef().put(stringPool[nameIdx], codeRef) + */ public static final short STORE_GLOBAL_CODE = 97; - /** Create closure with captured variables: rd = createClosure(template, registers[rs1], registers[rs2], ...) - * Format: CREATE_CLOSURE rd template_const_idx num_captures reg1 reg2 ... */ + /** + * Create closure with captured variables: rd = createClosure(template, registers[rs1], registers[rs2], ...) + * Format: CREATE_CLOSURE rd template_const_idx num_captures reg1 reg2 ... + */ public static final short CREATE_CLOSURE = 98; - /** Set scalar value: ((RuntimeScalar)registers[rd]).set((RuntimeScalar)registers[rs]) + /** + * Set scalar value: ((RuntimeScalar)registers[rd]).set((RuntimeScalar)registers[rs]) * Format: SET_SCALAR rd rs - * Used to set the value in a persistent scalar without overwriting the reference */ + * Used to set the value in a persistent scalar without overwriting the reference + */ public static final short SET_SCALAR = 99; - /** Grep operator: rd = ListOperators.grep(list_reg, closure_reg, context) */ + /** + * Grep operator: rd = ListOperators.grep(list_reg, closure_reg, context) + */ public static final short GREP = 100; - /** Sort operator: rd = ListOperators.sort(list_reg, closure_reg, package_name) */ + /** + * Sort operator: rd = ListOperators.sort(list_reg, closure_reg, package_name) + */ public static final short SORT = 101; - /** Defined operator: rd = defined(rs) - check if value is defined */ + /** + * Defined operator: rd = defined(rs) - check if value is defined + */ public static final short DEFINED = 102; - /** Ref operator: rd = ref(rs) - get reference type as string */ + /** + * Ref operator: rd = ref(rs) - get reference type as string + */ public static final short REF = 103; - /** Bless operator: rd = bless(rs_ref, rs_package) - bless a reference into a package */ + /** + * Bless operator: rd = bless(rs_ref, rs_package) - bless a reference into a package + */ public static final short BLESS = 104; - /** ISA operator: rd = isa(rs_obj, rs_package) - check if object is instance of package */ + /** + * ISA operator: rd = isa(rs_obj, rs_package) - check if object is instance of package + */ public static final short ISA = 105; // ================================================================= // ITERATOR OPERATIONS - For efficient foreach loops // ================================================================= - /** Create iterator: rd = rs.iterator() - get Iterator from Iterable */ + /** + * Create iterator: rd = rs.iterator() - get Iterator from Iterable + */ public static final short ITERATOR_CREATE = 106; - /** Check iterator: rd = iterator.hasNext() - returns boolean as RuntimeScalar */ + /** + * Check iterator: rd = iterator.hasNext() - returns boolean as RuntimeScalar + */ public static final short ITERATOR_HAS_NEXT = 107; - /** Get next element: rd = iterator.next() - returns RuntimeScalar */ + /** + * Get next element: rd = iterator.next() - returns RuntimeScalar + */ public static final short ITERATOR_NEXT = 108; - /** Superinstruction for foreach loops: check hasNext, get next element, or jump to target if done + /** + * Superinstruction for foreach loops: check hasNext, get next element, or jump to target if done * Format: FOREACH_NEXT_OR_EXIT rd iter_reg exit_target(int) * If iterator.hasNext(): rd = iterator.next(), continue to next instruction - * Else: pc = exit_target (absolute address, like GOTO) */ + * Else: pc = exit_target (absolute address, like GOTO) + */ public static final short FOREACH_NEXT_OR_EXIT = 109; // Compound assignment operators with overload support @@ -501,109 +711,197 @@ public class Opcodes { // IMPORTANT: Keep ranges CONTIGUOUS for JVM tableswitch optimization! // Group 1: Dereferencing (114-115) - CONTIGUOUS - /** Dereference array for multidimensional access: rd = deref_array(rs) */ + /** + * Dereference array for multidimensional access: rd = deref_array(rs) + */ public static final short DEREF_ARRAY = 114; - /** Dereference hash for hashref access: rd = deref_hash(rs) */ + /** + * Dereference hash for hashref access: rd = deref_hash(rs) + */ public static final short DEREF_HASH = 115; // Group 2: Slice Operations (116-121) - CONTIGUOUS - /** Array slice: rd = array.getSlice(indices_list) */ + /** + * Array slice: rd = array.getSlice(indices_list) + */ public static final short ARRAY_SLICE = 116; - /** Array slice assignment: array.setSlice(indices, values) */ + /** + * Array slice assignment: array.setSlice(indices, values) + */ public static final short ARRAY_SLICE_SET = 117; - /** Hash slice: rd = hash.getSlice(keys_list) */ + /** + * Hash slice: rd = hash.getSlice(keys_list) + */ public static final short HASH_SLICE = 118; - /** Hash slice assignment: hash.setSlice(keys, values) */ + /** + * Hash slice assignment: hash.setSlice(keys, values) + */ public static final short HASH_SLICE_SET = 119; - /** Hash slice delete: rd = hash.deleteSlice(keys_list) */ + /** + * Hash slice delete: rd = hash.deleteSlice(keys_list) + */ public static final short HASH_SLICE_DELETE = 120; - /** List slice from index: rd = list[start..] */ + /** + * List slice from index: rd = list[start..] + */ public static final short LIST_SLICE_FROM = 121; // Group 3: Array/String Ops (122-125) - CONTIGUOUS - /** Splice array: rd = Operator.splice(array, args_list) */ + /** + * Splice array: rd = Operator.splice(array, args_list) + */ public static final short SPLICE = 122; - /** Reverse array or string: rd = Operator.reverse(ctx, args...) */ + /** + * Reverse array or string: rd = Operator.reverse(ctx, args...) + */ public static final short REVERSE = 123; - /** Split string into array: rd = Operator.split(pattern, args, ctx) */ + /** + * Split string into array: rd = Operator.split(pattern, args, ctx) + */ public static final short SPLIT = 124; - /** String length: rd = length(string) */ + /** + * String length: rd = length(string) + */ public static final short LENGTH_OP = 125; // Group 4: Exists/Delete (126-127) - CONTIGUOUS - /** Exists operator: rd = exists(key) */ + /** + * Exists operator: rd = exists(key) + */ public static final short EXISTS = 126; - /** Delete operator: rd = delete(key) */ + /** + * Delete operator: rd = delete(key) + */ public static final short DELETE = 127; // Group 5: Closure/Scope (128-131) - CONTIGUOUS - /** Retrieve BEGIN scalar: rd = PersistentVariable.retrieveBeginScalar(var_name, begin_id) */ + /** + * Retrieve BEGIN scalar: rd = PersistentVariable.retrieveBeginScalar(var_name, begin_id) + */ public static final short RETRIEVE_BEGIN_SCALAR = 128; - /** Retrieve BEGIN array: rd = PersistentVariable.retrieveBeginArray(var_name, begin_id) */ + /** + * Retrieve BEGIN array: rd = PersistentVariable.retrieveBeginArray(var_name, begin_id) + */ public static final short RETRIEVE_BEGIN_ARRAY = 129; - /** Retrieve BEGIN hash: rd = PersistentVariable.retrieveBeginHash(var_name, begin_id) */ + /** + * Retrieve BEGIN hash: rd = PersistentVariable.retrieveBeginHash(var_name, begin_id) + */ public static final short RETRIEVE_BEGIN_HASH = 130; - /** Localize global variable: rd = GlobalRuntimeScalar.makeLocal(var_name) */ + /** + * Localize global variable: rd = GlobalRuntimeScalar.makeLocal(var_name) + */ public static final short LOCAL_SCALAR = 131; - /** Localize global array: rd = GlobalVariable.getGlobalArray(var_name) (dynamicSaveState via DynamicVariableManager) */ + /** + * Localize global array: rd = GlobalVariable.getGlobalArray(var_name) (dynamicSaveState via DynamicVariableManager) + */ public static final short LOCAL_ARRAY = 343; - /** Localize global hash: rd = GlobalVariable.getGlobalHash(var_name) (dynamicSaveState via DynamicVariableManager) */ + /** + * Localize global hash: rd = GlobalVariable.getGlobalHash(var_name) (dynamicSaveState via DynamicVariableManager) + */ public static final short LOCAL_HASH = 344; // Group 6: System Calls (132-141) - CONTIGUOUS - /** chown(list, uid, gid) */ - public static final short CHOWN = 132; - /** rd = waitpid(pid, flags) */ + /** + * chown(list, uid, gid) + */ + public static final short CHOWN = 132; + /** + * rd = waitpid(pid, flags) + */ public static final short WAITPID = 133; - /** rd = fork() */ + /** + * rd = fork() + */ public static final short FORK = 134; - /** rd = getppid() */ + /** + * rd = getppid() + */ public static final short GETPPID = 135; - /** rd = getpgrp(pid) */ + /** + * rd = getpgrp(pid) + */ public static final short GETPGRP = 136; - /** setpgrp(pid, pgrp) */ + /** + * setpgrp(pid, pgrp) + */ public static final short SETPGRP = 137; - /** rd = getpriority(which, who) */ + /** + * rd = getpriority(which, who) + */ public static final short GETPRIORITY = 138; - /** setpriority(which, who, priority) */ + /** + * setpriority(which, who, priority) + */ public static final short SETPRIORITY = 139; - /** rd = getsockopt(socket, level, optname) */ + /** + * rd = getsockopt(socket, level, optname) + */ public static final short GETSOCKOPT = 140; - /** setsockopt(socket, level, optname, optval) */ + /** + * setsockopt(socket, level, optname, optval) + */ public static final short SETSOCKOPT = 141; // Group 7: IPC Operations (142-148) - CONTIGUOUS - /** rd = syscall(number, args...) */ + /** + * rd = syscall(number, args...) + */ public static final short SYSCALL = 142; - /** rd = semget(key, nsems, flags) */ + /** + * rd = semget(key, nsems, flags) + */ public static final short SEMGET = 143; - /** rd = semop(semid, opstring) */ + /** + * rd = semop(semid, opstring) + */ public static final short SEMOP = 144; - /** rd = msgget(key, flags) */ + /** + * rd = msgget(key, flags) + */ public static final short MSGGET = 145; - /** rd = msgsnd(id, msg, flags) */ + /** + * rd = msgsnd(id, msg, flags) + */ public static final short MSGSND = 146; - /** rd = msgrcv(id, size, type, flags) */ + /** + * rd = msgrcv(id, size, type, flags) + */ public static final short MSGRCV = 147; - /** rd = shmget(key, size, flags) */ + /** + * rd = shmget(key, size, flags) + */ public static final short SHMGET = 148; // Group 8: Shared Memory (149-150) - CONTIGUOUS - /** rd = shmread(id, pos, size) */ + /** + * rd = shmread(id, pos, size) + */ public static final short SHMREAD = 149; - /** shmwrite(id, pos, string) */ + /** + * shmwrite(id, pos, string) + */ public static final short SHMWRITE = 150; // Group 9: Special I/O (151-154) - CONTIGUOUS - /** rd = eval(string) - dynamic code evaluation */ + /** + * rd = eval(string) - dynamic code evaluation + */ public static final short EVAL_STRING = 151; - /** rd = select(list) - set/get default output filehandle */ + /** + * rd = select(list) - set/get default output filehandle + */ public static final short SELECT_OP = 152; - /** rd = getGlobalIO(name) - load glob/filehandle from global variables */ + /** + * rd = getGlobalIO(name) - load glob/filehandle from global variables + */ public static final short LOAD_GLOB = 153; - /** rd = Time.sleep(seconds) - sleep for specified seconds */ + /** + * rd = Time.sleep(seconds) - sleep for specified seconds + */ public static final short SLEEP_OP = 154; - /** rd = Time.alarm(seconds) - set alarm timer */ + /** + * rd = Time.alarm(seconds) - set alarm timer + */ public static final short ALARM_OP = 155; // ================================================================= @@ -612,268 +910,422 @@ public class Opcodes { // IMPORTANT: Keep CONTIGUOUS for JVM tableswitch optimization! // Math Operators (400-409) - CONTIGUOUS - /** Power operator: rd = MathOperators.pow(rs1, rs2) - equivalent to rs1 ** rs2 */ + /** + * Power operator: rd = MathOperators.pow(rs1, rs2) - equivalent to rs1 ** rs2 + */ public static final short OP_POW = 310; - /** Absolute value: rd = MathOperators.abs(rs) - equivalent to abs(rs) */ + /** + * Absolute value: rd = MathOperators.abs(rs) - equivalent to abs(rs) + */ public static final short OP_ABS = 156; - /** Integer conversion: rd = MathOperators.integer(rs) - equivalent to int(rs) */ + /** + * Integer conversion: rd = MathOperators.integer(rs) - equivalent to int(rs) + */ public static final short OP_INT = 157; - /** Prototype operator: rd = RuntimeCode.prototype(rs_coderef, package_name) - * Format: PROTOTYPE rd rs package_name_idx(int) */ + /** + * Prototype operator: rd = RuntimeCode.prototype(rs_coderef, package_name) + * Format: PROTOTYPE rd rs package_name_idx(int) + */ public static final short PROTOTYPE = 158; - /** Quote regex operator: rd = RuntimeRegex.getQuotedRegex(pattern_reg, flags_reg) - * Format: QUOTE_REGEX rd pattern_reg flags_reg */ + /** + * Quote regex operator: rd = RuntimeRegex.getQuotedRegex(pattern_reg, flags_reg) + * Format: QUOTE_REGEX rd pattern_reg flags_reg + */ public static final short QUOTE_REGEX = 159; - /** Less than or equal: rd = CompareOperators.numericLessThanOrEqual(rs1, rs2) */ + /** + * Less than or equal: rd = CompareOperators.numericLessThanOrEqual(rs1, rs2) + */ public static final short LE_NUM = 160; - /** Greater than or equal: rd = CompareOperators.numericGreaterThanOrEqual(rs1, rs2) */ + /** + * Greater than or equal: rd = CompareOperators.numericGreaterThanOrEqual(rs1, rs2) + */ public static final short GE_NUM = 161; - /** String concatenation assignment: rd .= rs (appends rs to rd) - * Format: STRING_CONCAT_ASSIGN rd rs */ + /** + * String concatenation assignment: rd .= rs (appends rs to rd) + * Format: STRING_CONCAT_ASSIGN rd rs + */ public static final short STRING_CONCAT_ASSIGN = 162; - /** Push variable to local stack: DynamicVariableManager.pushLocalVariable(rs) - * Format: PUSH_LOCAL_VARIABLE rs */ + /** + * Push variable to local stack: DynamicVariableManager.pushLocalVariable(rs) + * Format: PUSH_LOCAL_VARIABLE rs + */ public static final short PUSH_LOCAL_VARIABLE = 163; - /** Store to glob: glob.set(rs) - * Format: STORE_GLOB globReg valueReg */ + /** + * Store to glob: glob.set(rs) + * Format: STORE_GLOB globReg valueReg + */ public static final short STORE_GLOB = 164; - /** Localize a typeglob: rd = DynamicVariableManager.pushLocalVariable(LOAD_GLOB(nameIdx)) + /** + * Localize a typeglob: rd = DynamicVariableManager.pushLocalVariable(LOAD_GLOB(nameIdx)) * Saves current glob state and returns the glob for potential assignment. - * Format: LOCAL_GLOB rd nameIdx */ + * Format: LOCAL_GLOB rd nameIdx + */ public static final short LOCAL_GLOB = 340; - /** Flip-flop operator: rd = ScalarFlipFlopOperator.evaluate(flipFlopId, rs1, rs2) + /** + * Flip-flop operator: rd = ScalarFlipFlopOperator.evaluate(flipFlopId, rs1, rs2) * flipFlopId is a unique per-call-site int constant. - * Format: FLIP_FLOP rd flipFlopId rs1 rs2 isExclusive */ + * Format: FLIP_FLOP rd flipFlopId rs1 rs2 isExclusive + */ public static final short FLIP_FLOP = 341; - /** Open file: rd = IOOperator.open(ctx, args...) - * Format: OPEN rd ctx argsReg */ + /** + * Open file: rd = IOOperator.open(ctx, args...) + * Format: OPEN rd ctx argsReg + */ public static final short OPEN = 165; - /** Read line from filehandle: rd = Readline.readline(fh_ref, ctx) - * Format: READLINE rd fhReg ctx */ + /** + * Read line from filehandle: rd = Readline.readline(fh_ref, ctx) + * Format: READLINE rd fhReg ctx + */ public static final short READLINE = 166; - /** Match regex: rd = RuntimeRegex.matchRegex(string, regex, ctx) - * Format: MATCH_REGEX rd stringReg regexReg ctx */ + /** + * Match regex: rd = RuntimeRegex.matchRegex(string, regex, ctx) + * Format: MATCH_REGEX rd stringReg regexReg ctx + */ public static final short MATCH_REGEX = 167; - /** Chomp: rd = rs.chomp() - * Format: CHOMP rd rs */ + /** + * Chomp: rd = rs.chomp() + * Format: CHOMP rd rs + */ public static final short CHOMP = 168; - /** Get wantarray context: rd = Operator.wantarray(wantarrayReg) - * Format: WANTARRAY rd wantarrayReg */ + /** + * Get wantarray context: rd = Operator.wantarray(wantarrayReg) + * Format: WANTARRAY rd wantarrayReg + */ public static final short WANTARRAY = 169; - /** Require module or version: rd = ModuleOperators.require(rs) - * Format: REQUIRE rd rs */ + /** + * Require module or version: rd = ModuleOperators.require(rs) + * Format: REQUIRE rd rs + */ public static final short REQUIRE = 170; - /** Get regex position: rd = rs.pos() (returns lvalue for assignment) - * Format: POS rd rs */ + /** + * Get regex position: rd = rs.pos() (returns lvalue for assignment) + * Format: POS rd rs + */ public static final short POS = 171; - /** Find substring position: rd = StringOperators.index(str, substr, pos) - * Format: INDEX rd str substr pos */ + /** + * Find substring position: rd = StringOperators.index(str, substr, pos) + * Format: INDEX rd str substr pos + */ public static final short INDEX = 172; - /** Find substring position from end: rd = StringOperators.rindex(str, substr, pos) - * Format: RINDEX rd str substr pos */ + /** + * Find substring position from end: rd = StringOperators.rindex(str, substr, pos) + * Format: RINDEX rd str substr pos + */ public static final short RINDEX = 173; - /** Bitwise AND assignment: target &= value - * Format: BITWISE_AND_ASSIGN target value */ + /** + * Bitwise AND assignment: target &= value + * Format: BITWISE_AND_ASSIGN target value + */ public static final short BITWISE_AND_ASSIGN = 174; - /** Bitwise OR assignment: target |= value - * Format: BITWISE_OR_ASSIGN target value */ + /** + * Bitwise OR assignment: target |= value + * Format: BITWISE_OR_ASSIGN target value + */ public static final short BITWISE_OR_ASSIGN = 175; - /** Bitwise XOR assignment: target ^= value - * Format: BITWISE_XOR_ASSIGN target value */ + /** + * Bitwise XOR assignment: target ^= value + * Format: BITWISE_XOR_ASSIGN target value + */ public static final short BITWISE_XOR_ASSIGN = 176; - /** String bitwise AND assignment: target &.= value - * Format: STRING_BITWISE_AND_ASSIGN target value */ + /** + * String bitwise AND assignment: target &.= value + * Format: STRING_BITWISE_AND_ASSIGN target value + */ public static final short STRING_BITWISE_AND_ASSIGN = 177; - /** String bitwise OR assignment: target |.= value - * Format: STRING_BITWISE_OR_ASSIGN target value */ + /** + * String bitwise OR assignment: target |.= value + * Format: STRING_BITWISE_OR_ASSIGN target value + */ public static final short STRING_BITWISE_OR_ASSIGN = 178; - /** String bitwise XOR assignment: target ^.= value - * Format: STRING_BITWISE_XOR_ASSIGN target value */ + /** + * String bitwise XOR assignment: target ^.= value + * Format: STRING_BITWISE_XOR_ASSIGN target value + */ public static final short STRING_BITWISE_XOR_ASSIGN = 179; - /** Numeric bitwise AND: rd = rs1 binary& rs2 - * Format: BITWISE_AND_BINARY rd rs1 rs2 */ + /** + * Numeric bitwise AND: rd = rs1 binary& rs2 + * Format: BITWISE_AND_BINARY rd rs1 rs2 + */ public static final short BITWISE_AND_BINARY = 180; - /** Numeric bitwise OR: rd = rs1 binary| rs2 - * Format: BITWISE_OR_BINARY rd rs1 rs2 */ + /** + * Numeric bitwise OR: rd = rs1 binary| rs2 + * Format: BITWISE_OR_BINARY rd rs1 rs2 + */ public static final short BITWISE_OR_BINARY = 181; - /** Numeric bitwise XOR: rd = rs1 binary^ rs2 - * Format: BITWISE_XOR_BINARY rd rs1 rs2 */ + /** + * Numeric bitwise XOR: rd = rs1 binary^ rs2 + * Format: BITWISE_XOR_BINARY rd rs1 rs2 + */ public static final short BITWISE_XOR_BINARY = 182; - /** String bitwise AND: rd = rs1 &. rs2 - * Format: STRING_BITWISE_AND rd rs1 rs2 */ + /** + * String bitwise AND: rd = rs1 &. rs2 + * Format: STRING_BITWISE_AND rd rs1 rs2 + */ public static final short STRING_BITWISE_AND = 183; - /** String bitwise OR: rd = rs1 |. rs2 - * Format: STRING_BITWISE_OR rd rs1 rs2 */ + /** + * String bitwise OR: rd = rs1 |. rs2 + * Format: STRING_BITWISE_OR rd rs1 rs2 + */ public static final short STRING_BITWISE_OR = 184; - /** String bitwise XOR: rd = rs1 ^. rs2 - * Format: STRING_BITWISE_XOR rd rs1 rs2 */ + /** + * String bitwise XOR: rd = rs1 ^. rs2 + * Format: STRING_BITWISE_XOR rd rs1 rs2 + */ public static final short STRING_BITWISE_XOR = 185; - /** Numeric bitwise NOT: rd = binary~ rs - * Format: BITWISE_NOT_BINARY rd rs */ + /** + * Numeric bitwise NOT: rd = binary~ rs + * Format: BITWISE_NOT_BINARY rd rs + */ public static final short BITWISE_NOT_BINARY = 186; - /** String bitwise NOT: rd = ~. rs - * Format: BITWISE_NOT_STRING rd rs */ + /** + * String bitwise NOT: rd = ~. rs + * Format: BITWISE_NOT_STRING rd rs + */ public static final short BITWISE_NOT_STRING = 187; // ================================================================= // FILE TEST AND STAT OPERATIONS // ================================================================= - /** stat operator: rd = stat(rs) [context] - * Format: STAT rd rs ctx */ + /** + * stat operator: rd = stat(rs) [context] + * Format: STAT rd rs ctx + */ public static final short STAT = 188; - /** lstat operator: rd = lstat(rs) [context] - * Format: LSTAT rd rs ctx */ + /** + * lstat operator: rd = lstat(rs) [context] + * Format: LSTAT rd rs ctx + */ public static final short LSTAT = 189; // File test operators (unary operators returning boolean or value) - /** -r FILE: readable */ + /** + * -r FILE: readable + */ public static final short FILETEST_R = 190; - /** -w FILE: writable */ + /** + * -w FILE: writable + */ public static final short FILETEST_W = 191; - /** -x FILE: executable */ + /** + * -x FILE: executable + */ public static final short FILETEST_X = 192; - /** -o FILE: owned by effective uid */ + /** + * -o FILE: owned by effective uid + */ public static final short FILETEST_O = 193; - /** -R FILE: readable by real uid */ + /** + * -R FILE: readable by real uid + */ public static final short FILETEST_R_REAL = 194; - /** -W FILE: writable by real uid */ + /** + * -W FILE: writable by real uid + */ public static final short FILETEST_W_REAL = 195; - /** -X FILE: executable by real uid */ + /** + * -X FILE: executable by real uid + */ public static final short FILETEST_X_REAL = 196; - /** -O FILE: owned by real uid */ + /** + * -O FILE: owned by real uid + */ public static final short FILETEST_O_REAL = 197; - /** -e FILE: exists */ + /** + * -e FILE: exists + */ public static final short FILETEST_E = 198; - /** -z FILE: zero size */ + /** + * -z FILE: zero size + */ public static final short FILETEST_Z = 199; - /** -s FILE: size in bytes */ + /** + * -s FILE: size in bytes + */ public static final short FILETEST_S = 200; - /** -f FILE: plain file */ + /** + * -f FILE: plain file + */ public static final short FILETEST_F = 201; - /** -d FILE: directory */ + /** + * -d FILE: directory + */ public static final short FILETEST_D = 202; - /** -l FILE: symbolic link */ + /** + * -l FILE: symbolic link + */ public static final short FILETEST_L = 203; - /** -p FILE: named pipe */ + /** + * -p FILE: named pipe + */ public static final short FILETEST_P = 204; - /** -S FILE: socket */ + /** + * -S FILE: socket + */ public static final short FILETEST_S_UPPER = 205; - /** -b FILE: block special */ + /** + * -b FILE: block special + */ public static final short FILETEST_B = 206; - /** -c FILE: character special */ + /** + * -c FILE: character special + */ public static final short FILETEST_C = 207; - /** -t FILE: tty */ + /** + * -t FILE: tty + */ public static final short FILETEST_T = 208; - /** -u FILE: setuid */ + /** + * -u FILE: setuid + */ public static final short FILETEST_U = 209; - /** -g FILE: setgid */ + /** + * -g FILE: setgid + */ public static final short FILETEST_G = 210; - /** -k FILE: sticky bit */ + /** + * -k FILE: sticky bit + */ public static final short FILETEST_K = 211; - /** -T FILE: text file */ + /** + * -T FILE: text file + */ public static final short FILETEST_T_UPPER = 212; - /** -B FILE: binary file */ + /** + * -B FILE: binary file + */ public static final short FILETEST_B_UPPER = 213; - /** -M FILE: modification age (days) */ + /** + * -M FILE: modification age (days) + */ public static final short FILETEST_M = 214; - /** -A FILE: access age (days) */ + /** + * -A FILE: access age (days) + */ public static final short FILETEST_A = 215; - /** -C FILE: inode change age (days) */ + /** + * -C FILE: inode change age (days) + */ public static final short FILETEST_C_UPPER = 216; - /** Match regex (negated): rd = !RuntimeRegex.matchRegex(string, regex, ctx) - * Format: MATCH_REGEX_NOT rd stringReg regexReg ctx */ + /** + * Match regex (negated): rd = !RuntimeRegex.matchRegex(string, regex, ctx) + * Format: MATCH_REGEX_NOT rd stringReg regexReg ctx + */ public static final short MATCH_REGEX_NOT = 217; // ================================================================= // LOOP CONTROL OPERATIONS - last/next/redo // ================================================================= - /** Loop last: Jump to end of loop or return RuntimeControlFlowList for non-local + /** + * Loop last: Jump to end of loop or return RuntimeControlFlowList for non-local * Format: LAST labelIndex - * labelIndex: index into stringPool for label name (or -1 for unlabeled) */ + * labelIndex: index into stringPool for label name (or -1 for unlabeled) + */ public static final short LAST = 218; - /** Loop next: Jump to continue/next label or return RuntimeControlFlowList for non-local + /** + * Loop next: Jump to continue/next label or return RuntimeControlFlowList for non-local * Format: NEXT labelIndex - * labelIndex: index into stringPool for label name (or -1 for unlabeled) */ + * labelIndex: index into stringPool for label name (or -1 for unlabeled) + */ public static final short NEXT = 219; - /** Loop redo: Jump to start of loop or return RuntimeControlFlowList for non-local + /** + * Loop redo: Jump to start of loop or return RuntimeControlFlowList for non-local * Format: REDO labelIndex - * labelIndex: index into stringPool for label name (or -1 for unlabeled) */ + * labelIndex: index into stringPool for label name (or -1 for unlabeled) + */ public static final short REDO = 220; - /** Transliterate operator: Apply tr/// or y/// pattern to string + /** + * Transliterate operator: Apply tr/// or y/// pattern to string * Format: TR_TRANSLITERATE rd searchReg replaceReg modifiersReg targetReg context * rd: destination register for result (count of transliterated characters) * searchReg: register containing search pattern (RuntimeScalar) * replaceReg: register containing replacement pattern (RuntimeScalar) * modifiersReg: register containing modifiers string (RuntimeScalar) * targetReg: register containing target variable to modify (RuntimeScalar) - * context: call context (SCALAR/LIST/VOID) */ + * context: call context (SCALAR/LIST/VOID) + */ public static final short TR_TRANSLITERATE = 221; // ================================================================= // SHIFT AND COMPOUND ASSIGNMENT OPERATORS (222-229) - CONTIGUOUS // ================================================================= - /** Left shift: rd = rs1 << rs2 */ + /** + * Left shift: rd = rs1 << rs2 + */ public static final short LEFT_SHIFT = 222; - /** Right shift: rd = rs1 >> rs2 */ + /** + * Right shift: rd = rs1 >> rs2 + */ public static final short RIGHT_SHIFT = 223; - /** String repetition assignment: target x= value */ + /** + * String repetition assignment: target x= value + */ public static final short REPEAT_ASSIGN = 224; - /** Exponentiation assignment: target **= value */ + /** + * Exponentiation assignment: target **= value + */ public static final short POW_ASSIGN = 225; - /** Left shift assignment: target <<= value */ + /** + * Left shift assignment: target <<= value + */ public static final short LEFT_SHIFT_ASSIGN = 226; - /** Right shift assignment: target >>= value */ + /** + * Right shift assignment: target >>= value + */ public static final short RIGHT_SHIFT_ASSIGN = 227; - /** Logical AND assignment: target &&= value */ + /** + * Logical AND assignment: target &&= value + */ public static final short LOGICAL_AND_ASSIGN = 228; - /** Logical OR assignment: target ||= value */ + /** + * Logical OR assignment: target ||= value + */ public static final short LOGICAL_OR_ASSIGN = 229; // ================================================================= @@ -883,48 +1335,70 @@ public class Opcodes { // After adding an opcode here, increment LASTOP to match the highest opcode number. // ================================================================= - /** Glob slot access: rd = glob.hashDerefGetNonStrict(key, "main") - * Used for *X{HASH} style access to glob slots */ + /** + * Glob slot access: rd = glob.hashDerefGetNonStrict(key, "main") + * 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 */ + /** + * 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 */ + /** + * 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 */ + /** + * 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 */ + /** + * 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 */ + /** + * 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 */ + /** + * 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 */ + /** + * substr with variable args: rd = Operator.substr(ctx, args...) + * Format: SUBSTR_VAR rd argsListReg ctx + */ public static final short SUBSTR_VAR = 237; - /** tie($var, $classname, @args): rd = TieOperators.tie(ctx, argsListReg) - * Format: TIE rd argsListReg context */ + /** + * tie($var, $classname, @args): rd = TieOperators.tie(ctx, argsListReg) + * Format: TIE rd argsListReg context + */ public static final short TIE = 238; - /** untie($var): rd = TieOperators.untie(ctx, argsListReg) - * Format: UNTIE rd argsListReg context */ + /** + * untie($var): rd = TieOperators.untie(ctx, argsListReg) + * Format: UNTIE rd argsListReg context + */ public static final short UNTIE = 239; - /** tied($var): rd = TieOperators.tied(ctx, argsListReg) - * Format: TIED rd argsListReg context */ + /** + * tied($var): rd = TieOperators.tied(ctx, argsListReg) + * Format: TIED rd argsListReg context + */ public static final short TIED = 240; // ================================================================= @@ -1003,197 +1477,298 @@ public class Opcodes { public static final short GMTIME = 300; public static final short CRYPT = 301; - /** Superinstruction: save dynamic level BEFORE makeLocal, then localize global scalar. + /** + * Superinstruction: save dynamic level BEFORE makeLocal, then localize global scalar. * Atomically: levelReg = getLocalLevel(), rd = makeLocal(stringPool[nameIdx]). * The saved pre-push level is used by POP_LOCAL_LEVEL after the loop to fully restore $_. - * Format: LOCAL_SCALAR_SAVE_LEVEL rd levelReg nameIdx */ + * Format: LOCAL_SCALAR_SAVE_LEVEL rd levelReg nameIdx + */ public static final short LOCAL_SCALAR_SAVE_LEVEL = 302; - /** Restore DynamicVariableManager to a previously saved local level. + /** + * Restore DynamicVariableManager to a previously saved local level. * Matches JVM compiler's DynamicVariableManager.popToLocalLevel(savedLevel) call. - * Format: POP_LOCAL_LEVEL rs */ + * Format: POP_LOCAL_LEVEL rs + */ public static final short POP_LOCAL_LEVEL = 303; - /** Save current DynamicVariableManager local level into register rd. + /** + * Save current DynamicVariableManager local level into register rd. * Used to bracket scoped package blocks so local pushes (PUSH_PACKAGE etc) are restored. - * Format: GET_LOCAL_LEVEL rd */ + * Format: GET_LOCAL_LEVEL rd + */ public static final short GET_LOCAL_LEVEL = 339; - /** Superinstruction: foreach loop step for a global loop variable (e.g. $_). + /** + * Superinstruction: foreach loop step for a global loop variable (e.g. $_). * Combines: hasNext check, next() into varReg, aliasGlobalVariable(name, varReg), conditional exit. * If iterator has next: varReg = next(), aliasGlobalVariable(name, varReg), fall through. * If iterator exhausted: jump to exitTarget (absolute address). - * Format: FOREACH_GLOBAL_NEXT_OR_EXIT varReg iterReg nameIdx exitTarget */ + * Format: FOREACH_GLOBAL_NEXT_OR_EXIT varReg iterReg nameIdx exitTarget + */ public static final short FOREACH_GLOBAL_NEXT_OR_EXIT = 304; - /** Unpack binary data into a list of scalars. - * Format: UNPACK rd argsReg ctx */ + /** + * Unpack binary data into a list of scalars. + * Format: UNPACK rd argsReg ctx + */ public static final short UNPACK = 305; - /** Set current package at runtime (non-scoped: package Foo;). + /** + * Set current package at runtime (non-scoped: package Foo;). * Format: SET_PACKAGE nameIdx - * Effect: Updates InterpreterState current frame's packageName to stringPool[nameIdx] */ + * Effect: Updates InterpreterState current frame's packageName to stringPool[nameIdx] + */ public static final short SET_PACKAGE = 306; // ================================================================= // I/O OPERATORS (309-329) - truly new ones not already defined above // Note: OPEN=165, READLINE=166, TELL=LASTOP+37 already exist // ================================================================= - /** close FILEHANDLE: Format: CLOSE rd argsReg ctx */ + /** + * close FILEHANDLE: Format: CLOSE rd argsReg ctx + */ public static final short CLOSE = 309; - /** binmode FILEHANDLE,LAYER: Format: BINMODE rd argsReg ctx */ + /** + * binmode FILEHANDLE,LAYER: Format: BINMODE rd argsReg ctx + */ public static final short BINMODE = 311; - /** seek FILEHANDLE,POS,WHENCE: Format: SEEK rd argsReg ctx */ + /** + * seek FILEHANDLE,POS,WHENCE: Format: SEEK rd argsReg ctx + */ public static final short SEEK = 312; - /** eof FILEHANDLE: Format: EOF_OP rd argsReg ctx */ + /** + * eof FILEHANDLE: Format: EOF_OP rd argsReg ctx + */ public static final short EOF_OP = 313; - /** sysread FILEHANDLE,SCALAR,LENGTH: Format: SYSREAD rd argsReg ctx */ + /** + * sysread FILEHANDLE,SCALAR,LENGTH: Format: SYSREAD rd argsReg ctx + */ public static final short SYSREAD = 314; - /** syswrite FILEHANDLE,SCALAR: Format: SYSWRITE rd argsReg ctx */ + /** + * syswrite FILEHANDLE,SCALAR: Format: SYSWRITE rd argsReg ctx + */ public static final short SYSWRITE = 315; - /** sysopen FILEHANDLE,FILENAME,MODE: Format: SYSOPEN rd argsReg ctx */ + /** + * sysopen FILEHANDLE,FILENAME,MODE: Format: SYSOPEN rd argsReg ctx + */ public static final short SYSOPEN = 316; - /** socket SOCKET,DOMAIN,TYPE,PROTOCOL: Format: SOCKET rd argsReg ctx */ + /** + * socket SOCKET,DOMAIN,TYPE,PROTOCOL: Format: SOCKET rd argsReg ctx + */ public static final short SOCKET = 317; - /** bind SOCKET,NAME: Format: BIND rd argsReg ctx */ + /** + * bind SOCKET,NAME: Format: BIND rd argsReg ctx + */ public static final short BIND = 318; - /** connect SOCKET,NAME: Format: CONNECT rd argsReg ctx */ + /** + * connect SOCKET,NAME: Format: CONNECT rd argsReg ctx + */ public static final short CONNECT = 319; - /** listen SOCKET,QUEUESIZE: Format: LISTEN rd argsReg ctx */ + /** + * listen SOCKET,QUEUESIZE: Format: LISTEN rd argsReg ctx + */ public static final short LISTEN = 320; - /** write FILEHANDLE: Format: WRITE rd argsReg ctx */ + /** + * write FILEHANDLE: Format: WRITE rd argsReg ctx + */ public static final short WRITE = 321; - /** formline PICTURE,LIST: Format: FORMLINE rd argsReg ctx */ + /** + * formline PICTURE,LIST: Format: FORMLINE rd argsReg ctx + */ public static final short FORMLINE = 322; - /** printf FILEHANDLE,FORMAT,LIST: Format: PRINTF rd argsReg ctx */ + /** + * printf FILEHANDLE,FORMAT,LIST: Format: PRINTF rd argsReg ctx + */ public static final short PRINTF = 323; - /** accept NEWSOCKET,GENERICSOCKET: Format: ACCEPT rd argsReg ctx */ + /** + * accept NEWSOCKET,GENERICSOCKET: Format: ACCEPT rd argsReg ctx + */ public static final short ACCEPT = 324; - /** sysseek FILEHANDLE,POS,WHENCE: Format: SYSSEEK rd argsReg ctx */ + /** + * sysseek FILEHANDLE,POS,WHENCE: Format: SYSSEEK rd argsReg ctx + */ public static final short SYSSEEK = 325; - /** truncate FILEHANDLE,LENGTH: Format: TRUNCATE rd argsReg ctx */ + /** + * truncate FILEHANDLE,LENGTH: Format: TRUNCATE rd argsReg ctx + */ public static final short TRUNCATE = 326; - /** read FILEHANDLE,SCALAR,LENGTH: Format: READ rd argsReg ctx */ + /** + * read FILEHANDLE,SCALAR,LENGTH: Format: READ rd argsReg ctx + */ public static final short READ = 327; - /** opendir DIRHANDLE,EXPR: Format: OPENDIR rd argsReg ctx */ + /** + * opendir DIRHANDLE,EXPR: Format: OPENDIR rd argsReg ctx + */ public static final short OPENDIR = 328; - /** readdir DIRHANDLE: Format: READDIR rd argsReg ctx */ + /** + * readdir DIRHANDLE: Format: READDIR rd argsReg ctx + */ public static final short READDIR = 329; - /** seekdir DIRHANDLE,POS: Format: SEEKDIR rd argsReg ctx */ + /** + * seekdir DIRHANDLE,POS: Format: SEEKDIR rd argsReg ctx + */ public static final short SEEKDIR = 330; - /** Enter scoped package block (package Foo { ...). + /** + * Enter scoped package block (package Foo { ...). * Format: PUSH_PACKAGE nameIdx - * Effect: Saves current packageName, sets new one */ + * Effect: Saves current packageName, sets new one + */ public static final short PUSH_PACKAGE = 307; - /** Exit scoped package block (closing } of package Foo { ...). + /** + * Exit scoped package block (closing } of package Foo { ...). * Format: POP_PACKAGE - * Effect: Restores previous packageName */ + * Effect: Restores previous packageName + */ public static final short POP_PACKAGE = 308; - /** Dereference a scalar as a glob: rd = rs.globDerefNonStrict(currentPackage) + /** + * Dereference a scalar as a glob: rd = rs.globDerefNonStrict(currentPackage) * Used for $ref->** postfix glob deref - * Format: DEREF_GLOB rd rs nameIdx(currentPackage) */ + * Format: DEREF_GLOB rd rs nameIdx(currentPackage) + */ public static final short DEREF_GLOB = 331; - /** Load glob by runtime name (symbolic ref): rd = GlobalVariable.getGlobalIO(normalize(nameReg, pkg)) + /** + * Load glob by runtime name (symbolic ref): rd = GlobalVariable.getGlobalIO(normalize(nameReg, pkg)) * Used for *{"name"} = value typeglob assignment with dynamic name - * Format: LOAD_GLOB_DYNAMIC rd nameReg pkgIdx */ + * Format: LOAD_GLOB_DYNAMIC rd nameReg pkgIdx + */ public static final short LOAD_GLOB_DYNAMIC = 332; - /** Scalar dereference (strict refs): rd = rs.scalarDeref() + /** + * Scalar dereference (strict refs): rd = rs.scalarDeref() * Throws "Can't use string as a SCALAR ref while strict refs in use" for non-refs. * Matches JVM path: scalarDeref() — used when strict refs is enabled. - * Format: DEREF_SCALAR_STRICT rd rs */ + * Format: DEREF_SCALAR_STRICT rd rs + */ public static final short DEREF_SCALAR_STRICT = 333; - /** Scalar dereference (no strict refs): rd = rs.scalarDerefNonStrict(pkg) + /** + * Scalar dereference (no strict refs): rd = rs.scalarDerefNonStrict(pkg) * Allows symbolic references (string name -> global variable lookup). * Matches JVM path: scalarDerefNonStrict(pkg) — used when strict refs is disabled. - * Format: DEREF_SCALAR_NONSTRICT rd rs pkgIdx */ + * Format: DEREF_SCALAR_NONSTRICT rd rs pkgIdx + */ public static final short DEREF_SCALAR_NONSTRICT = 334; - /** Load v-string literal: rd = new RuntimeScalar(stringPool[index]) with type=VSTRING + /** + * Load v-string literal: rd = new RuntimeScalar(stringPool[index]) with type=VSTRING * Mirrors JVM EmitLiteral handling of isVString nodes. - * Format: LOAD_VSTRING rd strIndex */ + * Format: LOAD_VSTRING rd strIndex + */ public static final short LOAD_VSTRING = 335; - /** Convert list/array to its last element in scalar context: rd = list.scalar() + /** + * Convert list/array to its last element in scalar context: rd = list.scalar() * A list in scalar context returns its last element (Perl semantics). * Contrast with LIST_TO_COUNT which returns list size. - * Format: LIST_TO_SCALAR rd rs */ + * Format: LIST_TO_SCALAR rd rs + */ public static final short LIST_TO_SCALAR = 336; - /** Glob operator: rd = ScalarGlobOperator.evaluate(globId, patternReg, ctx) + /** + * Glob operator: rd = ScalarGlobOperator.evaluate(globId, patternReg, ctx) * Mirrors JVM EmitOperator.handleGlobBuiltin — uses a per-call-site globId for * scalar-context iteration state across calls. - * Format: GLOB_OP rd globId patternReg ctx */ + * Format: GLOB_OP rd globId patternReg ctx + */ public static final short GLOB_OP = 337; - /** Execute a file: rd = ModuleOperators.doFile(fileReg, ctx) + /** + * Execute a file: rd = ModuleOperators.doFile(fileReg, ctx) * Implements Perl's do FILE operator. - * Format: DO_FILE rd fileReg ctx */ + * Format: DO_FILE rd fileReg ctx + */ public static final short DO_FILE = 338; - /** Hash key/value slice: rd = hash.getKeyValueSlice(keys_list) + /** + * Hash key/value slice: rd = hash.getKeyValueSlice(keys_list) * Perl: %hash{keys} returns alternating key/value pairs. - * Format: HASH_KEYVALUE_SLICE rd hashReg keysListReg */ + * Format: HASH_KEYVALUE_SLICE rd hashReg keysListReg + */ public static final short HASH_KEYVALUE_SLICE = 342; - /** Set $#array = value: Format: SET_ARRAY_LAST_INDEX arrayReg valueReg */ + /** + * Set $#array = value: Format: SET_ARRAY_LAST_INDEX arrayReg valueReg + */ public static final short SET_ARRAY_LAST_INDEX = 345; - /** Logical xor: rd = left xor right. Format: XOR_LOGICAL rd rs1 rs2 */ + /** + * Logical xor: rd = left xor right. Format: XOR_LOGICAL rd rs1 rs2 + */ public static final short XOR_LOGICAL = 346; - /** Defined-or assignment: rd //= rs. Format: DEFINED_OR_ASSIGN rd rs */ + /** + * Defined-or assignment: rd //= rs. Format: DEFINED_OR_ASSIGN rd rs + */ public static final short DEFINED_OR_ASSIGN = 347; - /** stat _ (use cached stat buffer): rd = Stat.statLastHandle() - * Format: STAT_LASTHANDLE rd ctx */ + /** + * stat _ (use cached stat buffer): rd = Stat.statLastHandle() + * Format: STAT_LASTHANDLE rd ctx + */ public static final short STAT_LASTHANDLE = 348; - /** lstat _ (use cached stat buffer): rd = Stat.lstatLastHandle() - * Format: LSTAT_LASTHANDLE rd ctx */ + /** + * lstat _ (use cached stat buffer): rd = Stat.lstatLastHandle() + * Format: LSTAT_LASTHANDLE rd ctx + */ public static final short LSTAT_LASTHANDLE = 349; - /** Mutable scalar assignment: rd = new RuntimeScalar(); rd.set(rs) + /** + * Mutable scalar assignment: rd = new RuntimeScalar(); rd.set(rs) * Superinstruction combining LOAD_UNDEF + SET_SCALAR for lexical scalar assignment. - * Format: MY_SCALAR rd rs */ + * Format: MY_SCALAR rd rs + */ public static final short MY_SCALAR = 350; - /** Undefine a scalar variable in-place: rd.undefine(). Used by `undef $x`. */ + /** + * Undefine a scalar variable in-place: rd.undefine(). Used by `undef $x`. + */ public static final short UNDEFINE_SCALAR = 351; - /** Push a labeled block entry for non-local last/next/redo handling. - * Format: PUSH_LABELED_BLOCK label_string_idx exit_pc(int) */ + /** + * Push a labeled block entry for non-local last/next/redo handling. + * Format: PUSH_LABELED_BLOCK label_string_idx exit_pc(int) + */ public static final short PUSH_LABELED_BLOCK = 352; - /** Pop a labeled block entry. - * Format: POP_LABELED_BLOCK */ + /** + * Pop a labeled block entry. + * Format: POP_LABELED_BLOCK + */ public static final short POP_LABELED_BLOCK = 353; - /** Save regex state (Perl 5 dynamic scoping of $1, $&, etc.) into register rd. - * The register receives an integer index into the interpreter's regexStateStack. - * Emitted at block entry for blocks containing regex operations. - * @see org.perlonjava.runtime.runtimetypes.RegexState - * Format: SAVE_REGEX_STATE rd */ + /** + * Save regex state (Perl 5 dynamic scoping of $1, $&, etc.) into register rd. + * The register receives an integer index into the interpreter's regexStateStack. + * Emitted at block entry for blocks containing regex operations. + * + * @see org.perlonjava.runtime.runtimetypes.RegexState + * Format: SAVE_REGEX_STATE rd + */ public static final short SAVE_REGEX_STATE = 354; - /** Restore regex state from the level stored in register rs, undoing all - * regex state changes made within the block. Also truncates any orphaned - * stack entries (from inner blocks skipped by last/next/redo/die). - * Emitted at block exit. - * Format: RESTORE_REGEX_STATE rs */ + /** + * Restore regex state from the level stored in register rs, undoing all + * regex state changes made within the block. Also truncates any orphaned + * stack entries (from inner blocks skipped by last/next/redo/die). + * Emitted at block exit. + * Format: RESTORE_REGEX_STATE rs + */ public static final short RESTORE_REGEX_STATE = 355; public static final short DEREF_HASH_NONSTRICT = 356; public static final short DEREF_ARRAY_NONSTRICT = 357; - /** Perl time() builtin: rd = current epoch seconds. - * Format: TIME_OP rd */ + /** + * Perl time() builtin: rd = current epoch seconds. + * Format: TIME_OP rd + */ public static final short TIME_OP = 358; public static final short INTEGER_LEFT_SHIFT = 359; @@ -1206,23 +1781,34 @@ public class Opcodes { public static final short INTEGER_MOD_ASSIGN = 366; public static final short RESET = 367; - /** Dereference a scalar as a glob (no strict refs): rd = rs.globDerefNonStrict(pkg) + /** + * Dereference a scalar as a glob (no strict refs): rd = rs.globDerefNonStrict(pkg) * Allows symbolic glob references (string names resolved to globs). - * Format: DEREF_GLOB_NONSTRICT rd rs pkgIdx */ + * Format: DEREF_GLOB_NONSTRICT rd rs pkgIdx + */ public static final short DEREF_GLOB_NONSTRICT = 368; - /** Array exists: rd = array_reg.exists(index_reg) */ + /** + * Array exists: rd = array_reg.exists(index_reg) + */ public static final short ARRAY_EXISTS = 369; - /** Array delete: rd = array_reg.delete(index_reg) */ + /** + * Array delete: rd = array_reg.delete(index_reg) + */ public static final short ARRAY_DELETE = 370; - /** List assignment: rd = lhs_list_reg.setFromList(rhs_list_reg) - * Format: SET_FROM_LIST rd lhsListReg rhsListReg */ + /** + * List assignment: rd = lhs_list_reg.setFromList(rhs_list_reg) + * Format: SET_FROM_LIST rd lhsListReg rhsListReg + */ public static final short SET_FROM_LIST = 371; - /** Load byte string: rd = new RuntimeScalar(stringPool[index]) with BYTE_STRING type. + /** + * Load byte string: rd = new RuntimeScalar(stringPool[index]) with BYTE_STRING type. * Used for string literals under `no utf8` (the default). - * Format: LOAD_BYTE_STRING rd strIndex */ + * Format: LOAD_BYTE_STRING rd strIndex + */ public static final short LOAD_BYTE_STRING = 372; - private Opcodes() {} // Utility class - no instantiation + private Opcodes() { + } // Utility class - no instantiation } diff --git a/src/main/java/org/perlonjava/backend/bytecode/ScalarBinaryOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/ScalarBinaryOpcodeHandler.java index 55722acdd..996bf835d 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/ScalarBinaryOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/ScalarBinaryOpcodeHandler.java @@ -1,11 +1,11 @@ package org.perlonjava.backend.bytecode; -import org.perlonjava.runtime.runtimetypes.RuntimeBase; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; import org.perlonjava.runtime.operators.BitwiseOperators; import org.perlonjava.runtime.operators.CompareOperators; import org.perlonjava.runtime.operators.MathOperators; import org.perlonjava.runtime.operators.Operator; +import org.perlonjava.runtime.runtimetypes.RuntimeBase; +import org.perlonjava.runtime.runtimetypes.RuntimeScalar; /** * Handler for scalar binary operations (atan2, eq, ne, lt, le, gt, ge, cmp, etc.) @@ -27,9 +27,12 @@ public static int execute(int opcode, int[] bytecode, int pc, // Dispatch based on specific opcode registers[rd] = switch (opcode) { case Opcodes.ATAN2 -> MathOperators.atan2((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); - case Opcodes.BINARY_AND -> BitwiseOperators.bitwiseAndBinary((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); - case Opcodes.BINARY_OR -> BitwiseOperators.bitwiseOrBinary((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); - case Opcodes.BINARY_XOR -> BitwiseOperators.bitwiseXorBinary((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); + case Opcodes.BINARY_AND -> + BitwiseOperators.bitwiseAndBinary((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); + case Opcodes.BINARY_OR -> + BitwiseOperators.bitwiseOrBinary((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); + case Opcodes.BINARY_XOR -> + BitwiseOperators.bitwiseXorBinary((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); case Opcodes.EQ -> CompareOperators.eq((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); case Opcodes.NE -> CompareOperators.ne((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); case Opcodes.LT -> CompareOperators.lt((RuntimeScalar) registers[rs1], (RuntimeScalar) registers[rs2]); @@ -48,24 +51,36 @@ public static int execute(int opcode, int[] bytecode, int pc, * Disassemble scalar binary operations (atan2, eq, ne, lt, le, gt, ge, cmp, etc.) operation. */ public static int disassemble(int opcode, int[] bytecode, int pc, - StringBuilder sb) { + StringBuilder sb) { int rd = bytecode[pc++]; int rs1 = bytecode[pc++]; int rs2 = bytecode[pc++]; switch (opcode) { - case Opcodes.ATAN2 -> sb.append("ATAN2 r").append(rd).append(" = atan2(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.BINARY_AND -> sb.append("BINARY_AND r").append(rd).append(" = binary&(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.BINARY_OR -> sb.append("BINARY_OR r").append(rd).append(" = binary|(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.BINARY_XOR -> sb.append("BINARY_XOR r").append(rd).append(" = binary^(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.EQ -> sb.append("EQ r").append(rd).append(" = eq(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.NE -> sb.append("NE r").append(rd).append(" = ne(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.LT -> sb.append("LT r").append(rd).append(" = lt(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.LE -> sb.append("LE r").append(rd).append(" = le(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.GT -> sb.append("GT r").append(rd).append(" = gt(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.GE -> sb.append("GE r").append(rd).append(" = ge(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.CMP -> sb.append("CMP r").append(rd).append(" = cmp(r").append(rs1).append(", r").append(rs2).append(")\n"); - case Opcodes.X -> sb.append("X r").append(rd).append(" = x(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.ATAN2 -> + sb.append("ATAN2 r").append(rd).append(" = atan2(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.BINARY_AND -> + sb.append("BINARY_AND r").append(rd).append(" = binary&(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.BINARY_OR -> + sb.append("BINARY_OR r").append(rd).append(" = binary|(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.BINARY_XOR -> + sb.append("BINARY_XOR r").append(rd).append(" = binary^(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.EQ -> + sb.append("EQ r").append(rd).append(" = eq(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.NE -> + sb.append("NE r").append(rd).append(" = ne(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.LT -> + sb.append("LT r").append(rd).append(" = lt(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.LE -> + sb.append("LE r").append(rd).append(" = le(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.GT -> + sb.append("GT r").append(rd).append(" = gt(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.GE -> + sb.append("GE r").append(rd).append(" = ge(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.CMP -> + sb.append("CMP r").append(rd).append(" = cmp(r").append(rs1).append(", r").append(rs2).append(")\n"); + case Opcodes.X -> + sb.append("X r").append(rd).append(" = x(r").append(rs1).append(", r").append(rs2).append(")\n"); default -> sb.append("UNKNOWN_").append(opcode).append("\n"); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/ScalarUnaryOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/ScalarUnaryOpcodeHandler.java index 971aacd13..f5f9515b4 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/ScalarUnaryOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/ScalarUnaryOpcodeHandler.java @@ -1,16 +1,8 @@ package org.perlonjava.backend.bytecode; +import org.perlonjava.runtime.operators.*; import org.perlonjava.runtime.runtimetypes.RuntimeBase; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.operators.BitwiseOperators; -import org.perlonjava.runtime.operators.Directory; -import org.perlonjava.runtime.operators.IOOperator; -import org.perlonjava.runtime.operators.MathOperators; -import org.perlonjava.runtime.operators.Random; -import org.perlonjava.runtime.operators.ScalarOperators; -import org.perlonjava.runtime.operators.StringOperators; -import org.perlonjava.runtime.operators.Time; -import org.perlonjava.runtime.operators.WarnDie; /** * Handler for scalar unary operations (chr, ord, abs, sin, cos, lc, uc, etc.) @@ -71,7 +63,7 @@ public static int execute(int opcode, int[] bytecode, int pc, * Disassemble scalar unary operations (chr, ord, abs, sin, cos, lc, uc, etc.) operation. */ public static int disassemble(int opcode, int[] bytecode, int pc, - StringBuilder sb) { + StringBuilder sb) { int rd = bytecode[pc++]; int rs = bytecode[pc++]; @@ -83,17 +75,23 @@ public static int disassemble(int opcode, int[] bytecode, int pc, case Opcodes.SIN -> sb.append("SIN r").append(rd).append(" = sin(r").append(rs).append(")\n"); case Opcodes.EXP -> sb.append("EXP r").append(rd).append(" = exp(r").append(rs).append(")\n"); case Opcodes.ABS -> sb.append("ABS r").append(rd).append(" = abs(r").append(rs).append(")\n"); - case Opcodes.BINARY_NOT -> sb.append("BINARY_NOT r").append(rd).append(" = binary~(r").append(rs).append(")\n"); - case Opcodes.INTEGER_BITWISE_NOT -> sb.append("INTEGER_BITWISE_NOT r").append(rd).append(" = integerBitwiseNot(r").append(rs).append(")\n"); + case Opcodes.BINARY_NOT -> + sb.append("BINARY_NOT r").append(rd).append(" = binary~(r").append(rs).append(")\n"); + case Opcodes.INTEGER_BITWISE_NOT -> + sb.append("INTEGER_BITWISE_NOT r").append(rd).append(" = integerBitwiseNot(r").append(rs).append(")\n"); case Opcodes.ORD -> sb.append("ORD r").append(rd).append(" = ord(r").append(rs).append(")\n"); - case Opcodes.ORD_BYTES -> sb.append("ORD_BYTES r").append(rd).append(" = ordBytes(r").append(rs).append(")\n"); + case Opcodes.ORD_BYTES -> + sb.append("ORD_BYTES r").append(rd).append(" = ordBytes(r").append(rs).append(")\n"); case Opcodes.OCT -> sb.append("OCT r").append(rd).append(" = oct(r").append(rs).append(")\n"); case Opcodes.HEX -> sb.append("HEX r").append(rd).append(" = hex(r").append(rs).append(")\n"); case Opcodes.SRAND -> sb.append("SRAND r").append(rd).append(" = srand(r").append(rs).append(")\n"); case Opcodes.CHR -> sb.append("CHR r").append(rd).append(" = chr(r").append(rs).append(")\n"); - case Opcodes.CHR_BYTES -> sb.append("CHR_BYTES r").append(rd).append(" = chrBytes(r").append(rs).append(")\n"); - case Opcodes.LENGTH_BYTES -> sb.append("LENGTH_BYTES r").append(rd).append(" = lengthBytes(r").append(rs).append(")\n"); - case Opcodes.QUOTEMETA -> sb.append("QUOTEMETA r").append(rd).append(" = quotemeta(r").append(rs).append(")\n"); + case Opcodes.CHR_BYTES -> + sb.append("CHR_BYTES r").append(rd).append(" = chrBytes(r").append(rs).append(")\n"); + case Opcodes.LENGTH_BYTES -> + sb.append("LENGTH_BYTES r").append(rd).append(" = lengthBytes(r").append(rs).append(")\n"); + case Opcodes.QUOTEMETA -> + sb.append("QUOTEMETA r").append(rd).append(" = quotemeta(r").append(rs).append(")\n"); case Opcodes.FC -> sb.append("FC r").append(rd).append(" = fc(r").append(rs).append(")\n"); case Opcodes.LC -> sb.append("LC r").append(rd).append(" = lc(r").append(rs).append(")\n"); case Opcodes.LCFIRST -> sb.append("LCFIRST r").append(rd).append(" = lcfirst(r").append(rs).append(")\n"); @@ -102,8 +100,10 @@ public static int disassemble(int opcode, int[] bytecode, int pc, case Opcodes.SLEEP -> sb.append("SLEEP r").append(rd).append(" = sleep(r").append(rs).append(")\n"); case Opcodes.TELL -> sb.append("TELL r").append(rd).append(" = tell(r").append(rs).append(")\n"); case Opcodes.RMDIR -> sb.append("RMDIR r").append(rd).append(" = rmdir(r").append(rs).append(")\n"); - case Opcodes.CLOSEDIR -> sb.append("CLOSEDIR r").append(rd).append(" = closedir(r").append(rs).append(")\n"); - case Opcodes.REWINDDIR -> sb.append("REWINDDIR r").append(rd).append(" = rewinddir(r").append(rs).append(")\n"); + case Opcodes.CLOSEDIR -> + sb.append("CLOSEDIR r").append(rd).append(" = closedir(r").append(rs).append(")\n"); + case Opcodes.REWINDDIR -> + sb.append("REWINDDIR r").append(rd).append(" = rewinddir(r").append(rs).append(")\n"); case Opcodes.TELLDIR -> sb.append("TELLDIR r").append(rd).append(" = telldir(r").append(rs).append(")\n"); case Opcodes.CHDIR -> sb.append("CHDIR r").append(rd).append(" = chdir(r").append(rs).append(")\n"); case Opcodes.EXIT -> sb.append("EXIT r").append(rd).append(" = exit(r").append(rs).append(")\n"); diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java index 53fb2cfae..a4af9a1c7 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -1,10 +1,6 @@ package org.perlonjava.backend.bytecode; -import org.perlonjava.runtime.operators.RuntimeTransliterate; -import org.perlonjava.runtime.operators.FileTestOperator; -import org.perlonjava.runtime.operators.IOOperator; -import org.perlonjava.runtime.operators.Operator; -import org.perlonjava.runtime.operators.Time; +import org.perlonjava.runtime.operators.*; import org.perlonjava.runtime.runtimetypes.*; import java.util.Map; @@ -61,10 +57,8 @@ public class SlowOpcodeHandler { private static final boolean EVAL_TRACE = System.getenv("JPERL_EVAL_TRACE") != null; - private static void evalTrace(String msg) { - if (EVAL_TRACE) { - System.err.println("[eval-trace] " + msg); - } + private SlowOpcodeHandler() { + // Utility class - no instantiation } // ================================================================= @@ -72,6 +66,12 @@ private static void evalTrace(String msg) { // ================================================================= // ================================================================= + private static void evalTrace(String msg) { + if (EVAL_TRACE) { + System.err.println("[eval-trace] " + msg); + } + } + /** * SLOW_GETPPID: rd = getppid() * Format: [SLOW_GETPPID] [rd] @@ -368,8 +368,8 @@ public static int executeSelect( // Call IOOperator.select() which handles the logic RuntimeScalar result = IOOperator.select( - list, - RuntimeContextType.SCALAR + list, + RuntimeContextType.SCALAR ); registers[rd] = result; @@ -415,7 +415,7 @@ public static int executeLoadGlobDynamic( int pkgIdx = bytecode[pc++]; String pkg = code.stringPool[pkgIdx]; - String name = ((RuntimeScalar) registers[nameReg]).toString(); + String name = registers[nameReg].toString(); String globalName = NameNormalizer.normalizeVariableName(name, pkg); registers[rd] = GlobalVariable.getGlobalIO(globalName); @@ -561,7 +561,7 @@ public static int executeDerefArray( } if (scalarBase instanceof RuntimeList) { RuntimeArray arr = new RuntimeArray(); - ((RuntimeList) scalarBase).addToArray(arr); + scalarBase.addToArray(arr); registers[rd] = arr; return pc; } @@ -808,8 +808,8 @@ public static int executeExists( // For now, throw unsupported - basic exists should use fast path throw new UnsupportedOperationException( - "exists() slow path not yet implemented in interpreter. " + - "Use simple hash access: exists $hash{key}" + "exists() slow path not yet implemented in interpreter. " + + "Use simple hash access: exists $hash{key}" ); } @@ -828,8 +828,8 @@ public static int executeDelete( // For now, throw unsupported - basic delete should use fast path throw new UnsupportedOperationException( - "delete() slow path not yet implemented in interpreter. " + - "Use simple hash access: delete $hash{key}" + "delete() slow path not yet implemented in interpreter. " + + "Use simple hash access: delete $hash{key}" ); } @@ -895,7 +895,7 @@ public static int executeDerefArrayNonStrict(int[] bytecode, int pc, RuntimeBase } if (scalarBase instanceof RuntimeList) { RuntimeArray arr = new RuntimeArray(); - ((RuntimeList) scalarBase).addToArray(arr); + scalarBase.addToArray(arr); registers[rd] = arr; return pc; } @@ -989,7 +989,7 @@ public static int executeHashSliceSet( } else if (valuesBase instanceof RuntimeArray) { // Convert RuntimeArray to RuntimeList valuesList = new RuntimeList(); - for (RuntimeScalar elem : (RuntimeArray) valuesBase) { + for (RuntimeScalar elem : valuesBase) { valuesList.elements.add(elem); } } else { @@ -1028,7 +1028,7 @@ public static int executeListSliceFrom( } else if (listBase instanceof RuntimeArray) { // Convert RuntimeArray to RuntimeList sourceList = new RuntimeList(); - for (RuntimeScalar elem : (RuntimeArray) listBase) { + for (RuntimeScalar elem : listBase) { sourceList.elements.add(elem); } } else { @@ -1175,8 +1175,4 @@ public static int executeGlobSlotGet( return pc; } - - private SlowOpcodeHandler() { - // Utility class - no instantiation - } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java b/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java index 1ed7c37cc..f588a4b19 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java +++ b/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java @@ -2,7 +2,10 @@ import org.perlonjava.frontend.astnode.*; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Analyzes which lexical variables in the main script are captured by named subroutines. @@ -37,7 +40,7 @@ public class VariableCaptureAnalyzer { /** * Analyzes which variables in the main script are captured by named subroutines. * - * @param mainScript The AST of the main script (typically a BlockNode) + * @param mainScript The AST of the main script (typically a BlockNode) * @param outerScopeVars Set of variable names declared in the outer (main) scope * @return Set of variable names that need persistent storage */ diff --git a/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java b/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java index 01c0a4483..a232ffc7e 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java +++ b/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java @@ -37,7 +37,7 @@ public void visit(OperatorNode node) { // Check if this is a variable reference (sigil + identifier) String op = node.operator; if ((op.equals("$") || op.equals("@") || op.equals("%") || op.equals("&")) - && node.operand instanceof IdentifierNode) { + && node.operand instanceof IdentifierNode) { // This is a variable reference IdentifierNode idNode = (IdentifierNode) node.operand; String varName = op + idNode.name; diff --git a/src/main/java/org/perlonjava/backend/jvm/ByteCodeSourceMapper.java b/src/main/java/org/perlonjava/backend/jvm/ByteCodeSourceMapper.java index 7a31d36b8..ae4a17c1b 100644 --- a/src/main/java/org/perlonjava/backend/jvm/ByteCodeSourceMapper.java +++ b/src/main/java/org/perlonjava/backend/jvm/ByteCodeSourceMapper.java @@ -155,14 +155,14 @@ public static SourceLocation parseStackTraceElement(StackTraceElement element, H } LineInfo lineInfo = entry.getValue(); - + // Retrieve subroutine name String subroutineName = subroutineNamePool.get(lineInfo.subroutineNameId()); // If subroutine name is empty string (main code), convert to null if (subroutineName != null && subroutineName.isEmpty()) { subroutineName = null; } - + // Create a unique location key using tokenIndex instead of line number // This prevents false duplicates when multiple statements are on the same line var locationKey = new SourceLocation( @@ -188,19 +188,6 @@ public static SourceLocation parseStackTraceElement(StackTraceElement element, H ); } - /** - * Holds debug information for a specific source file including - * mappings between token indices and line information. - */ - private static class SourceFileInfo { - final int fileId; - final TreeMap tokenToLineInfo = new TreeMap<>(); - - SourceFileInfo(int fileId) { - this.fileId = fileId; - } - } - /** * Associates a line number with its package context and subroutine name. */ @@ -213,4 +200,17 @@ private record LineInfo(int lineNumber, int packageNameId, int subroutineNameId) */ public record SourceLocation(String sourceFileName, String packageName, int lineNumber, String subroutineName) { } + + /** + * Holds debug information for a specific source file including + * mappings between token indices and line information. + */ + private static class SourceFileInfo { + final int fileId; + final TreeMap tokenToLineInfo = new TreeMap<>(); + + SourceFileInfo(int fileId) { + this.fileId = fileId; + } + } } diff --git a/src/main/java/org/perlonjava/backend/jvm/CompiledCode.java b/src/main/java/org/perlonjava/backend/jvm/CompiledCode.java index 823e72dc3..501f70f93 100644 --- a/src/main/java/org/perlonjava/backend/jvm/CompiledCode.java +++ b/src/main/java/org/perlonjava/backend/jvm/CompiledCode.java @@ -6,15 +6,15 @@ /** * Compiled bytecode that extends RuntimeCode. - * + *

* This class represents Perl code that has been compiled to JVM bytecode. * It wraps the generated Class and provides the same RuntimeCode interface * as InterpretedCode, enabling seamless switching between compiler and interpreter. - * + *

* DESIGN: Following the InterpretedCode pattern: * - InterpretedCode stores bytecode[] and overrides apply() to call BytecodeInterpreter * - CompiledCode stores Class and uses parent apply() to call MethodHandle - * + *

* This allows the EmitterMethodCreator.createRuntimeCode() factory to return either * CompiledCode or InterpretedCode based on whether compilation succeeded or fell * back to the interpreter. @@ -29,15 +29,15 @@ public class CompiledCode extends RuntimeCode { /** * Constructor for CompiledCode. * - * @param methodHandle The MethodHandle for the apply() method - * @param codeObject The instance of the generated class (with closure variables) - * @param prototype The subroutine prototype (e.g., "$" for one scalar parameter) + * @param methodHandle The MethodHandle for the apply() method + * @param codeObject The instance of the generated class (with closure variables) + * @param prototype The subroutine prototype (e.g., "$" for one scalar parameter) * @param generatedClass The compiled JVM class * @param compileContext The compiler context (optional, for debugging) */ public CompiledCode(MethodHandle methodHandle, Object codeObject, - String prototype, Class generatedClass, - EmitterContext compileContext) { + String prototype, Class generatedClass, + EmitterContext compileContext) { super(methodHandle, codeObject, prototype); this.generatedClass = generatedClass; this.compileContext = compileContext; @@ -49,9 +49,9 @@ public CompiledCode(MethodHandle methodHandle, Object codeObject, @Override public String toString() { return "CompiledCode{" + - "class=" + (generatedClass != null ? generatedClass.getName() : "null") + - ", prototype='" + prototype + '\'' + - ", defined=" + defined() + - '}'; + "class=" + (generatedClass != null ? generatedClass.getName() : "null") + + ", prototype='" + prototype + '\'' + + ", defined=" + defined() + + '}'; } } diff --git a/src/main/java/org/perlonjava/backend/jvm/Dereference.java b/src/main/java/org/perlonjava/backend/jvm/Dereference.java index 01b83042e..a49e03035 100644 --- a/src/main/java/org/perlonjava/backend/jvm/Dereference.java +++ b/src/main/java/org/perlonjava/backend/jvm/Dereference.java @@ -102,7 +102,7 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper * This allows ${$aref}[0] to work even though ${$aref} alone would fail. */ emitterVisitor.ctx.logDebug("visit(BinaryOperatorNode) ${BLOCK}[] "); - + // Evaluate the block expression to get a RuntimeScalar (might be array/hash ref) sigilNode.operand.accept(scalarVisitor); @@ -112,7 +112,7 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper baseSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); } emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, baseSlot); - + // Now apply the subscript using arrayDerefGet method ArrayLiteralNode right = (ArrayLiteralNode) node.right; if (right.elements.size() == 1) { @@ -130,7 +130,7 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper emitterVisitor.ctx.mv.visitInsn(Opcodes.SWAP); emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", "arrayDerefGetSlice", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeList;)Lorg/perlonjava/runtime/runtimetypes/RuntimeList;", false); - + // Handle context conversion if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) { emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", @@ -143,7 +143,7 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper if (pooledBase) { emitterVisitor.ctx.javaClassInfo.releaseSpillSlot(); } - + EmitOperator.handleVoidContext(emitterVisitor); return; } @@ -429,20 +429,20 @@ public static void handleHashElementOperator(EmitterVisitor emitterVisitor, Bina * This allows ${$href}{key} to work even though ${$href} alone would fail. */ emitterVisitor.ctx.logDebug("visit(BinaryOperatorNode) ${BLOCK}{} "); - + // Evaluate the block expression to get a RuntimeScalar (might be array/hash ref) sigilNode.operand.accept(scalarVisitor); - + // Now apply the subscript using hashDerefGet method ListNode nodeRight = ((HashLiteralNode) node.right).asListNode(); - + Node nodeZero = nodeRight.elements.getFirst(); if (nodeRight.elements.size() == 1 && nodeZero instanceof IdentifierNode) { // Convert IdentifierNode to StringNode: {a} to {"a"} nodeRight.elements.set(0, new StringNode(((IdentifierNode) nodeZero).name, ((IdentifierNode) nodeZero).tokenIndex)); nodeZero = nodeRight.elements.getFirst(); } - + // Apply hash subscript if (nodeRight.elements.size() == 1) { // Single element @@ -474,7 +474,7 @@ public static void handleHashElementOperator(EmitterVisitor emitterVisitor, Bina "hashDerefGetNonStrict", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } } - + EmitOperator.handleVoidContext(emitterVisitor); return; } @@ -793,11 +793,11 @@ public static void handleArrowArrayDeref(EmitterVisitor emitterVisitor, BinaryOp emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, leftSlot); ArrayLiteralNode right = (ArrayLiteralNode) node.right; - + boolean isSingleRange = right.elements.size() == 1 && - right.elements.getFirst() instanceof BinaryOperatorNode binOp && - "..".equals(binOp.operator); - + right.elements.getFirst() instanceof BinaryOperatorNode binOp && + "..".equals(binOp.operator); + if (right.elements.size() == 1 && !isSingleRange) { // Single index: use get/delete/exists methods Node elem = right.elements.getFirst(); diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperator.java index 396366a7c..978b8755a 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperator.java @@ -2,11 +2,11 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.astnode.BinaryOperatorNode; import org.perlonjava.frontend.astnode.IdentifierNode; import org.perlonjava.frontend.astnode.NumberNode; import org.perlonjava.frontend.astnode.StringNode; -import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.runtime.operators.OperatorHandler; import org.perlonjava.runtime.perlmodule.Strict; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperatorNode.java b/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperatorNode.java index bcea5e347..96eef2cc7 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperatorNode.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitBinaryOperatorNode.java @@ -1,8 +1,8 @@ package org.perlonjava.backend.jvm; import org.objectweb.asm.Opcodes; -import org.perlonjava.frontend.astnode.BinaryOperatorNode; import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.astnode.BinaryOperatorNode; import org.perlonjava.runtime.operators.OperatorHandler; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; @@ -30,8 +30,7 @@ public static void emitBinaryOperatorNode(EmitterVisitor emitterVisitor, BinaryO case "//=" -> EmitLogicalOperator.emitLogicalAssign(emitterVisitor, node, Opcodes.IFNE, "getDefinedBoolean"); - case "xor", "^^" -> - EmitLogicalOperator.emitXorOperator(emitterVisitor, node); + case "xor", "^^" -> EmitLogicalOperator.emitXorOperator(emitterVisitor, node); // Assignment operator case "=" -> EmitVariable.handleAssignOperator(emitterVisitor, node); diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java b/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java index 1e4514db8..855119691 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java @@ -6,14 +6,13 @@ import org.perlonjava.backend.jvm.astrefactor.LargeBlockRefactorer; import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.analysis.RegexUsageDetector; - import org.perlonjava.frontend.astnode.*; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; +import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.LinkedHashSet; -import java.util.ArrayList; public class EmitBlock { @@ -224,16 +223,16 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) { For1Node preEvalForNode = null; int savedPreEvaluatedArrayIndex = -1; - if (list.size() >= 2 && - list.get(0) instanceof OperatorNode localOp && localOp.operator.equals("local") && - list.get(1) instanceof For1Node forNode && forNode.needsArrayOfAlias) { - + if (list.size() >= 2 && + list.get(0) instanceof OperatorNode localOp && localOp.operator.equals("local") && + list.get(1) instanceof For1Node forNode && forNode.needsArrayOfAlias) { + // Pre-evaluate the For1Node's list to array of aliases before localizing $_ int tempArrayIndex = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); forNode.list.accept(emitterVisitor.with(RuntimeContextType.LIST)); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", "getArrayOfAlias", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;", false); mv.visitVarInsn(Opcodes.ASTORE, tempArrayIndex); - + // Mark the For1Node to use the pre-evaluated array preEvalForNode = forNode; savedPreEvaluatedArrayIndex = forNode.preEvaluatedArrayIndex; @@ -243,7 +242,7 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) { try { for (int i = 0; i < list.size(); i++) { Node element = list.get(i); - + // Skip null elements - these occur when parseStatement returns null to signal // "not a statement, continue parsing" (e.g., AUTOLOAD without {}, try without feature enabled) // ParseBlock.parseBlock() adds these null results to the statements list @@ -264,7 +263,7 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) { emitterVisitor.ctx.logDebug("Element: " + element); element.accept(voidVisitor); } - + // NOTE: Registry checks are DISABLED in EmitBlock because: // 1. They cause ASM frame computation errors in nested/refactored code // 2. Bare labeled blocks (like TODO:) don't need non-local control flow diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitControlFlow.java b/src/main/java/org/perlonjava/backend/jvm/EmitControlFlow.java index d9f662e7c..6745a8a0d 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitControlFlow.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitControlFlow.java @@ -2,12 +2,8 @@ import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; -import org.perlonjava.frontend.astnode.BinaryOperatorNode; -import org.perlonjava.frontend.astnode.IdentifierNode; -import org.perlonjava.frontend.astnode.ListNode; -import org.perlonjava.frontend.astnode.Node; -import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.astnode.*; import org.perlonjava.runtime.runtimetypes.ControlFlowType; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; @@ -21,9 +17,10 @@ public class EmitControlFlow { // Feature flags for control flow implementation // Set to true to enable tagged return values for non-local control flow (Phase 2 - ACTIVE) private static final boolean ENABLE_TAGGED_RETURNS = true; - + // Set to true to enable debug output for control flow operations private static final boolean DEBUG_CONTROL_FLOW = false; + /** * Handles the 'next', 'last', and 'redo' operators for loop control. * - 'next' is equivalent to 'continue' in Java @@ -62,29 +59,29 @@ static void handleNextOperator(EmitterContext ctx, OperatorNode node) { loopLabels = ctx.javaClassInfo.findLoopLabelsByName(labelStr); } ctx.logDebug("visit(next) operator: " + operator + " label: " + labelStr + " labels: " + loopLabels); - + // Check if we're trying to use next/last/redo in a pseudo-loop (do-while/bare block) if (loopLabels != null && !loopLabels.isTrueLoop) { - throw new PerlCompilerException(node.tokenIndex, - "Can't \"" + operator + "\" outside a loop block", - ctx.errorUtil); + throw new PerlCompilerException(node.tokenIndex, + "Can't \"" + operator + "\" outside a loop block", + ctx.errorUtil); } - + if (loopLabels == null) { // Non-local control flow: return tagged RuntimeControlFlowList ctx.logDebug("visit(next): Non-local control flow for " + operator + " " + labelStr); - + // Determine control flow type ControlFlowType type = operator.equals("next") ? ControlFlowType.NEXT : operator.equals("last") ? ControlFlowType.LAST : ControlFlowType.REDO; - + // Create RuntimeControlFlowList: new RuntimeControlFlowList(type, label, fileName, lineNumber) ctx.mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); ctx.mv.visitInsn(Opcodes.DUP); ctx.mv.visitFieldInsn(Opcodes.GETSTATIC, "org/perlonjava/runtime/runtimetypes/ControlFlowType", - type.name(), + type.name(), "Lorg/perlonjava/runtime/runtimetypes/ControlFlowType;"); if (labelStr != null) { ctx.mv.visitLdcInsn(labelStr); @@ -101,7 +98,7 @@ static void handleNextOperator(EmitterContext ctx, OperatorNode node) { "", "(Lorg/perlonjava/runtime/runtimetypes/ControlFlowType;Ljava/lang/String;Ljava/lang/String;I)V", false); - + // Return the tagged list (will be detected at subroutine return boundary) ctx.mv.visitInsn(Opcodes.ARETURN); return; @@ -143,14 +140,14 @@ static void handleReturnOperator(EmitterVisitor emitterVisitor, OperatorNode nod if (firstElement instanceof BinaryOperatorNode callNode && callNode.operator.equals("(")) { // This is a function call - check if it's a coderef form Node callTarget = callNode.left; - + // Handle &sub syntax if (callTarget instanceof OperatorNode opNode && opNode.operator.equals("&")) { ctx.logDebug("visit(return): Detected goto &NAME tail call"); handleGotoSubroutine(emitterVisitor, opNode, callNode.right); return; } - + // Handle __SUB__ and other code reference expressions // In Perl, goto EXPR where EXPR evaluates to a coderef is a tail call if (callTarget instanceof OperatorNode opNode && opNode.operator.equals("__SUB__")) { @@ -181,7 +178,7 @@ static void handleReturnOperator(EmitterVisitor emitterVisitor, OperatorNode nod ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot); ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel); } - + /** * Handles 'goto &NAME' tail call optimization. * Creates a TAILCALL marker with the coderef and arguments. @@ -192,9 +189,9 @@ static void handleReturnOperator(EmitterVisitor emitterVisitor, OperatorNode nod */ static void handleGotoSubroutine(EmitterVisitor emitterVisitor, OperatorNode subNode, Node argsNode) { EmitterContext ctx = emitterVisitor.ctx; - + ctx.logDebug("visit(goto &sub): Emitting TAILCALL marker"); - + subNode.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); int codeRefSlot = ctx.javaClassInfo.acquireSpillSlot(); boolean pooledCodeRef = codeRefSlot >= 0; @@ -240,7 +237,7 @@ static void handleGotoSubroutine(EmitterVisitor emitterVisitor, OperatorNode sub if (pooledCodeRef) { ctx.javaClassInfo.releaseSpillSlot(); } - + // Jump to returnLabel (trampoline will handle it) ctx.mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.returnValueSlot); ctx.mv.visitJumpInsn(Opcodes.GOTO, ctx.javaClassInfo.returnLabel); @@ -261,10 +258,10 @@ static void handleGotoLabel(EmitterVisitor emitterVisitor, OperatorNode node) { // Parse the goto argument String labelName = null; boolean isDynamic = false; - + if (node.operand instanceof ListNode labelNode && !labelNode.elements.isEmpty()) { Node arg = labelNode.elements.getFirst(); - + // Check if it's a static label (IdentifierNode) if (arg instanceof IdentifierNode) { labelName = ((IdentifierNode) arg).name; @@ -275,17 +272,17 @@ static void handleGotoLabel(EmitterVisitor emitterVisitor, OperatorNode node) { ctx.logDebug("visit(goto): Detected goto __SUB__ tail call"); // Create a ListNode with @_ as the argument ListNode argsNode = new ListNode(opNode.tokenIndex); - OperatorNode atUnderscore = new OperatorNode("@", + OperatorNode atUnderscore = new OperatorNode("@", new IdentifierNode("_", opNode.tokenIndex), opNode.tokenIndex); argsNode.elements.add(atUnderscore); handleGotoSubroutine(emitterVisitor, opNode, argsNode); return; } - + // Dynamic label (goto EXPR) - expression evaluated at runtime isDynamic = true; ctx.logDebug("visit(goto): Dynamic goto with expression"); - + // Evaluate the expression to get the label name at runtime arg.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitEval.java b/src/main/java/org/perlonjava/backend/jvm/EmitEval.java index c8e468031..d7a495f35 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitEval.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitEval.java @@ -5,13 +5,13 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.perlonjava.app.cli.CompilerOptions; +import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.astnode.EvalOperatorNode; import org.perlonjava.frontend.astnode.OperatorNode; -import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeCode; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; /** * EmitEval handles the bytecode generation for Perl's eval operator. @@ -59,6 +59,7 @@ private static void evalTrace(String msg) { System.err.println("[eval-trace] " + msg); } } + /** * Handles the emission of bytecode for the Perl 'eval' operator. * @@ -146,7 +147,7 @@ static void handleEvalOperator(EmitterVisitor emitterVisitor, OperatorNode node) // Store the captured environment array in the context // This ensures runtime uses the exact same array structure as compile-time evalCtx.capturedEnv = newEnv; - + // Mark if this is evalbytes - needed to prevent Unicode source detection evalCtx.isEvalbytes = node.operator.equals("evalbytes"); @@ -536,10 +537,10 @@ static void handleEvalOperator(EmitterVisitor emitterVisitor, OperatorNode node) * This path is used by default (disable with JPERL_EVAL_NO_INTERPRETER=1). * * @param emitterVisitor The visitor that traverses the AST - * @param evalTag The unique identifier for this eval site - * @param newEnv The captured environment variable names + * @param evalTag The unique identifier for this eval site + * @param newEnv The captured environment variable names * @param newSymbolTable The symbol table with captured variables - * @param skipVariables Number of reserved variables to skip (this, @_, wantarray) + * @param skipVariables Number of reserved variables to skip (this, @_, wantarray) */ private static void emitEvalInterpreterPath(EmitterVisitor emitterVisitor, String evalTag, String[] newEnv, ScopedSymbolTable newSymbolTable, @@ -606,14 +607,14 @@ private static void emitEvalInterpreterPath(EmitterVisitor emitterVisitor, Strin * This is the traditional path using JVM bytecode compilation. * * @param emitterVisitor The visitor that traverses the AST - * @param evalTag The unique identifier for this eval site - * @param newEnv The captured environment variable names + * @param evalTag The unique identifier for this eval site + * @param newEnv The captured environment variable names * @param newSymbolTable The symbol table with captured variables - * @param skipVariables Number of reserved variables to skip (this, @_, wantarray) + * @param skipVariables Number of reserved variables to skip (this, @_, wantarray) */ private static void emitEvalCompilerPath(EmitterVisitor emitterVisitor, String evalTag, - String[] newEnv, ScopedSymbolTable newSymbolTable, - int skipVariables) { + String[] newEnv, ScopedSymbolTable newSymbolTable, + int skipVariables) { MethodVisitor mv = emitterVisitor.ctx.mv; // Stack: [RuntimeScalar(String)] diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java b/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java index 00c8c6a7a..0da0c6502 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java @@ -5,7 +5,6 @@ import org.objectweb.asm.Opcodes; import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.analysis.RegexUsageDetector; - import org.perlonjava.frontend.astnode.*; import org.perlonjava.runtime.perlmodule.Warnings; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; @@ -35,7 +34,7 @@ public class EmitForeach { // Marked returns propagate through normal return paths all the way to the top level. // Local control flow (within the same loop) still uses fast JVM GOTO instructions. private static final boolean ENABLE_LOOP_HANDLERS = false; - + // Set to true to enable debug output for loop control flow private static final boolean DEBUG_LOOP_CONTROL_FLOW = false; @@ -277,17 +276,17 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { // This handles cases like: for (split(//, $_)) where the outer context // may have already localized $_, making it undef when we evaluate the list. boolean isStatementModifier = !node.useNewScope; - boolean isGlobalUnderscore = node.needsArrayOfAlias || - (loopVariableIsGlobal && globalVarName != null && - (globalVarName.equals("main::_") || globalVarName.endsWith("::_"))); - boolean needLocalizeUnderscore = isStatementModifier && loopVariableIsGlobal && globalVarName != null && - (globalVarName.equals("main::_") || globalVarName.endsWith("::_")); - + boolean isGlobalUnderscore = node.needsArrayOfAlias || + (loopVariableIsGlobal && globalVarName != null && + (globalVarName.equals("main::_") || globalVarName.endsWith("::_"))); + boolean needLocalizeUnderscore = isStatementModifier && loopVariableIsGlobal && globalVarName != null && + (globalVarName.equals("main::_") || globalVarName.endsWith("::_")); + int savedLoopVarIndex = -1; boolean needSaveRestoreLexicalLoopVar = !isDeclaredInFor && !isReferenceAliasing && !loopVariableIsGlobal && variableNode instanceof OperatorNode; if (needSaveRestoreLexicalLoopVar) { - String varName = extractSimpleVariableName((OperatorNode) variableNode); + String varName = extractSimpleVariableName(variableNode); if (varName != null) { int varIndex = emitterVisitor.ctx.symbolTable.getVariableIndex(varName); if (varIndex >= 0) { @@ -326,7 +325,7 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { false); mv.visitInsn(Opcodes.POP); } - + Local.localRecord localRecord = Local.localSetup(emitterVisitor.ctx, node, mv, true); int iteratorIndex = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); @@ -335,7 +334,7 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { if (node.preEvaluatedArrayIndex >= 0) { // Use the pre-evaluated array that was stored before local $_ was emitted mv.visitVarInsn(Opcodes.ALOAD, node.preEvaluatedArrayIndex); - + // For statement modifiers, localize $_ ourselves if (needLocalizeUnderscore) { mv.visitLdcInsn(globalVarName); @@ -346,7 +345,7 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { false); mv.visitInsn(Opcodes.POP); // Discard the returned scalar } - + // Get iterator from the pre-evaluated array mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeArray", "iterator", "()Ljava/util/Iterator;", false); mv.visitVarInsn(Opcodes.ASTORE, iteratorIndex); @@ -694,14 +693,14 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { if (ENABLE_LOOP_HANDLERS) { // Get parent loop labels (if any) LoopLabels parentLoopLabels = emitterVisitor.ctx.javaClassInfo.getParentLoopLabels(); - + // Get goto labels from current scope (TODO: implement getGotoLabels in EmitterContext) java.util.Map gotoLabels = null; // For now - + // Check if this is the outermost loop in the main program - boolean isMainProgramOutermostLoop = emitterVisitor.ctx.compilerOptions.isMainProgram + boolean isMainProgramOutermostLoop = emitterVisitor.ctx.compilerOptions.isMainProgram && parentLoopLabels == null; - + // Emit the handler emitControlFlowHandler( mv, @@ -711,7 +710,7 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { gotoLabels, isMainProgramOutermostLoop); } - + // Restore dynamic variable stack for our localization if ((needLocalizeUnderscore || needLocalizeGlobalLoopVar) && dynamicIndex != -1) { mv.visitVarInsn(Opcodes.ILOAD, dynamicIndex); @@ -721,7 +720,7 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { "(I)V", false); } - + Local.localTeardown(localRecord, mv); emitterVisitor.ctx.symbolTable.exitScope(scopeIndex); @@ -747,12 +746,12 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { /** * Emits control flow handler for a foreach loop. * Handles marked RuntimeList objects (last/next/redo/goto) that propagate from nested calls. - * - * @param mv The MethodVisitor to emit bytecode to - * @param loopLabels The loop labels (controlFlowHandler, next, redo, last) - * @param parentLoopLabels The parent loop's labels (null if this is the outermost loop) - * @param returnLabel The subroutine's return label - * @param gotoLabels Map of goto label names to ASM Labels in current scope + * + * @param mv The MethodVisitor to emit bytecode to + * @param loopLabels The loop labels (controlFlowHandler, next, redo, last) + * @param parentLoopLabels The parent loop's labels (null if this is the outermost loop) + * @param returnLabel The subroutine's return label + * @param gotoLabels Map of goto label names to ASM Labels in current scope * @param isMainProgramOutermostLoop True if this is the outermost loop in main program (should throw error instead of returning) */ private static void emitControlFlowHandler( @@ -762,23 +761,23 @@ private static void emitControlFlowHandler( org.objectweb.asm.Label returnLabel, java.util.Map gotoLabels, boolean isMainProgramOutermostLoop) { - + if (!ENABLE_LOOP_HANDLERS) { return; // Feature not enabled yet } - + if (DEBUG_LOOP_CONTROL_FLOW) { System.out.println("[DEBUG] Emitting control flow handler for loop: " + loopLabels.labelName); } - + // Handler label mv.visitLabel(loopLabels.controlFlowHandler); - + // Stack: [RuntimeControlFlowList] - guaranteed clean by call site or parent handler - + // Cast to RuntimeControlFlowList (we know it's marked) mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); - + // Get control flow type (enum) mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, @@ -786,21 +785,21 @@ private static void emitControlFlowHandler( "getControlFlowType", "()Lorg/perlonjava/runtime/runtimetypes/ControlFlowType;", false); - + // Convert enum to ordinal for switch mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/ControlFlowType", "ordinal", "()I", false); - + // Tableswitch on control flow type Label handleLast = new Label(); Label handleNext = new Label(); Label handleRedo = new Label(); Label handleGoto = new Label(); Label propagateToParent = new Label(); - + mv.visitTableSwitchInsn( 0, // min (LAST.ordinal()) 3, // max (GOTO.ordinal()) @@ -810,22 +809,22 @@ private static void emitControlFlowHandler( handleRedo, // 2: REDO handleGoto // 3: GOTO ); - + // Handle LAST mv.visitLabel(handleLast); emitLabelCheck(mv, loopLabels.labelName, loopLabels.lastLabel, propagateToParent); // emitLabelCheck never returns (either matches and jumps to target, or jumps to noMatch) - + // Handle NEXT mv.visitLabel(handleNext); emitLabelCheck(mv, loopLabels.labelName, loopLabels.nextLabel, propagateToParent); // emitLabelCheck never returns - + // Handle REDO mv.visitLabel(handleRedo); emitLabelCheck(mv, loopLabels.labelName, loopLabels.redoLabel, propagateToParent); // emitLabelCheck never returns - + // Handle GOTO mv.visitLabel(handleGoto); if (gotoLabels != null && !gotoLabels.isEmpty()) { @@ -845,16 +844,16 @@ private static void emitControlFlowHandler( false); Label notThisGoto = new Label(); mv.visitJumpInsn(Opcodes.IFEQ, notThisGoto); - + // Match! Pop marked RuntimeList and jump to goto label mv.visitInsn(Opcodes.POP); mv.visitJumpInsn(Opcodes.GOTO, entry.getValue()); - + mv.visitLabel(notThisGoto); } } // Fall through to propagateToParent if no goto label matched - + // Propagate to parent handler or return mv.visitLabel(propagateToParent); if (parentLoopLabels != null && parentLoopLabels.controlFlowHandler != null) { @@ -863,23 +862,23 @@ private static void emitControlFlowHandler( } else if (isMainProgramOutermostLoop) { // Outermost loop in main program - throw error immediately // Stack: [RuntimeControlFlowList] - + // Cast to RuntimeControlFlowList mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); - + // Get the marker field mv.visitFieldInsn(Opcodes.GETFIELD, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", "marker", "Lorg/perlonjava/runtime/runtimetypes/ControlFlowMarker;"); - + // Call marker.throwError() - this method never returns mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/ControlFlowMarker", "throwError", "()V", false); - + // Should never reach here (method throws), but add RETURN for verifier mv.visitInsn(Opcodes.ACONST_NULL); mv.visitInsn(Opcodes.ARETURN); @@ -888,28 +887,28 @@ private static void emitControlFlowHandler( mv.visitJumpInsn(Opcodes.GOTO, returnLabel); } } - + /** * Emits bytecode to check if a label matches the current loop, and jump if it does. * If the label is null (unlabeled) or matches, POPs the marked RuntimeList and jumps to target. * Otherwise, falls through to next handler. - * - * @param mv The MethodVisitor + * + * @param mv The MethodVisitor * @param loopLabelName The name of the current loop (null if unlabeled) - * @param targetLabel The ASM Label to jump to if this label matches - * @param noMatch The label to jump to if the label doesn't match + * @param targetLabel The ASM Label to jump to if this label matches + * @param noMatch The label to jump to if the label doesn't match */ private static void emitLabelCheck( MethodVisitor mv, String loopLabelName, Label targetLabel, Label noMatch) { - + // SIMPLIFIED PATTERN to avoid ASM frame computation issues: // Use a helper method that returns boolean instead of complex branching - + // Stack: [RuntimeControlFlowList] - + // Call helper method: RuntimeControlFlowList.matchesLabel(String loopLabel) // This returns true if the control flow label matches the loop label mv.visitInsn(Opcodes.DUP); // Duplicate for use after check @@ -923,16 +922,16 @@ private static void emitLabelCheck( "matchesLabel", "(Ljava/lang/String;)Z", false); - + // Stack: [RuntimeControlFlowList] [boolean] - + // If match, pop and jump to target mv.visitJumpInsn(Opcodes.IFEQ, noMatch); - + // Match! Pop RuntimeControlFlowList and jump to target mv.visitInsn(Opcodes.POP); mv.visitJumpInsn(Opcodes.GOTO, targetLabel); - + // No match label is handled by caller (falls through) } @@ -1004,29 +1003,29 @@ private static void emitFor1AsWhileLoop(EmitterVisitor emitterVisitor, For1Node } } } - + /** * Emit bytecode to check RuntimeControlFlowRegistry and handle any registered control flow. * This is called after loop body execution to catch non-local control flow markers. - * - * @param mv The MethodVisitor + * + * @param mv The MethodVisitor * @param loopLabels The current loop's labels - * @param redoLabel The redo target - * @param nextLabel The next/continue target - * @param lastLabel The last/exit target + * @param redoLabel The redo target + * @param nextLabel The next/continue target + * @param lastLabel The last/exit target */ - private static void emitRegistryCheck(MethodVisitor mv, LoopLabels loopLabels, - Label redoLabel, Label nextLabel, Label lastLabel) { + private static void emitRegistryCheck(MethodVisitor mv, LoopLabels loopLabels, + Label redoLabel, Label nextLabel, Label lastLabel) { // ULTRA-SIMPLE pattern to avoid ASM issues: // Call a single helper method that does ALL the checking and returns an action code - + String labelName = loopLabels.labelName; if (labelName != null) { mv.visitLdcInsn(labelName); } else { mv.visitInsn(Opcodes.ACONST_NULL); } - + // Call: int action = RuntimeControlFlowRegistry.checkLoopAndGetAction(String labelName) // Returns: 0=none, 1=last, 2=next, 3=redo mv.visitMethodInsn(Opcodes.INVOKESTATIC, @@ -1034,7 +1033,7 @@ private static void emitRegistryCheck(MethodVisitor mv, LoopLabels loopLabels, "checkLoopAndGetAction", "(Ljava/lang/String;)I", false); - + // Use TABLESWITCH for clean bytecode. // IMPORTANT: action 0 means "no marker" and must *not* jump. Label noAction = new Label(); diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitFormat.java b/src/main/java/org/perlonjava/backend/jvm/EmitFormat.java index 113844224..ffdb6dfe1 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitFormat.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitFormat.java @@ -2,9 +2,8 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import org.perlonjava.frontend.astnode.*; -import org.perlonjava.frontend.astnode.FormatNode; import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.astnode.*; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; /** diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitLabel.java b/src/main/java/org/perlonjava/backend/jvm/EmitLabel.java index 799a35911..c71f4089d 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitLabel.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitLabel.java @@ -1,7 +1,7 @@ package org.perlonjava.backend.jvm; -import org.perlonjava.frontend.astnode.LabelNode; import org.objectweb.asm.Label; +import org.perlonjava.frontend.astnode.LabelNode; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; /** diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java index 2bd966f57..4b8546000 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitLogicalOperator.java @@ -3,12 +3,12 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import org.perlonjava.frontend.astnode.Node; +import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.analysis.FindDeclarationVisitor; import org.perlonjava.frontend.astnode.BinaryOperatorNode; +import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.frontend.astnode.TernaryOperatorNode; -import org.perlonjava.frontend.analysis.EmitterVisitor; -import org.perlonjava.frontend.analysis.FindDeclarationVisitor; import org.perlonjava.runtime.operators.ScalarFlipFlopOperator; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; @@ -232,9 +232,9 @@ static void emitLogicalOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod * Emits bytecode for the xor operator (low-precedence logical XOR). * XOR evaluates both operands (no short-circuit) and returns: * - left if left is true and right is false - * - right if left is false and right is true + * - right if left is false and right is true * - false otherwise - * + *

* Note: If the right operand is a control flow statement like 'next', * it will jump away and the xor operation will never complete. * diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java index 187f05a86..08867fc82 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java @@ -5,16 +5,11 @@ import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.analysis.ReturnTypeVisitor; import org.perlonjava.frontend.astnode.*; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.runtime.operators.OperatorHandler; import org.perlonjava.runtime.operators.ScalarGlobOperator; import org.perlonjava.runtime.perlmodule.Strict; -import org.perlonjava.runtime.runtimetypes.GlobalVariable; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.runtimetypes.NameNormalizer; -import org.perlonjava.runtime.runtimetypes.PerlCompilerException; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.runtime.runtimetypes.RuntimeDescriptorConstants; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; +import org.perlonjava.runtime.runtimetypes.*; /** * The EmitOperator class is responsible for handling various operators @@ -895,7 +890,7 @@ static void handleStatOperator(EmitterVisitor emitterVisitor, OperatorNode node, // stat/lstat have special scalar context behavior: // - Empty list (failure) -> "" (empty string) // - Non-empty list (success) -> 1 (true) - + if (node.operand instanceof IdentifierNode identNode && identNode.name.equals("_")) { // stat _ or lstat _ - still use the old methods since they don't take args @@ -927,7 +922,7 @@ static void handleStatOperator(EmitterVisitor emitterVisitor, OperatorNode node, operator, "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;I)Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;", false); - + // Cast to the appropriate type for the bytecode verifier if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) { // In scalar context, stat returns RuntimeScalar @@ -937,7 +932,7 @@ static void handleStatOperator(EmitterVisitor emitterVisitor, OperatorNode node, emitterVisitor.ctx.mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeList"); } // In RUNTIME or VOID context, leave as RuntimeBase (no cast needed) - + // Handle void context if (emitterVisitor.ctx.contextType == RuntimeContextType.VOID) { handleVoidContext(emitterVisitor); @@ -1267,7 +1262,7 @@ static void handleCreateReference(EmitterVisitor emitterVisitor, OperatorNode no // Function calls and scalar expressions should use SCALAR context // Arrays, hashes, and lists should use LIST context int contextType = RuntimeContextType.LIST; - + if (node.operand instanceof BinaryOperatorNode binOp && binOp.operator.equals("(")) { // Function call - use SCALAR context to get single return value contextType = RuntimeContextType.SCALAR; @@ -1275,9 +1270,9 @@ static void handleCreateReference(EmitterVisitor emitterVisitor, OperatorNode no // Scalar variable - use SCALAR context contextType = RuntimeContextType.SCALAR; } - + node.operand.accept(emitterVisitor.with(contextType)); - + // Always create a proper reference - don't special case CODE references emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorChained.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorChained.java index 84006066a..15c5f9b50 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorChained.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorChained.java @@ -2,9 +2,9 @@ import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; +import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.astnode.BinaryOperatorNode; import org.perlonjava.frontend.astnode.Node; -import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; import java.util.ArrayList; diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorFileTest.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorFileTest.java index ba429374c..979dc77c5 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorFileTest.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorFileTest.java @@ -1,11 +1,11 @@ package org.perlonjava.backend.jvm; import org.objectweb.asm.Opcodes; +import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.astnode.IdentifierNode; import org.perlonjava.frontend.astnode.ListNode; import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.astnode.OperatorNode; -import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; import java.util.ArrayList; diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorLocal.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorLocal.java index 39717b18d..b962a0ecd 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorLocal.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorLocal.java @@ -2,12 +2,12 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.analysis.LValueVisitor; import org.perlonjava.frontend.astnode.IdentifierNode; import org.perlonjava.frontend.astnode.ListNode; import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.astnode.OperatorNode; -import org.perlonjava.frontend.analysis.EmitterVisitor; -import org.perlonjava.frontend.analysis.LValueVisitor; import org.perlonjava.runtime.runtimetypes.NameNormalizer; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; @@ -15,7 +15,7 @@ public class EmitOperatorLocal { // Handles the 'local' operator. static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) { MethodVisitor mv = emitterVisitor.ctx.mv; - + // Check if this is a declared reference (local \$x) boolean isDeclaredReference = node.annotations != null && Boolean.TRUE.equals(node.annotations.get("isDeclaredReference")); @@ -34,7 +34,7 @@ static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) { "makeLocal", "(Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); - + // If this is a declared reference and not void context, create and return a reference if (isDeclaredReference && emitterVisitor.ctx.contextType != RuntimeContextType.VOID) { mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, @@ -66,7 +66,7 @@ static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) { } handleLocal(emitterVisitor.with(RuntimeContextType.VOID), new OperatorNode("local", varToLocalize, node.tokenIndex)); } - + // Return the list with references if isDeclaredReference is set if (emitterVisitor.ctx.contextType != RuntimeContextType.VOID) { if (isDeclaredReference) { @@ -74,7 +74,7 @@ static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) { mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "", "()V", false); - + for (Node child : listNode.elements) { // Handle both direct variables ($f) and declared refs (\$f) Node varNode = child; @@ -84,7 +84,7 @@ static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) { varNode = innerOp; } } - + if (varNode instanceof OperatorNode varOpNode && "$@%".contains(varOpNode.operator)) { mv.visitInsn(Opcodes.DUP); varNode.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); @@ -116,7 +116,7 @@ static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) { varToLocal = opNode.operand; lvalueContext = LValueVisitor.getContext(varToLocal); } - + varToLocal.accept(emitterVisitor.with(lvalueContext)); boolean isTypeglob = varToLocal instanceof OperatorNode operatorNode && operatorNode.operator.equals("*"); // save the old value @@ -139,7 +139,7 @@ static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) { "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } - + // If this is a declared reference and not void context, create and return a reference if (isDeclaredReference && emitterVisitor.ctx.contextType != RuntimeContextType.VOID) { mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorNode.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorNode.java index d195f90b2..f15614e8d 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorNode.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorNode.java @@ -1,7 +1,7 @@ package org.perlonjava.backend.jvm; -import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.runtime.perlmodule.Strict; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java b/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java index 493f85465..cea656d35 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java @@ -6,8 +6,8 @@ import org.perlonjava.runtime.runtimetypes.PerlCompilerException; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; - import java.util.ArrayList; - import java.util.HashMap; +import java.util.ArrayList; +import java.util.HashMap; /** * The EmitRegex class is responsible for handling regex-related operations @@ -129,7 +129,7 @@ static void handleNotBindRegex(EmitterVisitor emitterVisitor, BinaryOperatorNode static void handleSystemCommand(EmitterVisitor emitterVisitor, OperatorNode node) { EmitterVisitor scalarVisitor = emitterVisitor.with(RuntimeContextType.SCALAR); Node commandNode; - + // Handle two cases: // 1. readpipe() with no args -> operand is OperatorNode for $_ // 2. readpipe($expr) or `cmd` -> operand is ListNode with command @@ -140,7 +140,7 @@ static void handleSystemCommand(EmitterVisitor emitterVisitor, OperatorNode node // readpipe() with no arguments uses $_ commandNode = node.operand; } - + commandNode.accept(scalarVisitor); emitterVisitor.pushCallContext(); // Create an OperatorNode for systemCommand diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java b/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java index 6039ca387..a6677eb72 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java @@ -3,16 +3,16 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.analysis.RegexUsageDetector; import org.perlonjava.frontend.astnode.For3Node; import org.perlonjava.frontend.astnode.IfNode; import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.frontend.astnode.TryNode; +import org.perlonjava.runtime.runtimetypes.RuntimeContextType; import java.util.ArrayList; import java.util.List; -import org.perlonjava.frontend.analysis.EmitterVisitor; -import org.perlonjava.frontend.analysis.RegexUsageDetector; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; /** * The EmitStatement class is responsible for handling various control flow statements @@ -285,7 +285,7 @@ static void emitDoWhile(EmitterVisitor emitterVisitor, For3Node node) { // Visit the loop body node.body.accept(emitterVisitor.with(RuntimeContextType.VOID)); - + // Check RuntimeControlFlowRegistry for non-local control flow // Use the loop labels we created earlier (don't look them up) LoopLabels loopLabels = new LoopLabels( @@ -410,29 +410,29 @@ public static void emitTryCatch(EmitterVisitor emitterVisitor, TryNode node) { emitterVisitor.ctx.logDebug("emitTryCatch end"); } - + /** * Emit bytecode to check RuntimeControlFlowRegistry and handle any registered control flow. * This is called after loop body execution to catch non-local control flow markers. - * - * @param mv The MethodVisitor + * + * @param mv The MethodVisitor * @param loopLabels The current loop's labels - * @param redoLabel The redo target - * @param nextLabel The next/continue target - * @param lastLabel The last/exit target + * @param redoLabel The redo target + * @param nextLabel The next/continue target + * @param lastLabel The last/exit target */ - private static void emitRegistryCheck(MethodVisitor mv, LoopLabels loopLabels, - Label redoLabel, Label nextLabel, Label lastLabel) { + private static void emitRegistryCheck(MethodVisitor mv, LoopLabels loopLabels, + Label redoLabel, Label nextLabel, Label lastLabel) { // ULTRA-SIMPLE pattern to avoid ASM issues: // Call a single helper method that does ALL the checking and returns an action code - + String labelName = loopLabels.labelName; if (labelName != null) { mv.visitLdcInsn(labelName); } else { mv.visitInsn(Opcodes.ACONST_NULL); } - + // Call: int action = RuntimeControlFlowRegistry.checkLoopAndGetAction(String labelName) // Returns: 0=none, 1=last, 2=next, 3=redo mv.visitMethodInsn(Opcodes.INVOKESTATIC, @@ -440,7 +440,7 @@ private static void emitRegistryCheck(MethodVisitor mv, LoopLabels loopLabels, "checkLoopAndGetAction", "(Ljava/lang/String;)I", false); - + // Use TABLESWITCH for clean bytecode. // IMPORTANT: action 0 means "no marker" and must *not* jump. Label noAction = new Label(); diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java b/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java index 9a5ad24fd..41a855475 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java @@ -5,11 +5,11 @@ import org.objectweb.asm.Opcodes; import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.astnode.*; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; +import org.perlonjava.frontend.semantic.SymbolTable; import org.perlonjava.runtime.runtimetypes.NameNormalizer; import org.perlonjava.runtime.runtimetypes.RuntimeCode; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; -import org.perlonjava.frontend.semantic.SymbolTable; import java.util.Arrays; import java.util.Map; @@ -67,7 +67,7 @@ public class EmitSubroutine { // This works correctly but is less efficient for deeply nested loops crossing subroutines. // Performance impact is minimal since most control flow is local (uses plain JVM GOTO). private static final boolean ENABLE_CONTROL_FLOW_CHECKS = true; - + // Set to true to enable debug output for control flow checks private static final boolean DEBUG_CONTROL_FLOW = false; @@ -87,7 +87,7 @@ public static void emitSubroutine(EmitterContext ctx, SubroutineNode node) { // Retrieve closure variable list // Alternately, scan the AST for variables and capture only the ones that are used Map visibleVariables = ctx.symbolTable.getAllVisibleVariables(); - + // IMPORTANT: Package-level subs (named subs) should NOT capture closure variables from their // definition context. Only anonymous subs (my sub, state sub, or true anonymous subs) should // capture variables. This prevents issues like defining 'sub bar::foo' inside a block with @@ -108,18 +108,18 @@ public static void emitSubroutine(EmitterContext ctx, SubroutineNode node) { return false; }); } - + ctx.logDebug("AnonSub ctx.symbolTable.getAllVisibleVariables"); // Create a new symbol table for the subroutine, but manually add only the filtered variables ScopedSymbolTable newSymbolTable = new ScopedSymbolTable(); newSymbolTable.enterScope(); - + // Add only the filtered visible variables (excluding 'our sub' entries) for (SymbolTable.SymbolEntry entry : visibleVariables.values()) { newSymbolTable.addVariable(entry.name(), entry.decl(), entry.ast()); } - + // Copy package, subroutine, and flags from the current context newSymbolTable.setCurrentPackage(ctx.symbolTable.getCurrentPackage(), ctx.symbolTable.currentPackageIsClass()); newSymbolTable.setCurrentSubroutine(ctx.symbolTable.getCurrentSubroutine()); @@ -250,7 +250,7 @@ static void handleApplyOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod } node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); // Target - left parameter: Code ref - + // Dereference the scalar to get the CODE reference if needed // When we have &$x() the left side is OperatorNode("$") (the & is consumed by the parser) // We need to look up the CODE slot from the glob if the scalar contains a string. @@ -258,26 +258,26 @@ static void handleApplyOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod boolean isScalarVariable = false; boolean isLexicalSub = false; OperatorNode scalarOpNode = null; - + if (node.left instanceof OperatorNode operatorNode && operatorNode.operator.equals("$")) { // This is &$var() or $var->() syntax isScalarVariable = true; scalarOpNode = operatorNode; } else if (node.left instanceof BlockNode blockNode && - blockNode.elements.size() == 1 && - blockNode.elements.get(0) instanceof OperatorNode opNode && - opNode.operator.equals("$")) { + blockNode.elements.size() == 1 && + blockNode.elements.get(0) instanceof OperatorNode opNode && + opNode.operator.equals("$")) { // This is &{$var} syntax isScalarVariable = true; scalarOpNode = opNode; } - + if (isScalarVariable && scalarOpNode != null) { // Check if the variable is a lexical subroutine (already a CODE reference) // Lexical subs have a "hiddenVarName" annotation and should not be dereferenced String hiddenVarName = (String) scalarOpNode.getAnnotation("hiddenVarName"); isLexicalSub = (hiddenVarName != null); - + // Only call codeDerefNonStrict when strict refs is disabled AND not a lexical sub // This allows symbolic references like: my $x = "main::test"; &$x() if (!isLexicalSub && !emitterVisitor.ctx.symbolTable.isStrictOptionEnabled(HINT_STRICT_REFS)) { @@ -286,7 +286,7 @@ static void handleApplyOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod emitterVisitor.pushCurrentPackage(); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", - "codeDerefNonStrict", + "codeDerefNonStrict", "(Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } @@ -479,25 +479,25 @@ static void handleSelfCallOperator(EmitterVisitor emitterVisitor, OperatorNode n // Now we have a RuntimeScalar representing the current subroutine (__SUB__) EmitOperator.handleVoidContext(emitterVisitor); } - + /** * Emits bytecode to check if a RuntimeList returned from a subroutine call * is marked with control flow information (last/next/redo/goto/tail call). * If marked, cleans the stack and jumps to returnLabel. - * + *

* Pattern: - * DUP // Duplicate result for test - * INVOKEVIRTUAL isNonLocalGoto // Check if marked - * IFNE handleControlFlow // Jump if marked - * POP // Discard duplicate - * // Continue with result on stack - * - * handleControlFlow: - * ASTORE temp // Save marked result - * emitPopInstructions(0) // Clean stack - * ALOAD temp // Load marked result - * GOTO returnLabel // Jump to return point - * + * DUP // Duplicate result for test + * INVOKEVIRTUAL isNonLocalGoto // Check if marked + * IFNE handleControlFlow // Jump if marked + * POP // Discard duplicate + * // Continue with result on stack + *

+ * handleControlFlow: + * ASTORE temp // Save marked result + * emitPopInstructions(0) // Clean stack + * ALOAD temp // Load marked result + * GOTO returnLabel // Jump to return point + * * @param ctx The emitter context */ private static void emitControlFlowCheck(EmitterContext ctx) { @@ -508,27 +508,27 @@ private static void emitControlFlowCheck(EmitterContext ctx) { // Instead of complex branching with TABLESWITCH or multiple IFs, // call a single helper method that does all the checking and returns // either the original result or a marked RuntimeControlFlowList - + // Stack: [RuntimeList result] - + // Check the registry for any pending control flow // Get the innermost loop labels (if we're inside a loop) LoopLabels innermostLoop = ctx.javaClassInfo.getInnermostLoopLabels(); - + if (innermostLoop != null) { // We're inside a loop - check if non-local control flow was registered // Call helper: RuntimeControlFlowRegistry.checkAndWrapIfNeeded(result, labelName) // Returns: either the original result or a marked RuntimeControlFlowList - + // Stack: [RuntimeList result] - + // Push the label name (or null if no label) if (innermostLoop.labelName != null) { ctx.mv.visitLdcInsn(innermostLoop.labelName); } else { ctx.mv.visitInsn(Opcodes.ACONST_NULL); } - + // Call: RuntimeList result = RuntimeControlFlowRegistry.checkAndWrapIfNeeded(result, labelName) // This method checks the registry and returns either: // - The original result if no action (action == 0) @@ -539,7 +539,7 @@ private static void emitControlFlowCheck(EmitterContext ctx) { "(Lorg/perlonjava/runtime/runtimetypes/RuntimeList;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeList;", false); // Stack: [RuntimeList result_or_marked] - + // No branching needed! The helper method handles everything. // The result is either the original or a marked list. // The loop level will check if it's marked and handle it. @@ -551,13 +551,13 @@ private static void emitControlFlowCheck(EmitterContext ctx) { * Emits the block-level dispatcher code that handles control flow for all call sites * with the same visible loop state. * - * @param mv MethodVisitor to emit bytecode - * @param emitterVisitor The emitter visitor context + * @param mv MethodVisitor to emit bytecode + * @param emitterVisitor The emitter visitor context * @param blockDispatcher The label for this block dispatcher - * @param baseSpills Array of spill references that need to be cleaned up + * @param baseSpills Array of spill references that need to be cleaned up */ private static void emitBlockDispatcher(MethodVisitor mv, EmitterVisitor emitterVisitor, - Label blockDispatcher, JavaClassInfo.SpillRef[] baseSpills) { + Label blockDispatcher, JavaClassInfo.SpillRef[] baseSpills) { Label propagateToCaller = new Label(); Label checkLoopLabels = new Label(); diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java index 6e9fd6cac..d19c97dd9 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java @@ -6,10 +6,10 @@ import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.analysis.LValueVisitor; import org.perlonjava.frontend.astnode.*; +import org.perlonjava.frontend.semantic.SymbolTable; import org.perlonjava.runtime.perlmodule.Strict; import org.perlonjava.runtime.perlmodule.Warnings; import org.perlonjava.runtime.runtimetypes.*; -import org.perlonjava.frontend.semantic.SymbolTable; import java.util.ArrayList; import java.util.List; @@ -19,7 +19,7 @@ /** * Bytecode emitter for Perl variable operations. - * + * *

This class generates JVM bytecode for accessing and manipulating Perl variables, * including: *

    @@ -30,7 +30,7 @@ *
  • Array/hash element access: {@code $array[0]}, {@code $hash{key}}
  • *
  • Array/hash slices: {@code @array[0,1,2]}, {@code @hash{keys}}
  • *
- * + * *

The class handles several important Perl semantics: *

    *
  • Strict vars checking: Enforces {@code use strict 'vars'} by preventing @@ -42,7 +42,7 @@ *
  • Variable vivification: Auto-creates variables when needed (except under strict)
  • *
  • Context-sensitive access: Handles scalar vs list context appropriately
  • *
- * + * *

Key methods: *

    *
  • {@link #handleVariableOperator} - Main entry point for variable operations
  • @@ -110,10 +110,10 @@ private static boolean isBuiltinSpecialContainerVar(String sigil, String name) { /** * Emits bytecode to fetch a global (package) variable. - * + * *

    This method generates JVM bytecode to access global variables stored in the * {@link GlobalVariable} registry. It handles several important cases: - * + * *

    Strict Vars Enforcement

    * When {@code use strict 'vars'} is enabled and {@code createIfNotExists} is false, * this method enforces that only the following variables are allowed: @@ -121,11 +121,11 @@ private static boolean isBuiltinSpecialContainerVar(String sigil, String name) { *
  • Built-in special variables (checked via {@link #isBuiltinSpecialVariable})
  • *
  • Variables that were explicitly allowed by the caller
  • *
- * + * *

Note: The strict vars checking is done in the caller (handleVariableOperator) * before this method is called. This method only fetches variables that have been * determined to be accessible. - * + * *

Variable Types Handled

*
    *
  • Scalars ($): Calls {@code GlobalVariable.getGlobalVariable()}
  • @@ -133,12 +133,12 @@ private static boolean isBuiltinSpecialContainerVar(String sigil, String name) { *
  • Hashes (%): Calls {@code GlobalVariable.getGlobalHash()}
  • *
  • Stashes (%Package::): Calls {@code HashSpecialVariable.getStash()}
  • *
- * - * @param ctx the emitter context containing the method visitor and symbol table + * + * @param ctx the emitter context containing the method visitor and symbol table * @param createIfNotExists if true, allows variable creation; if false, enforces strict checking - * @param sigil the variable sigil ($, @, %) - * @param varName the variable name (without sigil, may include package qualifier) - * @param tokenIndex the token index for error reporting + * @param sigil the variable sigil ($, @, %) + * @param varName the variable name (without sigil, may include package qualifier) + * @param tokenIndex the token index for error reporting * @throws PerlCompilerException if strict vars is enabled and the variable is not allowed */ private static void fetchGlobalVariable(EmitterContext ctx, boolean createIfNotExists, String sigil, String varName, int tokenIndex) { @@ -222,10 +222,10 @@ private static void fetchGlobalVariable(EmitterContext ctx, boolean createIfNotE /** * Main entry point for emitting bytecode for variable operations. - * + * *

This method handles all forms of Perl variable access and generates appropriate * JVM bytecode. It distinguishes between: - * + * *

Variable Types

*
    *
  • Simple variables: {@code $var}, {@code @array}, {@code %hash}
  • @@ -233,7 +233,7 @@ private static void fetchGlobalVariable(EmitterContext ctx, boolean createIfNotE *
  • Code references: {@code &sub} (subroutine references)
  • *
  • Dereferencing: {@code $$ref}, {@code @$ref}, {@code %$ref}
  • *
- * + * *

Variable Storage

* Variables can be stored in two places: *
    @@ -241,7 +241,7 @@ private static void fetchGlobalVariable(EmitterContext ctx, boolean createIfNotE * in JVM local variable slots *
  • Global (package): Package variables stored in {@link GlobalVariable} registry
  • *
- * + * *

Strict Vars Logic

* The method computes {@code createIfNotExists} flag based on: *
    @@ -251,23 +251,23 @@ private static void fetchGlobalVariable(EmitterContext ctx, boolean createIfNotE *
  • Strict mode: {@code use strict 'vars'} (disallows undeclared globals)
  • *
  • Lexical declaration: {@code my/our/state} (allowed under strict)
  • *
- * + * *

Context Handling

* In scalar context, array/hash variables are automatically converted to scalar * using {@code RuntimeBase.scalar()}. - * + * * @param emitterVisitor the visitor containing the emitter context and method visitor - * @param node the OperatorNode representing the variable operation + * @param node the OperatorNode representing the variable operation */ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode node) { // In void context, don't emit any code if (emitterVisitor.ctx.contextType == RuntimeContextType.VOID) { return; } - + String sigil = node.operator; MethodVisitor mv = emitterVisitor.ctx.mv; - + // Case 1: Simple variable with identifier (most common case) // Examples: $var, @array, %hash, *glob, &sub if (node.operand instanceof IdentifierNode identifierNode) { // $a @a %a @@ -303,10 +303,10 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n // ===== SYMBOL TABLE LOOKUP ===== // Check if this variable is declared in the current lexical scope SymbolTable.SymbolEntry symbolEntry = emitterVisitor.ctx.symbolTable.getSymbolEntry(sigil + name); - + // Note: @_ is lexical in PerlOnJava (unlike standard Perl where it's package-scoped) boolean isDeclared = symbolEntry != null; - + // A variable is lexical if it was declared with my/our/state // These are stored in JVM local variable slots, not in GlobalVariable registry boolean isLexical = isDeclared && ( @@ -320,7 +320,7 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n if (!isLexical) { // ===== GLOBAL VARIABLE ACCESS ===== // This is not a lexically declared variable, so fetch it from the global registry - + // If there's a symbol entry (e.g., from 'our' declaration), use its package if (symbolEntry != null) { name = NameNormalizer.normalizeVariableName(name, symbolEntry.perlPackage()); @@ -328,7 +328,7 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n // ===== STRICT VARS LOGIC ===== // Determine if this variable should be allowed under 'use strict "vars"' - + // Special case: $a and $b are exempt from strict // (they're used by sort() without declaration) String normalizedName = NameNormalizer.normalizeVariableName(name, emitterVisitor.ctx.symbolTable.getCurrentPackage()); @@ -370,7 +370,7 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n || allowIfAlreadyExists || !emitterVisitor.ctx.symbolTable.isStrictOptionEnabled(HINT_STRICT_VARS) // no strict 'vars' || (isDeclared && isLexical); // Lexically declared (my/our/state) - + // Fetch the global variable (may throw exception if strict and not allowed) fetchGlobalVariable(emitterVisitor.ctx, createIfNotExists, sigil, name, node.getIndex()); } else { @@ -378,13 +378,13 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n // Variable is lexical (my/our/state), load it from JVM local variable slot mv.visitVarInsn(Opcodes.ALOAD, symbolEntry.index()); } - + // ===== CONTEXT CONVERSION ===== // In scalar context, convert array/hash to scalar (e.g., array length, hash key count) if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR && !sigil.equals("$")) { mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", "scalar", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } - + emitterVisitor.ctx.logDebug("GETVAR end " + symbolEntry); return; } @@ -474,7 +474,7 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n } else { // Regular case: `&$a` node.operand.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); - + // Check if the variable is a lexical subroutine (already a CODE reference) // Lexical subs have a "hiddenVarName" annotation and should not be dereferenced boolean isLexicalSub = false; @@ -482,7 +482,7 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n String hiddenVarName = (String) opNode.getAnnotation("hiddenVarName"); isLexicalSub = (hiddenVarName != null); } - + // Dereference the scalar to get the CODE reference if (!isLexicalSub) { // Not a lexical sub: call codeDerefNonStrict to look up CODE slot from glob if needed @@ -490,14 +490,14 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n emitterVisitor.pushCurrentPackage(); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", - "codeDerefNonStrict", + "codeDerefNonStrict", "(Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } } emitterVisitor.ctx.logDebug("EmitVariable: about to call RuntimeCode.apply for &$var"); - + mv.visitVarInsn(Opcodes.ALOAD, 1); // push @_ to stack emitterVisitor.pushCallContext(); // push call context to stack mv.visitMethodInsn( @@ -979,34 +979,34 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { // Check if this is a declared reference (my \($b, $c)) boolean isDeclaredReference = node.annotations != null && Boolean.TRUE.equals(node.annotations.get("isDeclaredReference")); - + if (isDeclaredReference) { // For declared references, return a list of references to the variables emitterVisitor.ctx.logDebug("handleMyOperator: isDeclaredReference=true, emitting references for list elements"); MethodVisitor mv = emitterVisitor.ctx.mv; - + // Create a new RuntimeList mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "", "()V", false); - + // For each element in the list, emit the variable and create a reference for (Node element : listNode.elements) { ctx.logDebug("handleMyOperator: processing element: " + element + ", class=" + element.getClass().getSimpleName()); if (element instanceof OperatorNode elemOpNode && "$@%".contains(elemOpNode.operator)) { ctx.logDebug("handleMyOperator: emitting createReference for " + elemOpNode.operator); mv.visitInsn(Opcodes.DUP); // Dup the RuntimeList - + // Emit the variable in SCALAR context element.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); - + // Create a reference to the variable mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", "createReference", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); - + // Add to the list mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", @@ -1019,39 +1019,39 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { // Check if any element has isDeclaredReference annotation boolean hasAnyDeclaredRef = false; for (Node element : listNode.elements) { - if (element instanceof OperatorNode elemOpNode && - elemOpNode.annotations != null && - Boolean.TRUE.equals(elemOpNode.annotations.get("isDeclaredReference"))) { + if (element instanceof OperatorNode elemOpNode && + elemOpNode.annotations != null && + Boolean.TRUE.equals(elemOpNode.annotations.get("isDeclaredReference"))) { hasAnyDeclaredRef = true; break; } } - + if (hasAnyDeclaredRef) { // Mixed case: some elements are declared refs, some are not // Build the list manually, emitting references for declared refs emitterVisitor.ctx.logDebug("handleMyOperator: hasAnyDeclaredRef=true, building mixed list"); MethodVisitor mv = emitterVisitor.ctx.mv; - + mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "", "()V", false); - + for (Node element : listNode.elements) { if (element instanceof OperatorNode elemOpNode && "$@%".contains(elemOpNode.operator)) { mv.visitInsn(Opcodes.DUP); element.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); - + // If this element has isDeclaredReference, create a reference - if (elemOpNode.annotations != null && - Boolean.TRUE.equals(elemOpNode.annotations.get("isDeclaredReference"))) { + if (elemOpNode.annotations != null && + Boolean.TRUE.equals(elemOpNode.annotations.get("isDeclaredReference"))) { mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", "createReference", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } - + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "add", @@ -1068,22 +1068,22 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { return; } else if (node.operand instanceof OperatorNode sigilNode) { // [my our] followed by [$ @ %] String sigil = sigilNode.operator; - + // Handle my \\$x - reference to a declared reference - if (sigil.equals("\\") && node.annotations != null && - Boolean.TRUE.equals(node.annotations.get("isDeclaredReference"))) { + if (sigil.equals("\\") && node.annotations != null && + Boolean.TRUE.equals(node.annotations.get("isDeclaredReference"))) { // This is my \\$x which means: create a declared reference and then take a reference to it // The operand is \$x, so we need to emit the declared reference creation // and then take a reference to it - + // First, emit the declared reference variable (the inner part) sigilNode.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); - + // The variable is now on the stack, and we're in an assignment context // The assignment operator will handle storing the reference return; } - + if ("$@%".contains(sigil)) { Node identifierNode = sigilNode.operand; if (identifierNode instanceof IdentifierNode) { // my $a @@ -1189,7 +1189,7 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { } // Store the variable in a JVM local variable emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, varIndex); - + // For declared references in non-void context, return a reference to the variable if (isDeclaredReference && emitterVisitor.ctx.contextType != RuntimeContextType.VOID) { // Load the variable back from the local variable slot diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitterContext.java b/src/main/java/org/perlonjava/backend/jvm/EmitterContext.java index 8cfac5550..97a9fd5a4 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitterContext.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitterContext.java @@ -4,10 +4,10 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.perlonjava.app.cli.CompilerOptions; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.runtime.runtimetypes.ErrorMessageUtil; import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java index 919750bfd..022791a80 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java @@ -4,30 +4,24 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.AnalyzerException; -import org.objectweb.asm.tree.analysis.BasicValue; -import org.objectweb.asm.tree.analysis.BasicInterpreter; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; +import org.objectweb.asm.tree.analysis.*; import org.objectweb.asm.util.CheckClassAdapter; import org.objectweb.asm.util.Printer; import org.objectweb.asm.util.TraceClassVisitor; -import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.backend.bytecode.BytecodeCompiler; import org.perlonjava.backend.bytecode.InterpretedCode; +import org.perlonjava.frontend.analysis.EmitterVisitor; import org.perlonjava.frontend.analysis.TempLocalCountVisitor; import org.perlonjava.frontend.astnode.BlockNode; import org.perlonjava.frontend.astnode.Node; import org.perlonjava.runtime.runtimetypes.*; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; import java.io.PrintWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.lang.annotation.Annotation; -import java.lang.reflect.*; /** * EmitterMethodCreator is a utility class that uses the ASM library to dynamically generate Java @@ -39,13 +33,16 @@ public class EmitterMethodCreator implements Opcodes { // Feature flags for control flow implementation // Set to true to enable tail call trampoline (Phase 3) private static final boolean ENABLE_TAILCALL_TRAMPOLINE = true; - + // Set to true to enable debug output for control flow private static final boolean DEBUG_CONTROL_FLOW = false; - + // Feature flag for interpreter fallback (enabled by default, can be disabled) + private static final boolean USE_INTERPRETER_FALLBACK = + System.getenv("JPERL_DISABLE_INTERPRETER_FALLBACK") == null; + private static final boolean SHOW_FALLBACK = + System.getenv("JPERL_SHOW_FALLBACK") != null; // Number of local variables to skip when processing a closure (this, @_, wantarray) public static int skipVariables = 3; - // Counter for generating unique class names public static int classCounter = 0; @@ -74,7 +71,7 @@ private static String insnToString(AbstractInsnNode n) { return opName + " " + tn.desc; } if (n instanceof org.objectweb.asm.tree.LdcInsnNode ln) { - return opName + " " + String.valueOf(ln.cst); + return opName + " " + ln.cst; } if (n instanceof org.objectweb.asm.tree.IntInsnNode in) { return opName + " " + in.operand; @@ -412,7 +409,7 @@ private static byte[] getBytecodeInternal(EmitterContext ctx, Node ast, boolean || asmDebugClassFilter.isEmpty() || className.contains(asmDebugClassFilter) || className.replace('/', '.').contains(asmDebugClassFilter); - + try { // Use capturedEnv if available (for eval), otherwise get from symbol table String[] env = (ctx.capturedEnv != null) ? ctx.capturedEnv : ctx.symbolTable.getVariableNames(); @@ -543,7 +540,7 @@ private static byte[] getBytecodeInternal(EmitterContext ctx, Node ast, boolean // actual number needed based on AST structure rather than a fixed count. int preInitTempLocalsStart = ctx.symbolTable.getCurrentLocalVariableIndex(); TempLocalCountVisitor tempCountVisitor = - new TempLocalCountVisitor(); + new TempLocalCountVisitor(); ast.accept(tempCountVisitor); int preInitTempLocalsCount = tempCountVisitor.getMaxTempCount() + 64; // Optimized: removed min-128 baseline for (int i = preInitTempLocalsStart; i < preInitTempLocalsStart + preInitTempLocalsCount; i++) { @@ -563,7 +560,7 @@ private static byte[] getBytecodeInternal(EmitterContext ctx, Node ast, boolean mv.visitVarInsn(Opcodes.ASTORE, tailCallCodeRefSlot); mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ASTORE, tailCallArgsSlot); - + // Allocate slot for control flow check temp storage // This is used at call sites to temporarily store marked RuntimeControlFlowList int controlFlowTempSlot = ctx.symbolTable.allocateLocalVariable(); @@ -587,7 +584,7 @@ private static byte[] getBytecodeInternal(EmitterContext ctx, Node ast, boolean mv.visitInsn(Opcodes.ACONST_NULL); mv.visitVarInsn(Opcodes.ASTORE, slot); } - + // Create a label for the return point ctx.javaClassInfo.returnLabel = new Label(); @@ -700,235 +697,235 @@ private static byte[] getBytecodeInternal(EmitterContext ctx, Node ast, boolean // Phase 3: Check for control flow markers // RuntimeList is on stack after getList() - + if (ENABLE_TAILCALL_TRAMPOLINE) { - // First, check if it's a TAILCALL (global trampoline) - Label tailcallLoop = new Label(); - Label notTailcall = new Label(); - Label normalReturn = new Label(); - - mv.visitVarInsn(Opcodes.ALOAD, returnListSlot); - mv.visitInsn(Opcodes.DUP); // Duplicate for checking - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/RuntimeList", - "isNonLocalGoto", - "()Z", - false); - mv.visitJumpInsn(Opcodes.IFEQ, normalReturn); // Not marked, return normally - - // Marked: check if TAILCALL - // Cast to RuntimeControlFlowList to access getControlFlowType() - mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); - mv.visitInsn(Opcodes.DUP); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", - "getControlFlowType", - "()Lorg/perlonjava/runtime/runtimetypes/ControlFlowType;", - false); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/ControlFlowType", - "ordinal", - "()I", - false); - mv.visitInsn(Opcodes.ICONST_4); // TAILCALL.ordinal() = 4 - mv.visitJumpInsn(Opcodes.IF_ICMPNE, notTailcall); - - // TAILCALL trampoline loop - mv.visitLabel(tailcallLoop); - // Cast to RuntimeControlFlowList to access getTailCallCodeRef/getTailCallArgs - mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); - - if (DEBUG_CONTROL_FLOW) { - // Debug: print what we're about to process - mv.visitInsn(Opcodes.DUP); - mv.visitFieldInsn(Opcodes.GETFIELD, - "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", - "marker", - "Lorg/perlonjava/runtime/runtimetypes/ControlFlowMarker;"); - mv.visitLdcInsn("TRAMPOLINE_LOOP"); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/ControlFlowMarker", - "debugPrint", - "(Ljava/lang/String;)V", - false); - } - - // Extract codeRef and args - // Use allocated slots from symbol table - mv.visitInsn(Opcodes.DUP); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", - "getTailCallCodeRef", - "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", - false); - mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.tailCallCodeRefSlot); - - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", - "getTailCallArgs", - "()Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;", - false); - mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.tailCallArgsSlot); - - // Re-invoke: RuntimeCode.apply(codeRef, "tailcall", args, context) - mv.visitVarInsn(Opcodes.ALOAD, ctx.javaClassInfo.tailCallCodeRefSlot); - mv.visitLdcInsn("tailcall"); - mv.visitVarInsn(Opcodes.ALOAD, ctx.javaClassInfo.tailCallArgsSlot); - mv.visitVarInsn(Opcodes.ILOAD, 2); // context (from parameter) - mv.visitMethodInsn(Opcodes.INVOKESTATIC, - "org/perlonjava/runtime/runtimetypes/RuntimeCode", - "apply", - "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;I)Lorg/perlonjava/runtime/runtimetypes/RuntimeList;", - false); - - // Check if result is another TAILCALL - mv.visitInsn(Opcodes.DUP); - - if (DEBUG_CONTROL_FLOW) { - // Debug: print the result before checking - mv.visitInsn(Opcodes.DUP); - mv.visitFieldInsn(Opcodes.GETSTATIC, - "java/lang/System", - "err", - "Ljava/io/PrintStream;"); - mv.visitInsn(Opcodes.SWAP); + // First, check if it's a TAILCALL (global trampoline) + Label tailcallLoop = new Label(); + Label notTailcall = new Label(); + Label normalReturn = new Label(); + + mv.visitVarInsn(Opcodes.ALOAD, returnListSlot); + mv.visitInsn(Opcodes.DUP); // Duplicate for checking mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "java/io/PrintStream", - "println", - "(Ljava/lang/Object;)V", + "org/perlonjava/runtime/runtimetypes/RuntimeList", + "isNonLocalGoto", + "()Z", false); - } - - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/RuntimeList", - "isNonLocalGoto", - "()Z", - false); - - if (DEBUG_CONTROL_FLOW) { - // Debug: print the isNonLocalGoto result + mv.visitJumpInsn(Opcodes.IFEQ, normalReturn); // Not marked, return normally + + // Marked: check if TAILCALL + // Cast to RuntimeControlFlowList to access getControlFlowType() + mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); mv.visitInsn(Opcodes.DUP); - mv.visitFieldInsn(Opcodes.GETSTATIC, - "java/lang/System", - "err", - "Ljava/io/PrintStream;"); - mv.visitInsn(Opcodes.SWAP); - mv.visitLdcInsn("isNonLocalGoto: "); - mv.visitInsn(Opcodes.SWAP); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "java/io/PrintStream", - "print", - "(Ljava/lang/String;)V", + "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", + "getControlFlowType", + "()Lorg/perlonjava/runtime/runtimetypes/ControlFlowType;", false); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "java/io/PrintStream", - "println", - "(Z)V", + "org/perlonjava/runtime/runtimetypes/ControlFlowType", + "ordinal", + "()I", false); - } - - mv.visitJumpInsn(Opcodes.IFEQ, normalReturn); // Not marked, done - - // Cast to RuntimeControlFlowList to access getControlFlowType() - mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); - mv.visitInsn(Opcodes.DUP); - - if (DEBUG_CONTROL_FLOW) { - // Debug: print the control flow type + mv.visitInsn(Opcodes.ICONST_4); // TAILCALL.ordinal() = 4 + mv.visitJumpInsn(Opcodes.IF_ICMPNE, notTailcall); + + // TAILCALL trampoline loop + mv.visitLabel(tailcallLoop); + // Cast to RuntimeControlFlowList to access getTailCallCodeRef/getTailCallArgs + mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); + + if (DEBUG_CONTROL_FLOW) { + // Debug: print what we're about to process + mv.visitInsn(Opcodes.DUP); + mv.visitFieldInsn(Opcodes.GETFIELD, + "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", + "marker", + "Lorg/perlonjava/runtime/runtimetypes/ControlFlowMarker;"); + mv.visitLdcInsn("TRAMPOLINE_LOOP"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/ControlFlowMarker", + "debugPrint", + "(Ljava/lang/String;)V", + false); + } + + // Extract codeRef and args + // Use allocated slots from symbol table mv.visitInsn(Opcodes.DUP); - mv.visitFieldInsn(Opcodes.GETFIELD, - "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", - "marker", - "Lorg/perlonjava/runtime/runtimetypes/ControlFlowMarker;"); - mv.visitLdcInsn("TRAMPOLINE_CHECK"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/ControlFlowMarker", - "debugPrint", - "(Ljava/lang/String;)V", + "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", + "getTailCallCodeRef", + "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); - } - - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", - "getControlFlowType", - "()Lorg/perlonjava/runtime/runtimetypes/ControlFlowType;", - false); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/ControlFlowType", - "ordinal", - "()I", - false); - mv.visitInsn(Opcodes.ICONST_4); - mv.visitJumpInsn(Opcodes.IF_ICMPEQ, tailcallLoop); // Loop if still TAILCALL - // Not TAILCALL: check if we're inside a loop and should jump to loop handler - mv.visitLabel(notTailcall); - if (useTryCatch) { - // For eval BLOCK, any marked non-TAILCALL result is an eval failure. - // Stack here: [RuntimeControlFlowList] - int msgSlot = ctx.symbolTable.allocateLocalVariable(); + mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.tailCallCodeRefSlot); - // msg = marker.buildErrorMessage() - mv.visitInsn(Opcodes.DUP); - mv.visitFieldInsn(Opcodes.GETFIELD, - "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", - "marker", - "Lorg/perlonjava/runtime/runtimetypes/ControlFlowMarker;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/ControlFlowMarker", - "buildErrorMessage", - "()Ljava/lang/String;", + "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", + "getTailCallArgs", + "()Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;", false); - mv.visitVarInsn(Opcodes.ASTORE, msgSlot); + mv.visitVarInsn(Opcodes.ASTORE, ctx.javaClassInfo.tailCallArgsSlot); - // $@ = msg - mv.visitLdcInsn("main::@"); - mv.visitVarInsn(Opcodes.ALOAD, msgSlot); + // Re-invoke: RuntimeCode.apply(codeRef, "tailcall", args, context) + mv.visitVarInsn(Opcodes.ALOAD, ctx.javaClassInfo.tailCallCodeRefSlot); + mv.visitLdcInsn("tailcall"); + mv.visitVarInsn(Opcodes.ALOAD, ctx.javaClassInfo.tailCallArgsSlot); + mv.visitVarInsn(Opcodes.ILOAD, 2); // context (from parameter) mv.visitMethodInsn(Opcodes.INVOKESTATIC, - "org/perlonjava/runtime/runtimetypes/GlobalVariable", - "setGlobalVariable", - "(Ljava/lang/String;Ljava/lang/String;)V", + "org/perlonjava/runtime/runtimetypes/RuntimeCode", + "apply", + "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;I)Lorg/perlonjava/runtime/runtimetypes/RuntimeList;", false); - // Replace marker with undef/empty list - mv.visitInsn(Opcodes.POP); - Label evalBlockList = new Label(); - Label evalBlockDone = new Label(); - mv.visitVarInsn(Opcodes.ILOAD, 2); - mv.visitInsn(Opcodes.ICONST_2); // RuntimeContextType.LIST - mv.visitJumpInsn(Opcodes.IF_ICMPEQ, evalBlockList); - - mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); - mv.visitInsn(Opcodes.DUP); - mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeScalar"); + // Check if result is another TAILCALL mv.visitInsn(Opcodes.DUP); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", "", "()V", false); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)V", false); - mv.visitJumpInsn(Opcodes.GOTO, evalBlockDone); - mv.visitLabel(evalBlockList); - mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); + if (DEBUG_CONTROL_FLOW) { + // Debug: print the result before checking + mv.visitInsn(Opcodes.DUP); + mv.visitFieldInsn(Opcodes.GETSTATIC, + "java/lang/System", + "err", + "Ljava/io/PrintStream;"); + mv.visitInsn(Opcodes.SWAP); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/io/PrintStream", + "println", + "(Ljava/lang/Object;)V", + false); + } + + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/RuntimeList", + "isNonLocalGoto", + "()Z", + false); + + if (DEBUG_CONTROL_FLOW) { + // Debug: print the isNonLocalGoto result + mv.visitInsn(Opcodes.DUP); + mv.visitFieldInsn(Opcodes.GETSTATIC, + "java/lang/System", + "err", + "Ljava/io/PrintStream;"); + mv.visitInsn(Opcodes.SWAP); + mv.visitLdcInsn("isNonLocalGoto: "); + mv.visitInsn(Opcodes.SWAP); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/io/PrintStream", + "print", + "(Ljava/lang/String;)V", + false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/io/PrintStream", + "println", + "(Z)V", + false); + } + + mv.visitJumpInsn(Opcodes.IFEQ, normalReturn); // Not marked, done + + // Cast to RuntimeControlFlowList to access getControlFlowType() + mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList"); mv.visitInsn(Opcodes.DUP); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "", "()V", false); - mv.visitLabel(evalBlockDone); - // Materialize return value in local slot and jump to endCatch with empty stack. - mv.visitVarInsn(Opcodes.ASTORE, returnListSlot); + if (DEBUG_CONTROL_FLOW) { + // Debug: print the control flow type + mv.visitInsn(Opcodes.DUP); + mv.visitFieldInsn(Opcodes.GETFIELD, + "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", + "marker", + "Lorg/perlonjava/runtime/runtimetypes/ControlFlowMarker;"); + mv.visitLdcInsn("TRAMPOLINE_CHECK"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/ControlFlowMarker", + "debugPrint", + "(Ljava/lang/String;)V", + false); + } - // Skip the success epilogue that clears $@. - // This path represents an eval failure (bad goto/other marker), - // so $@ must be preserved. - mv.visitJumpInsn(Opcodes.GOTO, endCatch); - } - // TODO: Check ctx.javaClassInfo loop stack, if non-empty, jump to innermost loop handler - // For now, just propagate (return to caller) - - // Normal return - mv.visitLabel(normalReturn); - - // The RuntimeList is currently on stack when coming from the trampoline checks. - // When jumping here from the initial isNonLocalGoto check, we need to reload it. - // To normalize both paths, store any on-stack value and then reload from the slot. - mv.visitVarInsn(Opcodes.ASTORE, returnListSlot); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", + "getControlFlowType", + "()Lorg/perlonjava/runtime/runtimetypes/ControlFlowType;", + false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/ControlFlowType", + "ordinal", + "()I", + false); + mv.visitInsn(Opcodes.ICONST_4); + mv.visitJumpInsn(Opcodes.IF_ICMPEQ, tailcallLoop); // Loop if still TAILCALL + // Not TAILCALL: check if we're inside a loop and should jump to loop handler + mv.visitLabel(notTailcall); + if (useTryCatch) { + // For eval BLOCK, any marked non-TAILCALL result is an eval failure. + // Stack here: [RuntimeControlFlowList] + int msgSlot = ctx.symbolTable.allocateLocalVariable(); + + // msg = marker.buildErrorMessage() + mv.visitInsn(Opcodes.DUP); + mv.visitFieldInsn(Opcodes.GETFIELD, + "org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList", + "marker", + "Lorg/perlonjava/runtime/runtimetypes/ControlFlowMarker;"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/ControlFlowMarker", + "buildErrorMessage", + "()Ljava/lang/String;", + false); + mv.visitVarInsn(Opcodes.ASTORE, msgSlot); + + // $@ = msg + mv.visitLdcInsn("main::@"); + mv.visitVarInsn(Opcodes.ALOAD, msgSlot); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, + "org/perlonjava/runtime/runtimetypes/GlobalVariable", + "setGlobalVariable", + "(Ljava/lang/String;Ljava/lang/String;)V", + false); + + // Replace marker with undef/empty list + mv.visitInsn(Opcodes.POP); + Label evalBlockList = new Label(); + Label evalBlockDone = new Label(); + mv.visitVarInsn(Opcodes.ILOAD, 2); + mv.visitInsn(Opcodes.ICONST_2); // RuntimeContextType.LIST + mv.visitJumpInsn(Opcodes.IF_ICMPEQ, evalBlockList); + + mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); + mv.visitInsn(Opcodes.DUP); + mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeScalar"); + mv.visitInsn(Opcodes.DUP); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", "", "()V", false); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)V", false); + mv.visitJumpInsn(Opcodes.GOTO, evalBlockDone); + + mv.visitLabel(evalBlockList); + mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); + mv.visitInsn(Opcodes.DUP); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/perlonjava/runtime/runtimetypes/RuntimeList", "", "()V", false); + mv.visitLabel(evalBlockDone); + + // Materialize return value in local slot and jump to endCatch with empty stack. + mv.visitVarInsn(Opcodes.ASTORE, returnListSlot); + + // Skip the success epilogue that clears $@. + // This path represents an eval failure (bad goto/other marker), + // so $@ must be preserved. + mv.visitJumpInsn(Opcodes.GOTO, endCatch); + } + // TODO: Check ctx.javaClassInfo loop stack, if non-empty, jump to innermost loop handler + // For now, just propagate (return to caller) + + // Normal return + mv.visitLabel(normalReturn); + + // The RuntimeList is currently on stack when coming from the trampoline checks. + // When jumping here from the initial isNonLocalGoto check, we need to reload it. + // To normalize both paths, store any on-stack value and then reload from the slot. + mv.visitVarInsn(Opcodes.ASTORE, returnListSlot); } // End of if (ENABLE_TAILCALL_TRAMPOLINE) if (useTryCatch) { @@ -1010,7 +1007,7 @@ private static byte[] getBytecodeInternal(EmitterContext ctx, Node ast, boolean // on the operand stack. mv.visitVarInsn(Opcodes.ALOAD, returnListSlot); } - + // Materialize $1, $&, etc. into concrete scalars BEFORE restoring regex state. // The return list may contain lazy ScalarSpecialVariable references; if we // restored first, they would resolve to the caller's (stale) values. @@ -1410,24 +1407,24 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE StringBuilder errorMsg = new StringBuilder(); errorMsg.append(String.format( "Unexpected runtime error during bytecode generation\n" + - "Class: %s\n" + - "Method: %s\n" + - "AST Node: %s\n" + - "Actual bytecode size: %d bytes (limit: 65535)\n" + - "Error: %s\n", + "Class: %s\n" + + "Method: %s\n" + + "AST Node: %s\n" + + "Actual bytecode size: %d bytes (limit: 65535)\n" + + "Error: %s\n", className, methodName, ast.getClass().getSimpleName(), classData != null ? classData.length : 0, e.getMessage() )); - + // Add refactoring information if available if (ast instanceof BlockNode) { BlockNode blockNode = (BlockNode) ast; Object estimatedSize = blockNode.getAnnotation("estimatedBytecodeSize"); Object skipReason = blockNode.getAnnotation("refactorSkipReason"); - + if (estimatedSize != null) { errorMsg.append(String.format("Estimated bytecode size: %s bytes\n", estimatedSize)); } @@ -1476,20 +1473,14 @@ public static Class loadBytecode(EmitterContext ctx, byte[] classData) { return loader.defineClass(javaClassNameDot, classData); } - // Feature flag for interpreter fallback (enabled by default, can be disabled) - private static final boolean USE_INTERPRETER_FALLBACK = - System.getenv("JPERL_DISABLE_INTERPRETER_FALLBACK") == null; - private static final boolean SHOW_FALLBACK = - System.getenv("JPERL_SHOW_FALLBACK") != null; - /** * Unified factory method that returns RuntimeCode (either CompiledCode or InterpretedCode). - * + *

* This is the NEW API that replaces createClassWithMethod() for most use cases. * It handles the "Method too large" exception by falling back to the interpreter. * The interpreter fallback is ENABLED BY DEFAULT and can be disabled by setting * JPERL_DISABLE_INTERPRETER_FALLBACK environment variable. - * + *

* DESIGN: * - Try compiler first (createClassWithMethod) * - On MethodTooLargeException: fall back to interpreter (unless disabled) @@ -1550,7 +1541,7 @@ public static RuntimeCode createRuntimeCode( /** * Wrap a compiled Class as CompiledCode. - * + *

* This performs the same reflection steps that SubroutineParser.java currently does: * 1. Get constructor * 2. Create instance (codeObject) @@ -1592,7 +1583,7 @@ private static CompiledCode wrapAsCompiledCode(Class generatedClass, EmitterC // Get MethodHandle for apply method methodHandle = RuntimeCode.lookup.findVirtual( - generatedClass, "apply", RuntimeCode.methodType + generatedClass, "apply", RuntimeCode.methodType ); // Set __SUB__ field @@ -1613,13 +1604,13 @@ private static CompiledCode wrapAsCompiledCode(Class generatedClass, EmitterC } catch (Exception e) { throw new PerlCompilerException( - "Failed to wrap compiled class: " + e.getMessage()); + "Failed to wrap compiled class: " + e.getMessage()); } } /** * Compile AST to interpreter bytecode. - * + *

* This is the fallback path when JVM bytecode generation hits the 65535 byte limit. * The interpreter has no size limits because it doesn't generate JVM bytecode. * @@ -1633,9 +1624,9 @@ private static boolean needsInterpreterFallback(Throwable e) { String msg = t.getMessage(); if (msg != null && ( msg.contains("ASM frame computation failed") || - msg.contains("requires interpreter fallback") || - msg.contains("Unexpected runtime error during bytecode generation") || - msg.contains("dstFrame"))) { + msg.contains("requires interpreter fallback") || + msg.contains("Unexpected runtime error during bytecode generation") || + msg.contains("dstFrame"))) { return true; } } @@ -1654,11 +1645,11 @@ private static InterpretedCode compileToInterpreter( // Create bytecode compiler BytecodeCompiler compiler = - new BytecodeCompiler( - ctx.errorUtil.getFileName(), - 1, // line number - ctx.errorUtil - ); + new BytecodeCompiler( + ctx.errorUtil.getFileName(), + 1, // line number + ctx.errorUtil + ); // Compile AST to interpreter bytecode (pass ctx for package context and closure detection) InterpretedCode code = compiler.compile(ast, ctx); @@ -1673,7 +1664,7 @@ private static InterpretedCode compileToInterpreter( // Note: This is a simplified version - full implementation would need to // access the actual RuntimeBase objects from the symbol table RuntimeBase[] capturedVars = - new RuntimeBase[ctx.capturedEnv.length - skipVariables]; + new RuntimeBase[ctx.capturedEnv.length - skipVariables]; // For now, initialize with undef (actual values will be set by caller) for (int i = 0; i < capturedVars.length; i++) { diff --git a/src/main/java/org/perlonjava/backend/jvm/GotoLabels.java b/src/main/java/org/perlonjava/backend/jvm/GotoLabels.java index cfb5a74b8..90aad8ff0 100644 --- a/src/main/java/org/perlonjava/backend/jvm/GotoLabels.java +++ b/src/main/java/org/perlonjava/backend/jvm/GotoLabels.java @@ -20,8 +20,8 @@ public class GotoLabels { /** * Creates a new GotoLabels instance. * - * @param labelName The name of the label in source code - * @param gotoLabel The ASM Label object for bytecode generation + * @param labelName The name of the label in source code + * @param gotoLabel The ASM Label object for bytecode generation */ public GotoLabels(String labelName, Label gotoLabel) { this.labelName = labelName; diff --git a/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java b/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java index 148680e33..e6b538036 100644 --- a/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java +++ b/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java @@ -30,22 +30,22 @@ public class JavaClassInfo { /** * Local variable slot used to spill the value returned by `return`, `goto` markers, * and other non-local control-flow paths before jumping to {@link #returnLabel}. - * + *

* The {@link #returnLabel} join point must be stack-neutral: no incoming edge may * rely on operand stack contents. */ public int returnValueSlot; - + /** * Local variable slot for tail call trampoline - stores codeRef. */ public int tailCallCodeRefSlot; - + /** * Local variable slot for tail call trampoline - stores args. */ public int tailCallArgsSlot; - + /** * Local variable slot for temporarily storing marked RuntimeControlFlowList during call-site checks. */ @@ -55,24 +55,11 @@ public class JavaClassInfo { public int[] spillSlots; public int spillTop; - - public static final class SpillRef { - public final int slot; - public final boolean pooled; - - public SpillRef(int slot, boolean pooled) { - this.slot = slot; - this.pooled = pooled; - } - } - /** * A stack of loop labels for managing nested loops. */ public Deque loopLabelStack; - public Deque gotoLabelStack; - /** * Map of loop state signature to block-level dispatcher label. * Allows multiple call sites with the same visible loops to share one dispatcher. @@ -152,12 +139,12 @@ public void pushLoopLabels(String labelName, Label nextLabel, Label redoLabel, L /** * Pushes a new set of loop labels with isTrueLoop flag. * - * @param labelName the name of the loop label - * @param nextLabel the label for the next iteration - * @param redoLabel the label for redoing the current iteration - * @param lastLabel the label for exiting the loop - * @param context the context type - * @param isTrueLoop whether this is a true loop (for/while/until) or pseudo-loop (do-while/bare) + * @param labelName the name of the loop label + * @param nextLabel the label for the next iteration + * @param redoLabel the label for redoing the current iteration + * @param lastLabel the label for exiting the loop + * @param context the context type + * @param isTrueLoop whether this is a true loop (for/while/until) or pseudo-loop (do-while/bare) */ public void pushLoopLabels(String labelName, Label nextLabel, Label redoLabel, Label lastLabel, int context, boolean isTrueLoop) { loopLabelStack.push(new LoopLabels(labelName, nextLabel, redoLabel, lastLabel, context, isTrueLoop)); @@ -166,7 +153,7 @@ public void pushLoopLabels(String labelName, Label nextLabel, Label redoLabel, L public void pushLoopLabels(String labelName, Label nextLabel, Label redoLabel, Label lastLabel, int context, boolean isTrueLoop, boolean isUnlabeledControlFlowTarget) { loopLabelStack.push(new LoopLabels(labelName, nextLabel, redoLabel, lastLabel, context, isTrueLoop, isUnlabeledControlFlowTarget)); } - + /** * Pushes a LoopLabels object onto the loop label stack. * This is useful when you've already constructed a LoopLabels object with a control flow handler. @@ -185,7 +172,7 @@ public void pushLoopLabels(LoopLabels loopLabels) { public LoopLabels popLoopLabels() { return loopLabelStack.pop(); } - + /** * Gets the innermost (current) loop labels. * Returns null if not currently inside a loop. @@ -300,4 +287,14 @@ public String toString() { " gotoLabelStack=" + gotoLabelStack + "\n" + "}"; } + + public static final class SpillRef { + public final int slot; + public final boolean pooled; + + public SpillRef(int slot, boolean pooled) { + this.slot = slot; + this.pooled = pooled; + } + } } diff --git a/src/main/java/org/perlonjava/backend/jvm/Local.java b/src/main/java/org/perlonjava/backend/jvm/Local.java index b359566bf..91d9dd681 100644 --- a/src/main/java/org/perlonjava/backend/jvm/Local.java +++ b/src/main/java/org/perlonjava/backend/jvm/Local.java @@ -2,8 +2,8 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.analysis.FindDeclarationVisitor; +import org.perlonjava.frontend.astnode.Node; public class Local { diff --git a/src/main/java/org/perlonjava/backend/jvm/LoopLabels.java b/src/main/java/org/perlonjava/backend/jvm/LoopLabels.java index 81b9ffff2..d64910927 100644 --- a/src/main/java/org/perlonjava/backend/jvm/LoopLabels.java +++ b/src/main/java/org/perlonjava/backend/jvm/LoopLabels.java @@ -27,7 +27,7 @@ public class LoopLabels { * The ASM Label for the 'last' statement (exits the loop) */ public Label lastLabel; - + /** * The ASM Label for the control flow handler (processes marked RuntimeList) * This handler checks the control flow type and label, then either handles @@ -48,11 +48,11 @@ public class LoopLabels { /** * Whether unlabeled next/last/redo should target this loop/block. - * + *

* Perl semantics: * - Unlabeled next/last/redo target the nearest enclosing true loop. * - Labeled next/last/redo can target labeled blocks (e.g. next SKIP in SKIP: { ... }). - * + *

* We keep block loops on the stack so labeled control flow can find them, * but prevent them from being selected as the target for unlabeled control flow. */ @@ -61,25 +61,25 @@ public class LoopLabels { /** * Creates a new LoopLabels instance with all necessary label information. * - * @param labelName The name of the loop label in source code - * @param nextLabel The ASM Label for 'next' operations - * @param redoLabel The ASM Label for 'redo' operations - * @param lastLabel The ASM Label for 'last' operations - * @param context The context type for this loop + * @param labelName The name of the loop label in source code + * @param nextLabel The ASM Label for 'next' operations + * @param redoLabel The ASM Label for 'redo' operations + * @param lastLabel The ASM Label for 'last' operations + * @param context The context type for this loop */ public LoopLabels(String labelName, Label nextLabel, Label redoLabel, Label lastLabel, int context) { this(labelName, nextLabel, redoLabel, lastLabel, context, true, true); } - + /** * Creates a new LoopLabels instance with all necessary label information. * - * @param labelName The name of the loop label in source code - * @param nextLabel The ASM Label for 'next' operations - * @param redoLabel The ASM Label for 'redo' operations - * @param lastLabel The ASM Label for 'last' operations - * @param context The context type for this loop - * @param isTrueLoop Whether this is a true loop (for/while/until) or pseudo-loop (do-while/bare) + * @param labelName The name of the loop label in source code + * @param nextLabel The ASM Label for 'next' operations + * @param redoLabel The ASM Label for 'redo' operations + * @param lastLabel The ASM Label for 'last' operations + * @param context The context type for this loop + * @param isTrueLoop Whether this is a true loop (for/while/until) or pseudo-loop (do-while/bare) */ public LoopLabels(String labelName, Label nextLabel, Label redoLabel, Label lastLabel, int context, boolean isTrueLoop) { this(labelName, nextLabel, redoLabel, lastLabel, context, isTrueLoop, true); diff --git a/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeBlockRefactorer.java b/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeBlockRefactorer.java index c4b38fc62..b3c4f7bff 100644 --- a/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeBlockRefactorer.java +++ b/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeBlockRefactorer.java @@ -1,13 +1,13 @@ package org.perlonjava.backend.jvm.astrefactor; -import org.perlonjava.frontend.astnode.BinaryOperatorNode; -import org.perlonjava.frontend.astnode.BlockNode; -import org.perlonjava.frontend.astnode.LabelNode; -import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.analysis.BytecodeSizeEstimator; import org.perlonjava.frontend.analysis.ControlFlowDetectorVisitor; import org.perlonjava.frontend.analysis.ControlFlowFinder; import org.perlonjava.frontend.analysis.EmitterVisitor; +import org.perlonjava.frontend.astnode.BinaryOperatorNode; +import org.perlonjava.frontend.astnode.BlockNode; +import org.perlonjava.frontend.astnode.LabelNode; +import org.perlonjava.frontend.astnode.Node; import org.perlonjava.frontend.parser.Parser; import java.util.ArrayDeque; diff --git a/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeNodeRefactorer.java b/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeNodeRefactorer.java index 23d5ac393..292c5874c 100644 --- a/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeNodeRefactorer.java +++ b/src/main/java/org/perlonjava/backend/jvm/astrefactor/LargeNodeRefactorer.java @@ -1,9 +1,9 @@ package org.perlonjava.backend.jvm.astrefactor; +import org.perlonjava.frontend.analysis.BytecodeSizeEstimator; import org.perlonjava.frontend.astnode.LabelNode; import org.perlonjava.frontend.astnode.ListNode; import org.perlonjava.frontend.astnode.Node; -import org.perlonjava.frontend.analysis.BytecodeSizeEstimator; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/perlonjava/frontend/analysis/ControlFlowDetectorVisitor.java b/src/main/java/org/perlonjava/frontend/analysis/ControlFlowDetectorVisitor.java index 1fbacdd0f..3b09051d4 100644 --- a/src/main/java/org/perlonjava/frontend/analysis/ControlFlowDetectorVisitor.java +++ b/src/main/java/org/perlonjava/frontend/analysis/ControlFlowDetectorVisitor.java @@ -9,10 +9,10 @@ * that could potentially jump outside of a refactored block. */ public class ControlFlowDetectorVisitor implements Visitor { + private static final boolean DEBUG = "1".equals(System.getenv("JPERL_TRACE_CONTROLFLOW")); private boolean hasUnsafeControlFlow = false; private int loopDepth = 0; private Set allowedGotoLabels = null; - private static final boolean DEBUG = "1".equals(System.getenv("JPERL_TRACE_CONTROLFLOW")); /** * Check if unsafe control flow was detected during traversal. @@ -82,7 +82,8 @@ public void scan(Node root) { String oper = op.operator; if ("return".equals(oper)) { - if (DEBUG) System.err.println("ControlFlowDetector(scan): UNSAFE return at tokenIndex=" + op.tokenIndex); + if (DEBUG) + System.err.println("ControlFlowDetector(scan): UNSAFE return at tokenIndex=" + op.tokenIndex); hasUnsafeControlFlow = true; continue; } @@ -91,14 +92,17 @@ public void scan(Node root) { if (allowedGotoLabels != null && op.operand instanceof ListNode labelNode && !labelNode.elements.isEmpty()) { Node arg = labelNode.elements.getFirst(); if (arg instanceof IdentifierNode identifierNode && allowedGotoLabels.contains(identifierNode.name)) { - if (DEBUG) System.err.println("ControlFlowDetector(scan): goto " + identifierNode.name + " allowed (in allowedGotoLabels)"); + if (DEBUG) + System.err.println("ControlFlowDetector(scan): goto " + identifierNode.name + " allowed (in allowedGotoLabels)"); } else { - if (DEBUG) System.err.println("ControlFlowDetector(scan): UNSAFE goto at tokenIndex=" + op.tokenIndex); + if (DEBUG) + System.err.println("ControlFlowDetector(scan): UNSAFE goto at tokenIndex=" + op.tokenIndex); hasUnsafeControlFlow = true; continue; } } else { - if (DEBUG) System.err.println("ControlFlowDetector(scan): UNSAFE goto at tokenIndex=" + op.tokenIndex); + if (DEBUG) + System.err.println("ControlFlowDetector(scan): UNSAFE goto at tokenIndex=" + op.tokenIndex); hasUnsafeControlFlow = true; continue; } @@ -115,11 +119,13 @@ public void scan(Node root) { } if (isLabeled) { - if (DEBUG) System.err.println("ControlFlowDetector(scan): UNSAFE " + oper + " (labeled) at tokenIndex=" + op.tokenIndex + " label=" + label); + if (DEBUG) + System.err.println("ControlFlowDetector(scan): UNSAFE " + oper + " (labeled) at tokenIndex=" + op.tokenIndex + " label=" + label); hasUnsafeControlFlow = true; continue; } else if (currentLoopDepth == 0) { - if (DEBUG) System.err.println("ControlFlowDetector(scan): UNSAFE " + oper + " at tokenIndex=" + op.tokenIndex + " loopDepth=" + currentLoopDepth + " isLabeled=" + isLabeled + " label=" + label); + if (DEBUG) + System.err.println("ControlFlowDetector(scan): UNSAFE " + oper + " at tokenIndex=" + op.tokenIndex + " loopDepth=" + currentLoopDepth + " isLabeled=" + isLabeled + " label=" + label); hasUnsafeControlFlow = true; continue; } @@ -956,7 +962,8 @@ public void visit(OperatorNode node) { if (allowedGotoLabels != null && node.operand instanceof ListNode labelNode && !labelNode.elements.isEmpty()) { Node arg = labelNode.elements.getFirst(); if (arg instanceof IdentifierNode identifierNode && allowedGotoLabels.contains(identifierNode.name)) { - if (DEBUG) System.err.println("ControlFlowDetector: goto " + identifierNode.name + " allowed (in allowedGotoLabels)"); + if (DEBUG) + System.err.println("ControlFlowDetector: goto " + identifierNode.name + " allowed (in allowedGotoLabels)"); return; } } @@ -974,14 +981,16 @@ public void visit(OperatorNode node) { } } if ("next".equals(node.operator) && isLabeled) { - if (DEBUG) System.err.println("ControlFlowDetector: safe labeled next at tokenIndex=" + node.tokenIndex + " label=" + label); - } else - if (loopDepth == 0 || isLabeled) { - if (DEBUG) System.err.println("ControlFlowDetector: UNSAFE " + node.operator + " at tokenIndex=" + node.tokenIndex + " loopDepth=" + loopDepth + " isLabeled=" + isLabeled + " label=" + label); + if (DEBUG) + System.err.println("ControlFlowDetector: safe labeled next at tokenIndex=" + node.tokenIndex + " label=" + label); + } else if (loopDepth == 0 || isLabeled) { + if (DEBUG) + System.err.println("ControlFlowDetector: UNSAFE " + node.operator + " at tokenIndex=" + node.tokenIndex + " loopDepth=" + loopDepth + " isLabeled=" + isLabeled + " label=" + label); hasUnsafeControlFlow = true; return; } - if (DEBUG) System.err.println("ControlFlowDetector: safe " + node.operator + " at tokenIndex=" + node.tokenIndex + " loopDepth=" + loopDepth); + if (DEBUG) + System.err.println("ControlFlowDetector: safe " + node.operator + " at tokenIndex=" + node.tokenIndex + " loopDepth=" + loopDepth); } if (hasUnsafeControlFlow) { return; diff --git a/src/main/java/org/perlonjava/frontend/analysis/DepthFirstLiteralRefactorVisitor.java b/src/main/java/org/perlonjava/frontend/analysis/DepthFirstLiteralRefactorVisitor.java index 60cf24956..490edf04f 100644 --- a/src/main/java/org/perlonjava/frontend/analysis/DepthFirstLiteralRefactorVisitor.java +++ b/src/main/java/org/perlonjava/frontend/analysis/DepthFirstLiteralRefactorVisitor.java @@ -72,14 +72,14 @@ public void visit(ListNode node) { if (DEBUG) { System.err.println("DEBUG: Refactoring ListNode with " + node.elements.size() + " elements"); System.err.println("DEBUG: First few elements: " + - node.elements.stream().limit(3).map(Node::toString).collect(java.util.stream.Collectors.joining(", "))); + node.elements.stream().limit(3).map(Node::toString).collect(java.util.stream.Collectors.joining(", "))); } List original = node.elements; node.elements = LargeNodeRefactorer.forceRefactorElements(node.elements, node.getIndex()); if (DEBUG) { System.err.println("DEBUG: After refactoring: " + node.elements.size() + " elements"); System.err.println("DEBUG: Refactored structure: " + node.elements.stream().limit(2) - .map(n -> n.getClass().getSimpleName()).collect(java.util.stream.Collectors.joining(", "))); + .map(n -> n.getClass().getSimpleName()).collect(java.util.stream.Collectors.joining(", "))); } } } diff --git a/src/main/java/org/perlonjava/frontend/analysis/PrintVisitor.java b/src/main/java/org/perlonjava/frontend/analysis/PrintVisitor.java index fba0bc486..090cb1cc3 100644 --- a/src/main/java/org/perlonjava/frontend/analysis/PrintVisitor.java +++ b/src/main/java/org/perlonjava/frontend/analysis/PrintVisitor.java @@ -3,9 +3,9 @@ import org.perlonjava.frontend.astnode.*; import org.perlonjava.runtime.perlmodule.Strict; -import static org.perlonjava.runtime.runtimetypes.ScalarUtils.printable; import static org.perlonjava.frontend.semantic.ScopedSymbolTable.stringifyFeatureFlags; import static org.perlonjava.frontend.semantic.ScopedSymbolTable.stringifyWarningFlags; +import static org.perlonjava.runtime.runtimetypes.ScalarUtils.printable; /* * diff --git a/src/main/java/org/perlonjava/frontend/analysis/RegexUsageDetector.java b/src/main/java/org/perlonjava/frontend/analysis/RegexUsageDetector.java index e2337f8e2..dd7814604 100644 --- a/src/main/java/org/perlonjava/frontend/analysis/RegexUsageDetector.java +++ b/src/main/java/org/perlonjava/frontend/analysis/RegexUsageDetector.java @@ -20,10 +20,14 @@ */ public class RegexUsageDetector { - /** Unary operators that perform regex matching/substitution. */ + /** + * Unary operators that perform regex matching/substitution. + */ private static final java.util.Set REGEX_OPERATORS = java.util.Set.of("matchRegex", "replaceRegex"); - /** Binary operators that perform regex matching (=~, !~) or use regex internally (split). */ + /** + * Binary operators that perform regex matching (=~, !~) or use regex internally (split). + */ private static final java.util.Set REGEX_BINARY_OPERATORS = java.util.Set.of("=~", "!~", "split"); diff --git a/src/main/java/org/perlonjava/frontend/analysis/TempLocalCountVisitor.java b/src/main/java/org/perlonjava/frontend/analysis/TempLocalCountVisitor.java index d6e97e032..421af43fa 100644 --- a/src/main/java/org/perlonjava/frontend/analysis/TempLocalCountVisitor.java +++ b/src/main/java/org/perlonjava/frontend/analysis/TempLocalCountVisitor.java @@ -5,7 +5,7 @@ /** * Visitor that counts the maximum number of temporary local variables * that will be needed during bytecode emission. - * + *

* This is used to pre-initialize the correct number of slots to avoid * VerifyError when slots are in TOP state. */ @@ -105,13 +105,16 @@ public void visit(SubroutineNode node) { // Default implementations for other node types @Override - public void visit(IdentifierNode node) {} + public void visit(IdentifierNode node) { + } @Override - public void visit(NumberNode node) {} + public void visit(NumberNode node) { + } @Override - public void visit(StringNode node) {} + public void visit(StringNode node) { + } @Override public void visit(HashLiteralNode node) { @@ -158,8 +161,10 @@ public void visit(LabelNode node) { } @Override - public void visit(CompilerFlagNode node) {} + public void visit(CompilerFlagNode node) { + } @Override - public void visit(FormatNode node) {} + public void visit(FormatNode node) { + } } diff --git a/src/main/java/org/perlonjava/frontend/astnode/AbstractNode.java b/src/main/java/org/perlonjava/frontend/astnode/AbstractNode.java index bdeab0836..27a15f1ae 100644 --- a/src/main/java/org/perlonjava/frontend/astnode/AbstractNode.java +++ b/src/main/java/org/perlonjava/frontend/astnode/AbstractNode.java @@ -13,16 +13,13 @@ * It also provides deep toString() formatting using PrintVisitor */ public abstract class AbstractNode implements Node { + private static final int FLAG_BLOCK_ALREADY_REFACTORED = 1; + private static final int FLAG_QUEUED_FOR_REFACTOR = 2; + private static final int FLAG_CHUNK_ALREADY_REFACTORED = 4; public int tokenIndex; - // Lazy initialization - only created when first annotation is set public Map annotations; - private int internalAnnotationFlags; - private static final int FLAG_BLOCK_ALREADY_REFACTORED = 1; - private static final int FLAG_QUEUED_FOR_REFACTOR = 2; - private static final int FLAG_CHUNK_ALREADY_REFACTORED = 4; - private int cachedBytecodeSize = Integer.MIN_VALUE; private byte cachedHasAnyControlFlow = -1; diff --git a/src/main/java/org/perlonjava/frontend/astnode/ArrayLiteralNode.java b/src/main/java/org/perlonjava/frontend/astnode/ArrayLiteralNode.java index 54b6fed57..8ee83c6c2 100644 --- a/src/main/java/org/perlonjava/frontend/astnode/ArrayLiteralNode.java +++ b/src/main/java/org/perlonjava/frontend/astnode/ArrayLiteralNode.java @@ -1,7 +1,7 @@ package org.perlonjava.frontend.astnode; -import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.backend.jvm.astrefactor.LargeNodeRefactorer; +import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.frontend.parser.Parser; import java.util.List; diff --git a/src/main/java/org/perlonjava/frontend/astnode/BlockNode.java b/src/main/java/org/perlonjava/frontend/astnode/BlockNode.java index 75eda6c1b..363588395 100644 --- a/src/main/java/org/perlonjava/frontend/astnode/BlockNode.java +++ b/src/main/java/org/perlonjava/frontend/astnode/BlockNode.java @@ -1,7 +1,7 @@ package org.perlonjava.frontend.astnode; -import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.backend.jvm.astrefactor.LargeBlockRefactorer; +import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.frontend.parser.Parser; import java.util.ArrayList; diff --git a/src/main/java/org/perlonjava/frontend/astnode/HashLiteralNode.java b/src/main/java/org/perlonjava/frontend/astnode/HashLiteralNode.java index 9f4999f9d..ba9f18782 100644 --- a/src/main/java/org/perlonjava/frontend/astnode/HashLiteralNode.java +++ b/src/main/java/org/perlonjava/frontend/astnode/HashLiteralNode.java @@ -1,7 +1,7 @@ package org.perlonjava.frontend.astnode; -import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.backend.jvm.astrefactor.LargeNodeRefactorer; +import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.frontend.parser.Parser; import java.util.List; diff --git a/src/main/java/org/perlonjava/frontend/astnode/ListNode.java b/src/main/java/org/perlonjava/frontend/astnode/ListNode.java index 6cdf0270a..0bc933566 100644 --- a/src/main/java/org/perlonjava/frontend/astnode/ListNode.java +++ b/src/main/java/org/perlonjava/frontend/astnode/ListNode.java @@ -1,7 +1,7 @@ package org.perlonjava.frontend.astnode; -import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.backend.jvm.astrefactor.LargeNodeRefactorer; +import org.perlonjava.frontend.analysis.Visitor; import org.perlonjava.frontend.parser.Parser; import java.util.ArrayList; diff --git a/src/main/java/org/perlonjava/frontend/lexer/Lexer.java b/src/main/java/org/perlonjava/frontend/lexer/Lexer.java index 3b338af56..835c574bd 100644 --- a/src/main/java/org/perlonjava/frontend/lexer/Lexer.java +++ b/src/main/java/org/perlonjava/frontend/lexer/Lexer.java @@ -66,20 +66,6 @@ public Lexer(String input) { this.position = 0; } - private int getCurrentCodePoint() { - if (position >= length) { - return -1; - } - char c1 = input.charAt(position); - if (Character.isHighSurrogate(c1) && position + 1 < length) { - char c2 = input.charAt(position + 1); - if (Character.isLowSurrogate(c2)) { - return Character.toCodePoint(c1, c2); - } - } - return c1; - } - private static boolean isPerlIdentifierStart(int codePoint) { return codePoint == '_' || UCharacter.hasBinaryProperty(codePoint, UProperty.XID_START); } @@ -88,10 +74,6 @@ private static boolean isPerlIdentifierPart(int codePoint) { return codePoint == '_' || UCharacter.hasBinaryProperty(codePoint, UProperty.XID_CONTINUE); } - private void advanceCodePoint(int codePoint) { - position += Character.charCount(codePoint); - } - // Main method for testing the Lexer public static void main(String[] args) { // Sample code to be tokenized @@ -122,6 +104,24 @@ private static boolean isAsciiWhitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; } + private int getCurrentCodePoint() { + if (position >= length) { + return -1; + } + char c1 = input.charAt(position); + if (Character.isHighSurrogate(c1) && position + 1 < length) { + char c2 = input.charAt(position + 1); + if (Character.isLowSurrogate(c2)) { + return Character.toCodePoint(c1, c2); + } + } + return c1; + } + + private void advanceCodePoint(int codePoint) { + position += Character.charCount(codePoint); + } + // Method to tokenize the input string into a list of tokens public List tokenize() { List tokens = new ArrayList<>(); diff --git a/src/main/java/org/perlonjava/frontend/parser/ClassTransformer.java b/src/main/java/org/perlonjava/frontend/parser/ClassTransformer.java index 4045bf2e3..ad51a37c3 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ClassTransformer.java +++ b/src/main/java/org/perlonjava/frontend/parser/ClassTransformer.java @@ -150,7 +150,7 @@ public static BlockNode transformClassBlock(BlockNode block, String className, P // Generate constructor and accessors but DEFER their registration // These are synthetic methods and should NOT capture class-level lexicals // They will be registered AFTER scope exit in StatementParser - + // Generate constructor if not present if (existingConstructor == null) { SubroutineNode constructor = generateConstructor(fields, className, adjustNodes); @@ -186,8 +186,8 @@ public static BlockNode transformClassBlock(BlockNode block, String className, P if (stmt instanceof BinaryOperatorNode binOp && "=".equals(binOp.operator)) { // This is an assignment - keep it (includes lexical methods) block.elements.add(stmt); - } else if (stmt instanceof OperatorNode opNode && - ("my".equals(opNode.operator) || "state".equals(opNode.operator) || "our".equals(opNode.operator))) { + } else if (stmt instanceof OperatorNode opNode && + ("my".equals(opNode.operator) || "state".equals(opNode.operator) || "our".equals(opNode.operator))) { // This is a bare lexical declaration (my $count;) - skip it // It's already in the symbol table, and adding it to the AST would cause // the constructor to try capturing it as a closure variable diff --git a/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java b/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java index 0d20223e1..454ddf8c2 100644 --- a/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java +++ b/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java @@ -33,7 +33,8 @@ public static Node parseCoreOperator(Parser parser, LexerToken token, int startI return switch (operatorName) { case "__LINE__" -> { handleEmptyParentheses(parser); - yield new NumberNode(Integer.toString(parser.ctx.errorUtil.getLineNumber(parser.tokenIndex)), parser.tokenIndex); + yield + new NumberNode(Integer.toString(parser.ctx.errorUtil.getLineNumber(parser.tokenIndex)), parser.tokenIndex); } case "__FILE__" -> { handleEmptyParentheses(parser); diff --git a/src/main/java/org/perlonjava/frontend/parser/DataSection.java b/src/main/java/org/perlonjava/frontend/parser/DataSection.java index 12d15853f..c54672aef 100644 --- a/src/main/java/org/perlonjava/frontend/parser/DataSection.java +++ b/src/main/java/org/perlonjava/frontend/parser/DataSection.java @@ -1,8 +1,8 @@ package org.perlonjava.frontend.parser; -import org.perlonjava.runtime.io.ScalarBackedIO; import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; +import org.perlonjava.runtime.io.ScalarBackedIO; import org.perlonjava.runtime.runtimetypes.GlobalVariable; import org.perlonjava.runtime.runtimetypes.RuntimeIO; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; @@ -31,7 +31,7 @@ public class DataSection { */ public static void createPlaceholderDataHandle(Parser parser) { String handleName = parser.ctx.symbolTable.getCurrentPackage() + "::DATA"; - + if (placeholderCreated.contains(handleName)) { return; // Already created placeholder for this package } @@ -96,7 +96,7 @@ private static boolean isEndMarker(LexerToken token) { static int parseDataSection(Parser parser, int tokenIndex, List tokens, LexerToken token) { String handleName = parser.ctx.symbolTable.getCurrentPackage() + "::DATA"; - + // Check if this package has already processed its DATA section if (processedPackages.contains(handleName)) { return tokens.size(); diff --git a/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java b/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java index 4aa6604e5..158c27045 100644 --- a/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java @@ -412,7 +412,7 @@ public static String parseComplexIdentifierInner(Parser parser, boolean insideBr } } } - + variableName.append(token.text); // Check identifier length limit (Perl's limit is around 251 characters) @@ -553,15 +553,15 @@ public static String parseSubroutineIdentifier(Parser parser) { parser.tokenIndex++; token = parser.tokens.get(parser.tokenIndex); nextToken = parser.tokens.get(parser.tokenIndex + 1); - + // Validate that what follows :: is a valid identifier start // Allow EOF or closing tokens for package names that end with :: - if (token.type != LexerTokenType.IDENTIFIER && token.type != LexerTokenType.NUMBER && - !token.text.equals("'") && !token.text.equals("::") && !token.text.equals("->") && - token.type != LexerTokenType.EOF && - !(token.type == LexerTokenType.OPERATOR && (token.text.equals("}") || token.text.equals(";") || token.text.equals("=") || token.text.equals(")")))) { + if (token.type != LexerTokenType.IDENTIFIER && token.type != LexerTokenType.NUMBER && + !token.text.equals("'") && !token.text.equals("::") && !token.text.equals("->") && + token.type != LexerTokenType.EOF && + !(token.type == LexerTokenType.OPERATOR && (token.text.equals("}") || token.text.equals(";") || token.text.equals("=") || token.text.equals(")")))) { // Bad name after :: - parser.throwCleanError("Bad name after " + variableName.toString() + "::"); + parser.throwCleanError("Bad name after " + variableName + "::"); } continue; } @@ -587,7 +587,7 @@ public static String parseSubroutineIdentifier(Parser parser) { continue; } else { // Bad name after ' - parser.throwCleanError("Bad name after " + variableName.toString() + "'"); + parser.throwCleanError("Bad name after " + variableName + "'"); } } diff --git a/src/main/java/org/perlonjava/frontend/parser/ListParser.java b/src/main/java/org/perlonjava/frontend/parser/ListParser.java index d8541ea34..e9580ab76 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ListParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/ListParser.java @@ -101,7 +101,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo // Start with regex as left operand, then parse any infix operators Node left = regex; int precedence = parser.getPrecedence(","); - + // Check for infix operators after the regex (like . for concatenation) while (true) { token = TokenUtils.peek(parser); @@ -119,7 +119,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo left = ParseInfix.parseInfixOperation(parser, left, tokenPrecedence); } } - + expr.elements.add(left); token = TokenUtils.peek(parser); if (token.type != LexerTokenType.EOF && !isListTerminator(parser, token)) { @@ -221,7 +221,7 @@ static boolean isListTerminator(Parser parser, LexerToken token) { if (!ParserTables.LIST_TERMINATORS.contains(token.text)) { return false; } - + // Special case: and/or/xor/when before => should be treated as barewords, not terminators if (token.text.equals("and") || token.text.equals("or") || token.text.equals("xor") || token.text.equals("when")) { // Look ahead to see if => follows @@ -229,11 +229,9 @@ static boolean isListTerminator(Parser parser, LexerToken token) { TokenUtils.consume(parser); // consume and/or/xor/when LexerToken nextToken = TokenUtils.peek(parser); parser.tokenIndex = saveIndex; // restore - if (nextToken.text.equals("=>")) { - return false; // Not a terminator, it's a hash key - } + return !nextToken.text.equals("=>"); // Not a terminator, it's a hash key } - + return true; } @@ -273,8 +271,8 @@ static List parseList(Parser parser, String close, int minItems) { String fileName = parser.ctx.errorUtil.getFileName(); int lineNum = parser.ctx.errorUtil.getLineNumber(parser.tokenIndex); String errorMsg = "Missing right curly or square bracket at " + fileName + " line " + lineNum + ", at end of line\n" + - "syntax error at " + fileName + " line " + lineNum + ", at EOF\n" + - "Execution of " + fileName + " aborted due to compilation errors.\n"; + "syntax error at " + fileName + " line " + lineNum + ", at EOF\n" + + "Execution of " + fileName + " aborted due to compilation errors.\n"; throw new PerlCompilerException(errorMsg); } @@ -311,16 +309,12 @@ public static boolean looksLikeEmptyList(Parser parser) { if (ParserTables.LIST_TERMINATORS.contains(token.text)) { // Special case: check if and/or/xor/when followed by => if (token.text.equals("and") || token.text.equals("or") || token.text.equals("xor") || token.text.equals("when")) { - if (nextToken.text.equals("=>")) { - isTerminator = false; // Not a terminator, it's a hash key - } else { - isTerminator = true; - } + isTerminator = !nextToken.text.equals("=>"); // Not a terminator, it's a hash key } else { isTerminator = true; } } - + if (token.type == LexerTokenType.EOF || isTerminator || token.text.equals("->")) { isEmptyList = true; } else if (token.text.equals("-")) { diff --git a/src/main/java/org/perlonjava/frontend/parser/NumberParser.java b/src/main/java/org/perlonjava/frontend/parser/NumberParser.java index 6a131a7bc..e7508f9c6 100644 --- a/src/main/java/org/perlonjava/frontend/parser/NumberParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/NumberParser.java @@ -6,8 +6,8 @@ import org.perlonjava.frontend.lexer.LexerTokenType; import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarType; import org.perlonjava.runtime.runtimetypes.RuntimeScalarCache; +import org.perlonjava.runtime.runtimetypes.RuntimeScalarType; import java.util.LinkedHashMap; import java.util.Map; diff --git a/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java b/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java index f251bc578..d1d4cc82e 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java +++ b/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java @@ -18,19 +18,6 @@ * A block represents a sequence of statements enclosed in curly braces. */ public class ParseBlock { - /** - * Result of parseBlock when scope exit is delayed. - */ - public static class BlockWithScope { - public final BlockNode block; - public final int scopeIndex; - - public BlockWithScope(BlockNode block, int scopeIndex) { - this.block = block; - this.scopeIndex = scopeIndex; - } - } - /** * Parses a block of code and generates an Abstract Syntax Tree (AST) representation. * A block consists of zero or more statements and may include labeled statements. @@ -47,16 +34,16 @@ public BlockWithScope(BlockNode block, int scopeIndex) { public static BlockNode parseBlock(Parser parser) { return parseBlock(parser, true).block; } - + /** * Parses a block with optional delayed scope exit. - * + * *

When exitScope=false, the caller is responsible for calling * exitScope(scopeIndex) later. This is needed for class blocks where * methods must be registered while the scope is still active to capture * class-level lexical variables. * - * @param parser The parser instance + * @param parser The parser instance * @param exitScope Whether to exit the scope before returning * @return BlockWithScope containing the block and scope index * @see StatementParser#parseOptionalPackageBlock for usage with class blocks @@ -108,7 +95,7 @@ public static BlockWithScope parseBlock(Parser parser, boolean exitScope) { // Parse the actual statement, passing any label found Node statement = StatementResolver.parseStatement(parser, label); - + // parseStatement should never return null, but if it does, it's a parser bug // that should be fixed at the source. For now, add defensive check. if (statement != null) { @@ -160,4 +147,17 @@ private static String parseLabel(Parser parser, List statements, List tokens; + // List to store format nodes encountered during parsing. + private final List formatNodes = new ArrayList<>(); + // List to store completed format nodes after template parsing. + private final List completedFormatNodes = new ArrayList<>(); // Current index in the token list. public int tokenIndex = 0; // Flags to indicate special parsing states. @@ -41,10 +45,6 @@ public class Parser { public List classAdjustBlocks = new ArrayList<>(); // List to store heredoc nodes encountered during parsing. private List heredocNodes = new ArrayList<>(); - // List to store format nodes encountered during parsing. - private final List formatNodes = new ArrayList<>(); - // List to store completed format nodes after template parsing. - private final List completedFormatNodes = new ArrayList<>(); /** * Constructs a Parser with the given context and tokens. @@ -158,12 +158,12 @@ public Node parseExpression(int precedence) { // Get the precedence of the current token. int tokenPrecedence = getPrecedence(token.text); - + // Special case: if this is an IDENTIFIER that's a quote-like operator with high precedence, // it's not actually an infix operator - stop parsing here - if (token.type == LexerTokenType.IDENTIFIER && - tokenPrecedence == 24 && - ParsePrimary.isIsQuoteLikeOperator(token.text)) { + if (token.type == LexerTokenType.IDENTIFIER && + tokenPrecedence == 24 && + ParsePrimary.isIsQuoteLikeOperator(token.text)) { // This is a quote-like operator that should be parsed as a new expression, not as infix break; } diff --git a/src/main/java/org/perlonjava/frontend/parser/ParserTables.java b/src/main/java/org/perlonjava/frontend/parser/ParserTables.java index 90050c6e5..f20c588c8 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ParserTables.java +++ b/src/main/java/org/perlonjava/frontend/parser/ParserTables.java @@ -22,13 +22,6 @@ public class ParserTables { ); // Map of CORE operators to prototype strings public static final Map CORE_PROTOTYPES = new HashMap<>(); - // Set of operators that are right associative. - static final Set RIGHT_ASSOC_OP = Set.of( - "=", "**=", "+=", "*=", "&=", "&.=", "<<=", "&&=", "-=", "/=", "|=", "|.=", - ">>=", "||=", ".=", "%=", "^=", "^.=", "//=", "x=", "^^=", "**", "?" - ); - // Map to store operator precedence values. - static final Map precedenceMap = new HashMap<>(); // The list below was obtained by running this in the perl git: // ack 'CORE::GLOBAL::\w+' | perl -n -e ' /CORE::GLOBAL::(\w+)/ && print $1, "\n" ' | sort -u public static final Set OVERRIDABLE_OP = Set.of( @@ -46,6 +39,13 @@ public class ParserTables { "uc", "warn" ); + // Set of operators that are right associative. + static final Set RIGHT_ASSOC_OP = Set.of( + "=", "**=", "+=", "*=", "&=", "&.=", "<<=", "&&=", "-=", "/=", "|=", "|.=", + ">>=", "||=", ".=", "%=", "^=", "^.=", "//=", "x=", "^^=", "**", "?" + ); + // Map to store operator precedence values. + static final Map precedenceMap = new HashMap<>(); // Static block to initialize the CORE prototypes. static { diff --git a/src/main/java/org/perlonjava/frontend/parser/PrototypeArgs.java b/src/main/java/org/perlonjava/frontend/parser/PrototypeArgs.java index b3c5b424e..198d4a291 100644 --- a/src/main/java/org/perlonjava/frontend/parser/PrototypeArgs.java +++ b/src/main/java/org/perlonjava/frontend/parser/PrototypeArgs.java @@ -382,16 +382,16 @@ private static void handleScalarArgument(Parser parser, ListNode args, boolean i private static boolean isFilehandleOperator(String operatorName) { if (operatorName == null) return false; return operatorName.equals("truncate") || - operatorName.equals("seek") || - operatorName.equals("tell") || - operatorName.equals("eof") || - operatorName.equals("binmode") || - operatorName.equals("fileno") || - operatorName.equals("getc") || - operatorName.equals("read") || - operatorName.equals("sysread") || - operatorName.equals("syswrite") || - operatorName.equals("sysseek"); + operatorName.equals("seek") || + operatorName.equals("tell") || + operatorName.equals("eof") || + operatorName.equals("binmode") || + operatorName.equals("fileno") || + operatorName.equals("getc") || + operatorName.equals("read") || + operatorName.equals("sysread") || + operatorName.equals("syswrite") || + operatorName.equals("sysseek"); } private static void handleUnderscoreArgument(Parser parser, ListNode args, boolean isOptional, boolean needComma) { @@ -514,16 +514,16 @@ private static boolean handleCodeReferenceArgument(Parser parser, ListNode args, // Reject bare arrays, hashes, and scalars String subName = parser.ctx.symbolTable.getCurrentSubroutine(); if (subName != null && !subName.isEmpty()) { - parser.throwError("Type of arg 1 to " + subName + " must be block or sub {} (not " + - (opNode.operator.equals("@") ? "array" : - opNode.operator.equals("%") ? "hash" : "scalar variable") + ")"); + parser.throwError("Type of arg 1 to " + subName + " must be block or sub {} (not " + + (opNode.operator.equals("@") ? "array" : + opNode.operator.equals("%") ? "hash" : "scalar variable") + ")"); } else { - parser.throwError("Type of arg 1 must be block or sub {} (not " + - (opNode.operator.equals("@") ? "array" : - opNode.operator.equals("%") ? "hash" : "scalar variable") + ")"); + parser.throwError("Type of arg 1 must be block or sub {} (not " + + (opNode.operator.equals("@") ? "array" : + opNode.operator.equals("%") ? "hash" : "scalar variable") + ")"); } } - + // Unwrap reference to code reference: \(&code) should be treated as &code // This matches Perl's behavior where prototype (&) unwraps REF to CODE if (opNode.operator.equals("\\")) { @@ -590,7 +590,7 @@ private static void handlePlusArgument(Parser parser, ListNode args, boolean isO * Unwraps unary plus from expressions like +(%hash) or +(@array) for backslash prototypes. * In Perl, +() is used for disambiguation but should be transparent for \% and \@ prototypes. * - * @param arg The argument node to potentially unwrap + * @param arg The argument node to potentially unwrap * @param refType The reference type from the prototype ('%', '@', etc.) * @return The unwrapped node if applicable, or the original node */ @@ -599,20 +599,20 @@ private static Node unwrapUnaryPlus(Node arg, char refType) { if (refType != '%' && refType != '@') { return arg; } - + // Check if arg is unary plus: OperatorNode with operator "+" if (!(arg instanceof OperatorNode plusOp) || !plusOp.operator.equals("+")) { return arg; } - + // Get the operand of the unary plus Node operand = plusOp.operand; - + // If the operand is a ListNode with a single element, extract it if (operand instanceof ListNode listNode && listNode.elements.size() == 1) { operand = listNode.elements.get(0); } - + // Check if the operand is the expected type (hash or array variable) if (operand instanceof OperatorNode varOp) { String expectedSigil = (refType == '%') ? "%" : "@"; @@ -620,7 +620,7 @@ private static Node unwrapUnaryPlus(Node arg, char refType) { return operand; } } - + // Also check if operand is directly a ListNode containing a hash/array expression // This handles cases like +(%hash) where the parentheses create a list context if (operand instanceof ListNode listNode) { @@ -636,7 +636,7 @@ private static Node unwrapUnaryPlus(Node arg, char refType) { } } } - + // Return original if no match return arg; } @@ -656,9 +656,9 @@ private static int handleBackslashArgument(Parser parser, ListNode args, String if (refType == '&') { parser.parsingTakeReference = true; } - + Node referenceArg = parseArgumentWithComma(parser, isOptional, needComma, expectedType); - + // Restore flag parser.parsingTakeReference = oldParsingTakeReference; if (referenceArg != null) { @@ -669,20 +669,20 @@ private static int handleBackslashArgument(Parser parser, ListNode args, String if (refType == '&') { String subName = parser.ctx.symbolTable.getCurrentSubroutine(); String subNamePart = (subName == null || subName.isEmpty()) ? "" : " to " + subName; - + // Check for function calls: &foo() or foo() if (referenceArg instanceof BinaryOperatorNode binOp && binOp.operator.equals("(")) { parser.throwError("Type of arg " + (args.elements.size() + 1) + subNamePart + " must be subroutine (not subroutine entry)"); } - + // Check for bareword (identifier without &) if (referenceArg instanceof IdentifierNode) { parser.throwError("Type of arg " + (args.elements.size() + 1) + subNamePart + " must be subroutine (not subroutine entry)"); } } - + // Check if user passed an explicit reference when prototype expects auto-reference if (refType == '$' && referenceArg instanceof OperatorNode opNode && opNode.operator.equals("\\")) { diff --git a/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java b/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java index aee0dba46..61d8315ad 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java @@ -70,7 +70,7 @@ public static ListNode parseSignature(Parser parser) { /** * Parses a Perl subroutine signature and generates the corresponding AST. * - * @param parser The parser instance + * @param parser The parser instance * @param subroutineName The name of the subroutine for error messages * @return A ListNode containing the generated AST nodes * @throws PerlCompilerException if the signature syntax is invalid @@ -83,9 +83,9 @@ public static ListNode parseSignature(Parser parser, String subroutineName) { * Parses a Perl method signature and generates the corresponding AST. * Methods have an implicit $self parameter that affects argument counts in error messages. * - * @param parser The parser instance + * @param parser The parser instance * @param methodName The name of the method for error messages - * @param isMethod True if this is a method (has implicit $self) + * @param isMethod True if this is a method (has implicit $self) * @return A ListNode containing the generated AST nodes * @throws PerlCompilerException if the signature syntax is invalid */ @@ -185,7 +185,7 @@ private void parseParameter() { // Create parameter variable or undef placeholder Node paramVariable = createParameterVariable(sigil, paramName); - + if (isNamed) { // Named parameters are handled separately, not part of @_ unpacking namedParameterNodes.add(paramVariable); @@ -278,7 +278,7 @@ private void handleNamedParameter(Node paramVariable, String paramName) { // Named parameters are always optional and extracted from a hash in @_ // Generate: my %h = @_; $named = (delete $h{named}) // default_value; - + Node defaultValue = null; String defaultOp = "//="; // default to defined-or operator for named params @@ -291,7 +291,7 @@ private void handleNamedParameter(Node paramVariable, String paramName) { // Generate the extraction code for named parameter // This returns a ListNode with the hash declaration and extraction statements Node extractionCode = generateNamedParameterExtraction(paramVariable, paramName, defaultValue, defaultOp); - + // Add the extraction statements to astNodes if (extractionCode instanceof ListNode) { astNodes.addAll(((ListNode) extractionCode).elements); @@ -346,13 +346,13 @@ private Node parseDefaultValue(Node paramVariable) { /** * Generates AST for extracting a named parameter from @_. - * + * *

Named parameters are passed as key-value pairs in @_. This method generates: *

{@code
      * my %__named_args__ = @_;  # Only once for all named params
      * my $paramName = (delete $__named_args__{paramName}) // defaultValue;
      * }
- * + * *

The generated AST structure: *

    *
  1. Hash declaration: {@code my %__named_args__ = @_} (first param only)
  2. @@ -362,20 +362,20 @@ private Node parseDefaultValue(Node paramVariable) { *
* * @param paramVariable The variable node to assign the extracted value to - * @param paramName The name of the parameter (for hash key lookup) - * @param defaultValue The default value expression, or null if no default - * @param defaultOp The default operator ("=", "//=", or "||=") + * @param paramName The name of the parameter (for hash key lookup) + * @param defaultValue The default value expression, or null if no default + * @param defaultOp The default operator ("=", "//=", or "||=") * @return A ListNode containing the hash declaration and extraction statements */ private Node generateNamedParameterExtraction(Node paramVariable, String paramName, Node defaultValue, String defaultOp) { List statements = new ArrayList<>(); - + // Create the hash only once for all named parameters if (namedArgsHashName == null) { namedArgsHashName = "__named_args__"; IdentifierNode hashIdent = new IdentifierNode(namedArgsHashName, parser.tokenIndex); Node hashVar = new OperatorNode("%", hashIdent, parser.tokenIndex); - + // Create: my %__named_args__ = @_ Node hashDecl = new BinaryOperatorNode( "=", @@ -384,7 +384,7 @@ private Node generateNamedParameterExtraction(Node paramVariable, String paramNa parser.tokenIndex); statements.add(hashDecl); } - + // Create: $__named_args__{named} // Note: use $ sigil for single element access, not % IdentifierNode hashIdent = new IdentifierNode(namedArgsHashName, parser.tokenIndex); @@ -398,11 +398,11 @@ private Node generateNamedParameterExtraction(Node paramVariable, String paramNa new OperatorNode("$", hashIdent, parser.tokenIndex), hashKey, parser.tokenIndex); - + // Create: delete $__named_args__{named} // The delete operator expects its operand to be a ListNode Node deleteExpr = new OperatorNode("delete", new ListNode(List.of(hashAccess), parser.tokenIndex), parser.tokenIndex); - + Node extractionValue; if (defaultValue != null) { // (delete $h{named}) // defaultValue @@ -413,7 +413,7 @@ private Node generateNamedParameterExtraction(Node paramVariable, String paramNa } else if (defaultOp.equals("//=")) { defaultOp = "//"; } - + extractionValue = new BinaryOperatorNode( defaultOp, deleteExpr, @@ -422,12 +422,12 @@ private Node generateNamedParameterExtraction(Node paramVariable, String paramNa } else { extractionValue = deleteExpr; } - + // Add the extraction assignment with 'my' declaration // my $named = (delete $h{named}) // default Node myParam = new OperatorNode("my", paramVariable, parser.tokenIndex); statements.add(new BinaryOperatorNode("=", myParam, extractionValue, parser.tokenIndex)); - + // Return a list node containing the hash declaration (if first time) and the extraction return new ListNode(statements, parser.tokenIndex); } @@ -470,7 +470,7 @@ private Node generateArgCountValidation() { } else { // Without named parameters: check both min and max // We need to check separately for too few vs too many to generate appropriate error messages - + // First check: minParams <= @_ (too few arguments check) Node tooFewCheck = new BinaryOperatorNode( "||", @@ -483,7 +483,7 @@ private Node generateArgCountValidation() { dieWarnNode(parser, "die", new ListNode(List.of( generateTooFewArgsMessage()), parser.tokenIndex), parser.tokenIndex), parser.tokenIndex); - + // Second check: @_ <= maxParams (too many arguments check) Node tooManyCheck = new BinaryOperatorNode( "||", @@ -496,7 +496,7 @@ private Node generateArgCountValidation() { dieWarnNode(parser, "die", new ListNode(List.of( generateTooManyArgsMessage()), parser.tokenIndex), parser.tokenIndex), parser.tokenIndex); - + // Return both checks in sequence return new ListNode(List.of(tooFewCheck, tooManyCheck), parser.tokenIndex); } @@ -508,7 +508,7 @@ private Node generateTooFewArgsMessage() { String fullName = NameNormalizer.normalizeVariableName(subroutineName, parser.ctx.symbolTable.getCurrentPackage()); // For methods, add 1 to account for implicit $self parameter (both in got and expected) int adjustedMin = isMethod ? minParams + 1 : minParams; - + Node argCount; if (isMethod) { // For methods: scalar(@_) + 1 (to account for $self that was already shifted) @@ -519,7 +519,7 @@ private Node generateTooFewArgsMessage() { } else { argCount = new OperatorNode("scalar", atUnderscore(parser), parser.tokenIndex); } - + return new BinaryOperatorNode(".", new BinaryOperatorNode(".", new BinaryOperatorNode(".", @@ -544,7 +544,7 @@ private Node generateTooManyArgsMessage() { String fullName = NameNormalizer.normalizeVariableName(subroutineName, parser.ctx.symbolTable.getCurrentPackage()); // For methods, add 1 to account for implicit $self parameter (both in got and expected) int adjustedMax = isMethod ? maxParams + 1 : maxParams; - + Node argCount; if (isMethod) { // For methods: scalar(@_) + 1 (to account for $self that was already shifted) @@ -555,7 +555,7 @@ private Node generateTooManyArgsMessage() { } else { argCount = new OperatorNode("scalar", atUnderscore(parser), parser.tokenIndex); } - + return new BinaryOperatorNode(".", new BinaryOperatorNode(".", new BinaryOperatorNode(".", diff --git a/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java b/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java index 56f61757a..8e8d9f4ab 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java @@ -1,13 +1,13 @@ package org.perlonjava.frontend.parser; import org.perlonjava.app.cli.CompilerOptions; +import org.perlonjava.app.scriptengine.PerlLanguageProvider; import org.perlonjava.backend.jvm.EmitterMethodCreator; import org.perlonjava.frontend.astnode.*; import org.perlonjava.frontend.lexer.LexerTokenType; -import org.perlonjava.runtime.runtimetypes.*; -import org.perlonjava.app.scriptengine.PerlLanguageProvider; import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.frontend.semantic.SymbolTable; +import org.perlonjava.runtime.runtimetypes.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java index 7d0339d84..a6890eeb8 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java @@ -1,8 +1,8 @@ package org.perlonjava.frontend.parser; +import org.perlonjava.backend.jvm.EmitterContext; import org.perlonjava.core.Configuration; import org.perlonjava.frontend.analysis.ExtractValueVisitor; -import org.perlonjava.backend.jvm.EmitterContext; import org.perlonjava.frontend.astnode.*; import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; @@ -15,13 +15,13 @@ import java.util.ArrayList; import java.util.List; -import static org.perlonjava.runtime.operators.VersionHelper.normalizeVersion; import static org.perlonjava.frontend.parser.NumberParser.parseNumber; import static org.perlonjava.frontend.parser.ParserNodeUtils.atUnderscoreArgs; import static org.perlonjava.frontend.parser.ParserNodeUtils.scalarUnderscore; import static org.perlonjava.frontend.parser.SpecialBlockParser.runSpecialBlock; import static org.perlonjava.frontend.parser.SpecialBlockParser.setCurrentScope; import static org.perlonjava.frontend.parser.StringParser.parseVstring; +import static org.perlonjava.runtime.operators.VersionHelper.normalizeVersion; import static org.perlonjava.runtime.perlmodule.Feature.featureManager; import static org.perlonjava.runtime.perlmodule.Strict.useStrict; import static org.perlonjava.runtime.perlmodule.Warnings.useWarnings; @@ -335,7 +335,7 @@ public static Node parseTryStatement(Parser parser) { /** * Parses a when statement (part of given/when feature from Perl 5.10). - * + *

* when(COND) { BLOCK } becomes: if ($_ ~~ COND) { BLOCK } * * @param parser The Parser instance @@ -375,7 +375,7 @@ public static Node parseWhenStatement(Parser parser) { /** * Parses a default statement (part of given/when feature from Perl 5.10). - * + *

* default { BLOCK } just returns the BLOCK (it's like an else clause) * * @param parser The Parser instance @@ -394,13 +394,13 @@ public static Node parseDefaultStatement(Parser parser) { /** * Parses a given-when statement (deprecated feature from Perl 5.10). - * + *

* Transforms: - * given(EXPR) { when(COND1) { BLOCK1 } when(COND2) { BLOCK2 } default { BLOCK3 } } - * + * given(EXPR) { when(COND1) { BLOCK1 } when(COND2) { BLOCK2 } default { BLOCK3 } } + *

* Into AST equivalent of: - * do { $_ = EXPR; when/default statements } - * + * do { $_ = EXPR; when/default statements } + *

* Where when/default are parsed as regular statements that check $_. * This is a pure AST transformation - no special emitter code needed. * @@ -424,23 +424,23 @@ public static Node parseGivenStatement(Parser parser) { // Parse the entire block content as a normal block // This handles regular statements as well as when/default BlockNode blockContent = ParseBlock.parseBlock(parser); - + TokenUtils.consume(parser, LexerTokenType.OPERATOR, "}"); parser.ctx.symbolTable.exitScope(scopeIndex); // Create the complete block: { $_ = EXPR; blockContent } List statements = new ArrayList<>(); - + // $_ = condition (use proper $_ structure) - Node dollarUnderscore = new OperatorNode("$", - new IdentifierNode("_", index), + Node dollarUnderscore = new OperatorNode("$", + new IdentifierNode("_", index), index); statements.add(new BinaryOperatorNode("=", dollarUnderscore, condition, index)); - + // Add all the statements from the block statements.addAll(blockContent.elements); @@ -694,7 +694,7 @@ public static Node parsePackageDeclaration(Parser parser, LexerToken token) { // Register deferred methods (constructor and any accessors) // Same logic as in parseOptionalPackageBlock - + // Register user-defined methods (none for unit class) @SuppressWarnings("unchecked") List deferredMethods = (List) emptyBlock.getAnnotation("deferredMethods"); @@ -704,14 +704,14 @@ public static Node parsePackageDeclaration(Parser parser, LexerToken token) { method.attributes, (BlockNode) method.block, false, null); } } - + // Register generated methods (constructor and accessors) SubroutineNode deferredConstructor = (SubroutineNode) emptyBlock.getAnnotation("deferredConstructor"); if (deferredConstructor != null) { SubroutineParser.handleNamedSubWithFilter(parser, deferredConstructor.name, deferredConstructor.prototype, deferredConstructor.attributes, (BlockNode) deferredConstructor.block, true, null); } - + @SuppressWarnings("unchecked") List deferredAccessors = (List) emptyBlock.getAnnotation("deferredAccessors"); if (deferredAccessors != null) { @@ -844,13 +844,13 @@ public static BlockNode parseOptionalPackageBlock(Parser parser, IdentifierNode // so methods can capture class-level lexical variables TokenUtils.consume(parser, LexerTokenType.OPERATOR, "{"); int scopeIndex = parser.ctx.symbolTable.enterScope(); - + boolean isClass = packageNode.getBooleanAnnotation("isClass"); - + // Save the current package and class state to restore later String previousPackage = parser.ctx.symbolTable.getCurrentPackage(); boolean previousPackageIsClass = parser.ctx.symbolTable.currentPackageIsClass(); - + parser.ctx.symbolTable.setCurrentPackage(nameNode.name, isClass); // Set flag if we're entering a class block @@ -861,7 +861,7 @@ public static BlockNode parseOptionalPackageBlock(Parser parser, IdentifierNode BlockNode block; int blockScopeIndex; - + try { if (isClass) { // For classes, delay scope exit until after ClassTransformer runs @@ -892,7 +892,7 @@ public static BlockNode parseOptionalPackageBlock(Parser parser, IdentifierNode // For packages: subroutines were already registered during parseBlock if (isClass) { block = ClassTransformer.transformClassBlock(block, nameNode.name, parser); - + // Register user-defined methods BEFORE exiting scope // This allows them to capture class-level lexicals @SuppressWarnings("unchecked") @@ -903,17 +903,17 @@ public static BlockNode parseOptionalPackageBlock(Parser parser, IdentifierNode method.attributes, (BlockNode) method.block, false, null); } } - + // NOW exit the block scope AFTER user-defined methods are registered parser.ctx.symbolTable.exitScope(blockScopeIndex); - + // Register generated methods WITH filtering (skip lexical sub/method hidden variables) SubroutineNode deferredConstructor = (SubroutineNode) block.getAnnotation("deferredConstructor"); if (deferredConstructor != null) { SubroutineParser.handleNamedSubWithFilter(parser, deferredConstructor.name, deferredConstructor.prototype, deferredConstructor.attributes, (BlockNode) deferredConstructor.block, true, null); } - + @SuppressWarnings("unchecked") List deferredAccessors = (List) block.getAnnotation("deferredAccessors"); if (deferredAccessors != null) { @@ -922,14 +922,14 @@ public static BlockNode parseOptionalPackageBlock(Parser parser, IdentifierNode accessor.attributes, (BlockNode) accessor.block, true, null); } } - + // Restore the package context after class transformation parser.ctx.symbolTable.setCurrentPackage(previousPackage, previousPackageIsClass); } else { // For regular packages, just restore context (scope already exited) parser.ctx.symbolTable.setCurrentPackage(previousPackage, previousPackageIsClass); } - + // Exit the outer scope (from line 644) parser.ctx.symbolTable.exitScope(scopeIndex); diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java index f004efefb..c1d4e32c6 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java @@ -5,14 +5,13 @@ import org.perlonjava.frontend.astnode.*; import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; +import org.perlonjava.frontend.semantic.SymbolTable; import org.perlonjava.runtime.runtimetypes.NameNormalizer; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; -import org.perlonjava.frontend.semantic.SymbolTable; import java.util.ArrayList; import java.util.List; -import static org.perlonjava.frontend.parser.OperatorParser.dieWarnNode; import static org.perlonjava.frontend.parser.ParserNodeUtils.scalarUnderscore; import static org.perlonjava.frontend.parser.TokenUtils.consume; import static org.perlonjava.frontend.parser.TokenUtils.peek; @@ -96,9 +95,9 @@ public static Node parseStatement(Parser parser, String label) { case "sub" -> { parser.tokenIndex++; LexerToken nextToken = peek(parser); - if (nextToken.type == LexerTokenType.IDENTIFIER || - nextToken.text.equals("'") || - nextToken.text.equals("::")) { + if (nextToken.type == LexerTokenType.IDENTIFIER || + nextToken.text.equals("'") || + nextToken.text.equals("::")) { yield SubroutineParser.parseSubroutineDefinition(parser, true, "our"); } // Otherwise backtrack @@ -208,12 +207,12 @@ public static Node parseStatement(Parser parser, String label) { // our sub works like our var - it creates a package sub AND a lexical alias // The lexical alias stores the fully qualified name so it always resolves // to the correct package sub regardless of the current package - + // Parse as normal package sub parser.tokenIndex--; // back up to just after "sub" - + Node packageSub = SubroutineParser.parseSubroutineDefinition(parser, true, "our"); - + // Store the fully qualified name in the symbol table // This allows calls to resolve to the correct package sub even after package switch String fullSubName = NameNormalizer.normalizeVariableName(subName, parser.ctx.symbolTable.getCurrentPackage()); @@ -223,7 +222,7 @@ public static Node parseStatement(Parser parser, String label) { marker.setAnnotation("isOurSub", true); marker.setAnnotation("fullSubName", fullSubName); // Store the full qualified name! parser.ctx.symbolTable.addVariable("&" + subName, "our", marker); - + // Return the package sub yield packageSub; } else { @@ -234,12 +233,12 @@ public static Node parseStatement(Parser parser, String label) { // Create the declaration: my/state $hiddenVarName // First create the inner operand (the $hiddenVarName part) OperatorNode innerVarNode = new OperatorNode("$", new IdentifierNode(hiddenVarName, parser.tokenIndex), parser.tokenIndex); - + // For state variables, assign a unique ID for persistent tracking if (declaration.equals("state")) { innerVarNode.id = EmitterMethodCreator.classCounter++; } - + // Now create the outer declaration node (state/my $hiddenVarName) OperatorNode varDecl = new OperatorNode(declaration, innerVarNode, parser.tokenIndex); @@ -250,21 +249,21 @@ public static Node parseStatement(Parser parser, String label) { // IMPORTANT: Manually add the hidden variable to the symbol table // Since we're returning an assignment node, parseVariableDeclaration won't be called again // So we need to register both the sub name (&p) and the hidden variable ($p__lexsub_N) - + // IMPORTANT: Add the hidden variable NOW (before parsing body) // But delay adding &subName until AFTER parsing the body to make the sub "invisible inside itself" - + // For my/state subs: If there's already a forward declaration, we need to handle it SymbolTable.SymbolEntry existingEntry = parser.ctx.symbolTable.getSymbolEntry("&" + subName); boolean hadForwardDecl = existingEntry != null; - + // Add the hidden variable immediately (needed for state variable tracking) if (hadForwardDecl) { parser.ctx.symbolTable.replaceVariable("$" + hiddenVarName, declaration, innerVarNode); } else { parser.ctx.symbolTable.addVariable("$" + hiddenVarName, declaration, innerVarNode); } - + // DO NOT add &subName yet - it will be added after parsing the body // Check if this is a forward declaration or a full definition @@ -272,7 +271,7 @@ public static Node parseStatement(Parser parser, String label) { boolean hasBody = false; String prototype = null; List attributes = new ArrayList<>(); - + // Parse attributes first (e.g., :prototype()) while (peek(parser).text.equals(":")) { String attrProto = SubroutineParser.consumeAttributes(parser, attributes); @@ -280,7 +279,7 @@ public static Node parseStatement(Parser parser, String label) { prototype = attrProto; } } - + // Then check for prototype/signature // When signatures are enabled, we need to look ahead: // - (...) { ... } means signature + body @@ -292,7 +291,7 @@ public static Node parseStatement(Parser parser, String label) { StringParser.parseRawString(parser, "q"); // consume the parens boolean hasBodyAfterParens = peek(parser).text.equals("{"); parser.tokenIndex = savedIndex; // restore position - + if (!hasBodyAfterParens) { // Forward declaration: (...) ; - parse as prototype prototype = ((StringNode) StringParser.parseRawString(parser, "q")).value; @@ -303,12 +302,12 @@ public static Node parseStatement(Parser parser, String label) { prototype = ((StringNode) StringParser.parseRawString(parser, "q")).value; } } - + // Now check if there's a body // When signatures are enabled, (...) followed by { also indicates a body String peekText = peek(parser).text; - hasBody = peekText.equals("{") || - (peekText.equals("(") && parser.ctx.symbolTable.isFeatureCategoryEnabled("signatures")); + hasBody = peekText.equals("{") || + (peekText.equals("(") && parser.ctx.symbolTable.isFeatureCategoryEnabled("signatures")); if (hasBody) { // Full definition: my sub name {...} or my sub name (...) {...} @@ -333,7 +332,7 @@ public static Node parseStatement(Parser parser, String label) { // Use a fully qualified name to ensure it resolves correctly regardless of package context String declaringPackage = parser.ctx.symbolTable.getCurrentPackage(); String qualifiedHiddenVarName = declaringPackage + "::" + hiddenVarName; - + OperatorNode varRef = new OperatorNode("$", new IdentifierNode(qualifiedHiddenVarName, parser.tokenIndex), parser.tokenIndex); // For state variables, copy the ID so runtime can track the state if (declaration.equals("state")) { @@ -344,7 +343,7 @@ public static Node parseStatement(Parser parser, String label) { // Check if we're inside a subroutine String currentSub = parser.ctx.symbolTable.getCurrentSubroutine(); boolean insideSubroutine = currentSub != null && !currentSub.isEmpty(); - + if (declaration.equals("state") || !insideSubroutine) { // For state sub: Execute assignment immediately during parsing (like a BEGIN block) // This is crucial for cases like: state sub foo{...}; use overload => \&foo; @@ -354,7 +353,7 @@ public static Node parseStatement(Parser parser, String label) { // use statements (like use overload) can access the sub reference BlockNode beginBlock = new BlockNode(new ArrayList<>(List.of(assignment)), parser.tokenIndex); SpecialBlockParser.runSpecialBlock(parser, "BEGIN", beginBlock); - + // Return empty list since the assignment already executed yield new ListNode(parser.tokenIndex); } else { @@ -371,16 +370,16 @@ public static Node parseStatement(Parser parser, String label) { } else { parser.ctx.symbolTable.addVariable("&" + subName, declaration, varDecl); } - + if (prototype != null) { // Store prototype in varDecl annotation varDecl.setAnnotation("prototype", prototype); - + // Create a stub RuntimeCode with the prototype set // This is needed so that prototype(\&sub) works for forward declarations String declaringPackage = parser.ctx.symbolTable.getCurrentPackage(); String qualifiedHiddenVarName = declaringPackage + "::" + hiddenVarName; - + // Create a subroutine stub with the prototype that returns undef // The body needs at least a return statement to avoid bytecode issues List stubBody = new ArrayList<>(); @@ -393,17 +392,17 @@ public static Node parseStatement(Parser parser, String label) { false, // useTryCatch parser.tokenIndex ); - + // Create assignment: $hiddenVarName = sub { } - OperatorNode varRef = new OperatorNode("$", - new IdentifierNode(qualifiedHiddenVarName, parser.tokenIndex), + OperatorNode varRef = new OperatorNode("$", + new IdentifierNode(qualifiedHiddenVarName, parser.tokenIndex), parser.tokenIndex); BinaryOperatorNode stubAssignment = new BinaryOperatorNode("=", varRef, stubSub, parser.tokenIndex); - + // Execute the stub assignment in a BEGIN block BlockNode beginBlock = new BlockNode(new ArrayList<>(List.of(stubAssignment)), parser.tokenIndex); SpecialBlockParser.runSpecialBlock(parser, "BEGIN", beginBlock); - + // Return empty list since stub was created yield new ListNode(parser.tokenIndex); } @@ -439,10 +438,10 @@ public static Node parseStatement(Parser parser, String label) { consume(parser, LexerTokenType.OPERATOR, "{"); boolean wasInMethod = parser.isInMethod; parser.isInMethod = true; // Set method context for lexical method - + // Enter scope for the lexical method's body int scopeIndex = parser.ctx.symbolTable.enterScope(); - + // Add temp $self to THIS scope (the method's inner scope) // so field access works during parsing // This will be matched by the actual `my $self = shift;` injected during transformation @@ -450,7 +449,7 @@ public static Node parseStatement(Parser parser, String label) { new OperatorNode("$", new IdentifierNode("self", parser.tokenIndex), parser.tokenIndex), parser.tokenIndex); parser.ctx.symbolTable.addVariable("$self", "my", tempSelf); - + // Parse the block contents (without creating another scope) List elements = new ArrayList<>(); while (!peek(parser).text.equals("}")) { @@ -460,10 +459,10 @@ public static Node parseStatement(Parser parser, String label) { } } block = new BlockNode(elements, parser.tokenIndex); - + // Exit the method's scope (this removes temp $self) parser.ctx.symbolTable.exitScope(scopeIndex); - + parser.isInMethod = wasInMethod; // Restore previous context consume(parser, LexerTokenType.OPERATOR, "}"); } else if (peek(parser).text.equals(";")) { @@ -554,13 +553,13 @@ public static Node parseStatement(Parser parser, String label) { LexerToken next = TokenUtils.peek(parser); boolean isStatementTerminator = next.type == LexerTokenType.EOF || - next.text.equals(";") || - next.text.equals("}"); + next.text.equals(";") || + next.text.equals("}"); if (!isStatementTerminator) { throw new PerlCompilerException(parser.tokenIndex, "syntax error", parser.ctx.errorUtil); } - yield dieWarnNode(parser, "die", new ListNode(List.of( - new StringNode("Unimplemented", parser.tokenIndex)), parser.tokenIndex), parser.tokenIndex); + yield dieWarnNode (parser, "die", new ListNode(List.of( + new StringNode("Unimplemented", parser.tokenIndex)), parser.tokenIndex), parser.tokenIndex) } case "{" -> { @@ -576,8 +575,8 @@ yield dieWarnNode(parser, "die", new ListNode(List.of( String fileName = parser.ctx.errorUtil.getFileName(); int lineNum = parser.ctx.errorUtil.getLineNumber(parser.tokenIndex); String errorMsg = "Missing right curly or square bracket at " + fileName + " line " + lineNum + ", at end of line\n" + - "syntax error at " + fileName + " line " + lineNum + ", at EOF\n" + - "Execution of " + fileName + " aborted due to compilation errors.\n"; + "syntax error at " + fileName + " line " + lineNum + ", at EOF\n" + + "Execution of " + fileName + " aborted due to compilation errors.\n"; throw new PerlCompilerException(errorMsg); } TokenUtils.consume(parser, LexerTokenType.OPERATOR, "}"); @@ -781,20 +780,20 @@ public static boolean isHashLiteral(Parser parser) { } searchIndex--; } - + // Also check if this looks like an assignment by looking at the next token boolean looksLikeAssignment = false; if (parser.tokenIndex < parser.tokens.size()) { LexerToken nextToken = parser.tokens.get(parser.tokenIndex); // Assignment would be followed by a variable, number, string, or expression - if (nextToken.type == LexerTokenType.IDENTIFIER && - (nextToken.text.startsWith("$") || nextToken.text.startsWith("@") || nextToken.text.startsWith("%"))) { + if (nextToken.type == LexerTokenType.IDENTIFIER && + (nextToken.text.startsWith("$") || nextToken.text.startsWith("@") || nextToken.text.startsWith("%"))) { looksLikeAssignment = true; } else if (nextToken.type == LexerTokenType.NUMBER || nextToken.type == LexerTokenType.STRING) { looksLikeAssignment = true; } } - + if (!isQStringDelimiter && looksLikeAssignment) { // This looks like an assignment parser.ctx.logDebug("isHashLiteral found = (block indicator)"); diff --git a/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java b/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java index 72920c5bc..29195e7af 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java +++ b/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java @@ -328,7 +328,8 @@ private Node createJoinNode(List nodes) { default -> { var listNode = new ListNode(parser.tokenIndex); listNode.elements.addAll(nodes); - yield new BinaryOperatorNode("join", new StringNode("", parser.tokenIndex), listNode, parser.tokenIndex); + yield + new BinaryOperatorNode("join", new StringNode("", parser.tokenIndex), listNode, parser.tokenIndex); } }; } diff --git a/src/main/java/org/perlonjava/frontend/parser/StringParser.java b/src/main/java/org/perlonjava/frontend/parser/StringParser.java index 678b9e8fd..c2958f21c 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StringParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/StringParser.java @@ -65,8 +65,8 @@ public static ParsedString parseRawStringWithDelimiter(EmitterContext ctx, List< LexerToken currentToken = tokens.get(tokPos); if (currentToken.type == LexerTokenType.EOF) { String errorMsg = endDelim == '/' - ? "Search pattern not terminated" - : "Can't find string terminator " + endDelim + " anywhere before EOF"; + ? "Search pattern not terminated" + : "Can't find string terminator " + endDelim + " anywhere before EOF"; throw new PerlCompilerException(tokPos, errorMsg, ctx.errorUtil); } diff --git a/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java b/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java index 44f685cae..af1a1ef7f 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java @@ -397,8 +397,8 @@ private Node parseSimpleVariableInterpolation(String sigil) { // Special case: empty identifier for $ sigil (like $ at end of string) if ("$".equals(sigil) && identifier.isEmpty()) { // Check if we're at end of string - if (parser.tokenIndex >= parser.tokens.size() || - parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF) { + if (parser.tokenIndex >= parser.tokens.size() || + parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF) { throw new PerlCompilerException(tokenIndex, "Final $ should be \\$ or $name", ctx.errorUtil); } } @@ -407,11 +407,11 @@ private Node parseSimpleVariableInterpolation(String sigil) { } else { // No identifier found after sigil // Check if we're at end of string for $ sigil - if ("$".equals(sigil) && (parser.tokenIndex >= parser.tokens.size() || - parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF)) { + if ("$".equals(sigil) && (parser.tokenIndex >= parser.tokens.size() || + parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF)) { throw new PerlCompilerException(tokenIndex, "Final $ should be \\$ or $name", ctx.errorUtil); } - + // For array sigils, check if next token starts with $ (e.g., @$b means array of $b) if ("@".equals(sigil) && parser.tokenIndex < parser.tokens.size()) { LexerToken nextToken = parser.tokens.get(parser.tokenIndex); @@ -431,10 +431,10 @@ private Node parseSimpleVariableInterpolation(String sigil) { if (!"$".equals(sigil)) { throw new PerlCompilerException(tokenIndex, "Missing identifier after " + sigil, ctx.errorUtil); } - + // For $ sigil with no identifier, check if we're at end of string - if (parser.tokenIndex >= parser.tokens.size() || - parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF) { + if (parser.tokenIndex >= parser.tokens.size() || + parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF) { throw new PerlCompilerException(tokenIndex, "Final $ should be \\$ or $name", ctx.errorUtil); } } @@ -819,17 +819,17 @@ public void setOriginalTokenOffset(int offset) { } /** - * Sets the original string content for better error context. + * Gets the original string content. */ - public void setOriginalStringContent(String content) { - this.originalStringContent = content; + protected String getOriginalStringContent() { + return originalStringContent; } /** - * Gets the original string content. + * Sets the original string content for better error context. */ - protected String getOriginalStringContent() { - return originalStringContent; + public void setOriginalStringContent(String content) { + this.originalStringContent = content; } /** @@ -942,10 +942,7 @@ private boolean shouldInterpolateVariable(String sigil) { if (nextToken.type == LexerTokenType.EOF) { // Special case: $ at EOF in double-quoted string should generate error // But only for StringDoubleQuoted, not for other contexts like regex - if ("$".equals(sigil) && interpolateVariable && !isRegex && !isRegexReplacement) { - return true; - } - return false; + return "$".equals(sigil) && interpolateVariable && !isRegex && !isRegexReplacement; } // Regex: don't interpolate "$" if followed by whitespace or newlines diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java index 8b54f16c3..8147c9650 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -9,9 +9,9 @@ import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; import org.perlonjava.frontend.semantic.ScopedSymbolTable; +import org.perlonjava.frontend.semantic.SymbolTable; import org.perlonjava.runtime.mro.InheritanceResolver; import org.perlonjava.runtime.runtimetypes.*; -import org.perlonjava.frontend.semantic.SymbolTable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -75,11 +75,11 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { } else { // This is a lexical sub (my/state) - handle it specially LexerToken nextToken = peek(parser); - + // Check if there's a prototype stored for this lexical sub - String lexicalPrototype = varNode.getAnnotation("prototype") != null ? - (String) varNode.getAnnotation("prototype") : null; - + String lexicalPrototype = varNode.getAnnotation("prototype") != null ? + (String) varNode.getAnnotation("prototype") : null; + // Use lexical sub when: // 1. There are explicit parentheses, OR // 2. There's a prototype, OR @@ -88,31 +88,31 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { boolean useExplicitParen = nextToken.text.equals("("); boolean hasPrototype = lexicalPrototype != null; boolean nextIsIdentifier = nextToken.type == LexerTokenType.IDENTIFIER; - + if (useExplicitParen || hasPrototype || !nextIsIdentifier || parser.parsingForLoopVariable) { // This is a lexical sub/method - use the hidden variable instead of package lookup // The varNode is the "my $name__lexsub_123" or "my $name__lexmethod_123" variable - + // Get the hidden variable name for the lexical sub String hiddenVarName = (String) varNode.getAnnotation("hiddenVarName"); if (hiddenVarName != null) { // Get the package where this lexical sub was declared String declaringPackage = (String) varNode.getAnnotation("declaringPackage"); - + // Make the hidden variable name fully qualified with the declaring package String qualifiedHiddenVarName = hiddenVarName; if (declaringPackage != null && !hiddenVarName.contains("::")) { qualifiedHiddenVarName = declaringPackage + "::" + hiddenVarName; } - + // Get the hidden variable entry from the symbol table for the ID String hiddenVarKey = "$" + hiddenVarName; SymbolTable.SymbolEntry hiddenEntry = parser.ctx.symbolTable.getSymbolEntry(hiddenVarKey); - + // Always create a fresh variable reference to avoid AST reuse issues - OperatorNode dollarOp = new OperatorNode("$", - new IdentifierNode(qualifiedHiddenVarName, currentIndex), currentIndex); - + OperatorNode dollarOp = new OperatorNode("$", + new IdentifierNode(qualifiedHiddenVarName, currentIndex), currentIndex); + // Copy the ID from the symbol table entry for state variables if (hiddenEntry != null && hiddenEntry.ast() instanceof OperatorNode hiddenVarNode) { dollarOp.id = hiddenVarNode.id; @@ -120,13 +120,13 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { // Fallback: copy ID from the declaration dollarOp.id = innerNode.id; } - + // If parsingForLoopVariable is set, we just need the code reference, not a call // This is used by sort/map/grep when parsing the comparison sub if (parser.parsingForLoopVariable) { return dollarOp; } - + // Parse arguments using prototype if available ListNode arguments; if (useExplicitParen) { @@ -144,7 +144,7 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { // This matches behavior of regular package subs which call consumeArgsWithPrototype arguments = consumeArgsWithPrototype(parser, lexicalPrototype); } - + // Call the hidden variable directly: $hiddenVar(arguments) // The () operator will handle dereferencing and calling return new BinaryOperatorNode("(", @@ -158,9 +158,9 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { // Normalize the subroutine name to include the current package // If subName already contains "::", it's already fully qualified (e.g., from "our sub") - String fullName = subName.contains("::") - ? subName - : NameNormalizer.normalizeVariableName(subName, parser.ctx.symbolTable.getCurrentPackage()); + String fullName = subName.contains("::") + ? subName + : NameNormalizer.normalizeVariableName(subName, parser.ctx.symbolTable.getCurrentPackage()); // Check if we are parsing a method; // Otherwise, check that the subroutine exists in the global namespace - then fetch prototype and attributes @@ -216,7 +216,7 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { || runtimeCode.attributes != null; } } - + // Reject if: // 1. Explicitly marked as non-package (false in cache), OR // 2. Unknown package (null) AND unknown subroutine (!isKnownSub) AND followed by '(' @@ -230,23 +230,23 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { // Not a known subroutine, check if it's valid indirect object syntax if (!isKnownSub && !isLexicalSub && isValidIndirectMethod(packageName)) { if (!(token.text.equals("->") || token.text.equals("=>") || INFIX_OP.contains(token.text))) { - // System.out.println(" package loaded: " + packageName + "->" + subName); + // System.out.println(" package loaded: " + packageName + "->" + subName); - ListNode arguments; - if (token.text.equals(",")) { - arguments = new ListNode(currentIndex); - } else { - arguments = consumeArgsWithPrototype(parser, "@"); - } - return new BinaryOperatorNode( - "->", - new IdentifierNode(packageName, currentIndex2), - new BinaryOperatorNode("(", - new OperatorNode("&", - new IdentifierNode(subName, currentIndex2), - currentIndex), - arguments, currentIndex2), - currentIndex2); + ListNode arguments; + if (token.text.equals(",")) { + arguments = new ListNode(currentIndex); + } else { + arguments = consumeArgsWithPrototype(parser, "@"); + } + return new BinaryOperatorNode( + "->", + new IdentifierNode(packageName, currentIndex2), + new BinaryOperatorNode("(", + new OperatorNode("&", + new IdentifierNode(subName, currentIndex2), + currentIndex), + arguments, currentIndex2), + currentIndex2); } } @@ -285,15 +285,15 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { || nextTok.type == LexerTokenType.EOF; boolean infixOp = nextTok.type == LexerTokenType.OPERATOR && (INFIX_OP.contains(nextTok.text) - || nextTok.text.equals("?") - || nextTok.text.equals(":")); + || nextTok.text.equals("?") + || nextTok.text.equals(":")); if (!terminator && !infixOp && nextTok.type != LexerTokenType.IDENTIFIER && !nextTok.text.equals("->") && !nextTok.text.equals("=>")) { ListNode arguments = consumeArgsWithPrototype(parser, "@"); - + // Check if this is indirect object syntax like "s2 $f" if (arguments.elements.size() > 0) { Node firstArg = arguments.elements.get(0); @@ -308,7 +308,7 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) { return new BinaryOperatorNode("->", object, methodCall, currentIndex); } } - + return new BinaryOperatorNode("(", new OperatorNode("&", nameNode, currentIndex), arguments, @@ -390,7 +390,7 @@ private static Node parseIndirectMethodCall(Parser parser, IdentifierNode nameNo if (peek(parser).text.equals("$")) { ListNode arguments = consumeArgsWithPrototype(parser, "$"); int index = parser.tokenIndex; - + // For indirect object syntax like "s2 $f", this should be treated as "$f->s2()" // not as "s2($f)". The first argument becomes the object. if (arguments.elements.size() > 0) { @@ -398,7 +398,7 @@ private static Node parseIndirectMethodCall(Parser parser, IdentifierNode nameNo // Create method call: object->method() return new BinaryOperatorNode("->", object, nameNode, index); } - + // Fallback to subroutine call if no arguments return new BinaryOperatorNode( "(", @@ -430,7 +430,7 @@ public static Node parseSubroutineDefinition(Parser parser, boolean wantName, St // 'parseSubroutineIdentifier' is called to handle cases where the subroutine name might be complex // (e.g., namespaced, fully qualified names). It may return null if no valid name is found. subName = IdentifierParser.parseSubroutineIdentifier(parser); - + // Mark named subroutines as non-packages in packageExistsCache immediately // This helps indirect object detection distinguish subs from packages if (subName != null) { @@ -443,7 +443,7 @@ public static Node parseSubroutineDefinition(Parser parser, boolean wantName, St // Initialize a list to store any attributes the subroutine might have. List attributes = new ArrayList<>(); - + // Check for invalid prototype-like constructs without parentheses if (peek(parser).text.equals("<") || peek(parser).text.equals("__FILE__")) { // This looks like a prototype but without parentheses - it's invalid @@ -454,7 +454,7 @@ public static Node parseSubroutineDefinition(Parser parser, boolean wantName, St parser.throwCleanError("Illegal declaration of anonymous subroutine"); } } - + // While there are attributes (denoted by a colon ':'), we keep parsing them. while (peek(parser).text.equals(":")) { prototype = consumeAttributes(parser, attributes); @@ -475,7 +475,7 @@ public static Node parseSubroutineDefinition(Parser parser, boolean wantName, St // If a prototype exists, we parse it using 'parseRawString' method which handles it like the 'q()' operator. // This means it will take everything inside the parentheses as a literal string. prototype = ((StringNode) StringParser.parseRawString(parser, "q")).value; - + // Validate prototype - certain characters are not allowed if (prototype.contains("<>") || prototype.contains("__FILE__")) { if (subName != null) { @@ -526,8 +526,8 @@ public static Node parseSubroutineDefinition(Parser parser, boolean wantName, St // After the block, we expect a closing curly brace '}' to denote the end of the subroutine. // Check if we reached EOF instead of finding the closing brace - if (parser.tokenIndex >= parser.tokens.size() || - parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF) { + if (parser.tokenIndex >= parser.tokens.size() || + parser.tokens.get(parser.tokenIndex).type == LexerTokenType.EOF) { parser.throwCleanError("Missing right curly"); } TokenUtils.consume(parser, LexerTokenType.OPERATOR, "}"); @@ -582,7 +582,7 @@ static String consumeAttributes(Parser parser, List attributes) { public static ListNode handleNamedSub(Parser parser, String subName, String prototype, List attributes, BlockNode block, String declaration) { return handleNamedSubWithFilter(parser, subName, prototype, attributes, block, false, declaration); } - + public static ListNode handleNamedSubWithFilter(Parser parser, String subName, String prototype, List attributes, BlockNode block, boolean filterLexicalMethods, String declaration) { // Check if there's a lexical forward declaration (our/my/state sub name;) that this definition should fulfill String lexicalKey = "&" + subName; @@ -592,7 +592,7 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S // If the package stash has been aliased (e.g. via `*{Pkg::} = *{Other::}`), then // new symbols defined in this package should land in the effective stash. packageToUse = GlobalVariable.resolveStashAlias(packageToUse); - + if (lexicalEntry != null && lexicalEntry.ast() instanceof OperatorNode varNode) { // Check if this is an "our sub" forward declaration Boolean isOurSub = (Boolean) varNode.getAnnotation("isOurSub"); @@ -619,7 +619,7 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S false, // useTryCatch parser.tokenIndex ); - + // Create assignment that will execute at runtime // Use the declaring package to create a fully qualified variable name String declaringPackage = (String) varNode.getAnnotation("declaringPackage"); @@ -627,29 +627,29 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S if (declaringPackage != null && !hiddenVarName.contains("::")) { qualifiedHiddenVarName = declaringPackage + "::" + hiddenVarName; } - - OperatorNode varRef = new OperatorNode("$", - new IdentifierNode(qualifiedHiddenVarName, parser.tokenIndex), + + OperatorNode varRef = new OperatorNode("$", + new IdentifierNode(qualifiedHiddenVarName, parser.tokenIndex), parser.tokenIndex); - + BinaryOperatorNode assignment = new BinaryOperatorNode("=", varRef, anonSub, parser.tokenIndex); - + // Wrap the assignment in a BEGIN block so it executes at compile time // This ensures that "sub name { }" inside another sub still fills the forward declaration immediately List blockElements = new ArrayList<>(); blockElements.add(assignment); BlockNode beginBlock = new BlockNode(blockElements, parser.tokenIndex); - + // Execute the BEGIN block immediately during parsing SpecialBlockParser.runSpecialBlock(parser, "BEGIN", beginBlock); - + ListNode result = new ListNode(parser.tokenIndex); result.setAnnotation("compileTimeOnly", true); return result; } } } - + // - register the subroutine in the namespace String fullName = NameNormalizer.normalizeVariableName(subName, packageToUse); RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(fullName); @@ -680,12 +680,12 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S } String sigil = entry.name().substring(0, 1); - + // Skip code references (subroutines/methods) - they are not captured as closure variables if (sigil.equals("&")) { continue; } - + // For generated methods (constructor, readers, writers), skip lexical sub/method hidden variables // These variables (like $priv__lexmethod_123) are implementation details // User-defined methods can capture them, but generated methods should not @@ -695,7 +695,7 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S continue; } } - + String variableName = null; if (entry.decl().equals("our")) { // Normalize variable name for 'our' declarations @@ -737,7 +737,7 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S // Code references (&) should not be captured as closure variables ScopedSymbolTable filteredSnapshot = new ScopedSymbolTable(); filteredSnapshot.enterScope(); - + // Copy all visible variables except field declarations and code references Map visibleVars = parser.ctx.symbolTable.getAllVisibleVariables(); for (SymbolTable.SymbolEntry entry : visibleVars.values()) { @@ -752,26 +752,26 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S } filteredSnapshot.addVariable(entry.name(), entry.decl(), entry.ast()); } - + // Clone the current package - filteredSnapshot.setCurrentPackage(parser.ctx.symbolTable.getCurrentPackage(), + filteredSnapshot.setCurrentPackage(parser.ctx.symbolTable.getCurrentPackage(), parser.ctx.symbolTable.currentPackageIsClass()); - + // Clone the current subroutine filteredSnapshot.setCurrentSubroutine(parser.ctx.symbolTable.getCurrentSubroutine()); - + // Clone warning flags (critical for 'no warnings' pragmas) filteredSnapshot.warningFlagsStack.pop(); // Remove the initial value pushed by enterScope filteredSnapshot.warningFlagsStack.push(parser.ctx.symbolTable.warningFlagsStack.peek()); - + // Clone feature flags (critical for 'use feature' pragmas like refaliasing) filteredSnapshot.featureFlagsStack.pop(); // Remove the initial value pushed by enterScope filteredSnapshot.featureFlagsStack.push(parser.ctx.symbolTable.featureFlagsStack.peek()); - + // Clone strict options (critical for 'use strict' pragma) filteredSnapshot.strictOptionsStack.pop(); // Remove the initial value pushed by enterScope filteredSnapshot.strictOptionsStack.push(parser.ctx.symbolTable.strictOptionsStack.peek()); - + EmitterContext newCtx = new EmitterContext( new JavaClassInfo(), filteredSnapshot, @@ -792,13 +792,13 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S Supplier subroutineCreationTaskSupplier = () -> { // Try unified API (returns RuntimeCode - either CompiledCode or InterpretedCode) RuntimeCode runtimeCode = - EmitterMethodCreator.createRuntimeCode(newCtx, block, false); + EmitterMethodCreator.createRuntimeCode(newCtx, block, false); try { if (runtimeCode instanceof CompiledCode) { // CompiledCode path - fill in the existing placeholder CompiledCode compiledCode = - (CompiledCode) runtimeCode; + (CompiledCode) runtimeCode; Class generatedClass = compiledCode.generatedClass; // Prepare constructor with the captured variable types @@ -823,13 +823,13 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S // the update. By setting methodHandle/codeObject on the placeholder, ALL // references (including hash copies) will see the compiled code. InterpretedCode interpretedCode = - (InterpretedCode) runtimeCode; + (InterpretedCode) runtimeCode; // Set captured variables if there are any if (!paramList.isEmpty()) { Object[] parameters = paramList.toArray(); RuntimeBase[] capturedVars = - new RuntimeBase[parameters.length]; + new RuntimeBase[parameters.length]; for (int i = 0; i < parameters.length; i++) { capturedVars[i] = (RuntimeBase) parameters[i]; } @@ -844,7 +844,7 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S // Update placeholder in-place: set methodHandle to delegate to InterpretedCode placeholder.methodHandle = RuntimeCode.lookup.findVirtual( - InterpretedCode.class, "apply", RuntimeCode.methodType); + InterpretedCode.class, "apply", RuntimeCode.methodType); placeholder.codeObject = interpretedCode; } } catch (Exception e) { diff --git a/src/main/java/org/perlonjava/frontend/parser/Variable.java b/src/main/java/org/perlonjava/frontend/parser/Variable.java index 205dde18e..983678a65 100644 --- a/src/main/java/org/perlonjava/frontend/parser/Variable.java +++ b/src/main/java/org/perlonjava/frontend/parser/Variable.java @@ -5,11 +5,7 @@ import org.perlonjava.frontend.lexer.LexerTokenType; import org.perlonjava.frontend.semantic.SymbolTable; import org.perlonjava.runtime.operators.WarnDie; -import org.perlonjava.runtime.runtimetypes.PerlParserException; -import org.perlonjava.runtime.runtimetypes.GlobalVariable; -import org.perlonjava.runtime.runtimetypes.NameNormalizer; -import org.perlonjava.runtime.runtimetypes.PerlCompilerException; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; +import org.perlonjava.runtime.runtimetypes.*; import java.util.ArrayList; import java.util.List; @@ -20,7 +16,7 @@ /** * Parser for Perl variables with sigils ($, @, %, &, *). - * + * *

This class handles the parsing of Perl variables including: *

    *
  • Simple variables: {@code $var}, {@code @array}, {@code %hash}
  • @@ -31,7 +27,7 @@ *
  • Code references: {@code &sub}
  • *
  • Class field access: automatic transformation of {@code $field} to {@code $self->{field}} in methods
  • *
- * + * *

The parser also handles special cases like: *

    *
  • Special variables: {@code $@}, {@code $_}, {@code $!}, etc.
  • @@ -75,7 +71,7 @@ public static boolean isFieldInClassHierarchy(Parser parser, String fieldName) { /** * Parses a variable from the given lexer token. - * + * *

    This is the main entry point for parsing Perl variables. It handles various forms: *

      *
    • Simple variables: {@code $var}, {@code @array}, {@code %hash}
    • @@ -84,7 +80,7 @@ public static boolean isFieldInClassHierarchy(Parser parser, String fieldName) { *
    • Special cases: {@code $#array} (array size), {@code $#[...]} (empty string)
    • *
    • Class fields: automatic transformation in method context
    • *
    - * + * *

    The method also handles special parsing rules: *

      *
    • Validates variable names according to Perl rules
    • @@ -520,7 +516,7 @@ static Node parseCoderefVariable(Parser parser, LexerToken token) { String subName = peeked.text; String lexicalKey = "&" + subName; SymbolTable.SymbolEntry lexicalEntry = parser.ctx.symbolTable.getSymbolEntry(lexicalKey); - + if (lexicalEntry != null && lexicalEntry.ast() instanceof OperatorNode varNode) { // Check if this is an "our sub" - if so, replace with fully qualified name Boolean isOurSub = (Boolean) varNode.getAnnotation("isOurSub"); @@ -530,9 +526,9 @@ static Node parseCoderefVariable(Parser parser, LexerToken token) { // Consume the identifier token TokenUtils.consume(parser); // Create node with fully qualified name - Node qualifiedNode = new OperatorNode("&", - new IdentifierNode(storedFullName, index), index); - + Node qualifiedNode = new OperatorNode("&", + new IdentifierNode(storedFullName, index), index); + // Handle arguments if present Node list; if (!TokenUtils.peek(parser).text.equals("(")) { @@ -543,39 +539,39 @@ static Node parseCoderefVariable(Parser parser, LexerToken token) { return new BinaryOperatorNode("(", qualifiedNode, list, index); } } - + // Check if this is a "my sub" or "state sub" - use hidden variable String hiddenVarName = (String) varNode.getAnnotation("hiddenVarName"); if (hiddenVarName != null) { // Consume the identifier token TokenUtils.consume(parser); - + // Get the package where this lexical sub was declared String declaringPackage = (String) varNode.getAnnotation("declaringPackage"); - + // Make the hidden variable name fully qualified with the declaring package String qualifiedHiddenVarName = hiddenVarName; if (declaringPackage != null && !hiddenVarName.contains("::")) { qualifiedHiddenVarName = declaringPackage + "::" + hiddenVarName; } - + // Create reference to hidden variable: &$hiddenVar // IMPORTANT: For state variables, we need to preserve the ID from the declaration! - OperatorNode dollarOp = new OperatorNode("$", - new IdentifierNode(qualifiedHiddenVarName, index), index); - + OperatorNode dollarOp = new OperatorNode("$", + new IdentifierNode(qualifiedHiddenVarName, index), index); + // Copy the ID from the original declaration if it's a state variable if (varNode.operator.equals("state") && varNode.operand instanceof OperatorNode innerNode) { dollarOp.id = innerNode.id; } - + // If we're taking a reference (\&foo), return &$hiddenVar // This becomes \&$hiddenVar which calls createCodeReference // createCodeReference will detect the CODE value and return it directly if (parser.parsingTakeReference) { return new OperatorNode("&", dollarOp, index); } - + // Handle arguments for actual calls (&foo or &foo()) // Use $hiddenVar directly - the () operator will handle dereferencing Node list; @@ -588,7 +584,7 @@ static Node parseCoderefVariable(Parser parser, LexerToken token) { } } } - + // Set a flag to allow parentheses after a variable, as in &$sub(...) parser.parsingForLoopVariable = true; // Parse the variable following the `&` sigil @@ -651,7 +647,7 @@ static Node parseCoderefVariable(Parser parser, LexerToken token) { /** * Parses a braced variable expression like {@code ${var}} or {@code ${expr}}. - * + * *

      This method handles various braced forms: *

        *
      • Simple braced variables: {@code ${var}}, {@code @{array}}, {@code %{hash}}
      • @@ -659,13 +655,13 @@ static Node parseCoderefVariable(Parser parser, LexerToken token) { *
      • Array/hash access: {@code ${array[0]}}, {@code ${hash{key}}}
      • *
      • Empty braces: {@code ${}} (returns empty string)
      • *
      - * + * *

      The method is shared between regular variable parsing and string interpolation. * When used in string interpolation context, it handles special escaping rules for * quotes inside the braces (e.g., {@code "${\"quoted\"}"})

      - * - * @param parser the parser instance - * @param sigil the sigil that precedes the braced expression ($, @, %, etc.) + * + * @param parser the parser instance + * @param sigil the sigil that precedes the braced expression ($, @, %, etc.) * @param isStringInterpolation true if parsing within a string interpolation context * @return A Node representing the parsed braced variable expression * @throws PerlCompilerException if the braced expression is malformed or unterminated @@ -921,7 +917,7 @@ private static boolean isAmbiguousOperatorName(String identifier) { /** * Determines if a '[' in regex context should be treated as an array subscript * rather than a character class by looking ahead for character class patterns. - * + * *

      This is a critical disambiguation in regex string interpolation. Consider: *

            * /$foo[$A]/    # Array subscript - interpolate $foo[$A]
      @@ -929,7 +925,7 @@ private static boolean isAmbiguousOperatorName(String identifier) {
            * /$foo[0]/     # Array subscript - interpolate $foo[0]
            * /$foo[a-z]/   # Character class - do NOT interpolate
            * 
      - * + * *

      The method uses lookahead to detect the pattern: *

        *
      • Array subscript: {@code [expr]} where expr is a simple variable or number
      • diff --git a/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java b/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java index 83fe3b7bf..76988211c 100644 --- a/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java +++ b/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java @@ -272,8 +272,8 @@ public void replaceVariable(String name, String variableDeclType, OperatorNode a if (existing != null) { // Replace with new entry using the same index currentScope.variableIndex.put(name, - new SymbolTable.SymbolEntry(existing.index(), name, variableDeclType, - getCurrentPackage(), ast)); + new SymbolTable.SymbolEntry(existing.index(), name, variableDeclType, + getCurrentPackage(), ast)); } else { // If it doesn't exist in current scope, just add it currentScope.addVariable(name, variableDeclType, getCurrentPackage(), ast); diff --git a/src/main/java/org/perlonjava/runtime/io/DirectoryIO.java b/src/main/java/org/perlonjava/runtime/io/DirectoryIO.java index 7610c1c62..2d63313dd 100644 --- a/src/main/java/org/perlonjava/runtime/io/DirectoryIO.java +++ b/src/main/java/org/perlonjava/runtime/io/DirectoryIO.java @@ -1,10 +1,6 @@ package org.perlonjava.runtime.io; -import org.perlonjava.runtime.runtimetypes.PerlCompilerException; -import org.perlonjava.runtime.runtimetypes.RuntimeBase; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; +import org.perlonjava.runtime.runtimetypes.*; import java.nio.file.DirectoryStream; import java.nio.file.Path; diff --git a/src/main/java/org/perlonjava/runtime/io/SocketIO.java b/src/main/java/org/perlonjava/runtime/io/SocketIO.java index 3747173e0..e62b23922 100644 --- a/src/main/java/org/perlonjava/runtime/io/SocketIO.java +++ b/src/main/java/org/perlonjava/runtime/io/SocketIO.java @@ -23,6 +23,8 @@ * data over sockets. */ public class SocketIO implements IOHandle { + // Socket options storage: key is "level:optname", value is the option value + private final Map socketOptions; private Socket socket; private ServerSocket serverSocket; private SocketChannel socketChannel; @@ -32,9 +34,6 @@ public class SocketIO implements IOHandle { private boolean isEOF; private CharsetDecoderHelper decoderHelper; - // Socket options storage: key is "level:optname", value is the option value - private final Map socketOptions; - /** * Constructs a SocketIO instance for a client socket. * diff --git a/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java b/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java index 36227b6c6..0201db5dd 100644 --- a/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java +++ b/src/main/java/org/perlonjava/runtime/mro/InheritanceResolver.java @@ -10,10 +10,9 @@ * for method resolution and linearized class hierarchies to improve performance. */ public class InheritanceResolver { - private static final boolean TRACE_METHOD_RESOLUTION = false; - // Cache for linearized class hierarchies static final Map> linearizedClassesCache = new HashMap<>(); + private static final boolean TRACE_METHOD_RESOLUTION = false; // Per-package MRO settings private static final Map packageMRO = new HashMap<>(); // Method resolution cache @@ -241,7 +240,7 @@ private static void populateIsaMapHelper(String className, /** * Searches for a method in the class hierarchy starting from a specific index. * Uses method caching to improve performance for both found and not-found methods. - * + * *

        Method Resolution Process: *

          *
        1. Check method cache for previously resolved lookups
        2. @@ -251,7 +250,7 @@ private static void populateIsaMapHelper(String className, *
        3. Check if method exists in global symbol table
        4. *
        5. Fall back to AUTOLOAD if method not found (except for overload markers)
        6. *
        - * + * *

        Overload Methods: * Overload marker methods like {@code ((} and {@code ()} are exempt from AUTOLOAD * because they should be explicitly defined by the overload pragma. @@ -270,12 +269,12 @@ public static RuntimeScalar findMethodInHierarchy(String methodName, String perl System.err.println(" startFromIndex: " + startFromIndex); System.err.flush(); } - + if (cacheKey == null) { // Normalize the method name for consistent caching cacheKey = NameNormalizer.normalizeVariableName(methodName, perlClassName); } - + if (TRACE_METHOD_RESOLUTION) { System.err.println(" cacheKey: '" + cacheKey + "'"); System.err.flush(); @@ -297,7 +296,7 @@ public static RuntimeScalar findMethodInHierarchy(String methodName, String perl // Get the linearized inheritance hierarchy using the appropriate MRO List linearizedClasses = linearizeHierarchy(perlClassName); - + if (TRACE_METHOD_RESOLUTION) { System.err.println(" Linearized classes: " + linearizedClasses); System.err.flush(); diff --git a/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java b/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java index 83e7a6137..a6c7a632f 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java @@ -1,10 +1,9 @@ package org.perlonjava.runtime.nativ; +import jnr.posix.Passwd; import org.perlonjava.frontend.parser.StringParser; import org.perlonjava.runtime.runtimetypes.*; -import jnr.posix.Passwd; - import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.*; @@ -242,7 +241,10 @@ public static RuntimeArray getgrent(int ctx, RuntimeBase... args) { public static RuntimeScalar setpwent(int ctx, RuntimeBase... args) { if (!IS_WINDOWS) { - try { PosixLibrary.INSTANCE.setpwent(); } catch (Exception e) { } + try { + PosixLibrary.INSTANCE.setpwent(); + } catch (Exception e) { + } } userIterator.remove(); userInfoCache.clear(); @@ -257,7 +259,10 @@ public static RuntimeScalar setgrent(int ctx, RuntimeBase... args) { public static RuntimeScalar endpwent(int ctx, RuntimeBase... args) { if (!IS_WINDOWS) { - try { PosixLibrary.INSTANCE.endpwent(); } catch (Exception e) { } + try { + PosixLibrary.INSTANCE.endpwent(); + } catch (Exception e) { + } } userIterator.remove(); return new RuntimeScalar(1); diff --git a/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java b/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java index c2459cb63..7594efb48 100644 --- a/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java @@ -1,11 +1,7 @@ package org.perlonjava.runtime.operators; import org.perlonjava.frontend.parser.NumberParser; -import org.perlonjava.runtime.runtimetypes.PerlCompilerException; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarCache; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarType; -import org.perlonjava.runtime.runtimetypes.ScalarUtils; +import org.perlonjava.runtime.runtimetypes.*; /** * This class provides methods for performing bitwise operations on RuntimeScalar objects. @@ -360,7 +356,7 @@ public static RuntimeScalar shiftLeft(RuntimeScalar runtimeScalar, RuntimeScalar long value = runtimeScalar.getLong(); long shift = arg2.getLong(); - + // Handle negative shift (reverse direction: left shift becomes right shift) if (shift < 0) { shift = -shift; @@ -373,14 +369,14 @@ public static RuntimeScalar shiftLeft(RuntimeScalar runtimeScalar, RuntimeScalar if (shift >= 32) { return RuntimeScalarCache.scalarZero; } - + // Treat value as unsigned 32-bit (UV semantics) // Mask to 32 bits first to handle negative numbers correctly long unsignedValue = value & 0xFFFFFFFFL; - + // Perform the shift long result = (unsignedValue << shift) & 0xFFFFFFFFL; - + return new RuntimeScalar(result); } @@ -435,7 +431,7 @@ public static RuntimeScalar shiftRight(RuntimeScalar runtimeScalar, RuntimeScala long value = runtimeScalar.getLong(); long shift = arg2.getLong(); - + // Handle negative shift (reverse direction: right shift becomes left shift) if (shift < 0) { shift = -shift; @@ -447,15 +443,15 @@ public static RuntimeScalar shiftRight(RuntimeScalar runtimeScalar, RuntimeScala long result = (unsignedValue << shift) & 0xFFFFFFFFL; return new RuntimeScalar(result); } - + return shiftRightInternal(value, shift, false); } - + /** * Internal helper for right shift operations. - * - * @param value The value to shift - * @param shift The shift amount (must be non-negative) + * + * @param value The value to shift + * @param shift The shift amount (must be non-negative) * @param signed If true, use signed (arithmetic) shift; if false, use unsigned (logical) shift * @return A new RuntimeScalar with the shifted value */ @@ -469,7 +465,7 @@ private static RuntimeScalar shiftRightInternal(long value, long shift, boolean } return RuntimeScalarCache.scalarZero; } - + if (signed) { // Signed (arithmetic) shift - sign bit propagates // First convert to signed 32-bit, then shift, then mask @@ -484,11 +480,11 @@ private static RuntimeScalar shiftRightInternal(long value, long shift, boolean return new RuntimeScalar(result); } } - + /** * Performs a left shift operation with signed (integer) semantics. * This is used when "use integer" pragma is in effect. - * + *

        * IMPORTANT: Must use 32-bit int arithmetic and >= 32 boundaries (not 64-bit long / >= 64). * PerlOnJava reports ivsize=4 in Config.pm, so bop.t expects 32-bit word-size behavior: * "use integer; 1 << 32" must return 0, and "1 << 31" must return -2147483648 (signed). @@ -515,24 +511,24 @@ public static RuntimeScalar integerShiftLeft(RuntimeScalar runtimeScalar, Runtim // Use (int) getLong() — see integerBitwiseNot comment for why not getInt(). int value = (int) runtimeScalar.getLong(); long shift = arg2.getLong(); - + if (shift < 0) { shift = -shift; if (shift < 0 || shift >= 32) { return new RuntimeScalar(value < 0 ? -1 : 0); } - int result = value >> (int)shift; + int result = value >> (int) shift; return new RuntimeScalar(result); } if (shift >= 32) { return RuntimeScalarCache.scalarZero; } - - int result = value << (int)shift; + + int result = value << (int) shift; return new RuntimeScalar(result); } - + /** * Performs a right shift operation with signed (integer) semantics. * This is used when "use integer" pragma is in effect. @@ -559,21 +555,21 @@ public static RuntimeScalar integerShiftRight(RuntimeScalar runtimeScalar, Runti // Use (int) getLong() — see integerBitwiseNot comment for why not getInt(). int value = (int) runtimeScalar.getLong(); long shift = arg2.getLong(); - + if (shift < 0) { shift = -shift; if (shift < 0 || shift >= 32) { return RuntimeScalarCache.scalarZero; } - int result = value << (int)shift; + int result = value << (int) shift; return new RuntimeScalar(result); } if (shift >= 32) { return new RuntimeScalar(value < 0 ? -1 : 0); } - - int result = value >> (int)shift; + + int result = value >> (int) shift; return new RuntimeScalar(result); } } \ No newline at end of file diff --git a/src/main/java/org/perlonjava/runtime/operators/CompareOperators.java b/src/main/java/org/perlonjava/runtime/operators/CompareOperators.java index ee98c2812..402ad5c0c 100644 --- a/src/main/java/org/perlonjava/runtime/operators/CompareOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/CompareOperators.java @@ -464,7 +464,7 @@ public static RuntimeScalar ge(RuntimeScalar runtimeScalar, RuntimeScalar arg2) public static RuntimeScalar smartmatch(RuntimeScalar arg1, RuntimeScalar arg2) { // Simplified smartmatch: try string equality first, then numeric // This handles the basic case in the state.t test - + // Check if both are defined if (!arg1.getDefinedBoolean() && !arg2.getDefinedBoolean()) { return scalarTrue; // undef ~~ undef is true @@ -472,16 +472,16 @@ public static RuntimeScalar smartmatch(RuntimeScalar arg1, RuntimeScalar arg2) { if (!arg1.getDefinedBoolean() || !arg2.getDefinedBoolean()) { return scalarFalse; // one is undef, one is not } - + // Try string comparison if (arg1.toString().equals(arg2.toString())) { return scalarTrue; } - + // Try numeric comparison if both look like numbers try { if (arg1.type == RuntimeScalarType.INTEGER || arg1.type == RuntimeScalarType.DOUBLE || - arg2.type == RuntimeScalarType.INTEGER || arg2.type == RuntimeScalarType.DOUBLE) { + arg2.type == RuntimeScalarType.INTEGER || arg2.type == RuntimeScalarType.DOUBLE) { RuntimeScalar num1 = arg1.getNumber(); RuntimeScalar num2 = arg2.getNumber(); if (num1.type == RuntimeScalarType.DOUBLE || num2.type == RuntimeScalarType.DOUBLE) { @@ -493,7 +493,7 @@ public static RuntimeScalar smartmatch(RuntimeScalar arg1, RuntimeScalar arg2) { } catch (Exception e) { // Not numeric, fall through } - + return scalarFalse; } } diff --git a/src/main/java/org/perlonjava/runtime/operators/Directory.java b/src/main/java/org/perlonjava/runtime/operators/Directory.java index e40548b93..f75a70017 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Directory.java +++ b/src/main/java/org/perlonjava/runtime/operators/Directory.java @@ -37,7 +37,7 @@ public static RuntimeScalar chdir(RuntimeScalar runtimeScalar) { // fchdir(2), passing handles raises an exception. String dirName; - + // Check if argument is a filehandle or dirhandle if (runtimeScalar.value instanceof RuntimeIO || runtimeScalar.value instanceof RuntimeGlob) { // Try to get RuntimeIO from the scalar @@ -47,7 +47,7 @@ public static RuntimeScalar chdir(RuntimeScalar runtimeScalar) { throw new PerlCompilerException("The fchdir function is unimplemented"); } } - + // Handle chdir() with no arguments - check environment variables if (!runtimeScalar.defined().getBoolean()) { // Try HOME, then LOGDIR, then SYS$LOGIN (for VMS only) @@ -81,13 +81,13 @@ public static RuntimeScalar chdir(RuntimeScalar runtimeScalar) { } else { dirName = runtimeScalar.toString(); } - + // Check for empty string - should fail with ENOENT if (dirName.isEmpty()) { getGlobalVariable("main::!").set(2); // ENOENT return scalarFalse; } - + File absoluteDir = RuntimeIO.resolveFile(dirName); if (absoluteDir.exists() && absoluteDir.isDirectory()) { diff --git a/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java b/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java index 6c5077620..4699578cc 100644 --- a/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java @@ -4,27 +4,15 @@ import org.perlonjava.runtime.io.CustomFileChannel; import org.perlonjava.runtime.io.IOHandle; import org.perlonjava.runtime.io.LayeredIOHandle; -import org.perlonjava.runtime.runtimetypes.RuntimeGlob; -import org.perlonjava.runtime.runtimetypes.PerlCompilerException; -import org.perlonjava.runtime.runtimetypes.RuntimeCode; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.runtime.runtimetypes.RuntimeIO; -import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarType; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarCache; import org.perlonjava.runtime.perlmodule.Warnings; +import org.perlonjava.runtime.runtimetypes.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; -import java.nio.file.attribute.PosixFileAttributes; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.UserPrincipal; +import java.nio.file.attribute.*; import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalVariable; import static org.perlonjava.runtime.runtimetypes.RuntimeIO.resolvePath; @@ -177,7 +165,7 @@ private static RuntimeScalar fileTestFromLastStat(String operator) { case "-d" -> getScalarBoolean(lastBasicAttr.isDirectory()); case "-s" -> { long size = lastBasicAttr.size(); - yield size > 0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; + yield size >0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; } case "-z" -> getScalarBoolean(lastBasicAttr.size() == 0); case "-l" -> getScalarBoolean(lastBasicAttr.isSymbolicLink()); @@ -327,7 +315,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isReadable(path)); + yield getScalarBoolean (Files.isReadable(path)); } case "-w" -> { // Check if file is writable @@ -336,7 +324,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isWritable(path)); + yield getScalarBoolean (Files.isWritable(path)); } case "-x" -> { // Check if file is executable @@ -345,7 +333,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isExecutable(path)); + yield getScalarBoolean (Files.isExecutable(path)); } case "-e" -> { // Check if file exists @@ -364,7 +352,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.size(path) == 0); + yield getScalarBoolean (Files.size(path) == 0); } case "-s" -> { // Return file size if non-zero, otherwise return false @@ -372,7 +360,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } long size = lastBasicAttr.size(); - yield size > 0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; + yield size >0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; } case "-f" -> { // Check if path is a regular file @@ -381,7 +369,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isRegularFile(path)); + yield getScalarBoolean (Files.isRegularFile(path)); } case "-d" -> { // Check if path is a directory @@ -390,14 +378,14 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isDirectory(path)); + yield getScalarBoolean (Files.isDirectory(path)); } case "-l" -> { // Check if path is a symbolic link if (!lastStatOk) { yield scalarUndef; } - yield getScalarBoolean(lastBasicAttr.isSymbolicLink()); + yield getScalarBoolean (lastBasicAttr.isSymbolicLink()); } case "-o" -> { // Check if file is owned by the effective user id (approximate with current user) @@ -409,7 +397,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) UserPrincipal owner = Files.getOwner(path); UserPrincipal currentUser = path.getFileSystem().getUserPrincipalLookupService() .lookupPrincipalByName(System.getProperty("user.name")); - yield getScalarBoolean(owner.equals(currentUser)); + yield getScalarBoolean (owner.equals(currentUser)); } case "-p" -> { // Approximate check for named pipe (FIFO) @@ -418,7 +406,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isRegularFile(path) && filename.endsWith(".fifo")); + yield getScalarBoolean (Files.isRegularFile(path) && filename.endsWith(".fifo")); } case "-S" -> { // Approximate check for socket @@ -427,7 +415,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isRegularFile(path) && filename.endsWith(".sock")); + yield getScalarBoolean (Files.isRegularFile(path) && filename.endsWith(".sock")); } case "-b" -> { // Approximate check for block special file @@ -436,7 +424,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isRegularFile(path) && filename.startsWith("/dev/")); + yield getScalarBoolean (Files.isRegularFile(path) && filename.startsWith("/dev/")); } case "-c" -> { // Approximate check for character special file @@ -445,7 +433,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isRegularFile(path) && filename.startsWith("/dev/")); + yield getScalarBoolean (Files.isRegularFile(path) && filename.startsWith("/dev/")); } case "-u" -> { // Check if setuid bit is set @@ -454,7 +442,8 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OWNER_EXECUTE))); + yield getScalarBoolean + ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OWNER_EXECUTE))); } case "-g" -> { // Check if setgid bit is set @@ -463,7 +452,8 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean((Files.getPosixFilePermissions(path).contains(PosixFilePermission.GROUP_EXECUTE))); + yield getScalarBoolean + ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.GROUP_EXECUTE))); } case "-k" -> { // Approximate check for sticky bit (using others execute permission) @@ -472,7 +462,8 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OTHERS_EXECUTE))); + yield getScalarBoolean + ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OTHERS_EXECUTE))); } case "-T", "-B" -> { // Check if file is text (-T) or binary (-B) @@ -481,7 +472,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield isTextOrBinary(path, operator.equals("-T")); + yield isTextOrBinary (path, operator.equals("-T")) } case "-M", "-A", "-C" -> { // Get file time difference for modification (-M), access (-A), or creation (-C) time @@ -490,7 +481,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getFileTimeDifference(path, operator); + yield getFileTimeDifference (path, operator) } case "-R" -> { // Check if file is readable by the real user ID @@ -499,7 +490,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isReadable(path)); + yield getScalarBoolean (Files.isReadable(path)); } case "-W" -> { // Check if file is writable by the real user ID @@ -508,7 +499,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isWritable(path)); + yield getScalarBoolean (Files.isWritable(path)); } case "-X" -> { // Check if file is executable by the real user ID @@ -517,7 +508,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean(Files.isExecutable(path)); + yield getScalarBoolean (Files.isExecutable(path)); } case "-O" -> { // Check if file is owned by the current user @@ -529,7 +520,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) UserPrincipal owner = Files.getOwner(path); UserPrincipal currentUser = path.getFileSystem().getUserPrincipalLookupService() .lookupPrincipalByName(System.getProperty("user.name")); - yield getScalarBoolean(owner.equals(currentUser)); + yield getScalarBoolean (owner.equals(currentUser)); } case "-t" -> { // -t on a string filename is an error in Perl (expects a filehandle) diff --git a/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java b/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java index 5afc720ed..8ee50730a 100644 --- a/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java +++ b/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java @@ -1,5 +1,6 @@ package org.perlonjava.runtime.operators; +import org.perlonjava.runtime.operators.FormatModifierValidator.Modifier; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; import org.perlonjava.runtime.runtimetypes.RuntimeScalarCache; @@ -84,8 +85,8 @@ public static void validateFormatModifiers(char formatChar, List modi if (seen.contains(modifierChar)) { // Duplicate modifier - issue warning WarnDie.warn( - new RuntimeScalar("Duplicate modifier '" + modifierChar + "' after '" + formatChar + "' in " + context), - RuntimeScalarCache.scalarEmptyString); + new RuntimeScalar("Duplicate modifier '" + modifierChar + "' after '" + formatChar + "' in " + context), + RuntimeScalarCache.scalarEmptyString); } seen.add(modifierChar); } @@ -144,6 +145,23 @@ public static ValidationRule getValidationRule(char formatChar) { return VALIDATION_TABLE.get(formatChar); } + /** + * Validation rule for a format character + */ + public record ValidationRule(Set allowedModifiers, Set disallowedModifiers) { + public ValidationRule(Set < Modifier > allowedModifiers, Set < Modifier > disallowedModifiers) { + this.allowedModifiers = allowedModifiers != null ? allowedModifiers : Collections.emptySet(); + this.disallowedModifiers = disallowedModifiers != null ? disallowedModifiers : Collections.emptySet(); + } + + public boolean isModifierAllowed (Modifier modifier){ + if (!disallowedModifiers.isEmpty()) { + return !disallowedModifiers.contains(modifier); + } + return allowedModifiers.isEmpty() || allowedModifiers.contains(modifier); + } + } + /** * Enum for modifier types */ @@ -171,21 +189,4 @@ public char getSymbol() { return symbol; } } - - /** - * Validation rule for a format character - */ - public record ValidationRule(Set allowedModifiers, Set disallowedModifiers) { - public ValidationRule(Set allowedModifiers, Set disallowedModifiers) { - this.allowedModifiers = allowedModifiers != null ? allowedModifiers : Collections.emptySet(); - this.disallowedModifiers = disallowedModifiers != null ? disallowedModifiers : Collections.emptySet(); - } - - public boolean isModifierAllowed(Modifier modifier) { - if (!disallowedModifiers.isEmpty()) { - return !disallowedModifiers.contains(modifier); - } - return allowedModifiers.isEmpty() || allowedModifiers.contains(modifier); - } - } } diff --git a/src/main/java/org/perlonjava/runtime/operators/IOOperator.java b/src/main/java/org/perlonjava/runtime/operators/IOOperator.java index 54289fdeb..813729ac6 100644 --- a/src/main/java/org/perlonjava/runtime/operators/IOOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/IOOperator.java @@ -120,7 +120,7 @@ public static RuntimeScalar getc(int ctx, RuntimeBase... args) { public static RuntimeScalar tell(RuntimeScalar fileHandle) { boolean argless = !fileHandle.getDefinedBoolean(); RuntimeIO fh = fileHandle.getRuntimeIO(); - + // If no explicit filehandle was provided (tell with no args), // fall back to the last accessed handle like Perl does. if (fh == null) { diff --git a/src/main/java/org/perlonjava/runtime/operators/KillOperator.java b/src/main/java/org/perlonjava/runtime/operators/KillOperator.java index eeb700c0b..fecd7801b 100644 --- a/src/main/java/org/perlonjava/runtime/operators/KillOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/KillOperator.java @@ -68,10 +68,21 @@ public static RuntimeScalar kill(int ctx, RuntimeBase... args) { private static String getSignalName(int signal) { return switch (signal) { - case 1 -> "HUP"; case 2 -> "INT"; case 3 -> "QUIT"; case 4 -> "ILL"; - case 5 -> "TRAP"; case 6 -> "ABRT"; case 7 -> "BUS"; case 8 -> "FPE"; - case 9 -> "KILL"; case 10 -> "USR1"; case 11 -> "SEGV"; case 12 -> "USR2"; - case 13 -> "PIPE"; case 14 -> "ALRM"; case 15 -> "TERM"; + case 1 -> "HUP"; + case 2 -> "INT"; + case 3 -> "QUIT"; + case 4 -> "ILL"; + case 5 -> "TRAP"; + case 6 -> "ABRT"; + case 7 -> "BUS"; + case 8 -> "FPE"; + case 9 -> "KILL"; + case 10 -> "USR1"; + case 11 -> "SEGV"; + case 12 -> "USR2"; + case 13 -> "PIPE"; + case 14 -> "ALRM"; + case 15 -> "TERM"; default -> null; }; } @@ -112,7 +123,8 @@ private static boolean sendSignalToPid(int pid, int signal) { case 15: var p = ProcessHandle.of(pid); if (p.isPresent()) { - if (signal == 9) p.get().destroyForcibly(); else p.get().destroy(); + if (signal == 9) p.get().destroyForcibly(); + else p.get().destroy(); return true; } setErrno(3); diff --git a/src/main/java/org/perlonjava/runtime/operators/ListOperators.java b/src/main/java/org/perlonjava/runtime/operators/ListOperators.java index 154b44cd8..880a0a1b5 100644 --- a/src/main/java/org/perlonjava/runtime/operators/ListOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/ListOperators.java @@ -81,7 +81,7 @@ public static RuntimeList sort(RuntimeList runtimeList, RuntimeScalar perlCompar // If comparator is a string (subroutine name), resolve it to a code reference RuntimeScalar comparator = perlComparatorClosure; if (comparator.type == RuntimeScalarType.STRING || - comparator.type == RuntimeScalarType.BYTE_STRING) { + comparator.type == RuntimeScalarType.BYTE_STRING) { String subName = comparator.toString(); if (!subName.contains("::")) { subName = packageName + "::" + subName; diff --git a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java index 677b8e4ab..23dddac40 100644 --- a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java @@ -4,7 +4,6 @@ import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.*; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.*; -import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.blessedId; /** * Provides basic arithmetic operations for RuntimeScalar objects. @@ -270,18 +269,18 @@ public static RuntimeScalar modulus(RuntimeScalar arg1, RuntimeScalar arg2) { } return new RuntimeScalar(result); } - + // Use long arithmetic to handle large integers (beyond int range) long dividend = arg1.getLong(); long divisor = arg2.getLong(); long result = dividend % divisor; - + // Adjust result for Perl-style modulus behavior // In Perl, the result has the same sign as the divisor if (result != 0 && ((divisor > 0 && result < 0) || (divisor < 0 && result > 0))) { result += divisor; } - + // Return as int if it fits, otherwise as long if (result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE) { return new RuntimeScalar((int) result); @@ -727,7 +726,7 @@ public static RuntimeScalar not(RuntimeScalar runtimeScalar) { case DOUBLE -> getScalarBoolean((double) runtimeScalar.value == 0.0); case STRING, BYTE_STRING -> { String s = (String) runtimeScalar.value; - yield getScalarBoolean(s.isEmpty() || s.equals("0")); + yield getScalarBoolean (s.isEmpty() || s.equals("0")); } case BOOLEAN -> getScalarBoolean(!(boolean) runtimeScalar.value); case GLOB -> scalarFalse; diff --git a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java index de6430c8b..14b096be5 100644 --- a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java @@ -1,10 +1,10 @@ package org.perlonjava.runtime.operators; import org.perlonjava.app.cli.CompilerOptions; +import org.perlonjava.app.scriptengine.PerlLanguageProvider; import org.perlonjava.backend.bytecode.InterpreterState; import org.perlonjava.core.Configuration; import org.perlonjava.runtime.runtimetypes.*; -import org.perlonjava.app.scriptengine.PerlLanguageProvider; import java.io.BufferedReader; import java.io.IOException; @@ -23,7 +23,7 @@ /** * ModuleOperators implements Perl's module loading operators: `do`, `require`, and `use`. - * + * *

        This class handles multiple forms of code loading: *

          *
        • do FILE - Executes a file without checking %INC
        • @@ -34,7 +34,7 @@ *
        • require FILE - Loads module once, checks %INC, requires true value
        • *
        • require VERSION - Version checking
        • *
        - * + * *

        @INC Filter Support

        *

        When a code reference is passed to `do`, it's called repeatedly as a generator: *

          @@ -43,27 +43,27 @@ *
        • Return true to continue reading
        • *
        • Optional state parameters are passed as @_ (starting at $_[1])
        • *
        - * + * *

        Error Handling

        *

        Errors are stored in special variables: *

          *
        • $@ - Compilation/execution errors
        • *
        • $! - I/O errors (file not found, permissions, etc.)
        • *
        - * + * * @see perldoc do * @see perldoc require */ public class ModuleOperators { - + /** * Public entry point for `do` operator. - * + * *

        Always sets %INC and keeps the entry regardless of execution result. * This differs from `require` which removes %INC entries on failure. - * + * * @param runtimeScalar The file, coderef, filehandle, or array reference to execute - * @param ctx Execution context (scalar or list) + * @param ctx Execution context (scalar or list) * @return Result of execution (undef on error) */ public static RuntimeBase doFile(RuntimeScalar runtimeScalar, int ctx) { @@ -72,9 +72,9 @@ public static RuntimeBase doFile(RuntimeScalar runtimeScalar, int ctx) { /** * Internal implementation of `do` and `require` operators. - * + * *

        This method handles the complex dispatch logic for different argument types: - * + * *

        1. Array Reference: [\&coderef, state...]

        *

        When the first element is a code reference: *

          @@ -83,7 +83,7 @@ public static RuntimeBase doFile(RuntimeScalar runtimeScalar, int ctx) { *
        • Call coderef repeatedly until it returns false
        • *
        • Each call populates $_ with code chunks
        • *
        - * + * *

        2. Code Reference: \&generator

        *

        When a coderef is passed directly: *

          @@ -91,10 +91,10 @@ public static RuntimeBase doFile(RuntimeScalar runtimeScalar, int ctx) { *
        • Each call should set $_ to next chunk
        • *
        • Return false to signal EOF
        • *
        - * + * *

        3. Filehandle: $fh

        *

        Read entire contents from filehandle and execute. - * + * *

        4. Filename: "Module/Name.pm"

        *

        Standard file loading: *

          @@ -102,11 +102,11 @@ public static RuntimeBase doFile(RuntimeScalar runtimeScalar, int ctx) { *
        • Check for .pmc (compiled) version first
        • *
        • Read file and execute
        • *
        - * + * * @param runtimeScalar The argument to do/require - * @param setINC Whether to set %INC entry for this file - * @param isRequire True if called from require (affects %INC cleanup on failure) - * @param ctx Execution context (scalar or list) + * @param setINC Whether to set %INC entry for this file + * @param isRequire True if called from require (affects %INC cleanup on failure) + * @param ctx Execution context (scalar or list) * @return Result of execution (undef on error, with $@ or $! set) */ private static RuntimeBase doFile(RuntimeScalar runtimeScalar, boolean setINC, boolean isRequire, int ctx) { @@ -122,10 +122,10 @@ private static RuntimeBase doFile(RuntimeScalar runtimeScalar, boolean setINC, b // Variables for handling array references with state RuntimeCode codeRef = null; RuntimeArray stateArgs = null; - + // Variable for storing @INC hook reference RuntimeScalar incHookRef = null; - + // Flag to indicate if source filters should be applied boolean shouldApplyFilters = false; @@ -136,7 +136,7 @@ private static RuntimeBase doFile(RuntimeScalar runtimeScalar, boolean setINC, b RuntimeArray arr = (RuntimeArray) runtimeScalar.value; if (arr.size() > 0) { RuntimeScalar firstElem = arr.get(0); - + // Case 1a: Array with CODE reference [&coderef, state...] // Extract coderef and state parameters for later execution if (firstElem.type == RuntimeScalarType.CODE || @@ -154,7 +154,7 @@ private static RuntimeBase doFile(RuntimeScalar runtimeScalar, boolean setINC, b codeRef = (RuntimeCode) deref.value; } } - + // Create arguments array from remaining elements (state parameters) // These will be passed as @_ to the coderef // Note: $_[0] is reserved for filename (undef for generators), state starts at $_[1] @@ -171,7 +171,7 @@ else if (firstElem.type == RuntimeScalarType.GLOB || firstElem.type == RuntimeScalarType.GLOBREFERENCE) { // Read content from filehandle code = Readline.readline(firstElem, RuntimeContextType.LIST).toString(); - + // Check if there's a filter (second element) if (arr.size() > 1) { RuntimeScalar secondElem = arr.get(1); @@ -191,24 +191,24 @@ else if (firstElem.type == RuntimeScalarType.GLOB || filterRef = (RuntimeCode) deref.value; } } - + if (filterRef != null) { // Apply filter to the content RuntimeScalar savedDefaultVar = GlobalVariable.getGlobalVariable("main::_"); try { // Set $_ to the content GlobalVariable.getGlobalVariable("main::_").set(code); - + // Build filter args: $_[0] = undef, $_[1..N] = state RuntimeArray filterArgs = new RuntimeArray(); filterArgs.push(new RuntimeScalar()); // $_[0] = undef for (int i = 2; i < arr.size(); i++) { filterArgs.push(arr.get(i)); // $_[1..N] = state } - + // Call the filter filterRef.apply(filterArgs, RuntimeContextType.SCALAR); - + // Get modified content from $_ code = GlobalVariable.getGlobalVariable("main::_").toString(); } finally { @@ -230,17 +230,17 @@ else if (firstElem.type == RuntimeScalarType.REFERENCE) { StringBuilder filterableContent = new StringBuilder(); // Filehandle content (filterable) RuntimeCode filterRef = null; int filterIndex = -1; - + // Process array elements for (int i = 0; i < arr.size(); i++) { RuntimeScalar elem = arr.get(i); - + // Check if this is a filter (CODE ref) boolean isFilter = elem.type == RuntimeScalarType.CODE || - (elem.type == RuntimeScalarType.REFERENCE && - elem.scalarDeref() != null && - elem.scalarDeref().type == RuntimeScalarType.CODE); - + (elem.type == RuntimeScalarType.REFERENCE && + elem.scalarDeref() != null && + elem.scalarDeref().type == RuntimeScalarType.CODE); + if (isFilter) { // Found the filter - extract it filterIndex = i; @@ -260,7 +260,7 @@ else if (firstElem.type == RuntimeScalarType.REFERENCE) { else if (elem.type == RuntimeScalarType.REFERENCE) { RuntimeScalar deref = elem.scalarDeref(); if (deref != null) { - unfilteredPrefix.append(deref.toString()); + unfilteredPrefix.append(deref); } } // Filehandle - goes to filterable content @@ -268,35 +268,35 @@ else if (elem.type == RuntimeScalarType.GLOB || elem.type == RuntimeScalarType.G filterableContent.append(Readline.readline(elem, RuntimeContextType.LIST).toString()); } } - + // Apply filter to filehandle content only if (filterRef != null) { RuntimeScalar savedDefaultVar = GlobalVariable.getGlobalVariable("main::_"); try { GlobalVariable.getGlobalVariable("main::_").set(filterableContent.toString()); - + // Build filter args with remaining elements as state RuntimeArray filterArgs = new RuntimeArray(); filterArgs.push(new RuntimeScalar()); // $_[0] = undef for (int j = filterIndex + 1; j < arr.size(); j++) { filterArgs.push(arr.get(j)); // $_[1..N] = state } - + filterRef.apply(filterArgs, RuntimeContextType.SCALAR); filterableContent = new StringBuilder(GlobalVariable.getGlobalVariable("main::_").toString()); } finally { GlobalVariable.getGlobalVariable("main::_").set(savedDefaultVar.toString()); } } - + // Concatenate unfiltered prefix + filtered filehandle content - code = unfilteredPrefix.toString() + filterableContent.toString(); + code = unfilteredPrefix.toString() + filterableContent; // Enable BEGIN filter preprocessing shouldApplyFilters = true; } } } - + // ===== STEP 2: Handle direct CODE reference ===== // Check if the argument is a CODE reference (not already extracted from array) if (codeRef == null && (runtimeScalar.type == RuntimeScalarType.CODE || @@ -316,7 +316,7 @@ else if (elem.type == RuntimeScalarType.GLOB || elem.type == RuntimeScalarType.G } } } - + // Create args with filename placeholder if not already set (no state for direct coderef) if (stateArgs == null) { stateArgs = new RuntimeArray(); @@ -335,29 +335,29 @@ else if (elem.type == RuntimeScalarType.GLOB || elem.type == RuntimeScalarType.G // Each call should populate $_ with a chunk of code // State parameters (if any) are passed as @_ boolean continueReading = true; - + while (continueReading) { // Clear $_ before each call GlobalVariable.getGlobalVariable("main::_").set(""); - + // Call the CODE reference with state arguments // The coderef should populate $_ with content RuntimeBase result = codeRef.apply(stateArgs, RuntimeContextType.SCALAR); - + // Get the content from $_ RuntimeScalar defaultVar = GlobalVariable.getGlobalVariable("main::_"); String chunk = defaultVar.toString(); - + // Accumulate the chunk if not empty if (!chunk.isEmpty()) { accumulatedCode.append(chunk); } - + // Check if we should continue // Return value of 0/false means EOF continueReading = result.scalar().getBoolean(); } - + code = accumulatedCode.toString(); if (code.isEmpty()) { code = null; @@ -370,7 +370,7 @@ else if (elem.type == RuntimeScalarType.GLOB || elem.type == RuntimeScalarType.G // Restore $_ to its previous value GlobalVariable.getGlobalVariable("main::_").set(savedDefaultVar.toString()); } - } + } // ===== STEP 4: Handle filehandle ===== else if (runtimeScalar.type == RuntimeScalarType.GLOB || runtimeScalar.type == RuntimeScalarType.GLOBREFERENCE) { // Read entire contents from filehandle @@ -401,7 +401,7 @@ else if (code == null) { // and if it exists on the filesystem Path filePath = Paths.get(fileName); boolean tryDirectPath = filePath.isAbsolute() || fileName.startsWith("./") || fileName.startsWith("../"); - + if (tryDirectPath) { // For absolute or explicit relative paths, resolve using RuntimeIO.getPath filePath = RuntimeIO.resolvePath(fileName); @@ -415,7 +415,7 @@ else if (code == null) { actualFileName = fullName.toString(); } } - + // If we haven't found the file yet, search in INC directories // This handles: // 1. Relative module names (e.g., Foo::Bar) @@ -446,37 +446,37 @@ else if (code == null) { incSize = incArray.size(); for (int i = 0; i < incSize; i++) { RuntimeScalar dirScalar = incArray.get(i); - + // If this is a tied scalar, fetch the actual value if (dirScalar.type == RuntimeScalarType.TIED_SCALAR) { dirScalar = dirScalar.tiedFetch(); } - + // For absolute/relative paths (starting with /, ./, ../), only try hooks // Regular directory entries should be skipped for such paths - boolean isHook = dirScalar.type == RuntimeScalarType.CODE || - dirScalar.type == RuntimeScalarType.REFERENCE || - dirScalar.type == RuntimeScalarType.ARRAYREFERENCE || - dirScalar.type == RuntimeScalarType.HASHREFERENCE; - + boolean isHook = dirScalar.type == RuntimeScalarType.CODE || + dirScalar.type == RuntimeScalarType.REFERENCE || + dirScalar.type == RuntimeScalarType.ARRAYREFERENCE || + dirScalar.type == RuntimeScalarType.HASHREFERENCE; + if (tryDirectPath && !isHook) { // Skip regular directory entries for absolute/relative paths continue; } - + // Check if this @INC entry is a CODE reference, ARRAY reference, or blessed object if (isHook) { - + RuntimeBase hookResult = tryIncHook(dirScalar, fileName); if (hookResult != null) { // Hook returned something useful RuntimeScalar hookResultScalar = hookResult.scalar(); - + // Check if it's a filehandle (GLOB), array ref with filehandle, or scalar ref with code RuntimeScalar filehandle = null; - - if (hookResultScalar.type == RuntimeScalarType.GLOB || - hookResultScalar.type == RuntimeScalarType.GLOBREFERENCE) { + + if (hookResultScalar.type == RuntimeScalarType.GLOB || + hookResultScalar.type == RuntimeScalarType.GLOBREFERENCE) { filehandle = hookResultScalar; } else if (hookResultScalar.type == RuntimeScalarType.REFERENCE) { // Hook returned a scalar reference - treat the dereferenced value as code @@ -490,13 +490,13 @@ else if (code == null) { RuntimeArray resultArray = (RuntimeArray) hookResultScalar.value; if (resultArray.size() > 0) { RuntimeScalar firstElem = resultArray.get(0); - if (firstElem.type == RuntimeScalarType.GLOB || - firstElem.type == RuntimeScalarType.GLOBREFERENCE) { + if (firstElem.type == RuntimeScalarType.GLOB || + firstElem.type == RuntimeScalarType.GLOBREFERENCE) { filehandle = firstElem; } } } - + if (filehandle != null) { // Read content from the filehandle using the same method as STEP 4 try { @@ -512,7 +512,7 @@ else if (code == null) { // If hook returned undef or we couldn't use the result, continue to next @INC entry continue; } - + // Original string handling for directory paths String dirName = dirScalar.toString(); if (dirName.equals(GlobalContext.JAR_PERLLIB)) { @@ -600,13 +600,13 @@ else if (code == null) { // Check if the hook already set %INC to a custom value RuntimeHash incHash = getGlobalHash("main::INC"); RuntimeScalar existingIncValue = incHash.elements.get(fileName); - + // Only set %INC if the hook didn't already set it if (existingIncValue == null || !existingIncValue.defined().getBoolean()) { // If we used an @INC hook, store the hook reference; otherwise store the filename - RuntimeScalar incValue = (parsedArgs.incHook != null) - ? parsedArgs.incHook - : new RuntimeScalar(parsedArgs.fileName); + RuntimeScalar incValue = (parsedArgs.incHook != null) + ? parsedArgs.incHook + : new RuntimeScalar(parsedArgs.fileName); incHash.put(fileName, incValue); } else if (parsedArgs.incHook != null) { // Hook set %INC to a custom value - use that for actualFileName if it's a string @@ -667,9 +667,9 @@ else if (code == null) { /** * Implements Perl's `require` operator. - * + * *

        The `require` operator has two distinct behaviors: - * + * *

        1. Version Checking: require VERSION

        *

        When given a numeric or vstring value: *

          @@ -677,7 +677,7 @@ else if (code == null) { *
        • Throw exception if version requirement not met
        • *
        • Return 1 if version is sufficient
        • *
        - * + * *

        2. Module Loading: require MODULE

        *

        When given a string (module name or filename): *

          @@ -688,7 +688,7 @@ else if (code == null) { *
        • Add entry to %INC on success
        • *
        • Remove from %INC or mark as undef on failure
        • *
        - * + * *

        Error Handling

        *

        `require` is stricter than `do`: *

          @@ -697,11 +697,11 @@ else if (code == null) { *
        • Throws exception if module returns false value
        • *
        • Marks compilation failures as undef in %INC (cached failure)
        • *
        - * + * * @param runtimeScalar Module name, filename, or version to require * @return Always returns 1 on success (or throws exception) - * @throws PerlCompilerException if version insufficient, file not found, - * compilation fails, or module returns false + * @throws PerlCompilerException if version insufficient, file not found, + * compilation fails, or module returns false * @see perldoc require */ public static RuntimeScalar require(RuntimeScalar runtimeScalar) { @@ -788,32 +788,32 @@ public static RuntimeScalar require(RuntimeScalar runtimeScalar) { /** * Try to call an @INC hook to load a module. - * + * *

        @INC can contain: *

          *
        • CODE reference: call it with ($coderef, $filename)
        • *
        • ARRAY reference: call $array->[0] with ($array, $filename)
        • *
        • Blessed object: call $obj->INC($filename) if the method exists
        • *
        - * + * *

        The hook can return: *

          *
        • undef: this hook can't handle it, continue to next @INC entry
        • *
        • A filehandle: read the module code from this filehandle
        • *
        • An array ref [$fh, \&filter, state...]: filehandle with optional filter and state
        • *
        - * - * @param hook The @INC hook (CODE, ARRAY, or blessed reference) + * + * @param hook The @INC hook (CODE, ARRAY, or blessed reference) * @param fileName The file name being required * @return The result from the hook (undef, filehandle, or array ref), or null if hook can't be called */ private static RuntimeBase tryIncHook(RuntimeScalar hook, String fileName) { RuntimeCode codeRef = null; RuntimeScalar selfArg = hook; - + // First check if it's a blessed object (takes priority over plain refs) int blessIdInt = RuntimeScalarType.blessedId(hook); - + // Case 1: Blessed object - try to call INC method if (blessIdInt != 0) { String blessId = NameNormalizer.getBlessStr(blessIdInt); @@ -859,24 +859,24 @@ else if (hook.type == RuntimeScalarType.ARRAYREFERENCE && hook.value instanceof } } } - + if (codeRef == null) { return null; } - + // Call the hook with ($self, $filename) RuntimeArray args = new RuntimeArray(); args.push(selfArg); args.push(new RuntimeScalar(fileName)); - + try { RuntimeBase result = codeRef.apply(args, RuntimeContextType.SCALAR); - + // If result is undef, return null to continue to next @INC entry if (result == null || !result.scalar().defined().getBoolean()) { return null; } - + return result; } catch (Exception e) { // If hook throws an exception, continue to next @INC entry diff --git a/src/main/java/org/perlonjava/runtime/operators/Operator.java b/src/main/java/org/perlonjava/runtime/operators/Operator.java index 8a152a29e..478b42baa 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Operator.java +++ b/src/main/java/org/perlonjava/runtime/operators/Operator.java @@ -145,47 +145,47 @@ public static RuntimeList split(RuntimeScalar quotedRegex, RuntimeList args, int int splitCount = 0; try { - while (matcher.find() && (limit <= 0 || splitCount < limit - 1)) { - // Add the part before the match - - // System.out.println("matcher lastend " + lastEnd + " start " + matcher.start() + " end " + matcher.end() + " length " + inputStr.length()); - if (lastEnd == 0 && matcher.end() == 0) { - // if (lastEnd == 0 && matchStr.isEmpty()) { - // A zero-width match at the beginning of EXPR never produces an empty field - // System.out.println("matcher skip first"); - } else if (matcher.start() == matcher.end() && matcher.start() == lastEnd) { - // Skip consecutive zero-width matches at the same position - // This handles patterns like / */ that can match zero spaces - continue; - } else { - splitElements.add(new RuntimeScalar(inputStr.substring(lastEnd, matcher.start()))); - } + while (matcher.find() && (limit <= 0 || splitCount < limit - 1)) { + // Add the part before the match + + // System.out.println("matcher lastend " + lastEnd + " start " + matcher.start() + " end " + matcher.end() + " length " + inputStr.length()); + if (lastEnd == 0 && matcher.end() == 0) { + // if (lastEnd == 0 && matchStr.isEmpty()) { + // A zero-width match at the beginning of EXPR never produces an empty field + // System.out.println("matcher skip first"); + } else if (matcher.start() == matcher.end() && matcher.start() == lastEnd) { + // Skip consecutive zero-width matches at the same position + // This handles patterns like / */ that can match zero spaces + continue; + } else { + splitElements.add(new RuntimeScalar(inputStr.substring(lastEnd, matcher.start()))); + } - // Add captured groups if any (but skip code block captures) - Pattern p = matcher.pattern(); - Map namedGroups = p.namedGroups(); - for (int i = 1; i <= matcher.groupCount(); i++) { - // Check if this is a code block capture (starts with "cb") - boolean isCodeBlockCapture = false; - if (namedGroups != null) { - for (Map.Entry entry : namedGroups.entrySet()) { - if (entry.getValue() == i && entry.getKey().startsWith("cb")) { - isCodeBlockCapture = true; - break; + // Add captured groups if any (but skip code block captures) + Pattern p = matcher.pattern(); + Map namedGroups = p.namedGroups(); + for (int i = 1; i <= matcher.groupCount(); i++) { + // Check if this is a code block capture (starts with "cb") + boolean isCodeBlockCapture = false; + if (namedGroups != null) { + for (Map.Entry entry : namedGroups.entrySet()) { + if (entry.getValue() == i && entry.getKey().startsWith("cb")) { + isCodeBlockCapture = true; + break; + } } } + + // Only add non-code-block captures to split results + if (!isCodeBlockCapture) { + String group = matcher.group(i); + splitElements.add(group != null ? new RuntimeScalar(group) : scalarUndef); + } } - - // Only add non-code-block captures to split results - if (!isCodeBlockCapture) { - String group = matcher.group(i); - splitElements.add(group != null ? new RuntimeScalar(group) : scalarUndef); - } - } - lastEnd = matcher.end(); - splitCount++; - } + lastEnd = matcher.end(); + splitCount++; + } } catch (RegexTimeoutException e) { WarnDie.warn(new RuntimeScalar(e.getMessage() + "\n"), RuntimeScalarCache.scalarEmptyString); } @@ -381,7 +381,7 @@ public static RuntimeList splice(RuntimeArray runtimeArray, RuntimeList list) { } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield splice(runtimeArray, list); // Recursive call after vivification + yield splice (runtimeArray, list)// Recursive call after vivification } case TIED_ARRAY -> TieArray.tiedSplice(runtimeArray, list); default -> throw new IllegalStateException("Unknown array type: " + runtimeArray.type); @@ -534,7 +534,7 @@ private static RuntimeList reversePlainArray(RuntimeArray array) { public static RuntimeBase repeat(RuntimeBase value, RuntimeScalar timesScalar, int ctx) { // Check for uninitialized values and generate warnings // Use getDefinedBoolean() to handle tied scalars correctly - if (value instanceof RuntimeScalar && !((RuntimeScalar) value).getDefinedBoolean()) { + if (value instanceof RuntimeScalar && !value.getDefinedBoolean()) { WarnDie.warn(new RuntimeScalar("Use of uninitialized value in string repetition (x)"), RuntimeScalarCache.scalarEmptyString); } diff --git a/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java b/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java index 5d63913ad..557423c4c 100644 --- a/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java @@ -349,7 +349,7 @@ public record OperatorHandler(String className, String methodName, int methodTyp * @param methodName The name of the method to associate with the operator. * @param className The name of the class containing the method. */ - static void put(String operator, String methodName, String className) { + static void put (String operator, String methodName, String className){ operatorHandlers.put(operator, new OperatorHandler(className, methodName, @@ -365,7 +365,7 @@ static void put(String operator, String methodName, String className) { * @param className The name of the class containing the method. * @param descriptor The JVM parameter descriptor */ - static void put(String operator, String methodName, String className, String descriptor) { + static void put (String operator, String methodName, String className, String descriptor){ operatorHandlers.put(operator, new OperatorHandler(className, methodName, @@ -379,7 +379,7 @@ static void put(String operator, String methodName, String className, String des * @param operator The operator symbol. * @return The OperatorHandler associated with the operator, or null if not found. */ - public static OperatorHandler get(String operator) { + public static OperatorHandler get (String operator){ return operatorHandlers.get(operator); } @@ -389,7 +389,7 @@ public static OperatorHandler get(String operator) { * @return The class name. */ @Override - public String className() { + public String className () { return className; } @@ -399,7 +399,7 @@ public String className() { * @return The method name. */ @Override - public String methodName() { + public String methodName () { return methodName; } @@ -409,7 +409,7 @@ public String methodName() { * @return The method type. */ @Override - public int methodType() { + public int methodType () { return methodType; } @@ -419,7 +419,7 @@ public int methodType() { * @return The method descriptor. */ @Override - public String descriptor() { + public String descriptor () { return descriptor; } @@ -428,7 +428,7 @@ public String descriptor() { * * @return The modified method descriptor. */ - public String getDescriptorWithIntParameter() { + public String getDescriptorWithIntParameter () { String descriptor = this.descriptor; // replace last argument with `I` return descriptor.replace("Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)", "I)"); @@ -440,7 +440,7 @@ public String getDescriptorWithIntParameter() { * @return The return type class name with semicolon (e.g., "RuntimeScalar;"), * or null if return type cannot be determined */ - public String getReturnTypeDescriptor() { + public String getReturnTypeDescriptor () { if (descriptor == null) { return null; } diff --git a/src/main/java/org/perlonjava/runtime/operators/Pack.java b/src/main/java/org/perlonjava/runtime/operators/Pack.java index f794f14eb..81971bc5f 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Pack.java +++ b/src/main/java/org/perlonjava/runtime/operators/Pack.java @@ -51,18 +51,58 @@ * @see PackWriter */ public class Pack { + public static final Map handlers = new HashMap<>(); /** * Enable trace output for pack operations. * Set to true to debug pack template processing. */ private static final boolean TRACE_PACK = false; - private static final ThreadLocal> groupBaseStack = ThreadLocal.withInitial(() -> { Stack stack = new Stack<>(); stack.push(0); return stack; }); + static { + // Initialize format handlers + handlers.put('b', new BitStringPackHandler('b')); + handlers.put('B', new BitStringPackHandler('B')); + handlers.put('h', new HexStringPackHandler('h')); + handlers.put('H', new HexStringPackHandler('H')); + handlers.put('u', new UuencodePackHandler()); + handlers.put('p', new PointerPackHandler('p')); + handlers.put('P', new PointerPackHandler('P')); + // W format is handled specially like U format (see switch statement below) + // handlers.put('W', new WideCharacterPackHandler()); + handlers.put('x', new ControlPackHandler('x')); + handlers.put('X', new ControlPackHandler('X')); + handlers.put('@', new ControlPackHandler('@')); + handlers.put('.', new ControlPackHandler('.')); + + // Numeric format handlers + handlers.put('c', new NumericPackHandler('c')); + handlers.put('C', new NumericPackHandler('C')); + handlers.put('s', new NumericPackHandler('s')); + handlers.put('S', new NumericPackHandler('S')); + handlers.put('i', new NumericPackHandler('i')); + handlers.put('I', new NumericPackHandler('I')); + handlers.put('l', new NumericPackHandler('l')); + handlers.put('L', new NumericPackHandler('L')); + handlers.put('q', new NumericPackHandler('q')); + handlers.put('Q', new NumericPackHandler('Q')); + handlers.put('j', new NumericPackHandler('j')); + handlers.put('J', new NumericPackHandler('J')); + handlers.put('f', new NumericPackHandler('f')); + handlers.put('F', new NumericPackHandler('F')); + handlers.put('d', new NumericPackHandler('d')); + handlers.put('D', new NumericPackHandler('D')); + handlers.put('n', new NumericPackHandler('n')); + handlers.put('N', new NumericPackHandler('N')); + handlers.put('v', new NumericPackHandler('v')); + handlers.put('V', new NumericPackHandler('V')); + handlers.put('w', new NumericPackHandler('w')); + } + public static void pushGroupBase(int base) { groupBaseStack.get().push(base); } @@ -112,48 +152,6 @@ public static void adjustGroupBasesAfterTruncate(int newSize) { } } - public static final Map handlers = new HashMap<>(); - - static { - // Initialize format handlers - handlers.put('b', new BitStringPackHandler('b')); - handlers.put('B', new BitStringPackHandler('B')); - handlers.put('h', new HexStringPackHandler('h')); - handlers.put('H', new HexStringPackHandler('H')); - handlers.put('u', new UuencodePackHandler()); - handlers.put('p', new PointerPackHandler('p')); - handlers.put('P', new PointerPackHandler('P')); - // W format is handled specially like U format (see switch statement below) - // handlers.put('W', new WideCharacterPackHandler()); - handlers.put('x', new ControlPackHandler('x')); - handlers.put('X', new ControlPackHandler('X')); - handlers.put('@', new ControlPackHandler('@')); - handlers.put('.', new ControlPackHandler('.')); - - // Numeric format handlers - handlers.put('c', new NumericPackHandler('c')); - handlers.put('C', new NumericPackHandler('C')); - handlers.put('s', new NumericPackHandler('s')); - handlers.put('S', new NumericPackHandler('S')); - handlers.put('i', new NumericPackHandler('i')); - handlers.put('I', new NumericPackHandler('I')); - handlers.put('l', new NumericPackHandler('l')); - handlers.put('L', new NumericPackHandler('L')); - handlers.put('q', new NumericPackHandler('q')); - handlers.put('Q', new NumericPackHandler('Q')); - handlers.put('j', new NumericPackHandler('j')); - handlers.put('J', new NumericPackHandler('J')); - handlers.put('f', new NumericPackHandler('f')); - handlers.put('F', new NumericPackHandler('F')); - handlers.put('d', new NumericPackHandler('d')); - handlers.put('D', new NumericPackHandler('D')); - handlers.put('n', new NumericPackHandler('n')); - handlers.put('N', new NumericPackHandler('N')); - handlers.put('v', new NumericPackHandler('v')); - handlers.put('V', new NumericPackHandler('V')); - handlers.put('w', new NumericPackHandler('w')); - } - /** * Retrieves a string value associated with a pointer hash code. * This method is used by the unpack operation to retrieve strings diff --git a/src/main/java/org/perlonjava/runtime/operators/Readline.java b/src/main/java/org/perlonjava/runtime/operators/Readline.java index 28f85beb9..bcf8679b7 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Readline.java +++ b/src/main/java/org/perlonjava/runtime/operators/Readline.java @@ -64,7 +64,7 @@ public static RuntimeScalar readline(RuntimeIO runtimeIO) { // Handle different modes of $/ boolean isSlurp = (rs != null && rs.isSlurpMode()) || - (rs == null && rsScalar.type == RuntimeScalarType.UNDEF); + (rs == null && rsScalar.type == RuntimeScalarType.UNDEF); if (isSlurp) { StringBuilder content = new StringBuilder(); boolean isByteData = true; @@ -358,7 +358,10 @@ public static RuntimeScalar read(RuntimeList args) { String s = scalarValue.toString(); boolean safe = true; for (int i = 0; safe && i < s.length(); i++) { - if (s.charAt(i) > 255) safe = false; + if (s.charAt(i) > 255) { + safe = false; + break; + } } if (safe) { scalar.set(new RuntimeScalar(s.getBytes(StandardCharsets.ISO_8859_1))); diff --git a/src/main/java/org/perlonjava/runtime/operators/ReferenceOperators.java b/src/main/java/org/perlonjava/runtime/operators/ReferenceOperators.java index 26e91ecd4..b026c0bb4 100644 --- a/src/main/java/org/perlonjava/runtime/operators/ReferenceOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/ReferenceOperators.java @@ -65,21 +65,21 @@ public static RuntimeScalar ref(RuntimeScalar runtimeScalar) { // If only one slot is filled, return the type of that slot RuntimeGlob glob = (RuntimeGlob) runtimeScalar.value; String globName = glob.globName; - + // Special case: stash entries (RuntimeStashEntry) should always return empty string // because they represent stash entries, not regular globs if (runtimeScalar.value instanceof RuntimeStashEntry) { str = ""; break; } - + // Special case: stash globs (ending with ::) should always return empty string // because they represent the entire package stash, not a single slot if (globName.endsWith("::")) { str = ""; break; } - + // Check various slots boolean hasScalar = GlobalVariable.getGlobalVariable(globName).getDefinedBoolean(); boolean hasArray = GlobalVariable.getGlobalArray(globName).size() > 0; @@ -87,7 +87,7 @@ public static RuntimeScalar ref(RuntimeScalar runtimeScalar) { boolean hasCode = GlobalVariable.getGlobalCodeRef(globName).getDefinedBoolean(); boolean hasFormat = GlobalVariable.getGlobalFormatRef(globName).getDefinedBoolean(); boolean hasIO = GlobalVariable.getGlobalIO(globName).getRuntimeIO() != null; - + // Special case: constant subroutine created from scalar should return SCALAR if (hasScalar && hasCode) { RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(globName); @@ -98,17 +98,35 @@ public static RuntimeScalar ref(RuntimeScalar runtimeScalar) { break; } } - + // Count filled slots int filledSlots = 0; String slotType = ""; - if (hasScalar) { filledSlots++; slotType = "SCALAR"; } - if (hasArray) { filledSlots++; if (slotType.isEmpty()) slotType = "ARRAY"; } - if (hasHash) { filledSlots++; if (slotType.isEmpty()) slotType = "HASH"; } - if (hasCode) { filledSlots++; if (slotType.isEmpty()) slotType = "CODE"; } - if (hasFormat) { filledSlots++; if (slotType.isEmpty()) slotType = "FORMAT"; } - if (hasIO) { filledSlots++; if (slotType.isEmpty()) slotType = "IO"; } - + if (hasScalar) { + filledSlots++; + slotType = "SCALAR"; + } + if (hasArray) { + filledSlots++; + if (slotType.isEmpty()) slotType = "ARRAY"; + } + if (hasHash) { + filledSlots++; + if (slotType.isEmpty()) slotType = "HASH"; + } + if (hasCode) { + filledSlots++; + if (slotType.isEmpty()) slotType = "CODE"; + } + if (hasFormat) { + filledSlots++; + if (slotType.isEmpty()) slotType = "FORMAT"; + } + if (hasIO) { + filledSlots++; + if (slotType.isEmpty()) slotType = "IO"; + } + // If exactly one slot is filled, return its type // Otherwise return empty string (standard Perl behavior for multi-slot globs) str = (filledSlots == 1) ? slotType : ""; diff --git a/src/main/java/org/perlonjava/runtime/operators/RuntimeTransliterate.java b/src/main/java/org/perlonjava/runtime/operators/RuntimeTransliterate.java index e1df64013..6f172d6eb 100644 --- a/src/main/java/org/perlonjava/runtime/operators/RuntimeTransliterate.java +++ b/src/main/java/org/perlonjava/runtime/operators/RuntimeTransliterate.java @@ -53,7 +53,7 @@ public RuntimeScalar transliterate(RuntimeScalar originalString, int ctx) { // For complement mode, we need to track replacement index Map complementMap = new HashMap<>(); int replacementIndex = 0; - + for (int i = 0; i < input.length(); i++) { int codePoint = input.codePointAt(i); @@ -398,7 +398,7 @@ private int parseCharAt(String input, int pos, List result) { int closePos = input.indexOf('}', pos + 3); if (closePos > pos + 3) { String content = input.substring(pos + 3, closePos).trim(); - + // Check for empty character name if (content.isEmpty()) { throw new RuntimeException("Unknown charname ''"); diff --git a/src/main/java/org/perlonjava/runtime/operators/Stat.java b/src/main/java/org/perlonjava/runtime/operators/Stat.java index de24a7b02..6a39f218d 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Stat.java +++ b/src/main/java/org/perlonjava/runtime/operators/Stat.java @@ -7,12 +7,7 @@ import org.perlonjava.runtime.io.LayeredIOHandle; import org.perlonjava.runtime.nativ.NativeUtils; import org.perlonjava.runtime.nativ.PosixLibrary; -import org.perlonjava.runtime.runtimetypes.RuntimeBase; -import org.perlonjava.runtime.runtimetypes.RuntimeContextType; -import org.perlonjava.runtime.runtimetypes.RuntimeIO; -import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarType; +import org.perlonjava.runtime.runtimetypes.*; import java.io.IOException; import java.nio.file.Files; @@ -24,41 +19,27 @@ import java.nio.file.attribute.PosixFilePermission; import java.util.Set; -import static org.perlonjava.runtime.operators.FileTestOperator.lastBasicAttr; -import static org.perlonjava.runtime.operators.FileTestOperator.lastFileHandle; -import static org.perlonjava.runtime.operators.FileTestOperator.lastPosixAttr; -import static org.perlonjava.runtime.operators.FileTestOperator.lastStatOk; -import static org.perlonjava.runtime.operators.FileTestOperator.lastStatErrno; -import static org.perlonjava.runtime.operators.FileTestOperator.updateLastStat; +import static org.perlonjava.runtime.operators.FileTestOperator.*; import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalVariable; import static org.perlonjava.runtime.runtimetypes.RuntimeIO.resolvePath; -import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.getScalarInt; -import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarTrue; -import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef; +import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.*; public class Stat { - private record NativeStatFields( - long dev, long ino, long mode, long nlink, - long uid, long gid, long rdev, long size, - long atime, long mtime, long ctime, - long blksize, long blocks - ) {} - static NativeStatFields lastNativeStatFields; static NativeStatFields nativeStat(String path, boolean followLinks) { try { if (NativeUtils.IS_WINDOWS) return null; FileStat fs = followLinks - ? PosixLibrary.INSTANCE.stat(path) - : PosixLibrary.INSTANCE.lstat(path); + ? PosixLibrary.INSTANCE.stat(path) + : PosixLibrary.INSTANCE.lstat(path); if (fs == null) return null; return new NativeStatFields( - fs.dev(), fs.ino(), fs.mode(), fs.nlink(), - fs.uid(), fs.gid(), fs.rdev(), fs.st_size(), - fs.atime(), fs.mtime(), fs.ctime(), - fs.blockSize(), fs.blocks() + fs.dev(), fs.ino(), fs.mode(), fs.nlink(), + fs.uid(), fs.gid(), fs.rdev(), fs.st_size(), + fs.atime(), fs.mtime(), fs.ctime(), + fs.blockSize(), fs.blocks() ); } catch (Throwable e) { return null; @@ -336,4 +317,12 @@ private static void statInternalBasic(RuntimeList res, BasicFileAttributes basic res.add(scalarUndef); res.add(scalarUndef); } + + private record NativeStatFields( + long dev, long ino, long mode, long nlink, + long uid, long gid, long rdev, long size, + long atime, long mtime, long ctime, + long blksize, long blocks + ) { + } } diff --git a/src/main/java/org/perlonjava/runtime/operators/StringOperators.java b/src/main/java/org/perlonjava/runtime/operators/StringOperators.java index 1e9c52449..487ffe256 100644 --- a/src/main/java/org/perlonjava/runtime/operators/StringOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/StringOperators.java @@ -279,7 +279,7 @@ public static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeSca String bStr = b.toString(); if (runtimeScalar.type == RuntimeScalarType.STRING || b.type == RuntimeScalarType.STRING) { - return new RuntimeScalar(runtimeScalar.toString() + bStr); + return new RuntimeScalar(runtimeScalar + bStr); } if (runtimeScalar.type == BYTE_STRING || b.type == BYTE_STRING) { @@ -292,10 +292,16 @@ public static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeSca if (aIsByte && bIsByte) { boolean safe = true; for (int i = 0; safe && i < aStr.length(); i++) { - if (aStr.charAt(i) > 255) safe = false; + if (aStr.charAt(i) > 255) { + safe = false; + break; + } } for (int i = 0; safe && i < bStr.length(); i++) { - if (bStr.charAt(i) > 255) safe = false; + if (bStr.charAt(i) > 255) { + safe = false; + break; + } } if (safe) { byte[] aBytes = aStr.getBytes(StandardCharsets.ISO_8859_1); @@ -308,7 +314,7 @@ public static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeSca } } - return new RuntimeScalar(runtimeScalar.toString() + bStr); + return new RuntimeScalar(runtimeScalar + bStr); } public static RuntimeScalar stringConcatWarnUninitialized(RuntimeScalar runtimeScalar, RuntimeScalar b) { @@ -320,7 +326,7 @@ public static RuntimeScalar stringConcatWarnUninitialized(RuntimeScalar runtimeS String bStr = b.toString(); if (runtimeScalar.type == RuntimeScalarType.STRING || b.type == RuntimeScalarType.STRING) { - return new RuntimeScalar(runtimeScalar.toString() + bStr); + return new RuntimeScalar(runtimeScalar + bStr); } if (runtimeScalar.type == BYTE_STRING || b.type == BYTE_STRING) { @@ -333,10 +339,16 @@ public static RuntimeScalar stringConcatWarnUninitialized(RuntimeScalar runtimeS if (aIsByte && bIsByte) { boolean safe = true; for (int i = 0; safe && i < aStr.length(); i++) { - if (aStr.charAt(i) > 255) safe = false; + if (aStr.charAt(i) > 255) { + safe = false; + break; + } } for (int i = 0; safe && i < bStr.length(); i++) { - if (bStr.charAt(i) > 255) safe = false; + if (bStr.charAt(i) > 255) { + safe = false; + break; + } } if (safe) { byte[] aBytes = aStr.getBytes(StandardCharsets.ISO_8859_1); @@ -349,7 +361,7 @@ public static RuntimeScalar stringConcatWarnUninitialized(RuntimeScalar runtimeS } } - return new RuntimeScalar(runtimeScalar.toString() + bStr); + return new RuntimeScalar(runtimeScalar + bStr); } public static RuntimeScalar chompScalar(RuntimeScalar runtimeScalar) { @@ -441,7 +453,7 @@ public static RuntimeScalar chr(RuntimeScalar runtimeScalar) { // Perl's chr() accepts any non-negative integer value and creates a character // with that code point, even if it's not valid Unicode (surrogates, beyond 0x10FFFF). // Java's Character.isValidCodePoint() rejects these, so we need to handle them. - + // For values 0-0x10FFFF that Java accepts, use Java's built-in support if (Character.isValidCodePoint(codePoint) && codePoint <= 0x10FFFF) { RuntimeScalar res = new RuntimeScalar(new String(Character.toChars(codePoint))); @@ -451,13 +463,13 @@ public static RuntimeScalar chr(RuntimeScalar runtimeScalar) { } return res; } - + // For surrogates (0xD800-0xDFFF) and values beyond Unicode (> 0x10FFFF), // Perl still creates a character with that code point. We store it as a // special marker that will be properly encoded when converted to UTF-8. // For now, we create a string with the code point value, which will be // handled by the UTF-8 encoding logic in pack/unpack. - + // Create a character using the code point directly // Note: This may create invalid Unicode, but that's what Perl does if (codePoint <= 0x10FFFF) { @@ -466,7 +478,7 @@ public static RuntimeScalar chr(RuntimeScalar runtimeScalar) { RuntimeScalar res = new RuntimeScalar(new String(new int[]{codePoint}, 0, 1)); return res; } - + // For values beyond 0x10FFFF, Java's String can't represent them. // We need to store the code point value separately so UnpackState can encode it. // As a workaround, we'll store a special marker string with the code point embedded. @@ -523,8 +535,8 @@ public static RuntimeScalar join(RuntimeScalar runtimeScalar, RuntimeBase list) * Used for both explicit join() calls and string interpolation. * * @param runtimeScalar The separator - * @param list The list to join - * @param warnOnUndef Whether to warn about undef values + * @param list The list to join + * @param warnOnUndef Whether to warn about undef values * @return The joined string */ private static RuntimeScalar joinInternal(RuntimeScalar runtimeScalar, RuntimeBase list, boolean warnOnUndef) { @@ -539,10 +551,10 @@ private static RuntimeScalar joinInternal(RuntimeScalar runtimeScalar, RuntimeBa String delimiter = runtimeScalar.toString(); boolean isByteString = runtimeScalar.type == BYTE_STRING || delimiter.isEmpty(); - + // String interpolation uses empty delimiter - don't warn about undef in that case boolean isStringInterpolation = delimiter.isEmpty(); - + // Join the list into a string StringBuilder sb = new StringBuilder(); @@ -577,7 +589,7 @@ private static RuntimeScalar joinInternal(RuntimeScalar runtimeScalar, RuntimeBa * This is used internally by the compiler for string interpolation. * * @param runtimeScalar The separator (usually empty string) - * @param list The list to join + * @param list The list to join * @return The joined string */ public static RuntimeScalar joinForInterpolation(RuntimeScalar runtimeScalar, RuntimeBase list) { @@ -608,17 +620,17 @@ private static RuntimeScalar toUtf8Bytes(RuntimeScalar runtimeScalar) { private static RuntimeScalar caseFoldBytesAsciiOnly(RuntimeScalar runtimeScalar) { String str = runtimeScalar.toString(); StringBuilder result = new StringBuilder(str.length()); - + for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); // Only lowercase ASCII A-Z (0x41-0x5A) if (c >= 'A' && c <= 'Z') { - result.append((char)(c + 32)); // Convert to lowercase + result.append((char) (c + 32)); // Convert to lowercase } else { result.append(c); } } - + RuntimeScalar out = new RuntimeScalar(result.toString()); out.type = BYTE_STRING; return out; @@ -631,17 +643,17 @@ private static RuntimeScalar caseFoldBytesAsciiOnly(RuntimeScalar runtimeScalar) private static RuntimeScalar uppercaseBytesAsciiOnly(RuntimeScalar runtimeScalar) { String str = runtimeScalar.toString(); StringBuilder result = new StringBuilder(str.length()); - + for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); // Only uppercase ASCII a-z (0x61-0x7A) if (c >= 'a' && c <= 'z') { - result.append((char)(c - 32)); // Convert to uppercase + result.append((char) (c - 32)); // Convert to uppercase } else { result.append(c); } } - + RuntimeScalar out = new RuntimeScalar(result.toString()); out.type = BYTE_STRING; return out; diff --git a/src/main/java/org/perlonjava/runtime/operators/Time.java b/src/main/java/org/perlonjava/runtime/operators/Time.java index 5836ec8ec..fc790101b 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Time.java +++ b/src/main/java/org/perlonjava/runtime/operators/Time.java @@ -8,7 +8,6 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatterBuilder; import java.time.format.TextStyle; import java.util.Locale; import java.util.concurrent.Executors; diff --git a/src/main/java/org/perlonjava/runtime/operators/UnpackState.java b/src/main/java/org/perlonjava/runtime/operators/UnpackState.java index 67b85e4f6..99afa4e86 100644 --- a/src/main/java/org/perlonjava/runtime/operators/UnpackState.java +++ b/src/main/java/org/perlonjava/runtime/operators/UnpackState.java @@ -12,7 +12,7 @@ /** * Maintains state during unpack operations, managing the dual representation * of data as both character codes and bytes, along with position tracking. - * + * *

        Data Representation:

        *
          *
        • Character codes (codePoints array): Logical view of the string as an array @@ -22,7 +22,7 @@ *
        • isUTF8Data flag: True if the string contains characters > 255, indicating * that originalBytes contains UTF-8 encoded data.
        • *
        - * + * *

        Position Tracking:

        *
          *
        • Character position (codePointIndex): Current index in the codePoints array.
        • @@ -30,7 +30,7 @@ *
        • Group baselines: Stacks (groupCharBase, groupByteBase) track the starting * positions of nested groups for relative addressing (e.g., .(.). format).
        • *
        - * + * *

        Mode Management:

        *
          *
        • Character mode (default): Formats read from the codePoints array. @@ -39,7 +39,7 @@ * Used by: s, S, i, I, l, L, q, Q, n, N, v, V, f, d formats.
        • *
        • Mode switches: C0 forces byte mode, U0 forces character mode.
        • *
        - * + * *

        Critical Insight: Perl internally stores UTF-8 bytes but tracks character * length separately. When unpacking strings with the UTF-8 flag set: *

          @@ -47,7 +47,7 @@ *
        • N/V/I formats read raw bytes from originalBytes (UTF-8 encoded)
        • *
        • x format skips characters in character mode, bytes in byte mode
        • *
        - * + * *

        Group-Relative Positioning: The . and .! formats support multi-level * group-relative positioning: *

          @@ -56,7 +56,7 @@ *
        • .2 - Position relative to parent group
        • *
        • .* - Absolute position from start of string
        • *
        - * + * * @see Unpack * @see UnpackGroupProcessor * @see DotFormatHandler @@ -88,10 +88,10 @@ public UnpackState(String dataString, boolean startsWithU, boolean utf8Flagged) java.util.List codePointList = new java.util.ArrayList<>(); int i = 0; while (i < dataString.length()) { - if (i < dataString.length() - 10 && - dataString.charAt(i) == '\uFFFD' && - dataString.charAt(i + 1) == '<' && - dataString.charAt(i + 10) == '>') { + if (i < dataString.length() - 10 && + dataString.charAt(i) == '\uFFFD' && + dataString.charAt(i + 1) == '<' && + dataString.charAt(i + 10) == '>') { // Extract the hex code point String hexStr = dataString.substring(i + 2, i + 10); try { @@ -106,7 +106,7 @@ public UnpackState(String dataString, boolean startsWithU, boolean utf8Flagged) codePointList.add(dataString.codePointAt(i)); i += Character.charCount(dataString.codePointAt(i)); } - + this.codePoints = codePointList.stream().mapToInt(Integer::intValue).toArray(); // Determine if this is binary data or a Unicode string @@ -141,6 +141,45 @@ public UnpackState(String dataString, boolean startsWithU, boolean utf8Flagged) } } + /** + * Compute the extended UTF-8 length (Perl semantics) for a code point. + * Supports 1 to 6 byte sequences. + */ + private static int utf8Len(int cp) { + if (cp <= 0x7F) return 1; + if (cp <= 0x7FF) return 2; + if (cp <= 0xFFFF) return 3; + if (cp <= 0x1FFFFF) return 4; + if (cp <= 0x3FFFFFF) return 5; + return 6; + } + + /** + * Encode an array of code points into extended UTF-8 bytes (Perl semantics). + */ + private static byte[] encodeUtf8Extended(int[] cps) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int cp : cps) { + int len = utf8Len(cp); + byte[] out = new byte[len]; + int val = cp; + // Fill continuation bytes from the end + for (int i = len - 1; i >= 1; i--) { + out[i] = (byte) (0x80 | (val & 0x3F)); + val >>= 6; + } + // First byte prefix: 0xxxxxxx, 110xxxxx, 1110xxxx, 11110xxx, 111110xx, 1111110x + if (len == 1) { + out[0] = (byte) (val & 0x7F); + } else { + int prefix = (0xFF << (8 - len)) & 0xFF; // 11000000, 11100000, 11110000, 11111000, 11111100 + out[0] = (byte) (prefix | (val & ((1 << (8 - (len + 1))) - 1))); + } + baos.write(out, 0, out.length); + } + return baos.toByteArray(); + } + /** * Push current position as the baseline for a new group scope. */ @@ -193,7 +232,7 @@ public int getRelativeBytePosition() { /** * Get the byte position relative to the Nth group level up. - * + * * @param level 1 = innermost group, 2 = parent group, etc. * @return byte position relative to the specified group level, or absolute if level > depth */ @@ -206,7 +245,7 @@ public int getRelativeBytePosition(int level) { // Get the base at the specified level int baseIndex = depth - level; if (baseIndex < 0) baseIndex = 0; - + Integer[] bases = groupByteBase.toArray(new Integer[0]); int base = bases[baseIndex]; return getBytePosition() - base; @@ -222,7 +261,7 @@ public boolean hasGroupBase() { /** * Returns the current group nesting depth. * Used by dot format to determine which group level to use as baseline. - * + * * @return number of active groups (0 if no groups, 1 for innermost, etc.) */ public int getGroupDepth() { @@ -231,7 +270,7 @@ public int getGroupDepth() { /** * Get the position relative to the Nth group level up. - * + * * @param level 1 = innermost group, 2 = parent group, etc. * @return position relative to the specified group level, or absolute if level > depth */ @@ -246,7 +285,7 @@ public int getRelativePosition(int level) { // Stack index: size-level gives us the Nth element from top int baseIndex = depth - level; if (baseIndex < 0) baseIndex = 0; - + // Convert Deque to array to access by index Integer[] bases = groupCharBase.toArray(new Integer[0]); int base = bases[baseIndex]; @@ -259,7 +298,7 @@ public int getRelativePosition(int level) { } int baseIndex = depth - level; if (baseIndex < 0) baseIndex = 0; - + Integer[] bases = groupByteBase.toArray(new Integer[0]); int base = bases[baseIndex]; return getBytePosition() - base; @@ -458,43 +497,4 @@ public int getBytePosition() { return bytePos; } } - - /** - * Compute the extended UTF-8 length (Perl semantics) for a code point. - * Supports 1 to 6 byte sequences. - */ - private static int utf8Len(int cp) { - if (cp <= 0x7F) return 1; - if (cp <= 0x7FF) return 2; - if (cp <= 0xFFFF) return 3; - if (cp <= 0x1FFFFF) return 4; - if (cp <= 0x3FFFFFF) return 5; - return 6; - } - - /** - * Encode an array of code points into extended UTF-8 bytes (Perl semantics). - */ - private static byte[] encodeUtf8Extended(int[] cps) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (int cp : cps) { - int len = utf8Len(cp); - byte[] out = new byte[len]; - int val = cp; - // Fill continuation bytes from the end - for (int i = len - 1; i >= 1; i--) { - out[i] = (byte) (0x80 | (val & 0x3F)); - val >>= 6; - } - // First byte prefix: 0xxxxxxx, 110xxxxx, 1110xxxx, 11110xxx, 111110xx, 1111110x - if (len == 1) { - out[0] = (byte) (val & 0x7F); - } else { - int prefix = (0xFF << (8 - len)) & 0xFF; // 11000000, 11100000, 11110000, 11111000, 11111100 - out[0] = (byte) (prefix | (val & ((1 << (8 - (len + 1))) - 1))); - } - baos.write(out, 0, out.length); - } - return baos.toByteArray(); - } } \ No newline at end of file diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/ControlPackHandler.java b/src/main/java/org/perlonjava/runtime/operators/pack/ControlPackHandler.java index 097d9f9a9..d1e601925 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/ControlPackHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/ControlPackHandler.java @@ -8,7 +8,7 @@ /** * Handler for control formats 'x', 'X', '@', '.'. - * + * *

        Format Descriptions:

        *
          *
        • x: Insert null bytes (forward padding/skip) @@ -39,7 +39,7 @@ *
        * *
      - * + * *

      Critical Distinction - '.' Format:

      *
        *
      • . (no count or count > 0): Absolute positioning - move to position N @@ -55,14 +55,14 @@ *
      * *
    - * + * *

    Error Handling:

    *
      *
    • 'X' with count > current size: throws "'X' outside of string in pack"
    • *
    • '.' with negative absolute position: throws "'.' outside of string in pack"
    • *
    • '.0' with negative offset that goes before start: throws error
    • *
    - * + * * @see Pack */ public class ControlPackHandler implements PackFormatHandler { diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/NumericPackHandler.java b/src/main/java/org/perlonjava/runtime/operators/pack/NumericPackHandler.java index e471773c6..fa305a04e 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/NumericPackHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/NumericPackHandler.java @@ -10,7 +10,7 @@ /** * Handler for numeric formats (c, C, s, S, i, I, l, L, q, Q, j, J, f, F, d, D, n, N, v, V, w). - * + * *

    Format Categories:

    *
      *
    • 8-bit: c (signed char), C (unsigned char)
    • @@ -20,13 +20,13 @@ *
    • Float: f, F (single precision), d, D (double precision)
    • *
    • Special: w (BER compressed integer - variable length)
    • *
    - * + * *

    Endianness Handling:

    *
      *
    • Native formats (s, S, i, I, l, L, etc.) support < and > modifiers
    • *
    • Fixed formats (n, N = big-endian, v, V = little-endian) ignore modifiers
    • *
    - * + * *

    Overload Support:

    *

    For the 'w' format (BER compression), special handling is needed for blessed objects * like Math::BigInt that use operator overloading: @@ -36,7 +36,7 @@ *

  • Critical: Do NOT use {@code value.toString()} for blessed objects, * as it returns the hash representation (e.g., "HASH(0x7f8b3c80)")
  • *
- * + * *

BER Compression ('w' format):

*

The 'w' format uses BER (Basic Encoding Rules) compression for unsigned integers. * Each byte contains 7 bits of data, with the high bit indicating whether more bytes follow: @@ -46,13 +46,13 @@ *

  • Larger values: Continue adding bytes with high bit set until final byte
  • * * Example: 5000000000 (0x12A05F200) is encoded as: 0x95 0xA0 0xAF 0xD0 0x00 - * + * * @see Overload * @see RuntimeScalar#getNumber() */ public class NumericPackHandler implements PackFormatHandler { private static final boolean TRACE_PACK = false; - + private final char format; public NumericPackHandler(char format) { @@ -231,7 +231,7 @@ public int pack(List values, int valueIndex, int count, boolean h System.err.println(" numericValue type: " + numericValue.type); System.err.println(" doubleValue: " + doubleValue); System.err.println(" stringValue: '" + stringValue + "'"); - System.err.println(" numericValue.toString(): '" + numericValue.toString() + "'"); + System.err.println(" numericValue.toString(): '" + numericValue + "'"); System.err.println(" isNaN: " + Double.isNaN(doubleValue)); System.err.println(" isInfinite: " + Double.isInfinite(doubleValue)); System.err.println(" matches \\d{10,}e0: " + stringValue.matches("\\d{10,}e0")); diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/PackBuffer.java b/src/main/java/org/perlonjava/runtime/operators/pack/PackBuffer.java index b426d022d..5a3780258 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/PackBuffer.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/PackBuffer.java @@ -214,7 +214,7 @@ public void truncateToUtf8BytePos(int bytePos) { public void truncateToCharacter(int charPos) { if (charPos < 0) charPos = 0; if (charPos >= values.size()) return; - + while (values.size() > charPos) { values.remove(values.size() - 1); isCharacter.remove(isCharacter.size() - 1); diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java b/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java index 3d5bc9747..6edd005b4 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java @@ -30,7 +30,7 @@ public class PackGroupHandler { * Set to true to debug group nesting and processing. */ private static final boolean TRACE_PACK = false; - + // Thread-local to track group nesting depth private static final ThreadLocal nestingDepth = ThreadLocal.withInitial(() -> 0); private static final int MAX_NESTING_DEPTH = 100; @@ -42,13 +42,13 @@ public class PackGroupHandler { * This method processes the group content according to the specified repeat count * and handles special cases like backup operations within groups.

    * - * @param template The template string - * @param openPos The starting position of the group (points to '(') - * @param values The list of values to pack - * @param output The output stream - * @param valueIndex The current index in the values list - * @param byteMode The current byte mode - * @param byteModeUsed Whether byte mode has been used + * @param template The template string + * @param openPos The starting position of the group (points to '(') + * @param values The list of values to pack + * @param output The output stream + * @param valueIndex The current index in the values list + * @param byteMode The current byte mode + * @param byteModeUsed Whether byte mode has been used * @param hasUnicodeInNormalMode Whether Unicode has been used in normal mode * @return GroupResult containing the position after the group and updated value index * @throws PerlCompilerException if parentheses are unmatched or endianness conflicts @@ -58,20 +58,20 @@ public static GroupResult handleGroup(String template, int openPos, List MAX_NESTING_DEPTH) { throw new PerlCompilerException("Too deeply nested ()-groups in pack"); } nestingDepth.set(currentDepth); - + try { return handleGroupInternal(template, openPos, values, output, valueIndex, byteMode, byteModeUsed, hasUnicodeInNormalMode); } finally { @@ -92,10 +92,10 @@ public static GroupResult handleGroup(String template, int openPos, List values, - PackBuffer output, int valueIndex, - boolean byteMode, boolean byteModeUsed, boolean hasUnicodeInNormalMode) { + PackBuffer output, int valueIndex, + boolean byteMode, boolean byteModeUsed, boolean hasUnicodeInNormalMode) { // Find matching closing parenthesis int closePos = PackHelper.findMatchingParen(template, openPos); if (closePos == -1) { @@ -104,7 +104,7 @@ private static GroupResult handleGroupInternal(String template, int openPos, Lis // Extract group content String groupContent = template.substring(openPos + 1, closePos); - + if (TRACE_PACK) { System.err.println("TRACE PackGroupHandler.handleGroupInternal:"); System.err.println(" positions: " + openPos + " to " + closePos); @@ -155,7 +155,7 @@ private static GroupResult handleGroupInternal(String template, int openPos, Lis // Process the rest of the group normally if (xPos < groupContent.length()) { String remainingContent = groupContent.substring(xPos); - + // Pack directly into the parent buffer Pack.PackResult result = Pack.packInto(remainingContent, values, valueIndex, output, byteMode, hasUnicodeInNormalMode); valueIndex = result.valueIndex(); @@ -250,16 +250,16 @@ public static int getValueIndexAfterGroup(String template, int groupEndPos, List *

    This method handles both string formats (a, A, Z, U) and numeric * formats after the slash, with proper length calculation and data packing.

    * - * @param template The template string - * @param position The starting position of the slash construct - * @param slashPos The position of the slash character - * @param format The format character before the slash - * @param values The list of values to pack - * @param valueIndex The current index in the values list - * @param output The output stream - * @param modifiers The modifiers for the format - * @param byteMode The current byte mode - * @param byteModeUsed Whether byte mode has been used + * @param template The template string + * @param position The starting position of the slash construct + * @param slashPos The position of the slash character + * @param format The format character before the slash + * @param values The list of values to pack + * @param valueIndex The current index in the values list + * @param output The output stream + * @param modifiers The modifiers for the format + * @param byteMode The current byte mode + * @param byteModeUsed Whether byte mode has been used * @param hasUnicodeInNormalMode Whether Unicode has been used in normal mode * @return GroupResult containing template position and updated value index * @throws PerlCompilerException if slash construct is malformed diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/PackParser.java b/src/main/java/org/perlonjava/runtime/operators/pack/PackParser.java index 8732ed698..b5fd9bbd3 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/PackParser.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/PackParser.java @@ -179,7 +179,7 @@ public static ParsedCount parseRepeatCount(String template, int position) { throw new PerlCompilerException("Malformed integer in []"); } } - + // Template-based count - calculate the size of the template result.count = calculateTemplateSize(countStr); } @@ -311,12 +311,12 @@ public static GroupInfo parseGroupInfo(String template, int closePos) { *

    This method works by actually packing dummy data with the template and measuring * the resulting byte length. This approach handles all format types correctly, including * variable-length formats like bit strings and hex strings.

    - * + * *

    Implementation Note - Byte Length Calculation:

    *

    The method measures the result using ISO-8859-1 byte encoding, which treats each * character code (0-255) as a single byte. For packed data that contains only characters * 0-255, this gives the correct byte count.

    - * + * *

    UTF-8 Handling for W/U Formats:

    *

    For templates containing W or U formats with high Unicode characters (> 255), * this method correctly handles UTF-8 byte length calculation: @@ -328,11 +328,11 @@ public static GroupInfo parseGroupInfo(String template, int closePos) { *

  • Result: x[W] correctly skips 3 bytes for high Unicode characters, * matching Perl's behavior
  • * - * + * *

    This ensures that constructs like `unpack("x[W] N4", pack("W N4", 0x1FFC, ...))` * work correctly - the x[W] skips the exact UTF-8 byte length of the W character, * allowing subsequent N4 to read from the correct byte position.

    - * + * *

    See also: tests 5072-5154 for W format with binary format interaction

    * * @param template the pack template string @@ -362,10 +362,10 @@ public static int calculatePackedSize(String template) { // Pack the data and measure the result RuntimeScalar result = Pack.pack(args); - + // Measure the ACTUAL byte length, handling UTF-8 correctly String resultString = result.toString(); - + // Check if the result contains high Unicode characters (> 255) boolean hasHighUnicode = false; for (int j = 0; j < resultString.length(); j++) { @@ -374,14 +374,14 @@ public static int calculatePackedSize(String template) { break; } } - + // CRITICAL: For x[template] in unpack, we need to know how many units to skip // For UTF-8 strings (hasHighUnicode), formats work in CHARACTER domain // For binary strings, formats work in BYTE domain // So we return CHARACTER LENGTH for both cases - the unpack handler // will skip characters in char mode, bytes in byte mode int byteLength = resultString.length(); - + if (TRACE_PACK) { System.err.println("TRACE calculatePackedSize:"); System.err.println(" template: '" + template + "'"); @@ -390,7 +390,7 @@ public static int calculatePackedSize(String template) { System.err.println(" byte length: " + byteLength); System.err.flush(); } - + return byteLength; } catch (Exception e) { @@ -403,14 +403,14 @@ public static int calculatePackedSize(String template) { /** * Adds dummy values to args list for packing the given template. * Different format types need different dummy values to pack correctly. - * + * *

    Dummy Value Strategy:

    *
      *
    • Numeric formats (c, C, s, i, etc.): Use 0 as dummy value
    • *
    • String formats (a, A, Z, b, B, h, H): Use appropriate string values
    • *
    • Unicode formats (U, W): Use 0 as dummy value
    • *
    - * + * *

    Note on W/U Format Handling:

    *

    Previous versions used a high Unicode character (0x1FFC = 8188) as a dummy value * for W/U formats to ensure correct UTF-8 byte length calculation. However, this @@ -421,7 +421,7 @@ public static int calculatePackedSize(String template) { * don't produce accurate UTF-8 byte counts anyway *

  • Using 0 as dummy value is simpler and matches other numeric formats
  • * - * + * *

    See {@link #calculatePackedSize(String)} for details on the known limitation * with W/U format byte length calculation.

    * @@ -457,17 +457,17 @@ private static void addDummyValuesForTemplate(String template, RuntimeList args) else if (template.charAt(j) == ')') depth--; j++; } - + if (depth > 0) { // Unmatched parenthesis - skip it i++; continue; } - + // Extract group content (between parentheses) String groupContent = template.substring(i + 1, j - 1); i = j; // Move past closing paren - + // Parse repeat count after the group // Both (B)8 and (B)[8] mean repeat the group 8 times int groupRepeat = 1; @@ -502,14 +502,14 @@ private static void addDummyValuesForTemplate(String template, RuntimeList args) } } } - + // Recursively add dummy values for the group content, repeated groupRepeat times for (int r = 0; r < groupRepeat; r++) { addDummyValuesForTemplate(groupContent, args); } continue; } - + // Skip closing parenthesis (should be handled in group processing above) if (format == ')') { i++; @@ -562,7 +562,7 @@ private static void addDummyValuesForTemplate(String template, RuntimeList args) // Special case for P format: count is minimum string length, not repeat count // P always consumes exactly 1 value regardless of count int valuesToAdd = (format == 'P') ? 1 : count; - + for (int j = 0; j < valuesToAdd; j++) { switch (format) { case 'a', 'A', 'Z' -> { diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/PointerPackHandler.java b/src/main/java/org/perlonjava/runtime/operators/pack/PointerPackHandler.java index dca7aacb4..dccc2777d 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/PointerPackHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/PointerPackHandler.java @@ -8,7 +8,7 @@ /** * Handler for pointer formats 'p' and 'P'. - * + *

    * - 'p' format: count is a repeat count (p3 packs 3 pointers) * - 'P' format: count is minimum string length (P3 packs 1 pointer with min 3 bytes) */ @@ -18,13 +18,15 @@ public class PointerPackHandler implements PackFormatHandler { * Maps hash codes to their corresponding string values for later retrieval by unpack. */ private static final Map pointerMap = new HashMap<>(); - - /** The format character ('p' or 'P') */ + + /** + * The format character ('p' or 'P') + */ private final char format; /** * Creates a new PointerPackHandler for the specified format. - * + * * @param format The format character ('p' or 'P') */ public PointerPackHandler(char format) { diff --git a/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java b/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java index 2e62d33a9..a29345cd8 100644 --- a/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java +++ b/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java @@ -8,7 +8,7 @@ public SprintfValidationResult(Status status) { this(status, null); } - public boolean isValid() { + public boolean isValid () { return status == Status.VALID; } diff --git a/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfVectorFormatter.java b/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfVectorFormatter.java index fdb8f2e71..1fd8db5ed 100644 --- a/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfVectorFormatter.java +++ b/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfVectorFormatter.java @@ -4,7 +4,7 @@ import org.perlonjava.runtime.runtimetypes.RuntimeHash; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; - import java.nio.charset.StandardCharsets; +import java.nio.charset.StandardCharsets; /** * Handles vector string formatting for sprintf operations. diff --git a/src/main/java/org/perlonjava/runtime/operators/unpack/AtShriekFormatHandler.java b/src/main/java/org/perlonjava/runtime/operators/unpack/AtShriekFormatHandler.java index d45310a95..38b8db0ff 100644 --- a/src/main/java/org/perlonjava/runtime/operators/unpack/AtShriekFormatHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/unpack/AtShriekFormatHandler.java @@ -8,7 +8,7 @@ /** * Handler for the '@!' format character in unpack operations. * This format sets the absolute position in the string using byte offsets. - * + * *

    Unlike '@' which uses character positions for UTF-8 strings, * '@!' always uses byte positions even for UTF-8 strings. */ @@ -24,7 +24,7 @@ public void unpack(UnpackState state, List output, int count, boole if (wasCharMode) { state.switchToCharacterMode(); } - + // @! doesn't produce any output values } diff --git a/src/main/java/org/perlonjava/runtime/operators/unpack/DotFormatHandler.java b/src/main/java/org/perlonjava/runtime/operators/unpack/DotFormatHandler.java index b63c3e8e8..91a2ad08a 100644 --- a/src/main/java/org/perlonjava/runtime/operators/unpack/DotFormatHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/unpack/DotFormatHandler.java @@ -8,7 +8,7 @@ /** * Handler for the '.' format in unpack - returns the current offset in the string. - * + * *

    The dot format returns the current position with different behaviors based on count: *

      *
    • .0 - Returns 0 (position relative to current position = 0)
    • @@ -17,18 +17,18 @@ *
    • .N - Returns position relative to Nth group level up
    • *
    • .* - Returns absolute position (relative to start of string)
    • *
    - * + * *

    Example: unpack("x3(X2.2)", $data) * - x3: position = 3 * - (: start group at position 3 (group base = 3) * - X2: back up 2 (position = 1) * - .2: return position relative to 2nd group up (outer context) - * + * * @see UnpackState#getRelativePosition(int) */ public class DotFormatHandler implements FormatHandler { private static final boolean TRACE_UNPACK = false; - + @Override public void unpack(UnpackState state, List values, int count, boolean isStarCount) { // Get the current position (absolute and relative to current group) diff --git a/src/main/java/org/perlonjava/runtime/operators/unpack/DotShriekFormatHandler.java b/src/main/java/org/perlonjava/runtime/operators/unpack/DotShriekFormatHandler.java index a1e6a3257..96716953e 100644 --- a/src/main/java/org/perlonjava/runtime/operators/unpack/DotShriekFormatHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/unpack/DotShriekFormatHandler.java @@ -8,7 +8,7 @@ /** * Handler for the '.!' format in unpack - returns the current byte offset in the string. - * + * *

    The dot-shriek format returns the current BYTE position with different behaviors based on count: *

      *
    • .!0 - Returns 0 (position relative to current position = 0)
    • @@ -17,9 +17,9 @@ *
    • .!N - Returns byte position relative to Nth group level up
    • *
    • .!* - Returns absolute byte position (relative to start of string)
    • *
    - * + * *

    Similar to DotFormatHandler but always works in byte domain, not character domain. - * + * * @see DotFormatHandler * @see UnpackState#getRelativeBytePosition(int) */ diff --git a/src/main/java/org/perlonjava/runtime/operators/unpack/NumericFormatHandler.java b/src/main/java/org/perlonjava/runtime/operators/unpack/NumericFormatHandler.java index ed9e56fbd..f3f94aa71 100644 --- a/src/main/java/org/perlonjava/runtime/operators/unpack/NumericFormatHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/unpack/NumericFormatHandler.java @@ -9,24 +9,24 @@ /** * Base class for numeric format handlers (s, S, i, I, l, L, q, Q, n, N, v, V, f, d, etc.). - * + * *

    Key Principle: All numeric formats operate in byte mode, not character mode. * This means they read from the ByteBuffer wrapping the UTF-8 encoded bytes, not from the * character code array.

    - * + * *

    Mode Switching Behavior:

    *
      *
    • Before reading: Save current mode and switch to byte mode if in character mode
    • *
    • During reading: Read from ByteBuffer using appropriate byte order (endianness)
    • *
    • After reading: Restore original mode (if was in character mode, switch back)
    • *
    - * + * *

    Why Byte Mode?

    *

    Numeric formats represent multi-byte values in specific byte orders. For example, the * 32-bit integer 0x12345678 in big-endian is stored as bytes: 0x12, 0x34, 0x56, 0x78. * Reading from the ByteBuffer ensures correct byte order interpretation according to the * format's endianness (N=big-endian, V=little-endian, etc.).

    - * + * *

    UTF-8 String Handling:

    *

    When unpacking from a string with the UTF-8 flag set (characters > 255), the originalBytes * array contains UTF-8 encoded bytes. Numeric formats still read from this byte representation: @@ -34,13 +34,13 @@ *

  • Example: Character U+1FFC is stored as UTF-8 bytes: 0xE1, 0x9F, 0xBC
  • *
  • A subsequent 'n' format reads the next 2 bytes: 0x9F, 0xBC as a short
  • * - * + * *

    Contrast with Character Mode Formats:

    *
      *
    • C format: Reads character codes (0-255) from codePoints array
    • *
    • N format: Reads 4 bytes from ByteBuffer
    • *
    - * + * *

    Format Handlers:

    *
      *
    • ShortHandler: Reads 2 bytes (signed/unsigned)
    • @@ -49,7 +49,7 @@ *
    • FloatHandler: Reads 4 bytes as float
    • *
    • DoubleHandler: Reads 8 bytes as double
    • *
    - * + * * @see UnpackState#switchToByteMode() * @see UnpackState#switchToCharacterMode() */ @@ -70,7 +70,7 @@ public void unpack(UnpackState state, List output, int count, boole // Respects current byte order (little-endian by default, can be changed by < or >) ByteBuffer buffer = state.getBuffer(); boolean isBigEndian = (buffer.order() == java.nio.ByteOrder.BIG_ENDIAN); - + for (int i = 0; i < count; i++) { if (state.remainingCodePoints() < 2) { break; @@ -78,7 +78,7 @@ public void unpack(UnpackState state, List output, int count, boole int b1 = state.nextCodePoint() & 0xFF; int b2 = state.nextCodePoint() & 0xFF; int value = isBigEndian ? ((b1 << 8) | b2) : ((b2 << 8) | b1); - + if (signed) { output.add(new RuntimeScalar((short) value)); } else { @@ -87,7 +87,7 @@ public void unpack(UnpackState state, List output, int count, boole } return; } - + // For non-UTF-8 strings, use original byte buffer logic // Save current mode boolean wasCharacterMode = state.isCharacterMode(); @@ -138,14 +138,14 @@ public void unpack(UnpackState state, List output, int count, boole // Respects current byte order (little-endian by default, can be changed by < or >) ByteBuffer buffer = state.getBuffer(); boolean isBigEndian = (buffer.order() == java.nio.ByteOrder.BIG_ENDIAN); - + for (int i = 0; i < count; i++) { if (state.remainingCodePoints() < 4) { break; } long value; if (isBigEndian) { - value = ((long)(state.nextCodePoint() & 0xFF) << 24) | + value = ((long) (state.nextCodePoint() & 0xFF) << 24) | ((state.nextCodePoint() & 0xFF) << 16) | ((state.nextCodePoint() & 0xFF) << 8) | (state.nextCodePoint() & 0xFF); @@ -153,9 +153,9 @@ public void unpack(UnpackState state, List output, int count, boole value = (state.nextCodePoint() & 0xFF) | ((state.nextCodePoint() & 0xFF) << 8) | ((state.nextCodePoint() & 0xFF) << 16) | - ((long)(state.nextCodePoint() & 0xFF) << 24); + ((long) (state.nextCodePoint() & 0xFF) << 24); } - + if (signed) { output.add(new RuntimeScalar((int) value)); } else { @@ -164,7 +164,7 @@ public void unpack(UnpackState state, List output, int count, boole } return; } - + // For non-UTF-8 strings, use original byte buffer logic // Save current mode boolean wasCharacterMode = state.isCharacterMode(); @@ -219,32 +219,32 @@ public void unpack(UnpackState state, List output, int count, boole // Respects current byte order (little-endian by default, can be changed by < or >) ByteBuffer buffer = state.getBuffer(); boolean isBigEndian = (buffer.order() == java.nio.ByteOrder.BIG_ENDIAN); - + for (int i = 0; i < count; i++) { if (state.remainingCodePoints() < 8) { break; } long value; if (isBigEndian) { - value = ((long)(state.nextCodePoint() & 0xFF) << 56) | - ((long)(state.nextCodePoint() & 0xFF) << 48) | - ((long)(state.nextCodePoint() & 0xFF) << 40) | - ((long)(state.nextCodePoint() & 0xFF) << 32) | - ((long)(state.nextCodePoint() & 0xFF) << 24) | - ((long)(state.nextCodePoint() & 0xFF) << 16) | - ((long)(state.nextCodePoint() & 0xFF) << 8) | - (long)(state.nextCodePoint() & 0xFF); + value = ((long) (state.nextCodePoint() & 0xFF) << 56) | + ((long) (state.nextCodePoint() & 0xFF) << 48) | + ((long) (state.nextCodePoint() & 0xFF) << 40) | + ((long) (state.nextCodePoint() & 0xFF) << 32) | + ((long) (state.nextCodePoint() & 0xFF) << 24) | + ((long) (state.nextCodePoint() & 0xFF) << 16) | + ((long) (state.nextCodePoint() & 0xFF) << 8) | + (long) (state.nextCodePoint() & 0xFF); } else { - value = (long)(state.nextCodePoint() & 0xFF) | - ((long)(state.nextCodePoint() & 0xFF) << 8) | - ((long)(state.nextCodePoint() & 0xFF) << 16) | - ((long)(state.nextCodePoint() & 0xFF) << 24) | - ((long)(state.nextCodePoint() & 0xFF) << 32) | - ((long)(state.nextCodePoint() & 0xFF) << 40) | - ((long)(state.nextCodePoint() & 0xFF) << 48) | - ((long)(state.nextCodePoint() & 0xFF) << 56); + value = (long) (state.nextCodePoint() & 0xFF) | + ((long) (state.nextCodePoint() & 0xFF) << 8) | + ((long) (state.nextCodePoint() & 0xFF) << 16) | + ((long) (state.nextCodePoint() & 0xFF) << 24) | + ((long) (state.nextCodePoint() & 0xFF) << 32) | + ((long) (state.nextCodePoint() & 0xFF) << 40) | + ((long) (state.nextCodePoint() & 0xFF) << 48) | + ((long) (state.nextCodePoint() & 0xFF) << 56); } - + if (signed) { output.add(new RuntimeScalar(value)); } else { @@ -260,7 +260,7 @@ public void unpack(UnpackState state, List output, int count, boole } return; } - + // For non-UTF-8 strings, use original byte buffer logic // Save current mode boolean wasCharacterMode = state.isCharacterMode(); @@ -334,7 +334,7 @@ public void unpack(UnpackState state, List output, int count, boole int b1 = state.nextCodePoint() & 0xFF; int b2 = state.nextCodePoint() & 0xFF; int value = (b1 << 8) | b2; - + if (signed) { output.add(new RuntimeScalar((short) value)); } else { @@ -343,7 +343,7 @@ public void unpack(UnpackState state, List output, int count, boole } return; } - + // For non-UTF-8 strings, use original byte buffer logic // Save current mode boolean wasCharacterMode = state.isCharacterMode(); @@ -419,7 +419,7 @@ public void unpack(UnpackState state, List output, int count, boole } return; } - + // For non-UTF-8 strings, use original byte buffer logic // Save current mode boolean wasCharacterMode = state.isCharacterMode(); @@ -485,7 +485,7 @@ public void unpack(UnpackState state, List output, int count, boole int b1 = state.nextCodePoint() & 0xFF; int b2 = state.nextCodePoint() & 0xFF; int value = b1 | (b2 << 8); - + if (signed) { output.add(new RuntimeScalar((short) value)); } else { @@ -494,7 +494,7 @@ public void unpack(UnpackState state, List output, int count, boole } return; } - + // For non-UTF-8 strings, use original byte buffer logic // Save current mode boolean wasCharacterMode = state.isCharacterMode(); @@ -569,7 +569,7 @@ public void unpack(UnpackState state, List output, int count, boole } return; } - + // For non-UTF-8 strings, use original byte buffer logic // Save current mode boolean wasCharacterMode = state.isCharacterMode(); @@ -593,7 +593,7 @@ public void unpack(UnpackState state, List output, int count, boole if (signed) { // Convert to signed int (sign extend from 32 bits) output.add(new RuntimeScalar((int) value)); - } else{ + } else { // Unsigned output.add(new RuntimeScalar(value)); } diff --git a/src/main/java/org/perlonjava/runtime/operators/unpack/UnpackGroupProcessor.java b/src/main/java/org/perlonjava/runtime/operators/unpack/UnpackGroupProcessor.java index 1e9c75744..7f428fa6d 100644 --- a/src/main/java/org/perlonjava/runtime/operators/unpack/UnpackGroupProcessor.java +++ b/src/main/java/org/perlonjava/runtime/operators/unpack/UnpackGroupProcessor.java @@ -172,7 +172,7 @@ public static int parseGroupSyntax(String template, int position, UnpackState st // Push group baseline for this repetition state.pushGroupBase(); - + try { // Call unpack recursively with the group template RuntimeList groupResult = unpackFunction.unpack(effectiveContent, state, startsWithU, modeStack); @@ -576,7 +576,7 @@ public static void processGroupContent(String groupTemplate, UnpackState state, positionHistory.remove(0); } } - + // Pop the group baseline after processing this repetition state.popGroupBase(); } diff --git a/src/main/java/org/perlonjava/runtime/operators/unpack/XFormatHandler.java b/src/main/java/org/perlonjava/runtime/operators/unpack/XFormatHandler.java index 3fb887ed9..b3656182b 100644 --- a/src/main/java/org/perlonjava/runtime/operators/unpack/XFormatHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/unpack/XFormatHandler.java @@ -11,6 +11,7 @@ */ public class XFormatHandler implements FormatHandler { private static final boolean TRACE_X = false; + @Override public void unpack(UnpackState state, List output, int count, boolean isStarCount) { // x format should skip bytes but not add any values to the result diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java b/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java index e2ffc77a2..6eeb8df37 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java @@ -156,7 +156,7 @@ yield switch (scalar.type) { case CODE -> "CODE"; case GLOB, GLOBREFERENCE -> "GLOB"; default -> "SCALAR"; - }; + } } yield "REF"; } diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/BytesPragma.java b/src/main/java/org/perlonjava/runtime/perlmodule/BytesPragma.java index 24e8eb31c..84cd4003e 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/BytesPragma.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/BytesPragma.java @@ -1,8 +1,8 @@ package org.perlonjava.runtime.perlmodule; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; import static org.perlonjava.frontend.parser.SpecialBlockParser.getCurrentScope; diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java b/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java index 6b79fbaf3..4d1bba305 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java @@ -1,5 +1,6 @@ package org.perlonjava.runtime.perlmodule; +import org.perlonjava.runtime.operators.ReferenceOperators; import org.perlonjava.runtime.runtimetypes.*; import java.nio.charset.StandardCharsets; @@ -7,9 +8,7 @@ import java.util.zip.Deflater; import java.util.zip.Inflater; -import org.perlonjava.runtime.operators.ReferenceOperators; - -import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.*; +import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef; public class CompressZlib extends PerlModuleBase { @@ -160,8 +159,7 @@ public static RuntimeList inflateMethod(RuntimeArray args, int ctx) { return result; } - byte[] outputBytes = baos.toByteArray(); - String outputStr = new String(outputBytes, StandardCharsets.ISO_8859_1); + String outputStr = baos.toString(StandardCharsets.ISO_8859_1); RuntimeList result = new RuntimeList(); RuntimeScalar outputScalar = new RuntimeScalar(outputStr); @@ -198,8 +196,7 @@ public static RuntimeList deflateMethod(RuntimeArray args, int ctx) { baos.write(outputBuf, 0, count); } - byte[] outputBytes = baos.toByteArray(); - String outputStr = new String(outputBytes, StandardCharsets.ISO_8859_1); + String outputStr = baos.toString(StandardCharsets.ISO_8859_1); RuntimeScalar outputScalar = new RuntimeScalar(outputStr); outputScalar.type = RuntimeScalarType.BYTE_STRING; return outputScalar.getList(); @@ -232,8 +229,7 @@ public static RuntimeList flush(RuntimeArray args, int ctx) { } } - byte[] outputBytes = baos.toByteArray(); - String outputStr = new String(outputBytes, StandardCharsets.ISO_8859_1); + String outputStr = baos.toString(StandardCharsets.ISO_8859_1); RuntimeScalar outputScalar = new RuntimeScalar(outputStr); outputScalar.type = RuntimeScalarType.BYTE_STRING; return outputScalar.getList(); diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Cwd.java b/src/main/java/org/perlonjava/runtime/perlmodule/Cwd.java index 49ddccd32..c1092acdf 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Cwd.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Cwd.java @@ -93,7 +93,7 @@ public static RuntimeList abs_path(RuntimeArray args, int ctx) { String baseDir = System.getProperty("user.dir"); // Determine the path to resolve String path = args.size() > 0 ? args.get(0).toString() : baseDir; - + // Resolve the path: if already absolute, use it directly; otherwise resolve relative to baseDir java.nio.file.Path pathObj = Paths.get(path); if (!pathObj.isAbsolute()) { diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Exporter.java b/src/main/java/org/perlonjava/runtime/perlmodule/Exporter.java index 9debb3ce2..1ae27e8a0 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Exporter.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Exporter.java @@ -1,7 +1,7 @@ package org.perlonjava.runtime.perlmodule; -import org.perlonjava.runtime.operators.MathOperators; import org.perlonjava.frontend.parser.ParserTables; +import org.perlonjava.runtime.operators.MathOperators; import org.perlonjava.runtime.runtimetypes.*; import static org.perlonjava.runtime.runtimetypes.RuntimeContextType.SCALAR; @@ -66,18 +66,18 @@ public static RuntimeList export(RuntimeArray args, int ctx) { if (args.size() < 2) { throw new PerlCompilerException("Not enough arguments for export"); } - + RuntimeScalar packageScalar = args.get(0); // Source package RuntimeScalar targetPackage = args.get(1); // Target package (caller) String caller = targetPackage.toString(); String packageName = packageScalar.toString(); - + // Get symbols to export (everything after the first two args) RuntimeArray symbolsToExport = new RuntimeArray(); for (int i = 2; i < args.size(); i++) { symbolsToExport.elements.add(args.get(i)); } - + // If no symbols specified, export @EXPORT if (symbolsToExport.isEmpty()) { RuntimeArray export = GlobalVariable.getGlobalArray(packageName + "::EXPORT"); @@ -85,20 +85,20 @@ public static RuntimeList export(RuntimeArray args, int ctx) { symbolsToExport = export; } } - + // Import the symbols into the target package for (RuntimeBase symbolObj : symbolsToExport.elements) { String symbolString = symbolObj.toString(); - + // Check if symbol is exported RuntimeArray export = GlobalVariable.getGlobalArray(packageName + "::EXPORT"); RuntimeArray exportOk = GlobalVariable.getGlobalArray(packageName + "::EXPORT_OK"); - + boolean isExported = export.elements.stream() .anyMatch(e -> e.toString().equals(symbolString)); boolean isExportOk = exportOk.elements.stream() .anyMatch(e -> e.toString().equals(symbolString)); - + if (!isExported && !isExportOk && !symbolString.matches("^[$@%*]")) { // try with/without "&" String finalSymbolString; @@ -112,7 +112,7 @@ public static RuntimeList export(RuntimeArray args, int ctx) { isExportOk = exportOk.elements.stream() .anyMatch(e -> e.toString().equals(finalSymbolString)); } - + if (isExported || isExportOk) { if (symbolString.startsWith("&")) { importFunction(packageName, caller, symbolString.substring(1)); @@ -131,7 +131,7 @@ public static RuntimeList export(RuntimeArray args, int ctx) { throw new PerlCompilerException("\"" + symbolString + "\" is not exported by the " + packageName + " module\nCan't continue after import errors"); } } - + return new RuntimeList(); } @@ -180,7 +180,7 @@ public static RuntimeList exportToLevel(RuntimeArray args, int ctx) { if (symbolString.startsWith(":")) { String tagName = symbolString.substring(1); - + // Handle special :DEFAULT tag - it means "use @EXPORT" if ("DEFAULT".equals(tagName)) { if (export != null && !export.elements.isEmpty()) { @@ -283,7 +283,7 @@ private static void importFunction(String packageName, String caller, String fun if (exportSymbol.type == RuntimeScalarType.CODE) { String fullName = caller + "::" + functionName; RuntimeScalar importedRef = GlobalVariable.getGlobalCodeRef(fullName); - + if (exportSymbol.value instanceof RuntimeCode exportedCode) { if (exportedCode.defined()) { // Fully defined sub: import by aliasing the CODE ref. @@ -302,7 +302,7 @@ private static void importFunction(String packageName, String caller, String fun } } } - + // If this function name is an overridable operator (like 'time'), mark it in isSubs // so the parser knows to treat it as a subroutine call instead of the builtin if (ParserTables.OVERRIDABLE_OP.contains(functionName)) { diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Feature.java b/src/main/java/org/perlonjava/runtime/perlmodule/Feature.java index 5dd2bb9ec..67068568c 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Feature.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Feature.java @@ -1,7 +1,7 @@ package org.perlonjava.runtime.perlmodule; -import org.perlonjava.runtime.runtimetypes.*; import org.perlonjava.frontend.semantic.ScopedSymbolTable; +import org.perlonjava.runtime.runtimetypes.*; import static org.perlonjava.frontend.parser.SpecialBlockParser.getCurrentScope; import static org.perlonjava.runtime.runtimetypes.FeatureFlags.getFeatureList; diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java b/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java index 6602beb16..fbe105a01 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/FilterUtilCall.java @@ -9,10 +9,10 @@ /** * Java implementation of Filter::Util::Call XS functions. - * + *

    * This module implements Perl source filters by intercepting the parsing/compilation * of source code and transforming it through user-defined filter functions. - * + * *

    How Source Filters Work

    *
      *
    1. A filter is installed via filter_add() (usually in BEGIN block or import)
    2. @@ -21,7 +21,7 @@ *
    3. Filter reads input via filter_read(), modifies $_, returns status
    4. *
    5. Modified source is then compiled/executed
    6. *
    - * + * * @see perldoc Filter::Util::Call */ public class FilterUtilCall extends PerlModuleBase { @@ -32,21 +32,6 @@ public class FilterUtilCall extends PerlModuleBase { */ private static final ThreadLocal filterContext = ThreadLocal.withInitial(FilterContext::new); - /** - * Context for managing active source filters. - */ - static class FilterContext { - // Stack of active filters (LIFO - last added is first applied) - RuntimeList filterStack = new RuntimeList(); - - // Current input source being filtered (set during do/eval) - String[] sourceLines = null; - int currentLine = 0; - - // Whether we're currently inside a filter_read() call - boolean inFilterRead = false; - } - /** * Constructor for FilterUtilCall. * Note: We don't set %INC here because the Perl module file needs to be loaded @@ -73,13 +58,13 @@ public static void initialize() { /** * real_import - Install a source filter. - * + *

    * Called by filter_add() to actually install the filter. - * + * * @param args [0] = filter object (blessed ref or coderef) * [1] = caller package name * [2] = boolean: true if coderef, false if method filter - * @param ctx Execution context + * @param ctx Execution context * @return true on success */ public static RuntimeList real_import(RuntimeArray args, int ctx) { @@ -92,13 +77,13 @@ public static RuntimeList real_import(RuntimeArray args, int ctx) { RuntimeScalar isCodeRef = args.get(2); FilterContext context = filterContext.get(); - + // Create a filter entry RuntimeArray filterEntry = new RuntimeArray(); filterEntry.push(filterObj); // The filter object/coderef filterEntry.push(packageName); // Package name for method lookup filterEntry.push(isCodeRef); // Whether it's a coderef or method filter - + // Add to the filter stack context.filterStack.add(new RuntimeScalar(filterEntry)); @@ -107,55 +92,55 @@ public static RuntimeList real_import(RuntimeArray args, int ctx) { /** * filter_read - Read next chunk of source code through the filter chain. - * + *

    * This is called by the filter itself to get more input. * Returns status: - * > 0 : OK, more data available - * = 0 : EOF reached - * < 0 : Error occurred - * + * > 0 : OK, more data available + * = 0 : EOF reached + * < 0 : Error occurred + * * @param args [0] = optional: block size (if present, read block; else read line) - * @param ctx Execution context + * @param ctx Execution context * @return status code */ public static RuntimeList filter_read(RuntimeArray args, int ctx) { FilterContext context = filterContext.get(); - + // Prevent infinite recursion if (context.inFilterRead) { return scalarZero.getList(); // EOF } - + try { context.inFilterRead = true; - + // Get $_ to append data to RuntimeScalar defaultVar = GlobalVariable.getGlobalVariable("main::_"); String currentContent = defaultVar.toString(); - + // Determine read mode: line or block boolean blockMode = args.size() > 0; int blockSize = blockMode ? args.get(0).getInt() : -1; - + // Check if we have source lines to read from if (context.sourceLines == null || context.currentLine >= context.sourceLines.length) { // No more input return scalarZero.getList(); // EOF } - + String nextChunk; if (blockMode && blockSize > 0) { // Block mode: read up to blockSize bytes StringBuilder block = new StringBuilder(); int bytesRead = 0; - + while (bytesRead < blockSize && context.currentLine < context.sourceLines.length) { String line = context.sourceLines[context.currentLine]; if (bytesRead + line.length() <= blockSize) { block.append(line); bytesRead += line.length(); context.currentLine++; - + // Check if line ends with newline to stop if (line.endsWith("\n")) { break; @@ -176,13 +161,13 @@ public static RuntimeList filter_read(RuntimeArray args, int ctx) { nextChunk = context.sourceLines[context.currentLine]; context.currentLine++; } - + // Append to $_ defaultVar.set(currentContent + nextChunk); - + // Return status > 0 (success) return new RuntimeScalar(1).getList(); - + } finally { context.inFilterRead = false; } @@ -190,49 +175,49 @@ public static RuntimeList filter_read(RuntimeArray args, int ctx) { /** * filter_del - Remove the current filter from the filter stack. - * + *

    * This tells Perl to stop calling this filter. - * + * * @param args Unused - * @param ctx Execution context + * @param ctx Execution context * @return true on success */ public static RuntimeList filter_del(RuntimeArray args, int ctx) { FilterContext context = filterContext.get(); - + // Remove the top filter from the stack if (context.filterStack.size() > 0) { context.filterStack.elements.remove(context.filterStack.size() - 1); } - + return scalarTrue.getList(); } /** * Apply filters to source code before execution. - * + *

    * This is called internally by do/eval when filters are active. * This method applies all currently installed filters to the source code. - * + * * @param sourceCode The source code to filter * @return The filtered source code */ public static String applyFilters(String sourceCode) { FilterContext context = filterContext.get(); - + if (context.filterStack.size() == 0) { // No filters active return sourceCode; } - + // Set up the source for filter_read() context.sourceLines = sourceCode.split("(?<=\n)", -1); context.currentLine = 0; - + // Apply each filter in the stack (LIFO order) RuntimeScalar savedDefaultVar = GlobalVariable.getGlobalVariable("main::_"); StringBuilder filteredCode = new StringBuilder(); - + try { // Apply the first (most recent) filter if (context.filterStack.size() > 0) { @@ -241,32 +226,32 @@ public static String applyFilters(String sourceCode) { RuntimeScalar filterObj = filterEntry.get(0); RuntimeScalar packageName = filterEntry.get(1); RuntimeScalar isCodeRef = filterEntry.get(2); - + // Clear $_ GlobalVariable.getGlobalVariable("main::_").set(""); - + if (isCodeRef.getBoolean()) { // Closure filter: call the coderef repeatedly RuntimeCode code = (RuntimeCode) filterObj.value; boolean continueFiltering = true; - + while (continueFiltering) { // Call the filter RuntimeBase result = code.apply(new RuntimeArray(), RuntimeContextType.SCALAR); - + // Get the modified $_ String chunk = GlobalVariable.getGlobalVariable("main::_").toString(); if (!chunk.isEmpty()) { filteredCode.append(chunk); } - + // Check status - convert to scalar if it's a list RuntimeScalar statusScalar = result.scalar(); int status = statusScalar.getInt(); if (status <= 0) { continueFiltering = false; } - + // Prepare for next iteration GlobalVariable.getGlobalVariable("main::_").set(""); } @@ -278,26 +263,26 @@ public static String applyFilters(String sourceCode) { return sourceCode; } } - + return filteredCode.toString(); - + } finally { // Restore $_ GlobalVariable.getGlobalVariable("main::_").set(savedDefaultVar.toString()); - + // Clean up context context.sourceLines = null; context.currentLine = 0; } } - + /** * Check if source code contains BEGIN blocks that might install filters. * If so, execute a pre-parse pass to install the filters, then filter the remaining source. - * + *

    * This is a workaround for the limitation that our architecture tokenizes all source upfront, * while Perl's source filters need to be applied during incremental source reading. - * + * * @param sourceCode The original source code * @return The filtered source code if filters were installed, otherwise the original */ @@ -306,27 +291,27 @@ public static String preprocessWithBeginFilters(String sourceCode) { if (!sourceCode.contains("filter")) { return sourceCode; } - + // Check for BEGIN blocks - simple regex check if (!sourceCode.matches("(?s).*BEGIN\\s*\\{.*filter.*\\}.*")) { return sourceCode; } - + // Find the END of the first BEGIN block and split the source there // We'll execute everything up to and including the BEGIN block, // then apply any installed filters to the rest - + int beginPos = sourceCode.indexOf("BEGIN"); if (beginPos == -1) { return sourceCode; } - + // Find the matching closing brace for the BEGIN block int braceStart = sourceCode.indexOf('{', beginPos); if (braceStart == -1) { return sourceCode; } - + int braceCount = 1; int pos = braceStart + 1; while (pos < sourceCode.length() && braceCount > 0) { @@ -335,16 +320,16 @@ public static String preprocessWithBeginFilters(String sourceCode) { else if (c == '}') braceCount--; pos++; } - + if (braceCount != 0) { // Couldn't find matching brace return sourceCode; } - + // Split at the end of the BEGIN block String beginPart = sourceCode.substring(0, pos); String remainingPart = sourceCode.substring(pos); - + // Execute the BEGIN part to install any filters try { CompilerOptions options = new CompilerOptions(); @@ -355,10 +340,10 @@ public static String preprocessWithBeginFilters(String sourceCode) { // If execution fails, just return original source return sourceCode; } - + // Now apply any installed filters to the remaining source String filteredRemaining = applyFilters(remainingPart); - + // Return the BEGIN part + filtered remaining part return beginPart + filteredRemaining; } @@ -373,5 +358,20 @@ public static void clearFilters() { context.sourceLines = null; context.currentLine = 0; } + + /** + * Context for managing active source filters. + */ + static class FilterContext { + // Stack of active filters (LIFO - last added is first applied) + RuntimeList filterStack = new RuntimeList(); + + // Current input source being filtered (set during do/eval) + String[] sourceLines = null; + int currentLine = 0; + + // Whether we're currently inside a filter_read() call + boolean inFilterRead = false; + } } diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/IntegerPragma.java b/src/main/java/org/perlonjava/runtime/perlmodule/IntegerPragma.java index c8acf38bb..5f610c45f 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/IntegerPragma.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/IntegerPragma.java @@ -1,8 +1,8 @@ package org.perlonjava.runtime.perlmodule; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; import static org.perlonjava.frontend.parser.SpecialBlockParser.getCurrentScope; diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Internals.java b/src/main/java/org/perlonjava/runtime/perlmodule/Internals.java index 592cefc60..9ac53ba83 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Internals.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Internals.java @@ -68,7 +68,7 @@ public static RuntimeList svRefcount(RuntimeArray args, int ctx) { return new RuntimeList(); } - + /** * Sets or gets the read-only status of a variable. * diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/MIMEBase64.java b/src/main/java/org/perlonjava/runtime/perlmodule/MIMEBase64.java index 697cf4bed..8e102f090 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/MIMEBase64.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/MIMEBase64.java @@ -4,9 +4,9 @@ import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeList; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; +import org.perlonjava.runtime.util.Base64Util; import java.nio.charset.StandardCharsets; -import org.perlonjava.runtime.util.Base64Util; public class MIMEBase64 extends PerlModuleBase { diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Mro.java b/src/main/java/org/perlonjava/runtime/perlmodule/Mro.java index 9db54ede7..fd364f307 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Mro.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Mro.java @@ -1,9 +1,9 @@ package org.perlonjava.runtime.perlmodule; -import org.perlonjava.runtime.mro.InheritanceResolver; -import org.perlonjava.runtime.mro.InheritanceResolver.MROAlgorithm; import org.perlonjava.runtime.mro.C3; import org.perlonjava.runtime.mro.DFS; +import org.perlonjava.runtime.mro.InheritanceResolver; +import org.perlonjava.runtime.mro.InheritanceResolver.MROAlgorithm; import org.perlonjava.runtime.runtimetypes.*; import java.util.*; diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/PerlIO.java b/src/main/java/org/perlonjava/runtime/perlmodule/PerlIO.java index 43c2f0a11..4fa767fee 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/PerlIO.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/PerlIO.java @@ -50,10 +50,10 @@ public static RuntimeList get_layers(RuntimeArray args, int ctx) { if (fh instanceof TieHandle) { throw new PerlCompilerException("can't get_layers on tied handle"); } - + // Parse optional arguments (output => 1, details => 1, etc.) // For now, we ignore these options and just return layer names - + RuntimeArray layers = new RuntimeArray(); if (fh.ioHandle instanceof LayeredIOHandle layeredIOHandle) { for (IOLayer layer : layeredIOHandle.activeLayers) { diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Strict.java b/src/main/java/org/perlonjava/runtime/perlmodule/Strict.java index da13da5a4..5a4c01a17 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Strict.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Strict.java @@ -1,9 +1,9 @@ package org.perlonjava.runtime.perlmodule; +import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeList; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.frontend.semantic.ScopedSymbolTable; import static org.perlonjava.frontend.parser.SpecialBlockParser.getCurrentScope; diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Toml.java b/src/main/java/org/perlonjava/runtime/perlmodule/Toml.java index e6ac923ab..c03e697b7 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Toml.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Toml.java @@ -9,7 +9,8 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.*; @@ -56,12 +57,12 @@ public static RuntimeList from_toml(RuntimeArray args, int ctx) { if (args.size() < 1) { throw new PerlCompilerException("from_toml requires a TOML string argument"); } - + String tomlString = args.get(0).toString(); - + try { TomlParseResult result = org.tomlj.Toml.parse(tomlString); - + // Check for parse errors if (result.hasErrors()) { StringBuilder errorMsg = new StringBuilder(); @@ -69,7 +70,7 @@ public static RuntimeList from_toml(RuntimeArray args, int ctx) { if (errorMsg.length() > 0) errorMsg.append("; "); errorMsg.append(error.toString()); }); - + // In list context, return (undef, error_string) if (ctx == RuntimeContextType.LIST) { RuntimeList list = new RuntimeList(); @@ -80,9 +81,9 @@ public static RuntimeList from_toml(RuntimeArray args, int ctx) { // In scalar context, return undef return scalarUndef.getList(); } - + RuntimeScalar data = convertTomlToRuntimeScalar(result); - + // In list context, return (hashref, undef) if (ctx == RuntimeContextType.LIST) { RuntimeList list = new RuntimeList(); @@ -92,10 +93,10 @@ public static RuntimeList from_toml(RuntimeArray args, int ctx) { } // In scalar context, return just the hashref return data.getList(); - + } catch (Exception e) { String errorMsg = "Error parsing TOML: " + e.getMessage(); - + if (ctx == RuntimeContextType.LIST) { RuntimeList list = new RuntimeList(); list.add(scalarUndef); @@ -117,9 +118,9 @@ public static RuntimeList to_toml(RuntimeArray args, int ctx) { if (args.size() < 1) { throw new PerlCompilerException("to_toml requires a data structure argument"); } - + RuntimeScalar data = args.get(0); - + try { StringBuilder sb = new StringBuilder(); convertRuntimeScalarToToml(data, sb, "", false); @@ -136,7 +137,7 @@ private static RuntimeScalar convertTomlToRuntimeScalar(Object toml) { if (toml == null) { return scalarUndef; } - + if (toml instanceof TomlTable table) { RuntimeHash hash = new RuntimeHash(); for (String key : table.keySet()) { @@ -179,15 +180,15 @@ private static void convertRuntimeScalarToToml(RuntimeScalar scalar, StringBuild // TOML doesn't have null, skip or use empty string return; } - + switch (scalar.type) { case HASHREFERENCE -> { RuntimeHash hash = (RuntimeHash) scalar.value; - + // Separate simple values from nested tables/arrays List simpleKeys = new ArrayList<>(); List tableKeys = new ArrayList<>(); - + for (String key : hash.elements.keySet()) { RuntimeScalar value = hash.get(key); if (value.type == RuntimeScalarType.HASHREFERENCE) { @@ -203,7 +204,7 @@ private static void convertRuntimeScalarToToml(RuntimeScalar scalar, StringBuild simpleKeys.add(key); } } - + // Output simple key-value pairs first for (String key : simpleKeys) { RuntimeScalar value = hash.get(key); @@ -211,12 +212,12 @@ private static void convertRuntimeScalarToToml(RuntimeScalar scalar, StringBuild appendValue(value, sb); sb.append("\n"); } - + // Output nested tables for (String key : tableKeys) { RuntimeScalar value = hash.get(key); String newPrefix = prefix.isEmpty() ? key : prefix + "." + key; - + if (value.type == ARRAYREFERENCE) { RuntimeArray arr = (RuntimeArray) value.value; if (!arr.elements.isEmpty() && arr.elements.get(0).type == RuntimeScalarType.HASHREFERENCE) { diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeNormalize.java b/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeNormalize.java index f44d891a4..3dbbd8787 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeNormalize.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeNormalize.java @@ -32,50 +32,50 @@ public UnicodeNormalize() { public static void initialize() { UnicodeNormalize unicodeNormalize = new UnicodeNormalize(); unicodeNormalize.initializeExporter(); - + // Define @EXPORT - the functions exported by default unicodeNormalize.defineExport("EXPORT", "NFD", "NFC", "NFKD", "NFKC"); - + // Define @EXPORT_OK - all functions that can be exported on request // This matches the full list from perl5/dist/Unicode-Normalize/Normalize.pm lines 14-22 unicodeNormalize.defineExport("EXPORT_OK", - "normalize", "decompose", "reorder", "compose", - "checkNFD", "checkNFKD", "checkNFC", "checkNFKC", "check", - "getCanon", "getCompat", "getComposite", "getCombinClass", - "isExclusion", "isSingleton", "isNonStDecomp", "isComp2nd", "isComp_Ex", - "isNFD_NO", "isNFC_NO", "isNFC_MAYBE", "isNFKD_NO", "isNFKC_NO", "isNFKC_MAYBE", - "FCD", "checkFCD", "FCC", "checkFCC", "composeContiguous", "splitOnLastStarter", - "normalize_partial", "NFC_partial", "NFD_partial", "NFKC_partial", "NFKD_partial" + "normalize", "decompose", "reorder", "compose", + "checkNFD", "checkNFKD", "checkNFC", "checkNFKC", "check", + "getCanon", "getCompat", "getComposite", "getCombinClass", + "isExclusion", "isSingleton", "isNonStDecomp", "isComp2nd", "isComp_Ex", + "isNFD_NO", "isNFC_NO", "isNFC_MAYBE", "isNFKD_NO", "isNFKC_NO", "isNFKC_MAYBE", + "FCD", "checkFCD", "FCC", "checkFCC", "composeContiguous", "splitOnLastStarter", + "normalize_partial", "NFC_partial", "NFD_partial", "NFKC_partial", "NFKD_partial" ); - + // Define %EXPORT_TAGS - This matches perl5/dist/Unicode-Normalize/Normalize.pm lines 23-28 // :all tag exports everything from @EXPORT and @EXPORT_OK unicodeNormalize.defineExportTag("all", - "NFD", "NFC", "NFKD", "NFKC", // @EXPORT - "normalize", "decompose", "reorder", "compose", - "checkNFD", "checkNFKD", "checkNFC", "checkNFKC", "check", - "getCanon", "getCompat", "getComposite", "getCombinClass", - "isExclusion", "isSingleton", "isNonStDecomp", "isComp2nd", "isComp_Ex", - "isNFD_NO", "isNFC_NO", "isNFC_MAYBE", "isNFKD_NO", "isNFKC_NO", "isNFKC_MAYBE", - "FCD", "checkFCD", "FCC", "checkFCC", "composeContiguous", "splitOnLastStarter", - "normalize_partial", "NFC_partial", "NFD_partial", "NFKC_partial", "NFKD_partial" + "NFD", "NFC", "NFKD", "NFKC", // @EXPORT + "normalize", "decompose", "reorder", "compose", + "checkNFD", "checkNFKD", "checkNFC", "checkNFKC", "check", + "getCanon", "getCompat", "getComposite", "getCombinClass", + "isExclusion", "isSingleton", "isNonStDecomp", "isComp2nd", "isComp_Ex", + "isNFD_NO", "isNFC_NO", "isNFC_MAYBE", "isNFKD_NO", "isNFKC_NO", "isNFKC_MAYBE", + "FCD", "checkFCD", "FCC", "checkFCC", "composeContiguous", "splitOnLastStarter", + "normalize_partial", "NFC_partial", "NFD_partial", "NFKC_partial", "NFKD_partial" ); - + // :normalize tag unicodeNormalize.defineExportTag("normalize", - "NFD", "NFC", "NFKD", "NFKC", "normalize", "decompose", "reorder", "compose" + "NFD", "NFC", "NFKD", "NFKC", "normalize", "decompose", "reorder", "compose" ); - + // :check tag unicodeNormalize.defineExportTag("check", - "checkNFD", "checkNFKD", "checkNFC", "checkNFKC", "check" + "checkNFD", "checkNFKD", "checkNFC", "checkNFKC", "check" ); - + // :fast tag unicodeNormalize.defineExportTag("fast", - "FCD", "checkFCD", "FCC", "checkFCC", "composeContiguous" + "FCD", "checkFCD", "FCC", "checkFCC", "composeContiguous" ); - + try { unicodeNormalize.registerMethod("normalize", "$$"); unicodeNormalize.registerMethod("NFD", "$"); diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeUCD.java b/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeUCD.java index bc43e20e2..df4ab120a 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeUCD.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/UnicodeUCD.java @@ -23,14 +23,14 @@ public UnicodeUCD() { public static void initialize() { UnicodeUCD unicodeUCD = new UnicodeUCD(); unicodeUCD.initializeExporter(); - + // Define exports - main function is prop_invmap - unicodeUCD.defineExport("EXPORT_OK", - "prop_invmap", "prop_invlist", "prop_aliases", "prop_values", - "charinfo", "charblock", "charscript", "charprop", - "num", "charnames", "all_casefolds" + unicodeUCD.defineExport("EXPORT_OK", + "prop_invmap", "prop_invlist", "prop_aliases", "prop_values", + "charinfo", "charblock", "charscript", "charprop", + "num", "charnames", "all_casefolds" ); - + try { unicodeUCD.registerMethod("prop_invmap", null); unicodeUCD.registerMethod("prop_invlist", null); @@ -50,32 +50,29 @@ public static void initialize() { */ public static RuntimeList prop_invmap(RuntimeArray args, int ctx) { String propertyName = args.getFirst().toString(); - + // Normalize property name (remove spaces, hyphens, underscores, case-insensitive) String normalizedProp = normalizePropertyName(propertyName); - + try { // Handle case mapping properties // Note: These use SIMPLE case mappings (single code point), not FULL mappings - if (normalizedProp.equals("lowercasemapping") || - normalizedProp.equals("lc")) { + if (normalizedProp.equals("lowercasemapping") || + normalizedProp.equals("lc")) { return buildSimpleCaseMappingInvmap(UProperty.SIMPLE_LOWERCASE_MAPPING); - } - else if (normalizedProp.equals("uppercasemapping") || - normalizedProp.equals("uc")) { + } else if (normalizedProp.equals("uppercasemapping") || + normalizedProp.equals("uc")) { return buildSimpleCaseMappingInvmap(UProperty.SIMPLE_UPPERCASE_MAPPING); - } - else if (normalizedProp.equals("titlecasemapping") || - normalizedProp.equals("tc")) { + } else if (normalizedProp.equals("titlecasemapping") || + normalizedProp.equals("tc")) { return buildSimpleCaseMappingInvmap(UProperty.SIMPLE_TITLECASE_MAPPING); - } - else if (normalizedProp.equals("casefolding") || - normalizedProp.equals("cf")) { + } else if (normalizedProp.equals("casefolding") || + normalizedProp.equals("cf")) { return buildSimpleCaseMappingInvmap(UProperty.SIMPLE_CASE_FOLDING); } // Handle general category - else if (normalizedProp.equals("generalcategory") || - normalizedProp.equals("gc")) { + else if (normalizedProp.equals("generalcategory") || + normalizedProp.equals("gc")) { return buildGeneralCategoryInvmap(); } // Add more properties as needed @@ -85,12 +82,12 @@ else if (normalizedProp.equals("generalcategory") || RuntimeArray invmap = new RuntimeArray(); RuntimeScalar format = new RuntimeScalar("s"); RuntimeScalar defaultVal = new RuntimeScalar(0); - + return new RuntimeList( - invlist.createReference(), - invmap.createReference(), - format, - defaultVal + invlist.createReference(), + invmap.createReference(), + format, + defaultVal ); } } catch (Exception e) { @@ -103,7 +100,7 @@ else if (normalizedProp.equals("generalcategory") || /** * Build inversion map for case mapping properties using ICU4J. * Returns FULL case mappings (can be multiple code points). - * + *

    * Format "al" (adjustable list): invmap contains either: * - A scalar with the target code point (for simple 1-to-1 mappings) * - An array reference with multiple code points (for complex mappings) @@ -112,12 +109,12 @@ else if (normalizedProp.equals("generalcategory") || private static RuntimeList buildSimpleCaseMappingInvmap(int property) { RuntimeArray invlist = new RuntimeArray(); RuntimeArray invmap = new RuntimeArray(); - + // Track the last mapping to detect range boundaries String lastMappingKey = null; int rangeStart = -1; Object rangeStartMapping = null; - + // Determine which case mapping function to use FullCaseMapper mapper; switch (property) { @@ -139,21 +136,21 @@ private static RuntimeList buildSimpleCaseMappingInvmap(int property) { default: mapper = (cp) -> String.valueOf(Character.toChars(cp)); } - + // Scan all Unicode code points (BMP + supplementary) for (int cp = 0; cp <= 0x10FFFF; cp++) { // Skip surrogates if (cp >= 0xD800 && cp <= 0xDFFF) { continue; } - + String result = mapper.map(cp); int[] codePoints = result.codePoints().toArray(); - + // Determine the mapping representation Object mapping; String mappingKey; - + if (codePoints.length == 1 && codePoints[0] == cp) { // No change - use default mapping = 0; @@ -172,7 +169,7 @@ private static RuntimeList buildSimpleCaseMappingInvmap(int property) { mapping = arr.createReference(); mappingKey = "complex:" + cp; // Each complex mapping gets its own range } - + // Start new range if mapping pattern changes if (!mappingKey.equals(lastMappingKey)) { if (rangeStart >= 0) { @@ -188,7 +185,7 @@ private static RuntimeList buildSimpleCaseMappingInvmap(int property) { lastMappingKey = mappingKey; } } - + // Add final range if (rangeStart >= 0) { invlist.push(new RuntimeScalar(rangeStart)); @@ -198,30 +195,22 @@ private static RuntimeList buildSimpleCaseMappingInvmap(int property) { invmap.push((RuntimeScalar) rangeStartMapping); } } - + // Add sentinel invlist.push(new RuntimeScalar(0x110000)); - + // Format "al" means adjustable list - each element in range gets +1 RuntimeScalar format = new RuntimeScalar("al"); RuntimeScalar defaultVal = new RuntimeScalar(0); - + // Return array references, not wrapped in scalars return new RuntimeList( - invlist.createReference(), - invmap.createReference(), - format, - defaultVal + invlist.createReference(), + invmap.createReference(), + format, + defaultVal ); } - - /** - * Functional interface for full case mapping (can return multiple code points). - */ - @FunctionalInterface - private interface FullCaseMapper { - String map(int codePoint); - } /** * Build inversion map for General Category property. @@ -229,19 +218,19 @@ private interface FullCaseMapper { private static RuntimeList buildGeneralCategoryInvmap() { RuntimeArray invlist = new RuntimeArray(); RuntimeArray invmap = new RuntimeArray(); - + int lastCategory = -1; int rangeStart = 0; - + // Scan all Unicode code points for (int cp = 0; cp <= 0x10FFFF; cp++) { // Skip surrogates if (cp >= 0xD800 && cp <= 0xDFFF) { continue; } - + int category = UCharacter.getType(cp); - + // Start new range if category changes if (cp == 0 || category != lastCategory) { if (cp > 0) { @@ -252,24 +241,24 @@ private static RuntimeList buildGeneralCategoryInvmap() { lastCategory = category; } } - + // Add final range invlist.push(new RuntimeScalar(rangeStart)); invmap.push(new RuntimeScalar(getCategoryName(lastCategory))); - + // Add sentinel invlist.push(new RuntimeScalar(0x110000)); - + // Format "s" means string values RuntimeScalar format = new RuntimeScalar("s"); RuntimeScalar defaultVal = new RuntimeScalar("Cn"); // Unassigned - + // Return array references, not wrapped in scalars return new RuntimeList( - invlist.createReference(), - invmap.createReference(), - format, - defaultVal + invlist.createReference(), + invmap.createReference(), + format, + defaultVal ); } @@ -316,8 +305,8 @@ private static String getCategoryName(int category) { */ private static String normalizePropertyName(String name) { return name.toLowerCase() - .replaceAll("[\\s_-]", "") - .replace(":", ""); + .replaceAll("[\\s_-]", "") + .replace(":", ""); } /** @@ -331,7 +320,7 @@ public static RuntimeList prop_invlist(RuntimeArray args, int ctx) { /** * Returns all case folding mappings as a hash reference. - * + *

    * Returns a hash where keys are decimal code points and values are hash refs with: * - code: hex string of the code point (e.g., "0041") * - status: "C" (common), "F" (full), "S" (simple), "T" (turkic) @@ -341,39 +330,39 @@ public static RuntimeList prop_invlist(RuntimeArray args, int ctx) { * - turkic: hex string of turkic-specific fold (empty if none) * * @param args Unused - * @param ctx Context + * @param ctx Context * @return RuntimeList containing hash reference */ public static RuntimeList all_casefolds(RuntimeArray args, int ctx) { RuntimeHash result = new RuntimeHash(); - + // Scan all Unicode code points for (int cp = 0; cp <= 0x10FFFF; cp++) { // Skip surrogates if (cp >= 0xD800 && cp <= 0xDFFF) { continue; } - + // Get case folding for this code point String folded = UCharacter.foldCase(String.valueOf(Character.toChars(cp)), true); int[] foldedCodePoints = folded.codePoints().toArray(); - + // Skip if it folds to itself (no case folding) if (foldedCodePoints.length == 1 && foldedCodePoints[0] == cp) { continue; } - + // Create entry for this code point RuntimeHash entry = new RuntimeHash(); - + // code: hex string of original code point entry.put("code", new RuntimeScalar(String.format("%04X", cp))); - + // Determine status and create fold strings String fullFold = formatCodePoints(foldedCodePoints); String simpleFold = ""; String status; - + if (foldedCodePoints.length == 1) { // Simple case folding (1-to-1 mapping) simpleFold = fullFold; @@ -383,23 +372,23 @@ public static RuntimeList all_casefolds(RuntimeArray args, int ctx) { simpleFold = ""; // No simple fold for multi-char results status = "F"; // Full } - + entry.put("status", new RuntimeScalar(status)); entry.put("simple", new RuntimeScalar(simpleFold)); entry.put("full", new RuntimeScalar(fullFold)); entry.put("mapping", new RuntimeScalar(fullFold)); - + // Turkic-specific folding (for Turkish/Azeri) // ICU4J doesn't expose turkic-specific folding directly, so we leave it empty entry.put("turkic", new RuntimeScalar("")); - + // Add to result hash with decimal code point as key result.put(String.valueOf(cp), entry.createReference()); } - + return new RuntimeList(result.createReference()); } - + /** * Format an array of code points as space-separated hex strings. */ @@ -411,4 +400,12 @@ private static String formatCodePoints(int[] codePoints) { } return sb.toString(); } + + /** + * Functional interface for full case mapping (can return multiple code points). + */ + @FunctionalInterface + private interface FullCaseMapper { + String map(int codePoint); + } } diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Universal.java b/src/main/java/org/perlonjava/runtime/perlmodule/Universal.java index 77c1d8153..9313816c7 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Universal.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Universal.java @@ -22,6 +22,14 @@ */ public class Universal extends PerlModuleBase { + /** + * Constructor for Universal. + * Initializes the module with the name "UNIVERSAL". + */ + public Universal() { + super("UNIVERSAL"); + } + private static String tryDecodeUtf8Octets(String maybeOctets) { if (maybeOctets == null || maybeOctets.isEmpty()) { return null; @@ -61,14 +69,6 @@ private static String toUtf8OctetString(String unicodeString) { return out.toString(); } - /** - * Constructor for Universal. - * Initializes the module with the name "UNIVERSAL". - */ - public Universal() { - super("UNIVERSAL"); - } - /** * Static initializer to set up the UNIVERSAL module. * This method registers universally available methods. diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java b/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java index a4711b5bd..7c7bf7bf2 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java @@ -2,12 +2,8 @@ import com.ibm.icu.text.CharsetDetector; import com.ibm.icu.text.CharsetMatch; -import org.perlonjava.runtime.runtimetypes.RuntimeArray; -import org.perlonjava.runtime.runtimetypes.RuntimeList; -import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarCache; -import org.perlonjava.runtime.runtimetypes.RuntimeScalarReadOnly; import org.perlonjava.frontend.semantic.ScopedSymbolTable; +import org.perlonjava.runtime.runtimetypes.*; import java.nio.ByteBuffer; import java.nio.CharBuffer; diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/XSLoader.java b/src/main/java/org/perlonjava/runtime/perlmodule/XSLoader.java index eb7c98c9e..e25ab0774 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/XSLoader.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/XSLoader.java @@ -1,9 +1,9 @@ package org.perlonjava.runtime.perlmodule; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeList; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; -import org.perlonjava.runtime.operators.WarnDie; import java.lang.reflect.Method; diff --git a/src/main/java/org/perlonjava/runtime/regex/ExtendedCharClass.java b/src/main/java/org/perlonjava/runtime/regex/ExtendedCharClass.java index 9f9a7cf75..3c7b994c6 100644 --- a/src/main/java/org/perlonjava/runtime/regex/ExtendedCharClass.java +++ b/src/main/java/org/perlonjava/runtime/regex/ExtendedCharClass.java @@ -5,14 +5,14 @@ /** * ExtendedCharClass handles Perl's Extended Bracketed Character Classes (?[...]) - * + *

    * Extended character classes allow set operations on character classes: * - Union: (?[ [a] + [b] ]) or (?[ [a] | [b] ]) * - Intersection: (?[ [a-z] & [aeiou] ]) * - Subtraction: (?[ [a-z] - [aeiou] ]) * - Symmetric difference: (?[ [a-z] ^ [aeiou] ]) * - Complement: (?[ ! [a-z] ]) - * + *

    * Features: * - Automatic /xx mode (whitespace ignored) * - Comments with # and (?#...) @@ -21,7 +21,7 @@ * - POSIX classes ([:word:], etc.) * - Escape sequences (\t, \cX, etc.) * - Regex interpolation (qr// patterns) - * + *

    * The implementation tokenizes the content, parses it into an expression tree, * and transforms it into Java's character class syntax using intersection (&&) * and negation (^) operators. @@ -91,12 +91,12 @@ static int handleExtendedCharacterClass(String s, int offset, StringBuilder sb, /** * Find the end of the extended character class, handling nested brackets. - * + *

    * This method tracks bracket depth to handle nested character classes like [a[b]c]. * It also handles escape sequences, especially \c which consumes two characters. - * + *

    * The extended character class ends when we find ]) at depth 0. - * + * * @param s The regex string * @param start Position after the opening (?[ * @return Position of the closing ], or -1 if not found @@ -266,7 +266,7 @@ private static List tokenizeExtendedClass(String content, String original if (i >= content.length()) break; char c = content.charAt(i); - + // Handle comments (# to end of line) if (c == '#') { // Skip to end of line @@ -309,30 +309,30 @@ private static List tokenizeExtendedClass(String content, String original // When a qr// pattern containing (?[...]) is interpolated, it becomes: // (?^:(?[ ... ])) or (?^i:(?[ ... ])) etc. // We need to extract just the inner (?[ ... ]) part - + // Find the : after the flags (e.g., (?^: or (?^i:) int colonPos = i + 2; while (colonPos < content.length() && content.charAt(colonPos) != ':' && content.charAt(colonPos) != ')') { colonPos++; } - + // Check if this is followed by (?[ or another (?FLAGS: if (colonPos < content.length() && content.charAt(colonPos) == ':' && - colonPos + 1 < content.length() && content.charAt(colonPos + 1) == '(') { - + colonPos + 1 < content.length() && content.charAt(colonPos + 1) == '(') { + int searchPos = colonPos + 1; // Skip nested (?FLAGS: groups to find the actual (?[ while (searchPos < content.length() && content.charAt(searchPos) == '(' && - searchPos + 2 < content.length() && content.charAt(searchPos + 1) == '?') { + searchPos + 2 < content.length() && content.charAt(searchPos + 1) == '?') { // Find the : after these flags int nextColon = searchPos + 2; - while (nextColon < content.length() && content.charAt(nextColon) != ':' && - content.charAt(nextColon) != ')') { + while (nextColon < content.length() && content.charAt(nextColon) != ':' && + content.charAt(nextColon) != ')') { nextColon++; } if (nextColon < content.length() && content.charAt(nextColon) == ':') { if (nextColon + 3 < content.length() && content.charAt(nextColon + 1) == '(' && - content.charAt(nextColon + 2) == '?' && content.charAt(nextColon + 3) == '[') { + content.charAt(nextColon + 2) == '?' && content.charAt(nextColon + 3) == '[') { // Found (?[! searchPos = nextColon + 1; break; @@ -343,11 +343,11 @@ private static List tokenizeExtendedClass(String content, String original break; } } - + // Check if we found (?[ if (searchPos < content.length() && content.charAt(searchPos) == '(' && - searchPos + 2 < content.length() && content.charAt(searchPos + 1) == '?' && - content.charAt(searchPos + 2) == '[') { + searchPos + 2 < content.length() && content.charAt(searchPos + 1) == '?' && + content.charAt(searchPos + 2) == '[') { // Found (?FLAGS:...(?[ ... ])) - this is an interpolated extended char class int innerStart = searchPos; // Position of inner '(?[' int innerEnd = findMatchingParen(content, innerStart); @@ -601,13 +601,13 @@ private static ExprNode parseExtendedClass(List tokens, String originalRe /** * Process a character class element from extended syntax. - * + *

    * This handles: * - Nested extended character classes (?[...]) from interpolation * - POSIX classes ([:word:], [:digit:], etc.) * - Regular character classes ([a-z], [abc], etc.) * - Escape sequences (\d, \w, \s, etc.) - * + * * @param charClass The character class string to process * @return Java-compatible character class syntax */ @@ -619,7 +619,7 @@ private static String processCharacterClass(String charClass) { String nestedContent = charClass.substring(3, charClass.length() - 2); return transformExtendedClass(nestedContent, charClass, 0); } - + // Handle POSIX classes if (charClass.startsWith("::") && charClass.endsWith("::")) { // Transform ::word:: to [:word:] @@ -823,7 +823,7 @@ String evaluate() { /** * Binary operation node (union, intersection, subtraction, symmetric difference) - * + *

    * Operators: * - + or | : Union (A or B) * - & : Intersection (A and B) @@ -877,7 +877,7 @@ String evaluate() { /** * Unary operation node (complement) - * + *

    * The ! operator complements a character class. * Important: Double negation is handled specially: * - ! [^A] becomes [A] (NOT NOT A = A) @@ -899,7 +899,7 @@ String evaluate() { if (operator.equals("!")) { // Complement: [^A] operandEval = unwrapBrackets(operandEval); - + // Check if already negated (starts with ^) // This handles double negation: ! [^A] = [A] if (operandEval.startsWith("^")) { diff --git a/src/main/java/org/perlonjava/runtime/regex/MultiCharFoldMapper.java b/src/main/java/org/perlonjava/runtime/regex/MultiCharFoldMapper.java index 370360071..901d653f9 100644 --- a/src/main/java/org/perlonjava/runtime/regex/MultiCharFoldMapper.java +++ b/src/main/java/org/perlonjava/runtime/regex/MultiCharFoldMapper.java @@ -10,15 +10,15 @@ * single-character case folding, not multi-character folds like ß→ss. */ public class MultiCharFoldMapper { - + // Map of characters that fold to multiple characters // Format: character → fold string private static final Map MULTI_CHAR_FOLDS = new HashMap<>(); - + // Reverse map: fold string → List of characters that fold to it // Format: fold string → character private static final Map REVERSE_FOLDS = new HashMap<>(); - + static { // Latin MULTI_CHAR_FOLDS.put(0x00DF, "ss"); // ß → ss @@ -32,14 +32,14 @@ public class MultiCharFoldMapper { MULTI_CHAR_FOLDS.put(0x1E9A, "a\u02BE"); // ẚ → aʾ MULTI_CHAR_FOLDS.put(0x1E9B, "\u017Fs"); // ẛ → ẛ MULTI_CHAR_FOLDS.put(0x1F50, "\u03C5\u0313"); // ὐ → ὐ - + // Greek MULTI_CHAR_FOLDS.put(0x0390, "\u03B9\u0308\u0301"); // ΐ → ΐ MULTI_CHAR_FOLDS.put(0x03B0, "\u03C5\u0308\u0301"); // ΰ → ΰ - + // Armenian MULTI_CHAR_FOLDS.put(0x0587, "\u0565\u0582"); // և → եւ - + // Latin ligatures MULTI_CHAR_FOLDS.put(0xFB00, "ff"); // ff → ff MULTI_CHAR_FOLDS.put(0xFB01, "fi"); // fi → fi @@ -48,45 +48,45 @@ public class MultiCharFoldMapper { MULTI_CHAR_FOLDS.put(0xFB04, "ffl"); // ffl → ffl MULTI_CHAR_FOLDS.put(0xFB05, "\u017Ft"); // ſt → ſt MULTI_CHAR_FOLDS.put(0xFB06, "st"); // st → st - + // Armenian ligatures MULTI_CHAR_FOLDS.put(0xFB13, "\u0574\u0576"); // ﬓ → մն MULTI_CHAR_FOLDS.put(0xFB14, "\u0574\u0565"); // ﬔ → մե MULTI_CHAR_FOLDS.put(0xFB15, "\u0574\u056B"); // ﬕ → մի MULTI_CHAR_FOLDS.put(0xFB16, "\u057E\u0576"); // ﬖ → վն MULTI_CHAR_FOLDS.put(0xFB17, "\u0574\u056D"); // ﬗ → մխ - + // Build reverse map (lowercase versions only for simpler matching) for (Map.Entry entry : MULTI_CHAR_FOLDS.entrySet()) { String fold = entry.getValue().toLowerCase(); REVERSE_FOLDS.put(fold, entry.getKey()); } } - + /** * Check if a character has a multi-character case fold. - * + * * @param codePoint The Unicode code point to check * @return true if this character folds to multiple characters */ public static boolean hasMultiCharFold(int codePoint) { return MULTI_CHAR_FOLDS.containsKey(codePoint); } - + /** * Get the multi-character fold for a character. - * + * * @param codePoint The Unicode code point * @return The folded string, or null if no multi-char fold exists */ public static String getMultiCharFold(int codePoint) { return MULTI_CHAR_FOLDS.get(codePoint); } - + /** * Expand a character with a multi-char fold into a regex alternation. * For example: ß → (?:ß|ss|SS|Ss|sS) - * + * * @param codePoint The Unicode code point * @return A regex pattern that matches all case variants, or null if no multi-char fold */ @@ -95,16 +95,16 @@ public static String expandToAlternation(int codePoint) { if (fold == null) { return null; } - + // Build all case variations StringBuilder sb = new StringBuilder("(?:"); String original = new String(Character.toChars(codePoint)); sb.append(Pattern.quote(original)); sb.append("|"); - + // Add the basic fold sb.append(Pattern.quote(fold)); - + // Add case variations of the fold (if it's ASCII) if (fold.chars().allMatch(c -> c >= 'a' && c <= 'z')) { // Generate all case combinations for lowercase ASCII @@ -121,26 +121,26 @@ public static String expandToAlternation(int codePoint) { } } } - + sb.append(")"); return sb.toString(); } - + /** * Check if a string has a reverse fold (i.e., a character that folds to this string). * For example: "ss" has a reverse fold to ß - * + * * @param str The string to check (will be lowercased) * @return true if a character folds to this string */ public static boolean hasReverseFold(String str) { return REVERSE_FOLDS.containsKey(str.toLowerCase()); } - + /** * Get the character that folds to this string. * For example: "ss" → ß - * + * * @param str The string to look up (will be lowercased) * @return The character code point, or null if no reverse fold exists */ diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java b/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java index 5091c3c28..bc87b8aa9 100644 --- a/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java +++ b/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java @@ -24,7 +24,7 @@ public record RegexFlags(boolean isGlobalMatch, boolean keepCurrentPosition, boo boolean isNonCapturing, boolean isOptimized, boolean isCaseInsensitive, boolean isMultiLine, boolean isDotAll, boolean isExtended, boolean preservesMatch) { - public static RegexFlags fromModifiers(String modifiers, String patternString) { + public static RegexFlags fromModifiers (String modifiers, String patternString){ return new RegexFlags( modifiers.contains("g"), modifiers.contains("c"), @@ -42,7 +42,7 @@ public static RegexFlags fromModifiers(String modifiers, String patternString) { ); } - public static void validateModifiers(String modifiers) { + public static void validateModifiers (String modifiers){ // Valid modifiers based on what's actually handled in fromModifiers String validModifiers = "gcr?noimsxpadeul"; // Add 'xx' handling separately, 'l' for locale @@ -61,7 +61,7 @@ public static void validateModifiers(String modifiers) { } } - public int toPatternFlags() { + public int toPatternFlags () { int flags = 0; if (isCaseInsensitive) { // For proper Unicode case-insensitive matching, we need both flags: @@ -82,7 +82,7 @@ public int toPatternFlags() { return flags; } - public RegexFlags with(String positiveFlags, String negativeFlags) { + public RegexFlags with (String positiveFlags, String negativeFlags){ boolean newFlagN = this.isNonCapturing; boolean newIsCaseInsensitive = this.isCaseInsensitive; boolean newIsMultiLine = this.isMultiLine; @@ -122,7 +122,7 @@ public RegexFlags with(String positiveFlags, String negativeFlags) { ); } - public String toFlagString() { + public String toFlagString () { StringBuilder flagString = new StringBuilder(); if (isGlobalMatch) flagString.append('g'); diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java b/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java index 15e05862a..de4b014d1 100644 --- a/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java +++ b/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java @@ -42,9 +42,6 @@ public class RegexPreprocessor { // WIP: // named capture (? ... ) replace underscore in name - static int captureGroupCount; - static boolean deferredUnicodePropertyEncountered; - static boolean inlinePFlagEncountered; private static final Map SPECIAL_SINGLE_CHAR_FOLDS = Map.of( 0x00B5, 0x03BC, 0x212A, 0x006B, @@ -55,6 +52,9 @@ public class RegexPreprocessor { 0x006B, 0x212A, 0x00E5, 0x212B ); + static int captureGroupCount; + static boolean deferredUnicodePropertyEncountered; + static boolean inlinePFlagEncountered; static void markDeferredUnicodePropertyEncountered() { deferredUnicodePropertyEncountered = true; @@ -91,12 +91,12 @@ static String preProcessRegex(String s, RegexFlags regexFlags) { s = transformSimpleConditionals(s); s = removeUnderscoresFromEscapes(s); s = normalizeQuantifiers(s); - + // Expand multi-character case folds when case-insensitive flag is set if (regexFlags.isCaseInsensitive()) { s = expandMultiCharFolds(s); } - + StringBuilder sb = new StringBuilder(); handleRegex(s, 0, sb, regexFlags, false); String result = sb.toString(); @@ -107,38 +107,38 @@ static String preProcessRegex(String s, RegexFlags regexFlags) { * Escape unescaped braces that don't form valid quantifiers. * Perl allows invalid quantifier braces and treats them as literals. * Java Pattern.compile() rejects them, so we must escape them. - * + *

    * Valid quantifiers: {n}, {n,}, {n,m} where n and m are non-negative integers * Invalid quantifiers: {(.*?)}, {abc}, {}, {,5}, etc. - * + *

    * IMPORTANT: This is a high-risk preprocessing step that modifies brace characters. * Known edge cases that must be handled correctly: - * + *

    * 1. ESCAPE SEQUENCES WITH BRACES (must NOT be escaped): - * - \N{name} - Named Unicode character (e.g., \N{LATIN SMALL LETTER A}) - * - \x{...} - Hexadecimal character code (e.g., \x{1F600}) - * - \o{...} - Octal character code (e.g., \o{777}) - * - \p{...} - Unicode property (e.g., \p{Letter}) - * - \P{...} - Negated Unicode property (e.g., \P{Number}) - * - \g{...} - Named or relative backreference (e.g., \g{name}, \g{-1}) - * Currently handled: N, x, o, p, P, g - * + * - \N{name} - Named Unicode character (e.g., \N{LATIN SMALL LETTER A}) + * - \x{...} - Hexadecimal character code (e.g., \x{1F600}) + * - \o{...} - Octal character code (e.g., \o{777}) + * - \p{...} - Unicode property (e.g., \p{Letter}) + * - \P{...} - Negated Unicode property (e.g., \P{Number}) + * - \g{...} - Named or relative backreference (e.g., \g{name}, \g{-1}) + * Currently handled: N, x, o, p, P, g + *

    * 2. CHARACTER CLASSES (braces inside [...] are always literal): - * - [a{3}] means "match 'a', '{', '3', or '}'" not "match 'aaa'" - * - Nested classes like [a-z[0-9]{3}] must track nesting depth - * + * - [a{3}] means "match 'a', '{', '3', or '}'" not "match 'aaa'" + * - Nested classes like [a-z[0-9]{3}] must track nesting depth + *

    * 3. VALID QUANTIFIERS (must NOT be escaped): - * - {n} - exactly n times (e.g., a{3}) - * - {n,} - n or more times (e.g., a{2,}) - * - {n,m} - between n and m times (e.g., a{2,5}) - * + * - {n} - exactly n times (e.g., a{3}) + * - {n,} - n or more times (e.g., a{2,}) + * - {n,m} - between n and m times (e.g., a{2,5}) + *

    * 4. ALREADY ESCAPED BRACES (must NOT be double-escaped): - * - \{ and \} should remain as-is - * - Track backslash escaping carefully to avoid double-escaping - * + * - \{ and \} should remain as-is + * - Track backslash escaping carefully to avoid double-escaping + *

    * 5. POSSESSIVE AND LAZY QUANTIFIERS: - * - {n}+ (possessive) and {n}? (lazy) should work with valid quantifiers - * + * - {n}+ (possessive) and {n}? (lazy) should work with valid quantifiers + *

    * POTENTIAL ISSUES NOT YET HANDLED: * - Extended bracketed character classes: (?[...]) may contain braces * - Conditional patterns: (?(condition){yes}{no}) uses braces for branches @@ -146,7 +146,7 @@ static String preProcessRegex(String s, RegexFlags regexFlags) { * - Code blocks: (?{...}) and (??{...}) use braces but are handled elsewhere * - Named capture definitions: (?...) - are braces allowed in names? * - Unicode named sequences: \N{...} may contain nested braces in some contexts - * + *

    * If new regex features are added that use braces, this function MUST be updated. * Test changes thoroughly with unit/regex/unescaped_braces.t and regex test suite. */ @@ -164,7 +164,7 @@ private static String escapeInvalidQuantifierBraces(String pattern) { // Check if this is an escape sequence that uses braces: \N{...}, \x{...}, \o{...}, \p{...}, \P{...}, \g{...} if ((c == 'N' || c == 'x' || c == 'o' || c == 'p' || c == 'P' || c == 'g') && - i + 1 < pattern.length() && pattern.charAt(i + 1) == '{') { + i + 1 < pattern.length() && pattern.charAt(i + 1) == '{') { // Skip the entire escape sequence with braces result.append('{'); i++; // Move past '{' @@ -273,11 +273,7 @@ private static boolean isValidQuantifierContent(String pattern, int start, int e if (content.matches("\\d+,")) { return true; // {n,} } - if (content.matches("\\d+,\\d+")) { - return true; // {n,m} - } - - return false; + return content.matches("\\d+,\\d+"); // {n,m} } /** @@ -322,10 +318,10 @@ private static String expandMultiCharFolds(String pattern) { int len = pattern.length(); boolean inCharClass = false; boolean escaped = false; - + while (i < len) { char ch = pattern.charAt(i); - + // Track if we're inside a character class [...] if (!escaped) { if (ch == '[') { @@ -334,7 +330,7 @@ private static String expandMultiCharFolds(String pattern) { inCharClass = false; } } - + // Handle escape sequences if (ch == '\\' && !escaped) { escaped = true; @@ -342,7 +338,7 @@ private static String expandMultiCharFolds(String pattern) { i++; continue; } - + // If this is an escaped character or we're in a char class, don't expand if (escaped || inCharClass) { if (!escaped && inCharClass) { @@ -360,7 +356,7 @@ private static String expandMultiCharFolds(String pattern) { i++; continue; } - + // Check if this character has a multi-char fold int codePoint = pattern.codePointAt(i); if (MultiCharFoldMapper.hasMultiCharFold(codePoint)) { @@ -388,7 +384,7 @@ private static String expandMultiCharFolds(String pattern) { } } } - + if (!foundReverseFold) { String specialExpansion = expandSpecialSingleCharFold(codePoint); if (specialExpansion != null) { @@ -399,10 +395,10 @@ private static String expandMultiCharFolds(String pattern) { i += Character.charCount(codePoint); } } - + escaped = false; } - + return result.toString(); } @@ -498,7 +494,7 @@ private static void appendCharClassLiteral(StringBuilder sb, int codePoint) { } sb.appendCodePoint(codePoint); } - + /** * Remove underscores from \x{...} and \o{...} escape sequences. * Perl allows underscores in numeric literals for readability, but Java doesn't. diff --git a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java index e02888389..01d23b18f 100644 --- a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java +++ b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java @@ -1,7 +1,7 @@ package org.perlonjava.runtime.regex; -import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.operators.Time; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.*; import java.util.Iterator; @@ -60,12 +60,13 @@ protected boolean removeEldestEntry(Map.Entry eldest) { // Capture groups from the last successful match that had captures. // In Perl 5, $1/$2/etc persist across non-capturing matches. public static String[] lastCaptureGroups = null; - // Indicates if \G assertion is used (set from regexFlags during compilation) - private boolean useGAssertion = false; // Compiled regex pattern public Pattern pattern; int patternFlags; String patternString; + boolean hasPreservesMatch = false; // True if /p was used (outer or inline (?p)) + // Indicates if \G assertion is used (set from regexFlags during compilation) + private boolean useGAssertion = false; // Flags for regex behavior private RegexFlags regexFlags; // Replacement string for substitutions @@ -73,7 +74,6 @@ protected boolean removeEldestEntry(Map.Entry eldest) { // Tracks if a match has occurred: this is used as a counter for m?PAT? private boolean matched = false; private boolean hasCodeBlockCaptures = false; // True if regex has (?{...}) code blocks - boolean hasPreservesMatch = false; // True if /p was used (outer or inline (?p)) private boolean deferredUserDefinedUnicodeProperties = false; public RuntimeRegex() { @@ -413,7 +413,7 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc // Debug logging if (DEBUG_REGEX) { System.err.println("matchRegexDirect: pattern=" + regex.pattern.pattern() + - " input=" + string.toString() + " ctx=" + ctx); + " input=" + string.toString() + " ctx=" + ctx); } if (regex.regexFlags.isMatchExactlyOnce() && regex.matched) { @@ -438,7 +438,7 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc RuntimeScalar posScalar = RuntimePosLvalue.pos(string); boolean isPosDefined = posScalar.getDefinedBoolean(); int startPos = isPosDefined ? posScalar.getInt() : 0; - + // Only use pos() for /g matches - non-/g matches always start from 0 if (!regex.regexFlags.isGlobalMatch()) { isPosDefined = false; @@ -477,88 +477,88 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc // state and break tests that rely on @-/@+. try { - while (matcher.find()) { - // If \G is used, ensure the match starts at the expected position - if (regex.useGAssertion && isPosDefined && matcher.start() != startPos) { - break; - } + while (matcher.find()) { + // If \G is used, ensure the match starts at the expected position + if (regex.useGAssertion && isPosDefined && matcher.start() != startPos) { + break; + } - found = true; - int captureCount = matcher.groupCount(); + found = true; + int captureCount = matcher.groupCount(); - // Always initialize $1, $2, @+, @-, $`, $&, $' for every successful match - globalMatcher = matcher; - globalMatchString = inputStr; - if (captureCount > 0) { - lastCaptureGroups = new String[captureCount]; - for (int i = 0; i < captureCount; i++) { - lastCaptureGroups[i] = matcher.group(i + 1); + // Always initialize $1, $2, @+, @-, $`, $&, $' for every successful match + globalMatcher = matcher; + globalMatchString = inputStr; + if (captureCount > 0) { + lastCaptureGroups = new String[captureCount]; + for (int i = 0; i < captureCount; i++) { + lastCaptureGroups[i] = matcher.group(i + 1); + } + } else { + lastCaptureGroups = null; } - } else { - lastCaptureGroups = null; - } - lastMatchedString = matcher.group(0); - lastMatchStart = matcher.start(); - lastMatchEnd = matcher.end(); - - if (regex.regexFlags.isGlobalMatch() && captureCount < 1 && ctx == RuntimeContextType.LIST) { - // Global match and no captures, in list context return the matched string - String matchedStr = matcher.group(0); - matchedGroups.add(new RuntimeScalar(matchedStr)); - } else { - // save captures in return list if needed - if (ctx == RuntimeContextType.LIST) { - for (int i = 1; i <= captureCount; i++) { - String matchedStr = matcher.group(i); - if (matchedStr != null) { - matchedGroups.add(new RuntimeScalar(matchedStr)); + lastMatchedString = matcher.group(0); + lastMatchStart = matcher.start(); + lastMatchEnd = matcher.end(); + + if (regex.regexFlags.isGlobalMatch() && captureCount < 1 && ctx == RuntimeContextType.LIST) { + // Global match and no captures, in list context return the matched string + String matchedStr = matcher.group(0); + matchedGroups.add(new RuntimeScalar(matchedStr)); + } else { + // save captures in return list if needed + if (ctx == RuntimeContextType.LIST) { + for (int i = 1; i <= captureCount; i++) { + String matchedStr = matcher.group(i); + if (matchedStr != null) { + matchedGroups.add(new RuntimeScalar(matchedStr)); + } } } } - } - if (regex.regexFlags.isGlobalMatch()) { - // Update the position for the next match - int matchStart = matcher.start(); - int matchEnd = matcher.end(); - - // Detect zero-length match that would cause infinite loop - if (matchEnd == matchStart && matchStart == previousMatchEnd) { - // Consecutive zero-length match at same position - advance by 1 or stop - if (matchEnd >= inputStr.length()) { - // At end of string, stop matching - break; + if (regex.regexFlags.isGlobalMatch()) { + // Update the position for the next match + int matchStart = matcher.start(); + int matchEnd = matcher.end(); + + // Detect zero-length match that would cause infinite loop + if (matchEnd == matchStart && matchStart == previousMatchEnd) { + // Consecutive zero-length match at same position - advance by 1 or stop + if (matchEnd >= inputStr.length()) { + // At end of string, stop matching + break; + } + // In middle of string, advance by 1 to avoid infinite loop + matchEnd = matchStart + 1; } - // In middle of string, advance by 1 to avoid infinite loop - matchEnd = matchStart + 1; - } - - previousMatchEnd = matchEnd; - - if (ctx == RuntimeContextType.SCALAR || ctx == RuntimeContextType.VOID) { - // Set pos to the end of the current match to prepare for the next search - posScalar.set(matchEnd); - // Record zero-length match for cross-call tracking - if (matchEnd == matchStart) { - RuntimePosLvalue.recordZeroLengthMatch(string, matchEnd, regex.patternString); + + previousMatchEnd = matchEnd; + + if (ctx == RuntimeContextType.SCALAR || ctx == RuntimeContextType.VOID) { + // Set pos to the end of the current match to prepare for the next search + posScalar.set(matchEnd); + // Record zero-length match for cross-call tracking + if (matchEnd == matchStart) { + RuntimePosLvalue.recordZeroLengthMatch(string, matchEnd, regex.patternString); + } else { + RuntimePosLvalue.recordNonZeroLengthMatch(string); + } + break; // Break out of the loop after the first match in SCALAR context } else { - RuntimePosLvalue.recordNonZeroLengthMatch(string); - } - break; // Break out of the loop after the first match in SCALAR context - } else { - startPos = matchEnd; - posScalar.set(startPos); - // Update matcher region if we advanced past a zero-length match - if (startPos > matchStart) { - matcher.region(startPos, inputStr.length()); + startPos = matchEnd; + posScalar.set(startPos); + // Update matcher region if we advanced past a zero-length match + if (startPos > matchStart) { + matcher.region(startPos, inputStr.length()); + } } } - } - if (!regex.regexFlags.isGlobalMatch()) { - break; + if (!regex.regexFlags.isGlobalMatch()) { + break; + } } - } } catch (RegexTimeoutException e) { WarnDie.warn(new RuntimeScalar(e.getMessage() + "\n"), RuntimeScalarCache.scalarEmptyString); found = false; @@ -632,9 +632,9 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc * Regex matching with timeout wrapper to handle catastrophic backtracking. * Runs the regex in a separate thread with a timeout. * - * @param quotedRegex The regex pattern object - * @param string The string to match against - * @param ctx The context (LIST, SCALAR, VOID) + * @param quotedRegex The regex pattern object + * @param string The string to match against + * @param ctx The context (LIST, SCALAR, VOID) * @param timeoutSeconds Maximum seconds to allow for matching * @return Match result, or throws exception if timeout */ @@ -754,55 +754,55 @@ public static RuntimeBase replaceRegex(RuntimeScalar quotedRegex, RuntimeScalar // Perform the substitution try { - while (matcher.find()) { - found++; - - // Initialize $1, $2, @+, @- only when we have a match - globalMatcher = matcher; - globalMatchString = inputStr; - if (matcher.groupCount() > 0) { - lastCaptureGroups = new String[matcher.groupCount()]; - for (int i = 0; i < matcher.groupCount(); i++) { - lastCaptureGroups[i] = matcher.group(i + 1); + while (matcher.find()) { + found++; + + // Initialize $1, $2, @+, @- only when we have a match + globalMatcher = matcher; + globalMatchString = inputStr; + if (matcher.groupCount() > 0) { + lastCaptureGroups = new String[matcher.groupCount()]; + for (int i = 0; i < matcher.groupCount(); i++) { + lastCaptureGroups[i] = matcher.group(i + 1); + } + } else { + lastCaptureGroups = null; + } + lastMatchedString = matcher.group(0); + lastMatchStart = matcher.start(); + lastMatchEnd = matcher.end(); + + String replacementStr; + if (replacementIsCode) { + // Evaluate the replacement as code + RuntimeList result = RuntimeCode.apply(replacement, new RuntimeArray(), RuntimeContextType.SCALAR); + replacementStr = result.toString(); + } else { + // Replace the match with the replacement string + replacementStr = replacement.toString(); } - } else { - lastCaptureGroups = null; - } - lastMatchedString = matcher.group(0); - lastMatchStart = matcher.start(); - lastMatchEnd = matcher.end(); - - String replacementStr; - if (replacementIsCode) { - // Evaluate the replacement as code - RuntimeList result = RuntimeCode.apply(replacement, new RuntimeArray(), RuntimeContextType.SCALAR); - replacementStr = result.toString(); - } else { - // Replace the match with the replacement string - replacementStr = replacement.toString(); - } - if (replacementStr != null) { - // In Java regex replacement strings: - // - // - $1, $2, etc. refer to capture groups from the pattern - // - $0 refers to the entire match - // - \ is used for escaping - // - // When you pass $x as the replacement string, Java interprets it as trying to reference capture group "x", which doesn't exist (capture groups are numbered, not named with letters in basic Java regex). + if (replacementStr != null) { + // In Java regex replacement strings: + // + // - $1, $2, etc. refer to capture groups from the pattern + // - $0 refers to the entire match + // - \ is used for escaping + // + // When you pass $x as the replacement string, Java interprets it as trying to reference capture group "x", which doesn't exist (capture groups are numbered, not named with letters in basic Java regex). - // replacementStr = replacementStr.replaceAll("\\\\", "\\\\\\\\"); + // replacementStr = replacementStr.replaceAll("\\\\", "\\\\\\\\"); - // Append the text before the match and the replacement to the result buffer - // matcher.appendReplacement(resultBuffer, replacementStr); - matcher.appendReplacement(resultBuffer, Matcher.quoteReplacement(replacementStr)); - } + // Append the text before the match and the replacement to the result buffer + // matcher.appendReplacement(resultBuffer, replacementStr); + matcher.appendReplacement(resultBuffer, Matcher.quoteReplacement(replacementStr)); + } - // If not a global match, break after the first replacement - if (!regex.regexFlags.isGlobalMatch()) { - break; + // If not a global match, break after the first replacement + if (!regex.regexFlags.isGlobalMatch()) { + break; + } } - } } catch (RegexTimeoutException e) { WarnDie.warn(new RuntimeScalar(e.getMessage() + "\n"), RuntimeScalarCache.scalarEmptyString); found = 0; diff --git a/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java b/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java index 110846bd4..8740afe2e 100644 --- a/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java +++ b/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java @@ -36,17 +36,17 @@ public static int getCodePointFromName(String name) { // Control characters case "NEL" -> 0x0085; // NEXT LINE (NEL) case "BOM" -> 0xFEFF; // BYTE ORDER MARK - + // Spaces (Perl allows both hyphenated and non-hyphenated forms) case "NBSP", "NO-BREAK SPACE" -> 0x00A0; case "ZWSP", "ZERO WIDTH SPACE" -> 0x200B; case "ZWNJ", "ZERO WIDTH NON-JOINER" -> 0x200C; case "ZWJ", "ZERO WIDTH JOINER" -> 0x200D; - + // Try ICU4J's official Unicode name lookup default -> UCharacter.getCharFromName(name); }; - + if (codePoint == -1) { // Check if this is a named sequence (multi-character sequence) // Named sequences are not supported in some contexts like tr/// @@ -91,7 +91,7 @@ private static boolean isNamedSequence(String name) { * - Lines starting with - or ! remove a property * - Lines starting with & intersect with a property * - * @param definition The property definition string + * @param definition The property definition string * @param recursionSet Set to track recursive property calls * @param propertyName The name of the property being parsed (for error messages) * @return A character class pattern @@ -100,16 +100,16 @@ private static String parseUserDefinedProperty(String definition, Set re UnicodeSet resultSet = new UnicodeSet(); boolean hasIntersection = false; UnicodeSet intersectionSet = null; - + String[] lines = definition.split("\\n"); for (String line : lines) { line = line.trim(); - + // Skip empty lines and comments if (line.isEmpty() || line.startsWith("#")) { continue; } - + // Handle property references if (line.startsWith("+")) { // Add another property @@ -158,16 +158,16 @@ private static String parseUserDefinedProperty(String definition, Set re // Range String startHex = parts[0].trim(); String endHex = parts[1].trim(); - + // Check if they're valid hex strings if (!startHex.matches("[0-9A-Fa-f]+") || !endHex.matches("[0-9A-Fa-f]+")) { throw new IllegalArgumentException("Can't find Unicode property definition \"" + line.trim() + "\" in expansion of " + propertyName); } - + try { long start = Long.parseLong(startHex, 16); long end = Long.parseLong(endHex, 16); - + if (start > 0x10FFFF) { throw new IllegalArgumentException("Code point too large in \"" + line.trim() + "\" in expansion of " + propertyName); } @@ -177,7 +177,7 @@ private static String parseUserDefinedProperty(String definition, Set re if (start > end) { throw new IllegalArgumentException("Illegal range in \"" + line.trim() + "\" in expansion of " + propertyName); } - + resultSet.add((int) start, (int) end); } catch (NumberFormatException e) { throw new IllegalArgumentException("Can't find Unicode property definition \"" + line.trim() + "\" in expansion of " + propertyName); @@ -185,20 +185,20 @@ private static String parseUserDefinedProperty(String definition, Set re } } } - + // Apply intersection if any if (hasIntersection) { resultSet.retainAll(intersectionSet); } - + return resultSet.toPattern(false); } - + /** * Resolves a property reference (like utf8::InHiragana or main::IsMyProp). * - * @param propRef The property reference - * @param recursionSet Set to track recursive property calls + * @param propRef The property reference + * @param recursionSet Set to track recursive property calls * @param parentProperty The parent property name (for error messages) * @return A character class pattern */ @@ -219,7 +219,7 @@ private static String resolvePropertyReference(String propRef, Set recur chain.append(propRef); throw new IllegalArgumentException("Infinite recursion in user-defined property \"" + propRef + "\" in expansion of " + chain); } - + // Remove utf8:: prefix if present if (propRef.startsWith("utf8::")) { String stdProp = propRef.substring(6); @@ -231,15 +231,15 @@ private static String resolvePropertyReference(String propRef, Set recur propRef = "main::" + stdProp; } } - + // Try as user-defined property return translateUnicodeProperty(propRef, false, recursionSet); } - + /** * Tries to look up a user-defined property by calling a Perl subroutine. * - * @param property The property name (e.g., "IsMyUpper" or "main::IsMyUpper") + * @param property The property name (e.g., "IsMyUpper" or "main::IsMyUpper") * @param recursionSet Set to track recursive property calls * @return The property definition string, or null if not found */ @@ -247,34 +247,34 @@ private static String tryUserDefinedProperty(String property, Set recurs // Add to recursion set Set newRecursionSet = new HashSet<>(recursionSet); newRecursionSet.add(property); - + // Build the full subroutine name String subName = property; if (!subName.contains("::")) { // Try in main package subName = "main::" + subName; } - + // Look up the subroutine RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(subName); if (codeRef == null || !codeRef.getDefinedBoolean()) { return null; } - + try { // Call the subroutine with an empty argument list RuntimeArray args = new RuntimeArray(); RuntimeList result = RuntimeCode.apply(codeRef, args, RuntimeContextType.SCALAR); - + if (result.elements.isEmpty()) { return ""; } - + String definition = result.elements.getFirst().toString(); - + // Parse and return the property definition return parseUserDefinedProperty(definition, newRecursionSet, subName); - + } catch (PerlCompilerException e) { // Re-throw Perl exceptions (like die in IsDeath) String msg = e.getMessage(); @@ -294,7 +294,7 @@ private static String tryUserDefinedProperty(String property, Set recurs public static String translateUnicodeProperty(String property, boolean negated) { return translateUnicodeProperty(property, negated, new HashSet<>()); } - + private static String translateUnicodeProperty(String property, boolean negated, Set recursionSet) { try { // Check for user-defined properties (Is... or In...) @@ -305,7 +305,7 @@ private static String translateUnicodeProperty(String property, boolean negated, } // Property not found - fall through to throw error below } - + // Special cases - Perl XPosix properties not natively supported in Java switch (property) { case "lb=cr": @@ -388,7 +388,7 @@ private static String translateUnicodeProperty(String property, boolean negated, break; } } - + // Map Perl block aliases to Unicode block names if (property.equalsIgnoreCase("ASCII")) { property = "Basic_Latin"; diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java b/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java index ecef360de..2760ca8df 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java @@ -80,7 +80,7 @@ public record CallerInfo(String packageName, String filename, int line) { } @Override - public String toString() { + public String toString () { return String.format("(%s, %s, %d)", packageName, filename, line); } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowMarker.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowMarker.java index 81771ffb2..1735720d6 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowMarker.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowMarker.java @@ -5,30 +5,42 @@ * This is attached to RuntimeList objects to indicate they represent a control flow action. */ public class ControlFlowMarker { - /** The type of control flow (LAST, NEXT, REDO, GOTO, TAILCALL) */ + /** + * The type of control flow (LAST, NEXT, REDO, GOTO, TAILCALL) + */ public final ControlFlowType type; - - /** The label for LAST/NEXT/REDO/GOTO (null for unlabeled control flow) */ + + /** + * The label for LAST/NEXT/REDO/GOTO (null for unlabeled control flow) + */ public final String label; - - /** The code reference for TAILCALL (goto &NAME) */ + + /** + * The code reference for TAILCALL (goto &NAME) + */ public final RuntimeScalar codeRef; - - /** The arguments for TAILCALL (goto &NAME) */ + + /** + * The arguments for TAILCALL (goto &NAME) + */ public final RuntimeArray args; - - /** Source file name where the control flow originated (for error messages) */ + + /** + * Source file name where the control flow originated (for error messages) + */ public final String fileName; - - /** Line number where the control flow originated (for error messages) */ + + /** + * Line number where the control flow originated (for error messages) + */ public final int lineNumber; - + /** * Constructor for control flow (last/next/redo/goto). - * - * @param type The control flow type - * @param label The label to jump to (null for unlabeled) - * @param fileName Source file name (for error messages) + * + * @param type The control flow type + * @param label The label to jump to (null for unlabeled) + * @param fileName Source file name (for error messages) * @param lineNumber Line number (for error messages) */ public ControlFlowMarker(ControlFlowType type, String label, String fileName, int lineNumber) { @@ -39,13 +51,13 @@ public ControlFlowMarker(ControlFlowType type, String label, String fileName, in this.codeRef = null; this.args = null; } - + /** * Constructor for tail call (goto &NAME). - * - * @param codeRef The code reference to call - * @param args The arguments to pass - * @param fileName Source file name (for error messages) + * + * @param codeRef The code reference to call + * @param args The arguments to pass + * @param fileName Source file name (for error messages) * @param lineNumber Line number (for error messages) */ public ControlFlowMarker(RuntimeScalar codeRef, RuntimeArray args, String fileName, int lineNumber) { @@ -56,27 +68,27 @@ public ControlFlowMarker(RuntimeScalar codeRef, RuntimeArray args, String fileNa this.codeRef = codeRef; this.args = args; } - + /** * Debug method to print control flow information. */ public void debugPrint(String context) { - System.err.println("[DEBUG] " + context + ": type=" + type + - ", label=" + label + - ", codeRef=" + (codeRef != null ? codeRef : "null") + - ", args=" + (args != null ? args.size() : "null") + - " @ " + fileName + ":" + lineNumber); + System.err.println("[DEBUG] " + context + ": type=" + type + + ", label=" + label + + ", codeRef=" + (codeRef != null ? codeRef : "null") + + ", args=" + (args != null ? args.size() : "null") + + " @ " + fileName + ":" + lineNumber); } - + /** * Throws an appropriate PerlCompilerException for this control flow that couldn't be handled. * Includes source file and line number in the error message. - * + * * @throws PerlCompilerException Always throws with contextual error message */ public String buildErrorMessage() { String location = " at " + fileName + " line " + lineNumber; - + if (type == ControlFlowType.TAILCALL) { // Tail call should have been handled by trampoline at returnLabel return "Tail call escaped to top level (internal error)" + location; diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowType.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowType.java index 0fba9bdf4..48a86ac92 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowType.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ControlFlowType.java @@ -5,19 +5,29 @@ * Used to mark RuntimeList objects with control flow information. */ public enum ControlFlowType { - /** Exit loop immediately (like C's break) */ + /** + * Exit loop immediately (like C's break) + */ LAST, - - /** Start next iteration of loop (like C's continue) */ + + /** + * Start next iteration of loop (like C's continue) + */ NEXT, - - /** Restart loop block without re-evaluating conditional */ + + /** + * Restart loop block without re-evaluating conditional + */ REDO, - - /** Jump to a labeled statement */ + + /** + * Jump to a labeled statement + */ GOTO, - - /** Tail call optimization for goto &NAME */ + + /** + * Tail call optimization for goto &NAME + */ TAILCALL } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java index 7404e7749..da29893d6 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java @@ -7,7 +7,7 @@ public record DualVar(RuntimeScalar numericValue, RuntimeScalar stringValue) { @Override - public String toString() { + public String toString () { return stringValue.toString(); } } \ No newline at end of file diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java index 55823abaa..b4ae4542c 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java @@ -2,7 +2,6 @@ import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; -import org.perlonjava.frontend.parser.TokenUtils; import java.util.ArrayList; import java.util.List; @@ -19,9 +18,6 @@ public class ErrorMessageUtil { private int tokenIndex; private int lastLineNumber; - public record SourceLocation(String fileName, int lineNumber) { - } - /** * Constructs an ErrorMessageUtil with the specified file name and list of tokens. * @@ -182,6 +178,9 @@ public static String stringifyException(Throwable t, int skipLevels) { return sb.toString(); } + public record SourceLocation(String fileName, int lineNumber) { + } + public String getFileName() { return fileName; } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java index a57c306eb..f9e2fc33f 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java @@ -1,7 +1,7 @@ package org.perlonjava.runtime.runtimetypes; -import org.perlonjava.backend.jvm.ByteCodeSourceMapper; import org.perlonjava.backend.bytecode.InterpreterState; +import org.perlonjava.backend.jvm.ByteCodeSourceMapper; import java.util.ArrayList; import java.util.HashMap; @@ -74,7 +74,7 @@ private static ArrayList> formatThrowable(Throwable t) { callerStackIndex++; } } else if (element.getClassName().equals("org.perlonjava.backend.bytecode.BytecodeInterpreter") && - element.getMethodName().equals("execute")) { + element.getMethodName().equals("execute")) { // Consume the next interpreter frame in order. // Using current() always returned the same topmost frame; consuming // in order correctly maps each JVM execute() frame to its Perl level. @@ -128,12 +128,12 @@ private static ArrayList> formatThrowable(Throwable t) { if (loc != null) { // Get subroutine name from the source location (now preserved in bytecode metadata) String subName = loc.subroutineName(); - + // Prepend package name if subroutine name doesn't already include it if (subName != null && !subName.isEmpty() && !subName.contains("::")) { subName = loc.packageName() + "::" + subName; } - + var entry = new ArrayList(); entry.add(loc.packageName()); entry.add(loc.sourceFileName()); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java index 0d03ad249..fa695fcd2 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java @@ -48,7 +48,7 @@ public static void initializeGlobals(CompilerOptions compilerOptions) { GlobalVariable.globalVariables.put(encodeSpecialVar("N"), new RuntimeScalarReadOnly()); GlobalVariable.getGlobalVariable("main::" + Character.toString('O' - 'A' + 1)).set(SystemUtils.getPerlOsName()); // initialize $^O GlobalVariable.getGlobalVariable("main::" + Character.toString('V' - 'A' + 1)).set(Configuration.getPerlVersionVString()); // initialize $^V - GlobalVariable.getGlobalVariable("main::" + Character.toString('T' - 'A' + 1)).set((int)(System.currentTimeMillis() / 1000)); // initialize $^T to epoch time + GlobalVariable.getGlobalVariable("main::" + Character.toString('T' - 'A' + 1)).set((int) (System.currentTimeMillis() / 1000)); // initialize $^T to epoch time // Initialize $^X - the name used to execute the current copy of Perl // PERLONJAVA_EXECUTABLE is set by the `jperl` or `jperl.bat` launcher diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java index 5d45ce999..0a287357d 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java @@ -1,9 +1,9 @@ package org.perlonjava.runtime.runtimetypes; -import org.perlonjava.backend.jvm.CustomClassLoader; import org.perlonjava.backend.jvm.ByteCodeSourceMapper; -import org.perlonjava.runtime.mro.InheritanceResolver; +import org.perlonjava.backend.jvm.CustomClassLoader; import org.perlonjava.frontend.parser.ParserTables; +import org.perlonjava.runtime.mro.InheritanceResolver; import java.util.HashMap; import java.util.Map; @@ -47,7 +47,7 @@ public class GlobalVariable { // Global class loader for all generated classes - not final so we can replace it public static CustomClassLoader globalClassLoader = new CustomClassLoader(GlobalVariable.class.getClassLoader()); - + // Regular expression for regex variables like $main::1 static Pattern regexVariablePattern = Pattern.compile("^main::(\\d+)$"); @@ -322,11 +322,11 @@ public static RuntimeScalar existsGlobalCodeRefAsScalar(RuntimeScalar key, Strin public static RuntimeScalar definedGlobalCodeRefAsScalar(String key) { // For defined(&{string}) patterns, check actual subroutine existence to match standard Perl // Standard Perl: defined(&{existing}) = true, defined(&{nonexistent}) = false - + // Check if it's a built-in operator // Built-ins are ONLY accessible via CORE:: prefix int lastColonIndex = key.lastIndexOf("::"); - + if (lastColonIndex > 0) { String packageName = key.substring(0, lastColonIndex); String operatorName = key.substring(lastColonIndex + 2); @@ -335,7 +335,7 @@ public static RuntimeScalar definedGlobalCodeRefAsScalar(String key) { return scalarTrue; } } - + RuntimeScalar var = globalCodeRefs.get(key); if (var != null && var.type == RuntimeScalarType.CODE && var.value instanceof RuntimeCode runtimeCode) { return runtimeCode.defined() ? scalarTrue : scalarFalse; @@ -350,7 +350,7 @@ public static RuntimeScalar definedGlobalCodeRefAsScalar(RuntimeScalar key) { public static RuntimeScalar definedGlobalCodeRefAsScalar(RuntimeScalar key, String packageName) { // Use proper package name resolution like createCodeReference String name = NameNormalizer.normalizeVariableName(key.toString(), packageName); - + // Built-ins are ONLY accessible via CORE:: prefix, not from main:: or other packages // So just delegate to the main method which checks for CORE:: prefix return definedGlobalCodeRefAsScalar(name); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java index 02078f643..aeeb64b64 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java @@ -1,7 +1,7 @@ package org.perlonjava.runtime.runtimetypes; +import org.perlonjava.runtime.mro.InheritanceResolver; import org.perlonjava.runtime.regex.RuntimeRegex; - import org.perlonjava.runtime.mro.InheritanceResolver; import java.util.AbstractMap; import java.util.HashSet; diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/NameNormalizer.java b/src/main/java/org/perlonjava/runtime/runtimetypes/NameNormalizer.java index 3ad967a75..7af572976 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/NameNormalizer.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/NameNormalizer.java @@ -12,12 +12,6 @@ * retrieval of previously normalized names and blessed IDs. */ public class NameNormalizer { - /** - * Composite key for name cache to avoid string concatenation overhead. - * Using a record provides efficient hashCode/equals with no allocation. - */ - private record CacheKey(String packageName, String variable) {} - // Cache to store previously normalized variables for faster lookup // Using composite key avoids ~12ns string concatenation per lookup private static final Map nameCache = new HashMap<>(); @@ -75,7 +69,7 @@ private static boolean hasOverloadMarker(String className) { // normalizeVariableName (not getBlessId) try { RuntimeScalar method = InheritanceResolver.findMethodInHierarchy( - "((", className, null, 0); + "((", className, null, 0); return method != null; } catch (Exception e) { // If we can't check (e.g., during early initialization), assume no overload @@ -188,4 +182,11 @@ public static String moduleToFilename(String moduleName) { // Replace '::' with '/' and append '.pm' return moduleName.replace("::", "/") + ".pm"; } + + /** + * Composite key for name cache to avoid string concatenation overhead. + * Using a record provides efficient hashCode/equals with no allocation. + */ + private record CacheKey(String packageName, String variable) { + } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java index 8c383fa55..f9fab2ba0 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java @@ -7,9 +7,6 @@ */ public class OutputAutoFlushVariable extends RuntimeScalar { - private record State(RuntimeIO handle, boolean autoFlush) { - } - private static final Stack stateStack = new Stack<>(); private static RuntimeIO currentHandle() { @@ -17,6 +14,9 @@ private static RuntimeIO currentHandle() { return handle != null ? handle : RuntimeIO.stdout; } + private record State(RuntimeIO handle, boolean autoFlush) { + } + @Override public RuntimeScalar set(RuntimeScalar value) { RuntimeIO handle = currentHandle(); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/Overload.java b/src/main/java/org/perlonjava/runtime/runtimetypes/Overload.java index 4176c93ca..90a183a40 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/Overload.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/Overload.java @@ -72,7 +72,7 @@ public static RuntimeScalar stringify(RuntimeScalar runtimeScalar) { public static RuntimeScalar numify(RuntimeScalar runtimeScalar) { // Prepare overload context and check if object is eligible for overloading int blessId = RuntimeScalarType.blessedId(runtimeScalar); - + if (TRACE_OVERLOAD) { System.err.println("TRACE Overload.numify:"); System.err.println(" Input scalar: " + runtimeScalar); @@ -83,54 +83,54 @@ public static RuntimeScalar numify(RuntimeScalar runtimeScalar) { } System.err.flush(); } - + if (blessId < 0) { OverloadContext ctx = OverloadContext.prepare(blessId); - + if (TRACE_OVERLOAD) { System.err.println(" OverloadContext: " + (ctx != null ? "FOUND" : "NULL")); System.err.flush(); } - + if (ctx != null) { // Try primary overload method RuntimeScalar result = ctx.tryOverload("(0+", new RuntimeArray(runtimeScalar)); - + if (TRACE_OVERLOAD) { System.err.println(" tryOverload (0+: " + (result != null ? result : "NULL")); System.err.flush(); } - + if (result != null) return result; // Try fallback result = ctx.tryOverloadFallback(runtimeScalar, "(\"\"", "(bool"); - + if (TRACE_OVERLOAD) { System.err.println(" tryOverloadFallback: " + (result != null ? result : "NULL")); System.err.flush(); } - + if (result != null) return result; // Try nomethod result = ctx.tryOverloadNomethod(runtimeScalar, "0+"); - + if (TRACE_OVERLOAD) { System.err.println(" tryOverloadNomethod: " + (result != null ? result : "NULL")); System.err.flush(); } - + if (result != null) return result; } } // Default number conversion for non-blessed or non-overloaded objects RuntimeScalar defaultResult = new RuntimeScalar(((RuntimeBase) runtimeScalar.value).getDoubleRef()); - + if (TRACE_OVERLOAD) { System.err.println(" Returning DEFAULT hash code: " + defaultResult); System.err.flush(); } - + return defaultResult; } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/OverloadContext.java b/src/main/java/org/perlonjava/runtime/runtimetypes/OverloadContext.java index d77cf5931..c786d4c95 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/OverloadContext.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/OverloadContext.java @@ -10,7 +10,7 @@ /** * Helper class to manage overloading context for a given scalar in Perl-style object system. * Handles method overloading and fallback mechanisms for blessed objects. - * + * *

    How Perl Overloading Works: *

      *
    • Classes use {@code use overload} to define operator overloads
    • @@ -32,7 +32,7 @@ * * *
    - * + * *

    Math::BigInt Example: *

      * package Math::BigInt;
    @@ -43,14 +43,14 @@
      *     ;
      * # The overload pragma also creates (( and () markers
      * 
    - * + * * @see InheritanceResolver#findMethodInHierarchy * @see Overload#numify * @see Overload#stringify */ public class OverloadContext { private static final boolean TRACE_OVERLOAD_CONTEXT = false; - + /** * The Perl class name of the blessed object */ diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlDieException.java b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlDieException.java index 4d16d1620..687f514de 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlDieException.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlDieException.java @@ -4,7 +4,7 @@ /** * Exception used to implement Perl's die semantics. - * + *

    * This carries the original die payload (string or reference) so eval can * propagate it into $@ without stringifying. */ diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRange.java b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRange.java index 99f22ca8a..cf897866a 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRange.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRange.java @@ -28,7 +28,7 @@ public PerlRange(RuntimeScalar start, RuntimeScalar end) { // We do this by checking if the scalar is a proxy and creating a proper copy. RuntimeScalar evalStart = start; RuntimeScalar evalEnd = end; - + // Force evaluation of special variables by creating new RuntimeScalar with the actual value // But only if they're defined - undef special variables should stay undef if (start instanceof RuntimeBaseProxy) { @@ -49,7 +49,7 @@ public PerlRange(RuntimeScalar start, RuntimeScalar end) { evalEnd = new RuntimeScalar(); } } - + // Handle undef values: treat as 0 for numeric context or "" for string context // We'll determine context based on the other operand or default to numeric if (evalStart.type == RuntimeScalarType.UNDEF) { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlSignalQueue.java b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlSignalQueue.java index 5aa28775e..369b33bb7 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlSignalQueue.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlSignalQueue.java @@ -10,7 +10,7 @@ */ public class PerlSignalQueue { private static final Queue signalQueue = new ConcurrentLinkedQueue<>(); - + // Volatile flag for ultra-fast signal checking in hot paths // Reading a volatile boolean is ~2 CPU cycles, much faster than queue.isEmpty() private static volatile boolean hasPendingSignal = false; @@ -47,7 +47,7 @@ public static void checkPendingSignals() { public static void processSignals() { processSignalsImpl(); } - + /** * Internal implementation of signal processing. */ diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java index 04fec7318..36e90f840 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java @@ -98,7 +98,7 @@ public static RuntimeScalar pop(RuntimeArray runtimeArray) { } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield pop(runtimeArray); // Recursive call after vivification + yield pop (runtimeArray); // Recursive call after vivification } case TIED_ARRAY -> TieArray.tiedPop(runtimeArray); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -122,7 +122,7 @@ public static RuntimeScalar shift(RuntimeArray runtimeArray) { } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield shift(runtimeArray); // Recursive call after vivification + yield shift (runtimeArray); // Recursive call after vivification } case TIED_ARRAY -> TieArray.tiedShift(runtimeArray); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -154,11 +154,11 @@ public static RuntimeScalar push(RuntimeArray runtimeArray, RuntimeBase value) { return switch (runtimeArray.type) { case PLAIN_ARRAY -> { value.addToArray(runtimeArray); - yield getScalarInt(runtimeArray.elements.size()); + yield getScalarInt (runtimeArray.elements.size()); } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield push(runtimeArray, value); + yield push (runtimeArray, value) } case TIED_ARRAY -> TieArray.tiedPush(runtimeArray, value); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -180,11 +180,11 @@ public static RuntimeScalar unshift(RuntimeArray runtimeArray, RuntimeBase value RuntimeArray arr = new RuntimeArray(); RuntimeArray.push(arr, value); runtimeArray.elements.addAll(0, arr.elements); - yield getScalarInt(runtimeArray.elements.size()); + yield getScalarInt (runtimeArray.elements.size()); } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield unshift(runtimeArray, value); + yield unshift (runtimeArray, value) } case TIED_ARRAY -> TieArray.tiedUnshift(runtimeArray, value); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -214,10 +214,10 @@ public void addToArray(RuntimeArray array) { } List targetElements = array.elements; - + // If pushing array onto itself, make a copy to avoid ConcurrentModificationException - List sourceElements = (this == array) ? - new ArrayList<>(this.elements) : this.elements; + List sourceElements = (this == array) ? + new ArrayList<>(this.elements) : this.elements; for (RuntimeScalar arrElem : sourceElements) { if (arrElem == null) { @@ -300,7 +300,7 @@ public RuntimeScalar exists(int index) { } // Check if the element at index is null RuntimeScalar element = elements.get(index); - yield (element == null) ? scalarFalse : scalarTrue; + yield(element == null) ? scalarFalse : scalarTrue; } case AUTOVIVIFY_ARRAY -> scalarFalse; case TIED_ARRAY -> TieArray.tiedExists(this, getScalarInt(index)); @@ -312,7 +312,7 @@ public RuntimeScalar exists(int index) { yield scalarFalse; } RuntimeScalar element = elements.get(index); - yield (element == null) ? scalarFalse : scalarTrue; + yield(element == null) ? scalarFalse : scalarTrue; } default -> throw new IllegalStateException("Unknown array type: " + type); }; @@ -338,7 +338,7 @@ public RuntimeScalar delete(int index) { yield scalarUndef; } if (index == elements.size() - 1) { - yield pop(this); + yield pop (this); } RuntimeScalar previous = this.get(index); this.elements.set(index, null); @@ -508,7 +508,7 @@ public RuntimeArray setFromList(RuntimeList list) { this.elements.clear(); list.addToArray(this); } - + // Create a new array with scalarContextSize set for assignment return value // This is needed for eval context where assignment should return element count RuntimeArray result = new RuntimeArray(); @@ -592,7 +592,7 @@ public RuntimeList getList() { if (this.scalarContextSize != null) { return new RuntimeList(this); } - + // Otherwise, copy all elements to ensure independence from the original array // This is important for returning local arrays from functions RuntimeList result = new RuntimeList(); @@ -612,22 +612,22 @@ public RuntimeScalar scalar() { case PLAIN_ARRAY -> { // If this array was created from hash assignment, use the original list size if (scalarContextSize != null) { - yield getScalarInt(scalarContextSize); + yield getScalarInt (scalarContextSize); } - yield getScalarInt(elements.size()); + yield getScalarInt (elements.size()); } case AUTOVIVIFY_ARRAY -> { if (this.strictAutovivify) { throw new PerlCompilerException("Can't use an undefined value as an ARRAY reference"); } - yield getScalarInt(0); + yield getScalarInt (0); } case TIED_ARRAY -> TieArray.tiedFetchSize(this); case READONLY_ARRAY -> { if (scalarContextSize != null) { - yield getScalarInt(scalarContextSize); + yield getScalarInt (scalarContextSize); } - yield getScalarInt(elements.size()); + yield getScalarInt (elements.size()); } default -> throw new IllegalStateException("Unknown array type: " + type); }; @@ -641,7 +641,7 @@ public int lastElementIndex() { if (this.strictAutovivify) { throw new PerlCompilerException("Can't use an undefined value as an ARRAY reference"); } - yield -1; + yield - 1; } case TIED_ARRAY -> TieArray.tiedFetchSize(this).getInt() - 1; case READONLY_ARRAY -> elements.size() - 1; @@ -702,7 +702,7 @@ public RuntimeList getSlice(RuntimeList value) { * Sets a slice of the array. * * @param indices A RuntimeList containing the indices to set. - * @param values A RuntimeList containing the values to set at those indices. + * @param values A RuntimeList containing the values to set at those indices. */ public void setSlice(RuntimeList indices, RuntimeList values) { if (this.type == AUTOVIVIFY_ARRAY) { @@ -729,7 +729,7 @@ public void setSlice(RuntimeList indices, RuntimeList values) { public RuntimeArray keys() { // Reset the each iterator when keys() is called this.eachIteratorIndex = null; - + int count = this.countElements(); if (count == 0) { RuntimeArray empty = new RuntimeArray(); @@ -928,7 +928,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); for (RuntimeBase element : elements) { if (element != null) { - sb.append(element.toString()); + sb.append(element); } } return sb.toString(); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java index 09905d6b9..bc7c7e594 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java @@ -1,22 +1,22 @@ package org.perlonjava.runtime.runtimetypes; import org.perlonjava.app.cli.CompilerOptions; -import org.perlonjava.frontend.astnode.Node; -import org.perlonjava.frontend.astnode.OperatorNode; +import org.perlonjava.backend.bytecode.BytecodeCompiler; +import org.perlonjava.backend.bytecode.InterpretedCode; +import org.perlonjava.backend.bytecode.InterpreterState; import org.perlonjava.backend.jvm.EmitterContext; import org.perlonjava.backend.jvm.EmitterMethodCreator; import org.perlonjava.backend.jvm.JavaClassInfo; +import org.perlonjava.frontend.astnode.Node; +import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.frontend.lexer.Lexer; import org.perlonjava.frontend.lexer.LexerToken; -import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.frontend.parser.Parser; -import org.perlonjava.runtime.mro.InheritanceResolver; -import org.perlonjava.runtime.operators.ModuleOperators; import org.perlonjava.frontend.semantic.ScopedSymbolTable; import org.perlonjava.frontend.semantic.SymbolTable; -import org.perlonjava.backend.bytecode.BytecodeCompiler; -import org.perlonjava.backend.bytecode.InterpretedCode; -import org.perlonjava.backend.bytecode.InterpreterState; +import org.perlonjava.runtime.mro.InheritanceResolver; +import org.perlonjava.runtime.operators.ModuleOperators; +import org.perlonjava.runtime.operators.WarnDie; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -27,10 +27,10 @@ import java.util.function.Supplier; import static org.perlonjava.frontend.parser.ParserTables.CORE_PROTOTYPES; -import static org.perlonjava.runtime.runtimetypes.GlobalVariable.*; +import static org.perlonjava.frontend.parser.SpecialBlockParser.setCurrentScope; +import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalVariable; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.*; -import static org.perlonjava.frontend.parser.SpecialBlockParser.setCurrentScope; import static org.perlonjava.runtime.runtimetypes.SpecialBlock.runUnitcheckBlocks; /** @@ -48,7 +48,7 @@ public class RuntimeCode extends RuntimeBase implements RuntimeScalarReference { * Flag to control whether eval STRING should use the interpreter backend. * Enabled by default. eval STRING compiles to InterpretedCode instead of generating JVM bytecode. * This provides 46x faster compilation for workloads with many unique eval strings. - * + *

    * Set environment variable JPERL_EVAL_NO_INTERPRETER=1 to disable. */ public static final boolean EVAL_USE_INTERPRETER = @@ -57,7 +57,7 @@ public class RuntimeCode extends RuntimeBase implements RuntimeScalarReference { /** * Flag to control whether eval compilation errors should be printed to stderr. * By default, eval failures are silent (errors only stored in $@). - * + *

    * Set environment variable JPERL_EVAL_VERBOSE=1 to enable verbose error reporting. * This is useful for debugging eval compilation issues, especially when testing * the interpreter path. @@ -67,110 +67,30 @@ public class RuntimeCode extends RuntimeBase implements RuntimeScalarReference { public static final boolean EVAL_TRACE = System.getenv("JPERL_EVAL_TRACE") != null; - - private static void evalTrace(String msg) { - if (EVAL_TRACE) { - System.err.println("[eval-trace] " + msg); - } - } - - /** - * Flag to enable disassembly of eval STRING bytecode. - * When set, prints the interpreter bytecode for each eval STRING compilation. - * - * Set environment variable JPERL_DISASSEMBLE=1 to enable, or use --disassemble CLI flag. - * The --disassemble flag sets this via setDisassemble(). - */ - public static boolean DISASSEMBLE = - System.getenv("JPERL_DISASSEMBLE") != null; - - /** Called by CLI argument parser when --disassemble is set. */ - public static void setDisassemble(boolean value) { - DISASSEMBLE = value; - } - - public static boolean USE_INTERPRETER = - System.getenv("JPERL_INTERPRETER") != null; - - public static void setUseInterpreter(boolean value) { - USE_INTERPRETER = value; - } - /** * ThreadLocal storage for runtime values of captured variables during eval STRING compilation. - * + *

    * PROBLEM: In perl5, BEGIN blocks inside eval STRING can access outer lexical variables' runtime values: - * my @imports = qw(a b); - * eval q{ BEGIN { say @imports } }; # perl5 prints: a b - * + * my @imports = qw(a b); + * eval q{ BEGIN { say @imports } }; # perl5 prints: a b + *

    * In PerlOnJava, BEGIN blocks execute during parsing (before the eval class is instantiated), * so they couldn't access runtime values - they would see empty variables. - * + *

    * SOLUTION: When evalStringHelper() is called, the runtime values are stored in this ThreadLocal. * During parsing, when SpecialBlockParser sets up BEGIN blocks, it can access these runtime values * and use them to initialize the special globals that lexical variables become in BEGIN blocks. - * + *

    * This ThreadLocal stores: * - Key: The evalTag identifying this eval compilation * - Value: EvalRuntimeContext containing: - * - runtimeValues: Object[] of captured variable values - * - capturedEnv: String[] of captured variable names (matching array indices) - * + * - runtimeValues: Object[] of captured variable values + * - capturedEnv: String[] of captured variable names (matching array indices) + *

    * Thread-safety: Each thread's eval compilation uses its own ThreadLocal storage, so parallel * eval compilations don't interfere with each other. */ private static final ThreadLocal evalRuntimeContext = new ThreadLocal<>(); - - /** - * Container for runtime context during eval STRING compilation. - * Holds both the runtime values and variable names so SpecialBlockParser can - * match variables to their values. - */ - public static class EvalRuntimeContext { - public final Object[] runtimeValues; - public final String[] capturedEnv; - public final String evalTag; - - public EvalRuntimeContext(Object[] runtimeValues, String[] capturedEnv, String evalTag) { - this.runtimeValues = runtimeValues; - this.capturedEnv = capturedEnv; - this.evalTag = evalTag; - } - - /** - * Get the runtime value for a variable by name. - * - * IMPORTANT: The capturedEnv array includes all variables (including 'this', '@_', 'wantarray'), - * but runtimeValues array skips the first skipVariables (currently 3). - * So if @imports is at capturedEnv[5], its value is at runtimeValues[5-3=2]. - * - * @param varName The variable name (e.g., "@imports", "$scalar") - * @return The runtime value, or null if not found - */ - public Object getRuntimeValue(String varName) { - int skipVariables = 3; // 'this', '@_', 'wantarray' - for (int i = skipVariables; i < capturedEnv.length; i++) { - if (varName.equals(capturedEnv[i])) { - int runtimeIndex = i - skipVariables; - if (runtimeIndex >= 0 && runtimeIndex < runtimeValues.length) { - return runtimeValues[runtimeIndex]; - } - } - } - return null; - } - } - - /** - * Get the current eval runtime context for accessing variable runtime values during parsing. - * This is called by SpecialBlockParser when setting up BEGIN blocks. - * - * @return The current eval runtime context, or null if not in eval STRING compilation - */ - public static EvalRuntimeContext getEvalRuntimeContext() { - return evalRuntimeContext.get(); - } - // Cache for memoization of evalStringHelper results private static final int CLASS_CACHE_SIZE = 100; private static final Map> evalCache = new LinkedHashMap>(CLASS_CACHE_SIZE, 0.75f, true) { @@ -187,23 +107,23 @@ protected boolean removeEldestEntry(Map.Entry, MethodHandle> eldest) { return size() > METHOD_HANDLE_CACHE_SIZE; } }; + /** + * Flag to enable disassembly of eval STRING bytecode. + * When set, prints the interpreter bytecode for each eval STRING compilation. + *

    + * Set environment variable JPERL_DISASSEMBLE=1 to enable, or use --disassemble CLI flag. + * The --disassemble flag sets this via setDisassemble(). + */ + public static boolean DISASSEMBLE = + System.getenv("JPERL_DISASSEMBLE") != null; + public static boolean USE_INTERPRETER = + System.getenv("JPERL_INTERPRETER") != null; public static MethodType methodType = MethodType.methodType(RuntimeList.class, RuntimeArray.class, int.class); // Temporary storage for anonymous subroutines and eval string compiler context public static HashMap> anonSubs = new HashMap<>(); // temp storage for makeCodeObject() public static HashMap evalContext = new HashMap<>(); // storage for eval string compiler context // Runtime eval counter for generating unique filenames when $^P is set private static int runtimeEvalCounter = 1; - - /** - * Gets the next eval sequence number and generates a filename. - * Used by both baseline compiler and interpreter for consistent naming. - * - * @return Filename like "(eval 1)", "(eval 2)", etc. - */ - public static synchronized String getNextEvalFilename() { - return "(eval " + runtimeEvalCounter++ + ")"; - } - // Method object representing the compiled subroutine public MethodHandle methodHandle; public boolean isStatic; @@ -231,7 +151,6 @@ public static synchronized String getNextEvalFilename() { public RuntimeList constantValue; // Field to hold the thread compiling this code public Supplier compilerSupplier; - /** * Constructs a RuntimeCode instance with the specified prototype and attributes. * @@ -242,13 +161,49 @@ public RuntimeCode(String prototype, List attributes) { this.prototype = prototype; this.attributes = attributes; } - public RuntimeCode(MethodHandle methodObject, Object codeObject, String prototype) { this.methodHandle = methodObject; this.codeObject = codeObject; this.prototype = prototype; } + private static void evalTrace(String msg) { + if (EVAL_TRACE) { + System.err.println("[eval-trace] " + msg); + } + } + + /** + * Called by CLI argument parser when --disassemble is set. + */ + public static void setDisassemble(boolean value) { + DISASSEMBLE = value; + } + + public static void setUseInterpreter(boolean value) { + USE_INTERPRETER = value; + } + + /** + * Get the current eval runtime context for accessing variable runtime values during parsing. + * This is called by SpecialBlockParser when setting up BEGIN blocks. + * + * @return The current eval runtime context, or null if not in eval STRING compilation + */ + public static EvalRuntimeContext getEvalRuntimeContext() { + return evalRuntimeContext.get(); + } + + /** + * Gets the next eval sequence number and generates a filename. + * Used by both baseline compiler and interpreter for consistent naming. + * + * @return Filename like "(eval 1)", "(eval 2)", etc. + */ + public static synchronized String getNextEvalFilename() { + return "(eval " + runtimeEvalCounter++ + ")"; + } + // Add a method to clear caches when globals are reset public static void clearCaches() { evalCache.clear(); @@ -284,18 +239,18 @@ public static Class evalStringHelper(RuntimeScalar code, String evalTag) thro * After the Class is returned to the caller, an instance of the Class will be populated * with closure variables, and then makeCodeObject() will be called to transform the Class * instance into a Perl CODE object. - * + *

    * IMPORTANT CHANGE: This method now accepts runtime values of captured variables. - * + *

    * WHY THIS IS NEEDED: * In perl5, BEGIN blocks inside eval STRING can access outer lexical variables' runtime values. * For example: - * my @imports = qw(md5 md5_hex); - * eval q{ use Digest::MD5 @imports }; # BEGIN block sees @imports = (md5 md5_hex) - * + * my @imports = qw(md5 md5_hex); + * eval q{ use Digest::MD5 @imports }; # BEGIN block sees @imports = (md5 md5_hex) + *

    * Previously in PerlOnJava, BEGIN blocks would see empty variables because they execute * during parsing, before the eval class is instantiated with runtime values. - * + *

    * NOW: We pass runtime values to this method and store them in ThreadLocal storage. * SpecialBlockParser can then access these values when setting up BEGIN blocks, * allowing lexical variables to be initialized with their runtime values. @@ -331,279 +286,279 @@ public static Class evalStringHelper(RuntimeScalar code, String evalTag, Obje // If so, treat it as Unicode source to preserve Unicode characters during parsing // EXCEPT for evalbytes, which must treat everything as bytes String evalString = code.toString(); - boolean hasUnicode = false; - if (!ctx.isEvalbytes && code.type != RuntimeScalarType.BYTE_STRING) { - for (int i = 0; i < evalString.length(); i++) { - if (evalString.charAt(i) > 127) { - hasUnicode = true; - break; + boolean hasUnicode = false; + if (!ctx.isEvalbytes && code.type != RuntimeScalarType.BYTE_STRING) { + for (int i = 0; i < evalString.length(); i++) { + if (evalString.charAt(i) > 127) { + hasUnicode = true; + break; + } } } - } - - // Clone compiler options and set isUnicodeSource if needed - // This only affects string parsing, not symbol table or method resolution - CompilerOptions evalCompilerOptions = ctx.compilerOptions; - // The eval string can originate from either a Perl STRING or BYTE_STRING scalar. - // For BYTE_STRING source we must treat the source as raw bytes (latin-1-ish) and - // NOT re-encode characters to UTF-8 when simulating 'non-unicode source'. - boolean isByteStringSource = !ctx.isEvalbytes && code.type == RuntimeScalarType.BYTE_STRING; - if (hasUnicode || ctx.isEvalbytes || isByteStringSource) { - evalCompilerOptions = (CompilerOptions) ctx.compilerOptions.clone(); - if (hasUnicode) { - evalCompilerOptions.isUnicodeSource = true; - } - if (ctx.isEvalbytes) { - evalCompilerOptions.isEvalbytes = true; - } - if (isByteStringSource) { - evalCompilerOptions.isByteStringSource = true; - } - } - // Check $^P to determine if we should use caching - // When debugging is enabled, we want each eval to get a unique filename - int debugFlags = GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("P")).getInt(); - boolean isDebugging = debugFlags != 0; - - // Override the filename with a runtime-generated eval number when debugging - String actualFileName = evalCompilerOptions.fileName; - if (isDebugging) { - actualFileName = getNextEvalFilename(); - } - - // Check if the result is already cached (include hasUnicode, isEvalbytes, byte-string-source, and feature flags in cache key) - // Skip caching when $^P is set, so each eval gets a unique filename - int featureFlags = ctx.symbolTable.featureFlagsStack.peek(); - String cacheKey = code.toString() + '\0' + evalTag + '\0' + hasUnicode + '\0' + ctx.isEvalbytes + '\0' + isByteStringSource + '\0' + featureFlags; - Class cachedClass = null; - if (!isDebugging) { - synchronized (evalCache) { - if (evalCache.containsKey(cacheKey)) { - cachedClass = evalCache.get(cacheKey); + // Clone compiler options and set isUnicodeSource if needed + // This only affects string parsing, not symbol table or method resolution + CompilerOptions evalCompilerOptions = ctx.compilerOptions; + // The eval string can originate from either a Perl STRING or BYTE_STRING scalar. + // For BYTE_STRING source we must treat the source as raw bytes (latin-1-ish) and + // NOT re-encode characters to UTF-8 when simulating 'non-unicode source'. + boolean isByteStringSource = !ctx.isEvalbytes && code.type == RuntimeScalarType.BYTE_STRING; + if (hasUnicode || ctx.isEvalbytes || isByteStringSource) { + evalCompilerOptions = ctx.compilerOptions.clone(); + if (hasUnicode) { + evalCompilerOptions.isUnicodeSource = true; + } + if (ctx.isEvalbytes) { + evalCompilerOptions.isEvalbytes = true; + } + if (isByteStringSource) { + evalCompilerOptions.isByteStringSource = true; } } - - if (cachedClass != null) { - return cachedClass; - } - } - // IMPORTANT: The eval call site (EmitEval) computes the constructor signature from - // ctx.symbolTable (captured at compile-time). We must use that exact symbol table for - // codegen, otherwise the generated (...) descriptor may not match what the - // call site is looking up via reflection. - ScopedSymbolTable capturedSymbolTable = ctx.symbolTable; - - // %^H is the compile-time hints hash. eval STRING must not leak modifications to the - // caller scope, so snapshot and restore it across compilation. - RuntimeHash capturedHintHash = GlobalVariable.getGlobalHash(GlobalContext.encodeSpecialVar("H")); - Map savedHintHash = new HashMap<>(capturedHintHash.elements); - - // eval may include lexical pragmas (use strict/warnings/features). We need those flags - // during codegen of the eval body, but they must NOT leak back into the caller scope. - BitSet savedWarningFlags = (BitSet) capturedSymbolTable.warningFlagsStack.peek().clone(); - int savedFeatureFlags = capturedSymbolTable.featureFlagsStack.peek(); - int savedStrictOptions = capturedSymbolTable.strictOptionsStack.peek(); - - // Parse using a mutable clone so lexical declarations inside the eval do not - // change the captured environment / constructor signature. - // IMPORTANT: The parseSymbolTable starts with the captured flags so that - // the eval code is parsed with the correct feature/strict/warning context - ScopedSymbolTable parseSymbolTable = capturedSymbolTable.snapShot(); - - // CRITICAL: Pre-create aliases for captured variables BEFORE parsing - // This allows BEGIN blocks in the eval string to access outer lexical variables. - // - // When the eval string is parsed, variable references in BEGIN blocks will be - // resolved to these special package globals that we're aliasing now. - // - // Example: my @arr = qw(a b); eval q{ BEGIN { say @arr } }; - // We create: globalArrays["BEGIN_PKG_x::@arr"] = (the runtime @arr object) - // Then when "say @arr" is parsed in the BEGIN, it resolves to BEGIN_PKG_x::@arr - // which is aliased to the runtime array with values (a, b). - List evalAliasKeys = new ArrayList<>(); - Map capturedVars = capturedSymbolTable.getAllVisibleVariables(); - for (SymbolTable.SymbolEntry entry : capturedVars.values()) { - if (!entry.name().equals("@_") && !entry.decl().isEmpty() && !entry.name().startsWith("&")) { - if (!entry.decl().equals("our")) { - // "my" or "state" variables get special BEGIN package globals - Object runtimeValue = runtimeCtx.getRuntimeValue(entry.name()); - if (runtimeValue != null) { - // Get or create the special package ID. - // IMPORTANT: Do NOT mutate the AST node (ast.id) — the AST is - // shared with the JVM compiler and mutation would corrupt `my` - // variable reinitialization in loops. - OperatorNode ast = entry.ast(); - if (ast != null) { - int beginId = evalBeginIds.computeIfAbsent( - ast, - k -> EmitterMethodCreator.classCounter++); - String packageName = PersistentVariable.beginPackage(beginId); - String varNameWithoutSigil = entry.name().substring(1); - String fullName = packageName + "::" + varNameWithoutSigil; - - if (runtimeValue instanceof RuntimeArray) { - GlobalVariable.globalArrays.put(fullName, (RuntimeArray) runtimeValue); - } else if (runtimeValue instanceof RuntimeHash) { - GlobalVariable.globalHashes.put(fullName, (RuntimeHash) runtimeValue); - } else if (runtimeValue instanceof RuntimeScalar) { - GlobalVariable.globalVariables.put(fullName, (RuntimeScalar) runtimeValue); - } - evalAliasKeys.add(entry.name().substring(0, 1) + fullName); - } + // Check $^P to determine if we should use caching + // When debugging is enabled, we want each eval to get a unique filename + int debugFlags = GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("P")).getInt(); + boolean isDebugging = debugFlags != 0; + + // Override the filename with a runtime-generated eval number when debugging + String actualFileName = evalCompilerOptions.fileName; + if (isDebugging) { + actualFileName = getNextEvalFilename(); + } + + // Check if the result is already cached (include hasUnicode, isEvalbytes, byte-string-source, and feature flags in cache key) + // Skip caching when $^P is set, so each eval gets a unique filename + int featureFlags = ctx.symbolTable.featureFlagsStack.peek(); + String cacheKey = code.toString() + '\0' + evalTag + '\0' + hasUnicode + '\0' + ctx.isEvalbytes + '\0' + isByteStringSource + '\0' + featureFlags; + Class cachedClass = null; + if (!isDebugging) { + synchronized (evalCache) { + if (evalCache.containsKey(cacheKey)) { + cachedClass = evalCache.get(cacheKey); } } - } - } - EmitterContext evalCtx = new EmitterContext( - new JavaClassInfo(), // internal java class name - parseSymbolTable, // symbolTable - null, // method visitor - null, // class writer - ctx.contextType, // call context - true, // is boxed - ctx.errorUtil, // error message utility - evalCompilerOptions, // possibly modified for Unicode source - ctx.unitcheckBlocks); - // evalCtx.logDebug("evalStringHelper EmitterContext: " + evalCtx); - // evalCtx.logDebug("evalStringHelper Code: " + code); - - // Process the string source code to create the LexerToken list - Lexer lexer = new Lexer(evalString); - List tokens = lexer.tokenize(); // Tokenize the Perl code - Node ast = null; - Class generatedClass; - try { - // Create the AST - // Create an instance of ErrorMessageUtil with the file name and token list - evalCtx.errorUtil = new ErrorMessageUtil(evalCtx.compilerOptions.fileName, tokens); - Parser parser = new Parser(evalCtx, tokens); // Parse the tokens - ast = parser.parse(); // Generate the abstract syntax tree (AST) - - // ast = ConstantFoldingVisitor.foldConstants(ast); - - // Create a new instance of ErrorMessageUtil, resetting the line counter - evalCtx.errorUtil = new ErrorMessageUtil(ctx.compilerOptions.fileName, tokens); - ScopedSymbolTable postParseSymbolTable = evalCtx.symbolTable; - evalCtx.symbolTable = capturedSymbolTable; - evalCtx.symbolTable.copyFlagsFrom(postParseSymbolTable); - setCurrentScope(evalCtx.symbolTable); - - // Use the captured environment array from compile-time to ensure - // constructor signature matches what EmitEval generated bytecode for - if (ctx.capturedEnv != null) { - evalCtx.capturedEnv = ctx.capturedEnv; + if (cachedClass != null) { + return cachedClass; + } } - - ast.setAnnotation("blockIsSubroutine", true); - generatedClass = EmitterMethodCreator.createClassWithMethod( - evalCtx, - ast, - false // use try-catch - ); - runUnitcheckBlocks(ctx.unitcheckBlocks); - } catch (Throwable e) { - // Compilation error in eval-string - // Set the global error variable "$@" - RuntimeScalar err = GlobalVariable.getGlobalVariable("main::@"); - err.set(e.getMessage()); + // IMPORTANT: The eval call site (EmitEval) computes the constructor signature from + // ctx.symbolTable (captured at compile-time). We must use that exact symbol table for + // codegen, otherwise the generated (...) descriptor may not match what the + // call site is looking up via reflection. + ScopedSymbolTable capturedSymbolTable = ctx.symbolTable; - // If EVAL_VERBOSE is set, print the error to stderr for debugging - if (EVAL_VERBOSE) { - System.err.println("eval compilation error: " + e.getMessage()); - if (e.getCause() != null) { - System.err.println("Caused by: " + e.getCause().getMessage()); - } - } + // %^H is the compile-time hints hash. eval STRING must not leak modifications to the + // caller scope, so snapshot and restore it across compilation. + RuntimeHash capturedHintHash = GlobalVariable.getGlobalHash(GlobalContext.encodeSpecialVar("H")); + Map savedHintHash = new HashMap<>(capturedHintHash.elements); + + // eval may include lexical pragmas (use strict/warnings/features). We need those flags + // during codegen of the eval body, but they must NOT leak back into the caller scope. + BitSet savedWarningFlags = (BitSet) capturedSymbolTable.warningFlagsStack.peek().clone(); + int savedFeatureFlags = capturedSymbolTable.featureFlagsStack.peek(); + int savedStrictOptions = capturedSymbolTable.strictOptionsStack.peek(); + + // Parse using a mutable clone so lexical declarations inside the eval do not + // change the captured environment / constructor signature. + // IMPORTANT: The parseSymbolTable starts with the captured flags so that + // the eval code is parsed with the correct feature/strict/warning context + ScopedSymbolTable parseSymbolTable = capturedSymbolTable.snapShot(); + + // CRITICAL: Pre-create aliases for captured variables BEFORE parsing + // This allows BEGIN blocks in the eval string to access outer lexical variables. + // + // When the eval string is parsed, variable references in BEGIN blocks will be + // resolved to these special package globals that we're aliasing now. + // + // Example: my @arr = qw(a b); eval q{ BEGIN { say @arr } }; + // We create: globalArrays["BEGIN_PKG_x::@arr"] = (the runtime @arr object) + // Then when "say @arr" is parsed in the BEGIN, it resolves to BEGIN_PKG_x::@arr + // which is aliased to the runtime array with values (a, b). + List evalAliasKeys = new ArrayList<>(); + Map capturedVars = capturedSymbolTable.getAllVisibleVariables(); + for (SymbolTable.SymbolEntry entry : capturedVars.values()) { + if (!entry.name().equals("@_") && !entry.decl().isEmpty() && !entry.name().startsWith("&")) { + if (!entry.decl().equals("our")) { + // "my" or "state" variables get special BEGIN package globals + Object runtimeValue = runtimeCtx.getRuntimeValue(entry.name()); + if (runtimeValue != null) { + // Get or create the special package ID. + // IMPORTANT: Do NOT mutate the AST node (ast.id) — the AST is + // shared with the JVM compiler and mutation would corrupt `my` + // variable reinitialization in loops. + OperatorNode ast = entry.ast(); + if (ast != null) { + int beginId = evalBeginIds.computeIfAbsent( + ast, + k -> EmitterMethodCreator.classCounter++); + String packageName = PersistentVariable.beginPackage(beginId); + String varNameWithoutSigil = entry.name().substring(1); + String fullName = packageName + "::" + varNameWithoutSigil; - // Check if $SIG{__DIE__} handler is defined - RuntimeScalar sig = GlobalVariable.getGlobalHash("main::SIG").get("__DIE__"); - if (sig.getDefinedBoolean()) { - // Call the $SIG{__DIE__} handler (similar to what die() does) - RuntimeScalar sigHandler = new RuntimeScalar(sig); - - // Undefine $SIG{__DIE__} before calling to avoid infinite recursion - int level = DynamicVariableManager.getLocalLevel(); - DynamicVariableManager.pushLocalVariable(sig); - - try { - RuntimeArray args = new RuntimeArray(); - RuntimeArray.push(args, new RuntimeScalar(err)); - apply(sigHandler, args, RuntimeContextType.SCALAR); - } catch (Throwable handlerException) { - // If the handler dies, use its payload as the new error - if (handlerException instanceof RuntimeException && handlerException.getCause() instanceof PerlDieException) { - PerlDieException pde = (PerlDieException) handlerException.getCause(); - RuntimeBase handlerPayload = pde.getPayload(); - if (handlerPayload != null) { - err.set(handlerPayload.getFirst()); - } - } else if (handlerException instanceof PerlDieException) { - PerlDieException pde = (PerlDieException) handlerException; - RuntimeBase handlerPayload = pde.getPayload(); - if (handlerPayload != null) { - err.set(handlerPayload.getFirst()); + if (runtimeValue instanceof RuntimeArray) { + GlobalVariable.globalArrays.put(fullName, (RuntimeArray) runtimeValue); + } else if (runtimeValue instanceof RuntimeHash) { + GlobalVariable.globalHashes.put(fullName, (RuntimeHash) runtimeValue); + } else if (runtimeValue instanceof RuntimeScalar) { + GlobalVariable.globalVariables.put(fullName, (RuntimeScalar) runtimeValue); + } + evalAliasKeys.add(entry.name().substring(0, 1) + fullName); + } } } - // If handler throws other exceptions, ignore them (keep original error in $@) - } finally { - // Restore $SIG{__DIE__} - DynamicVariableManager.popToLocalLevel(level); } } - // Return null to signal compilation failure (don't throw exception) - // This prevents the exception from escaping to outer eval blocks - return null; - } finally { - // Restore caller lexical flags (do not leak eval pragmas). - capturedSymbolTable.warningFlagsStack.pop(); - capturedSymbolTable.warningFlagsStack.push((BitSet) savedWarningFlags.clone()); + EmitterContext evalCtx = new EmitterContext( + new JavaClassInfo(), // internal java class name + parseSymbolTable, // symbolTable + null, // method visitor + null, // class writer + ctx.contextType, // call context + true, // is boxed + ctx.errorUtil, // error message utility + evalCompilerOptions, // possibly modified for Unicode source + ctx.unitcheckBlocks); + // evalCtx.logDebug("evalStringHelper EmitterContext: " + evalCtx); + // evalCtx.logDebug("evalStringHelper Code: " + code); + + // Process the string source code to create the LexerToken list + Lexer lexer = new Lexer(evalString); + List tokens = lexer.tokenize(); // Tokenize the Perl code + Node ast = null; + Class generatedClass; + try { + // Create the AST + // Create an instance of ErrorMessageUtil with the file name and token list + evalCtx.errorUtil = new ErrorMessageUtil(evalCtx.compilerOptions.fileName, tokens); + Parser parser = new Parser(evalCtx, tokens); // Parse the tokens + ast = parser.parse(); // Generate the abstract syntax tree (AST) + + // ast = ConstantFoldingVisitor.foldConstants(ast); + + // Create a new instance of ErrorMessageUtil, resetting the line counter + evalCtx.errorUtil = new ErrorMessageUtil(ctx.compilerOptions.fileName, tokens); + ScopedSymbolTable postParseSymbolTable = evalCtx.symbolTable; + evalCtx.symbolTable = capturedSymbolTable; + evalCtx.symbolTable.copyFlagsFrom(postParseSymbolTable); + setCurrentScope(evalCtx.symbolTable); + + // Use the captured environment array from compile-time to ensure + // constructor signature matches what EmitEval generated bytecode for + if (ctx.capturedEnv != null) { + evalCtx.capturedEnv = ctx.capturedEnv; + } - capturedSymbolTable.featureFlagsStack.pop(); - capturedSymbolTable.featureFlagsStack.push(savedFeatureFlags); + ast.setAnnotation("blockIsSubroutine", true); + generatedClass = EmitterMethodCreator.createClassWithMethod( + evalCtx, + ast, + false // use try-catch + ); + runUnitcheckBlocks(ctx.unitcheckBlocks); + } catch (Throwable e) { + // Compilation error in eval-string - capturedSymbolTable.strictOptionsStack.pop(); - capturedSymbolTable.strictOptionsStack.push(savedStrictOptions); + // Set the global error variable "$@" + RuntimeScalar err = GlobalVariable.getGlobalVariable("main::@"); + err.set(e.getMessage()); - // Restore %^H (compile-time hints hash) to the caller snapshot. - capturedHintHash.elements.clear(); - capturedHintHash.elements.putAll(savedHintHash); + // If EVAL_VERBOSE is set, print the error to stderr for debugging + if (EVAL_VERBOSE) { + System.err.println("eval compilation error: " + e.getMessage()); + if (e.getCause() != null) { + System.err.println("Caused by: " + e.getCause().getMessage()); + } + } - setCurrentScope(capturedSymbolTable); + // Check if $SIG{__DIE__} handler is defined + RuntimeScalar sig = GlobalVariable.getGlobalHash("main::SIG").get("__DIE__"); + if (sig.getDefinedBoolean()) { + // Call the $SIG{__DIE__} handler (similar to what die() does) + RuntimeScalar sigHandler = new RuntimeScalar(sig); - // Clean up BEGIN aliases for captured variables after compilation. - // These aliases were only needed during parsing (for BEGIN blocks to access - // outer lexicals). Leaving them in GlobalVariable would cause corruption - // if a recursive call re-enters the same function and its `my` declaration - // calls retrieveBeginScalar, finding the stale alias instead of creating - // a fresh variable. - for (String key : evalAliasKeys) { - String fullName = key.substring(1); - switch (key.charAt(0)) { - case '$' -> GlobalVariable.globalVariables.remove(fullName); - case '@' -> GlobalVariable.globalArrays.remove(fullName); - case '%' -> GlobalVariable.globalHashes.remove(fullName); + // Undefine $SIG{__DIE__} before calling to avoid infinite recursion + int level = DynamicVariableManager.getLocalLevel(); + DynamicVariableManager.pushLocalVariable(sig); + + try { + RuntimeArray args = new RuntimeArray(); + RuntimeArray.push(args, new RuntimeScalar(err)); + apply(sigHandler, args, RuntimeContextType.SCALAR); + } catch (Throwable handlerException) { + // If the handler dies, use its payload as the new error + if (handlerException instanceof RuntimeException && handlerException.getCause() instanceof PerlDieException) { + PerlDieException pde = (PerlDieException) handlerException.getCause(); + RuntimeBase handlerPayload = pde.getPayload(); + if (handlerPayload != null) { + err.set(handlerPayload.getFirst()); + } + } else if (handlerException instanceof PerlDieException) { + PerlDieException pde = (PerlDieException) handlerException; + RuntimeBase handlerPayload = pde.getPayload(); + if (handlerPayload != null) { + err.set(handlerPayload.getFirst()); + } + } + // If handler throws other exceptions, ignore them (keep original error in $@) + } finally { + // Restore $SIG{__DIE__} + DynamicVariableManager.popToLocalLevel(level); + } } - } - // Store source lines in symbol table if $^P flags are set - // Do this on both success and failure paths when flags require retention - // Use the original evalString and actualFileName; AST may be null on failure - storeSourceLines(evalString, actualFileName, ast, tokens); - } + // Return null to signal compilation failure (don't throw exception) + // This prevents the exception from escaping to outer eval blocks + return null; + } finally { + // Restore caller lexical flags (do not leak eval pragmas). + capturedSymbolTable.warningFlagsStack.pop(); + capturedSymbolTable.warningFlagsStack.push((BitSet) savedWarningFlags.clone()); + + capturedSymbolTable.featureFlagsStack.pop(); + capturedSymbolTable.featureFlagsStack.push(savedFeatureFlags); + + capturedSymbolTable.strictOptionsStack.pop(); + capturedSymbolTable.strictOptionsStack.push(savedStrictOptions); + + // Restore %^H (compile-time hints hash) to the caller snapshot. + capturedHintHash.elements.clear(); + capturedHintHash.elements.putAll(savedHintHash); + + setCurrentScope(capturedSymbolTable); + + // Clean up BEGIN aliases for captured variables after compilation. + // These aliases were only needed during parsing (for BEGIN blocks to access + // outer lexicals). Leaving them in GlobalVariable would cause corruption + // if a recursive call re-enters the same function and its `my` declaration + // calls retrieveBeginScalar, finding the stale alias instead of creating + // a fresh variable. + for (String key : evalAliasKeys) { + String fullName = key.substring(1); + switch (key.charAt(0)) { + case '$' -> GlobalVariable.globalVariables.remove(fullName); + case '@' -> GlobalVariable.globalArrays.remove(fullName); + case '%' -> GlobalVariable.globalHashes.remove(fullName); + } + } - // Cache the result (unless debugging is enabled) - if (!isDebugging) { - synchronized (evalCache) { - evalCache.put(cacheKey, generatedClass); + // Store source lines in symbol table if $^P flags are set + // Do this on both success and failure paths when flags require retention + // Use the original evalString and actualFileName; AST may be null on failure + storeSourceLines(evalString, actualFileName, ast, tokens); + } + + // Cache the result (unless debugging is enabled) + if (!isDebugging) { + synchronized (evalCache) { + evalCache.put(cacheKey, generatedClass); + } } - } - return generatedClass; + return generatedClass; } finally { // Clean up ThreadLocal to prevent memory leaks // IMPORTANT: Always clean up ThreadLocal in finally block to ensure it's removed @@ -633,14 +588,14 @@ public static void storeSourceLines(String evalString, String filename, Node ast // 0x1000 (4096): Include source that did not compile boolean shouldSaveSource = (debugFlags & 0x02) != 0 || (debugFlags & 0x400) != 0; boolean saveWithoutSubs = (debugFlags & 0x800) != 0; - + if (shouldSaveSource) { // Note: We can't reliably detect subroutine definitions from the AST because // subroutines are processed at parse-time and removed from the AST. // Use a simple heuristic: check if the eval string contains "sub " followed by // an identifier or block. boolean definesSubs = evalString.matches("(?s).*\\bsub\\s+(?:\\w+|\\{).*"); - + // Only save if either: // - The eval defines subroutines, OR // - The 0x800 flag is set (save evals without subs) @@ -649,27 +604,27 @@ public static void storeSourceLines(String evalString, String filename, Node ast } // Store in the symbol table as @{"_<(eval N)"} String symbolKey = "_<" + filename; - + // Split the eval string into lines (without including trailing empty strings) String[] lines = evalString.split("\n"); - + // Create the array with the format expected by the debugger: // [0] = undef, [1..n] = lines with \n, [n+1] = \n, [n+2] = ; String arrayKey = "main::" + symbolKey; RuntimeArray sourceArray = GlobalVariable.getGlobalArray(arrayKey); sourceArray.elements.clear(); - + // Index 0: undef sourceArray.elements.add(RuntimeScalarCache.scalarUndef); - + // Indexes 1..n: each line with "\n" appended for (String line : lines) { sourceArray.elements.add(new RuntimeScalar(line + "\n")); } - + // Index n+1: "\n" sourceArray.elements.add(new RuntimeScalar("\n")); - + // Index n+2: ";" sourceArray.elements.add(new RuntimeScalar(";")); @@ -730,11 +685,11 @@ private static void processLineDirectives(String evalString, String[] lines, Lis * This method parses the eval string and compiles it to InterpretedCode instead * of generating JVM bytecode, which is 46x faster for workloads with many unique eval strings. * - * @param code The RuntimeScalar containing the eval string - * @param evalTag The unique identifier for this eval site + * @param code The RuntimeScalar containing the eval string + * @param evalTag The unique identifier for this eval site * @param runtimeValues The captured variable values from the outer scope - * @param args The @_ arguments to pass to the eval - * @param callContext The calling context (SCALAR/LIST/VOID) + * @param args The @_ arguments to pass to the eval + * @param callContext The calling context (SCALAR/LIST/VOID) * @return The result of executing the eval as a RuntimeList * @throws Throwable if compilation or execution fails */ @@ -798,7 +753,7 @@ public static RuntimeList evalStringWithInterpreter( CompilerOptions evalCompilerOptions = ctx.compilerOptions; boolean isByteStringSource = !ctx.isEvalbytes && code.type == RuntimeScalarType.BYTE_STRING; if (hasUnicode || ctx.isEvalbytes || isByteStringSource) { - evalCompilerOptions = (CompilerOptions) ctx.compilerOptions.clone(); + evalCompilerOptions = ctx.compilerOptions.clone(); if (hasUnicode) { evalCompilerOptions.isUnicodeSource = true; } @@ -1015,7 +970,7 @@ public static RuntimeList evalStringWithInterpreter( evalTrace("evalStringWithInterpreter exec ok tag=" + evalTag + " ctx=" + callContext + " resultClass=" + (result != null ? result.getClass().getSimpleName() : "null") + " resultScalar=" + (result != null ? result.scalar().toString() : "null") + - " resultBool=" + (result != null && result.scalar() != null ? result.scalar().getBoolean() : false)); + " resultBool=" + (result != null && result.scalar() != null && result.scalar().getBoolean())); // Clear $@ on successful execution RuntimeScalar err = GlobalVariable.getGlobalVariable("main::@"); @@ -1069,7 +1024,7 @@ public static RuntimeList evalStringWithInterpreter( } finally { evalTrace("evalStringWithInterpreter exit tag=" + evalTag + " ctx=" + callContext + - " $@=" + GlobalVariable.getGlobalVariable("main::@").toString()); + " $@=" + GlobalVariable.getGlobalVariable("main::@")); // Restore dynamic variables (local) to their state before eval DynamicVariableManager.popToLocalLevel(dynamicVarLevel); @@ -1339,7 +1294,7 @@ public static RuntimeList caller(RuntimeList args, int ctx) { res.add(new RuntimeScalar(frameInfo.get(0))); // package res.add(new RuntimeScalar(frameInfo.get(1))); // filename res.add(new RuntimeScalar(frameInfo.get(2))); // line - + // The subroutine name at frame N is actually stored at frame N-1 // because it represents the sub that IS CALLING frame N String subName = null; @@ -1349,7 +1304,7 @@ public static RuntimeList caller(RuntimeList args, int ctx) { subName = prevFrame.get(3); } } - + if (subName != null && !subName.isEmpty()) { res.add(new RuntimeScalar(subName)); // subroutine } else { @@ -1395,7 +1350,7 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, RuntimeArray a, int return apply(sourceAutoload, a, callContext); } } - + // Then check if AUTOLOAD exists in the current package String autoloadString = code.packageName + "::AUTOLOAD"; RuntimeScalar autoload = GlobalVariable.getGlobalCodeRef(autoloadString); @@ -1483,9 +1438,9 @@ private static RuntimeScalar handleCodeOverload(RuntimeScalar runtimeScalar) { // Method to apply (execute) a subroutine reference using native array for parameters public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineName, RuntimeBase[] args, int callContext) { // WORKAROUND for eval-defined subs not filling lexical forward declarations: - // If the RuntimeScalar is undef (forward declaration never filled), + // If the RuntimeScalar is undef (forward declaration never filled), // silently return undef so tests can continue running. - // This is a temporary workaround for the architectural limitation that eval + // This is a temporary workaround for the architectural limitation that eval // contexts are captured at compile time. if (runtimeScalar.type == RuntimeScalarType.UNDEF) { // Return undef in appropriate context @@ -1495,7 +1450,7 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineNa return new RuntimeList(new RuntimeScalar()); } } - + // Check if the type of this RuntimeScalar is CODE if (runtimeScalar.type == RuntimeScalarType.CODE) { @@ -1526,7 +1481,7 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineNa if (fullSubName.isEmpty() && code.packageName != null && code.subName != null) { fullSubName = code.packageName + "::" + code.subName; } - + if (!fullSubName.isEmpty()) { // If this is an imported forward declaration, check AUTOLOAD in the source package FIRST // This matches Perl semantics where imported subs resolve via the exporting package's AUTOLOAD @@ -1541,7 +1496,7 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineNa return apply(sourceAutoload, a, callContext); } } - + // Then check if AUTOLOAD exists in the current package String autoloadString = fullSubName.substring(0, fullSubName.lastIndexOf("::") + 2) + "AUTOLOAD"; RuntimeScalar autoload = GlobalVariable.getGlobalCodeRef(autoloadString); @@ -1573,9 +1528,9 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineNa public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineName, RuntimeBase list, int callContext) { // WORKAROUND for eval-defined subs not filling lexical forward declarations: - // If the RuntimeScalar is undef (forward declaration never filled), + // If the RuntimeScalar is undef (forward declaration never filled), // silently return undef so tests can continue running. - // This is a temporary workaround for the architectural limitation that eval + // This is a temporary workaround for the architectural limitation that eval // contexts are captured at compile time. if (runtimeScalar.type == RuntimeScalarType.UNDEF) { // Return undef in appropriate context @@ -1585,7 +1540,7 @@ public static RuntimeList apply(RuntimeScalar runtimeScalar, String subroutineNa return new RuntimeList(new RuntimeScalar()); } } - + // Check if the type of this RuntimeScalar is CODE if (runtimeScalar.type == RuntimeScalarType.CODE) { @@ -1664,7 +1619,7 @@ public static RuntimeScalar maybeUnwrapCodeReference(RuntimeBase base) { // For all other cases, create a normal reference return base.createReference(); } - + // Return a reference to the subroutine with this name: \&$a public static RuntimeScalar createCodeReference(RuntimeScalar runtimeScalar, String packageName) { // Special case: if the scalar already contains a CODE reference (lexical sub hidden variable), @@ -1678,7 +1633,7 @@ public static RuntimeScalar createCodeReference(RuntimeScalar runtimeScalar, Str } return runtimeScalar; } - + String name = NameNormalizer.normalizeVariableName(runtimeScalar.toString(), packageName); // System.out.println("Creating code reference: " + name + " got: " + GlobalContext.getGlobalCodeRef(name)); RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(name); @@ -1688,7 +1643,7 @@ public static RuntimeScalar createCodeReference(RuntimeScalar runtimeScalar, Str // Mark this as a symbolic reference created by \&{string} pattern // This ensures defined(\&{nonexistent}) returns true to match standard Perl behavior runtimeCode.isSymbolicReference = true; - + // For constant subroutines, return a reference to the constant value if (runtimeCode.constantValue != null && !runtimeCode.constantValue.isEmpty()) { RuntimeScalar constValue = runtimeCode.constantValue.getFirst(); @@ -1742,6 +1697,25 @@ public static String getCurrentPackage() { return "main::"; } + /** + * Replace lazy {@link ScalarSpecialVariable} references ($1, $&, etc.) in a return list + * with concrete {@link RuntimeScalar} copies. Must be called BEFORE {@link RegexState#restore()} + * so that the values reflect the subroutine's regex state, not the caller's. + */ + public static void materializeSpecialVarsInResult(RuntimeList result) { + List elems = result.elements; + for (int i = 0; i < elems.size(); i++) { + RuntimeBase elem = elems.get(i); + if (elem instanceof ScalarSpecialVariable ssv) { + RuntimeScalar resolved = ssv.getValueAsScalar(); + RuntimeScalar concrete = new RuntimeScalar(); + concrete.type = resolved.type; + concrete.value = resolved.value; + elems.set(i, concrete); + } + } + } + public boolean defined() { // Symbolic references created by \&{string} are always considered "defined" to match standard Perl if (this.isSymbolicReference) { @@ -1869,25 +1843,6 @@ public RuntimeList apply(String subroutineName, RuntimeArray a, int callContext) } } - /** - * Replace lazy {@link ScalarSpecialVariable} references ($1, $&, etc.) in a return list - * with concrete {@link RuntimeScalar} copies. Must be called BEFORE {@link RegexState#restore()} - * so that the values reflect the subroutine's regex state, not the caller's. - */ - public static void materializeSpecialVarsInResult(RuntimeList result) { - List elems = result.elements; - for (int i = 0; i < elems.size(); i++) { - RuntimeBase elem = elems.get(i); - if (elem instanceof ScalarSpecialVariable ssv) { - RuntimeScalar resolved = ssv.getValueAsScalar(); - RuntimeScalar concrete = new RuntimeScalar(); - concrete.type = resolved.type; - concrete.value = resolved.value; - elems.set(i, concrete); - } - } - } - /** * Returns a string representation of the CODE reference. * @@ -2009,4 +1964,44 @@ public void dynamicRestoreState() { throw new PerlCompilerException("Can't modify anonymous subroutine"); } + /** + * Container for runtime context during eval STRING compilation. + * Holds both the runtime values and variable names so SpecialBlockParser can + * match variables to their values. + */ + public static class EvalRuntimeContext { + public final Object[] runtimeValues; + public final String[] capturedEnv; + public final String evalTag; + + public EvalRuntimeContext(Object[] runtimeValues, String[] capturedEnv, String evalTag) { + this.runtimeValues = runtimeValues; + this.capturedEnv = capturedEnv; + this.evalTag = evalTag; + } + + /** + * Get the runtime value for a variable by name. + *

    + * IMPORTANT: The capturedEnv array includes all variables (including 'this', '@_', 'wantarray'), + * but runtimeValues array skips the first skipVariables (currently 3). + * So if @imports is at capturedEnv[5], its value is at runtimeValues[5-3=2]. + * + * @param varName The variable name (e.g., "@imports", "$scalar") + * @return The runtime value, or null if not found + */ + public Object getRuntimeValue(String varName) { + int skipVariables = 3; // 'this', '@_', 'wantarray' + for (int i = skipVariables; i < capturedEnv.length; i++) { + if (varName.equals(capturedEnv[i])) { + int runtimeIndex = i - skipVariables; + if (runtimeIndex >= 0 && runtimeIndex < runtimeValues.length) { + return runtimeValues[runtimeIndex]; + } + } + } + return null; + } + } + } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList.java index fa31d7840..9f6e6f73f 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowList.java @@ -8,49 +8,77 @@ public class RuntimeControlFlowList extends RuntimeList { // Debug flag - set to true to enable detailed tracing private static final boolean DEBUG_TAILCALL = false; - - /** The control flow marker with type and label/codeRef information */ + + /** + * The control flow marker with type and label/codeRef information + */ public final ControlFlowMarker marker; - + /** * Constructor for control flow (last/next/redo/goto). - * - * @param type The control flow type - * @param label The label to jump to (null for unlabeled) - * @param fileName Source file name (for error messages) + * + * @param type The control flow type + * @param label The label to jump to (null for unlabeled) + * @param fileName Source file name (for error messages) * @param lineNumber Line number (for error messages) */ public RuntimeControlFlowList(ControlFlowType type, String label, String fileName, int lineNumber) { super(); this.marker = new ControlFlowMarker(type, label, fileName, lineNumber); if (DEBUG_TAILCALL) { - System.err.println("[DEBUG-0a] RuntimeControlFlowList constructor (type,label): type=" + type + - ", label=" + label + " @ " + fileName + ":" + lineNumber); + System.err.println("[DEBUG-0a] RuntimeControlFlowList constructor (type,label): type=" + type + + ", label=" + label + " @ " + fileName + ":" + lineNumber); } } - + /** * Constructor for tail call (goto &NAME). - * - * @param codeRef The code reference to call - * @param args The arguments to pass - * @param fileName Source file name (for error messages) + * + * @param codeRef The code reference to call + * @param args The arguments to pass + * @param fileName Source file name (for error messages) * @param lineNumber Line number (for error messages) */ public RuntimeControlFlowList(RuntimeScalar codeRef, RuntimeArray args, String fileName, int lineNumber) { super(); this.marker = new ControlFlowMarker(codeRef, args, fileName, lineNumber); if (DEBUG_TAILCALL) { - System.err.println("[DEBUG-0b] RuntimeControlFlowList constructor (codeRef,args): codeRef=" + codeRef + - ", args.size=" + (args != null ? args.size() : "null") + - " @ " + fileName + ":" + lineNumber + - " marker.type=" + marker.type); + System.err.println("[DEBUG-0b] RuntimeControlFlowList constructor (codeRef,args): codeRef=" + codeRef + + ", args.size=" + (args != null ? args.size() : "null") + + " @ " + fileName + ":" + lineNumber + + " marker.type=" + marker.type); + } + } + + /** + * Create a RuntimeControlFlowList from a registry action code. + * Used by emitControlFlowCheck to convert registry action to marked list. + * + * @param action The action code (1=LAST, 2=NEXT, 3=REDO) + * @param label The loop label (or null) + * @return A marked RuntimeControlFlowList + */ + public static RuntimeControlFlowList createFromAction(int action, String label) { + ControlFlowType type; + switch (action) { + case 1: + type = ControlFlowType.LAST; + break; + case 2: + type = ControlFlowType.NEXT; + break; + case 3: + type = ControlFlowType.REDO; + break; + default: + throw new IllegalArgumentException("Invalid action code: " + action); } + return new RuntimeControlFlowList(type, label, "(registry)", 0); } - + /** * Get the control flow type. - * + * * @return The control flow type */ public ControlFlowType getControlFlowType() { @@ -59,41 +87,41 @@ public ControlFlowType getControlFlowType() { } return marker.type; } - + /** * Get the control flow label. - * + * * @return The label, or null if unlabeled */ public String getControlFlowLabel() { return marker.label; } - + /** * Check if this control flow matches the given loop label. * Perl semantics: * - If control flow is unlabeled (null), it matches any loop * - If control flow is labeled and loop is unlabeled (null), no match * - If both are labeled, they must match exactly - * + * * @param loopLabel The loop label to check against (null for unlabeled loop) * @return true if this control flow targets the given loop */ public boolean matchesLabel(String loopLabel) { String controlFlowLabel = marker.label; - + // Unlabeled control flow (null) matches any loop if (controlFlowLabel == null) { return true; } - + // Labeled control flow - check if it matches the loop label return controlFlowLabel.equals(loopLabel); } - + /** * Get the tail call code reference. - * + * * @return The code reference, or null if not a tail call */ public RuntimeScalar getTailCallCodeRef() { @@ -102,20 +130,20 @@ public RuntimeScalar getTailCallCodeRef() { } return marker.codeRef; } - + /** * Get the tail call arguments. - * + * * @return The arguments, or null if not a tail call */ public RuntimeArray getTailCallArgs() { if (DEBUG_TAILCALL) { - System.err.println("[DEBUG-4] getTailCallArgs() called, returning: " + - (marker.args != null ? marker.args.size() + " args" : "null")); + System.err.println("[DEBUG-4] getTailCallArgs() called, returning: " + + (marker.args != null ? marker.args.size() + " args" : "null")); } return marker.args; } - + /** * Debug method - print this control flow list's details. * Can be called from generated bytecode or Java code. @@ -127,31 +155,5 @@ public RuntimeControlFlowList debugTrace(String context) { } return this; // Return self for chaining } - - /** - * Create a RuntimeControlFlowList from a registry action code. - * Used by emitControlFlowCheck to convert registry action to marked list. - * - * @param action The action code (1=LAST, 2=NEXT, 3=REDO) - * @param label The loop label (or null) - * @return A marked RuntimeControlFlowList - */ - public static RuntimeControlFlowList createFromAction(int action, String label) { - ControlFlowType type; - switch (action) { - case 1: - type = ControlFlowType.LAST; - break; - case 2: - type = ControlFlowType.NEXT; - break; - case 3: - type = ControlFlowType.REDO; - break; - default: - throw new IllegalArgumentException("Invalid action code: " + action); - } - return new RuntimeControlFlowList(type, label, "(registry)", 0); - } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowRegistry.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowRegistry.java index 4559efef1..2d495f9a1 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowRegistry.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeControlFlowRegistry.java @@ -3,42 +3,42 @@ /** * Thread-local registry for non-local control flow markers. * This provides an alternative to call-site checks that works around ASM frame computation issues. - * + *

    * When a control flow statement (last/next/redo/goto) executes inside a subroutine, * it registers the marker here. Loop boundaries check the registry and handle the marker. */ public class RuntimeControlFlowRegistry { // Thread-local storage for control flow markers private static final ThreadLocal currentMarker = new ThreadLocal<>(); - + /** * Register a control flow marker. * This is called by last/next/redo/goto when they need to propagate across subroutine boundaries. - * + * * @param marker The control flow marker to register */ public static void register(ControlFlowMarker marker) { currentMarker.set(marker); } - + /** * Check if there's a pending control flow marker. - * + * * @return true if a marker is registered */ public static boolean hasMarker() { return currentMarker.get() != null; } - + /** * Get the current control flow marker. - * + * * @return The marker, or null if none */ public static ControlFlowMarker getMarker() { return currentMarker.get(); } - + /** * Clear the current marker. * This is called after the marker has been handled by a loop. @@ -46,12 +46,12 @@ public static ControlFlowMarker getMarker() { public static void clear() { currentMarker.remove(); } - + /** * Check if the current marker matches a given label and type. * If it matches, clears the marker and returns true. - * - * @param type The control flow type to check (LAST, NEXT, REDO, GOTO) + * + * @param type The control flow type to check (LAST, NEXT, REDO, GOTO) * @param label The label to match (null for unlabeled) * @return true if the marker matches and was cleared */ @@ -60,32 +60,32 @@ public static boolean checkAndClear(ControlFlowType type, String label) { if (marker == null) { return false; } - + // Check if type matches if (marker.type != type) { return false; } - + // Check if label matches (Perl semantics) if (marker.label == null) { // Unlabeled control flow matches any loop clear(); return true; } - + // Labeled control flow must match exactly if (marker.label.equals(label)) { clear(); return true; } - + return false; } - + /** * Check if the current marker is a GOTO that matches a specific label. * Used for goto LABEL (not last/next/redo). - * + * * @param label The goto label to check * @return true if there's a GOTO marker for this label */ @@ -94,19 +94,19 @@ public static boolean checkGoto(String label) { if (marker == null || marker.type != ControlFlowType.GOTO) { return false; } - + if (marker.label != null && marker.label.equals(label)) { clear(); return true; } - + return false; } - + /** * Check if there's a control flow marker that matches this loop, and return an action code. * This is an ultra-simplified version that does all checking in one call to avoid ASM issues. - * + * * @param labelName The loop's label (null for unlabeled) * @return 0=no action, 1=LAST, 2=NEXT, 3=REDO, 4=GOTO (leave in registry) */ @@ -115,7 +115,7 @@ public static int checkLoopAndGetAction(String labelName) { if (marker == null) { return 0; // No marker } - + // Check if marker's label matches (Perl semantics) boolean labelMatches; if (marker.label == null) { @@ -128,14 +128,14 @@ public static int checkLoopAndGetAction(String labelName) { // Both labeled - must match exactly labelMatches = marker.label.equals(labelName); } - + if (!labelMatches) { return 0; // Label doesn't match, leave marker for outer loop } - + // Label matches - return action and clear marker clear(); - + switch (marker.type) { case LAST: return 1; @@ -156,7 +156,7 @@ public static int checkLoopAndGetAction(String labelName) { return 0; } } - + /** * If there's an uncaught marker at the top level, throw an error. * This is called at program exit or when returning from a subroutine to the top level. @@ -168,15 +168,15 @@ public static void throwIfUncaught() { marker.throwError(); } } - + /** * Check the registry and wrap the result if needed. * This is the SIMPLEST approach to avoid ASM frame computation issues: * - No branching in bytecode (no TABLESWITCH, no IF chains, no GOTOs) * - Just a single method call that returns either the original or a marked list * - All the logic is in Java code, not bytecode - * - * @param result The original result from the subroutine call + * + * @param result The original result from the subroutine call * @param labelName The loop's label (null for unlabeled) * @return Either the original result or a marked RuntimeControlFlowList */ diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java index 775d7c4d8..79fd271b4 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java @@ -14,6 +14,7 @@ */ public class RuntimeGlob extends RuntimeScalar implements RuntimeScalarReference { + private static final Stack globSlotStack = new Stack<>(); // The name of the typeglob public String globName; public RuntimeScalar IO; @@ -169,10 +170,10 @@ public RuntimeScalar set(RuntimeGlob value) { // Create ALIASES by making both names point to the same objects in the global maps // This is the key difference from the old implementation which created references - + // Alias the CODE slot: both names point to the same code reference RuntimeScalar sourceCode = GlobalVariable.getGlobalCodeRef(globName); - GlobalVariable.globalCodeRefs.put(this.globName, (RuntimeScalar) sourceCode); + GlobalVariable.globalCodeRefs.put(this.globName, sourceCode); // Invalidate the method resolution cache InheritanceResolver.invalidateCache(); @@ -541,14 +542,13 @@ public RuntimeGlob undefine() { return this; } - private static final Stack globSlotStack = new Stack<>(); - private record GlobSlotSnapshot( String globName, RuntimeScalar scalar, RuntimeArray array, RuntimeHash hash, - RuntimeScalar code) {} + RuntimeScalar code) { + } @Override public void dynamicSaveState() { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java index e6256093d..99160d60e 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java @@ -21,6 +21,12 @@ public class RuntimeHash extends RuntimeBase implements RuntimeScalarReference, public static final int TIED_HASH = 2; // Static stack to store saved "local" states of RuntimeHash instances private static final Stack dynamicStateStack = new Stack<>(); + private static final RuntimeArray EMPTY_KEYS = new RuntimeArray(); + + static { + EMPTY_KEYS.scalarContextSize = 0; + } + // Internal type of array - PLAIN_HASH, AUTOVIVIFY_HASH, or TIED_HASH public int type; // Map to store the elements of the hash @@ -28,12 +34,6 @@ public class RuntimeHash extends RuntimeBase implements RuntimeScalarReference, // Iterator for traversing the hash elements Iterator hashIterator; - private static final RuntimeArray EMPTY_KEYS = new RuntimeArray(); - - static { - EMPTY_KEYS.scalarContextSize = 0; - } - /** * Constructor for RuntimeHash. * Initializes an empty hash map to store elements. @@ -42,7 +42,7 @@ public RuntimeHash() { type = PLAIN_HASH; elements = new StableHashMap<>(); } - + /** * Creates a hash with the elements of a list. * @@ -67,7 +67,7 @@ public static RuntimeHash createHashForAssignment(RuntimeBase value) { checkIterator.next(); elementCount++; } - + // Warn about odd elements (Perl does not warn about references in hash assignment) if (elementCount % 2 != 0) { return createHashInternal(value, "Odd number of elements in hash assignment"); @@ -79,14 +79,14 @@ public static RuntimeHash createHashForAssignment(RuntimeBase value) { /** * Internal method to create a hash with the elements of a list. * - * @param value The RuntimeBase containing the elements to populate the hash. + * @param value The RuntimeBase containing the elements to populate the hash. * @param oddWarningMessage The warning message to emit if there's an odd number of elements. * @return A new RuntimeHash populated with the elements from the list. */ private static RuntimeHash createHashInternal(RuntimeBase value, String oddWarningMessage) { RuntimeHash result = new RuntimeHash(); Map resultHash = result.elements; - + // Count elements to check for odd number int elementCount = 0; Iterator countIterator = value.iterator(); @@ -94,14 +94,14 @@ private static RuntimeHash createHashInternal(RuntimeBase value, String oddWarni countIterator.next(); elementCount++; } - + // Warn if odd number of elements if (elementCount % 2 != 0) { WarnDie.warn( - new RuntimeScalar(oddWarningMessage), - RuntimeScalarCache.scalarEmptyString); + new RuntimeScalar(oddWarningMessage), + RuntimeScalarCache.scalarEmptyString); } - + Iterator iterator = value.iterator(); while (iterator.hasNext()) { String key = iterator.next().toString(); @@ -209,13 +209,13 @@ public RuntimeArray setFromList(RuntimeList value) { // Warn about odd elements (Perl does not warn about references in hash assignment) if (originalSize % 2 != 0) { WarnDie.warn( - new RuntimeScalar("Odd number of elements in hash assignment"), - RuntimeScalarCache.scalarEmptyString); + new RuntimeScalar("Odd number of elements in hash assignment"), + RuntimeScalarCache.scalarEmptyString); } // Clear existing elements but keep the same Map instance to preserve capacity this.elements.clear(); - + // Populate the hash from the provided list // This reuses the existing StableHashMap and its capacity Iterator iter = value.iterator(); @@ -383,7 +383,7 @@ public RuntimeScalar delete(RuntimeScalar key) { } case AUTOVIVIFY_HASH -> { AutovivificationHash.vivify(this); - yield delete(key); + yield delete (key); } case TIED_HASH -> TieHash.tiedDelete(this, key); default -> throw new IllegalStateException("Unknown array type: " + type); @@ -401,7 +401,7 @@ public RuntimeScalar delete(String key) { } case AUTOVIVIFY_HASH -> { AutovivificationHash.vivify(this); - yield delete(key); + yield delete (key); } case TIED_HASH -> TieHash.tiedDelete(this, new RuntimeScalar(key)); default -> throw new IllegalStateException("Unknown array type: " + type); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java index 3792ceb2a..4e1ccdf6c 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java @@ -9,9 +9,9 @@ Handling pipes (e.g., |- or -| modes). */ import org.perlonjava.runtime.io.*; +import org.perlonjava.runtime.operators.IOOperator; import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.perlmodule.Warnings; -import org.perlonjava.runtime.operators.IOOperator; import java.io.File; import java.io.IOException; @@ -96,44 +96,26 @@ protected boolean removeEldestEntry(Map.Entry eldest) { }; private static final Map childProcesses = new java.util.concurrent.ConcurrentHashMap<>(); - - public static void registerChildProcess(Process p) { - if (p != null) childProcesses.put(p.pid(), p); - } - - public static Process getChildProcess(long pid) { - return childProcesses.get(pid); - } - - public static Process removeChildProcess(long pid) { - return childProcesses.remove(pid); - } - /** * Standard output stream handle (STDOUT) */ public static RuntimeIO stdout = new RuntimeIO(new StandardIO(System.out, true)); - /** * Standard error stream handle (STDERR) */ public static RuntimeIO stderr = new RuntimeIO(new StandardIO(System.err, false)); - /** * Standard input stream handle (STDIN) */ public static RuntimeIO stdin = new RuntimeIO(new StandardIO(System.in)); - /** * The last accessed filehandle, used for Perl's ${^LAST_FH} special variable. * Updated whenever a filehandle is used for I/O operations. */ public static RuntimeIO lastAccesseddHandle; - // Tracks the last handle used for output writes (print/say/etc). This must not // clobber lastAccesseddHandle, which is used for ${^LAST_FH} and $. public static RuntimeIO lastWrittenHandle; - /** * The currently selected filehandle for output operations. * Used by print/printf when no filehandle is specified. @@ -155,46 +137,27 @@ public static Process removeChildProcess(long pid) { * Incremented for each line read from this handle. */ public int currentLineNumber = 0; - /** * The underlying I/O handle that performs actual I/O operations. * Can be a file, socket, pipe, or custom I/O implementation. */ public IOHandle ioHandle = new ClosedIOHandle(); // Initialize with ClosedIOHandle - - public long getPid() { - Process p = null; - if (ioHandle instanceof PipeInputChannel pic) { - p = pic.getProcess(); - } else if (ioHandle instanceof PipeOutputChannel poc) { - p = poc.getProcess(); - } - if (p != null) { - registerChildProcess(p); - return p.pid(); - } - return -1; - } - /** * Directory handle for directory operations (opendir, readdir, etc.). * Mutually exclusive with ioHandle - a RuntimeIO is either a file or directory handle. */ public DirectoryIO directoryIO; - /** * The name of the glob that owns this IO handle (e.g., "main::STDOUT"). * Used for stringification when the filehandle is used in string context. * Null if this handle is not associated with a named glob. */ public String globName; - /** * Flag indicating if this handle has unflushed output. * Used to determine when automatic flushing is needed. */ boolean needFlush; - boolean autoFlush; /** @@ -223,6 +186,18 @@ public RuntimeIO(DirectoryIO directoryIO) { this.directoryIO = directoryIO; } + public static void registerChildProcess(Process p) { + if (p != null) childProcesses.put(p.pid(), p); + } + + public static Process getChildProcess(long pid) { + return childProcesses.get(pid); + } + + public static Process removeChildProcess(long pid) { + return childProcesses.remove(pid); + } + /** * Sets a custom output stream as the last accessed handle. * This is primarily used for testing and special output redirection. @@ -500,7 +475,7 @@ public static RuntimeIO open(RuntimeScalar scalarRef, String mode) { // Create ScalarBackedIO ScalarBackedIO scalarIO = new ScalarBackedIO(targetScalar); - + if (mode.equals(">>")) { // Enable append mode - writes always go to the end scalarIO.setAppendMode(true); @@ -854,6 +829,20 @@ public static RuntimeIO duplicateHandle(RuntimeIO sourceHandle, String mode) { return sourceHandle; } + public long getPid() { + Process p = null; + if (ioHandle instanceof PipeInputChannel pic) { + p = pic.getProcess(); + } else if (ioHandle instanceof PipeOutputChannel poc) { + p = poc.getProcess(); + } + if (p != null) { + registerChildProcess(p); + return p.pid(); + } + return -1; + } + /** * Sets or changes the I/O layers for this handle. * @@ -1055,9 +1044,9 @@ public RuntimeScalar write(String data) { // Only flush lastAccessedHandle if it's a different handle AND doesn't share the same ioHandle // (duplicated handles share the same ioHandle, so flushing would be redundant and could cause deadlocks) if (lastWrittenHandle != null && - lastWrittenHandle != this && - lastWrittenHandle.needFlush && - lastWrittenHandle.ioHandle != this.ioHandle) { + lastWrittenHandle != this && + lastWrittenHandle.needFlush && + lastWrittenHandle.ioHandle != this.ioHandle) { // Synchronize terminal output for stdout and stderr lastWrittenHandle.flush(); } @@ -1069,7 +1058,7 @@ public RuntimeScalar write(String data) { System.err.println("[JPERL_IO_DEBUG] write failed: glob=" + globName + " ioHandle=" + (ioHandle == null ? "null" : ioHandle.getClass().getName()) + " defined=" + result.getDefinedBoolean() + - " errno=" + getGlobalVariable("main::!").toString()); + " errno=" + getGlobalVariable("main::!")); System.err.flush(); } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java index 459364966..a5380b9b1 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java @@ -1,11 +1,6 @@ package org.perlonjava.runtime.runtimetypes; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; +import java.util.*; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarTrue; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef; @@ -591,6 +586,18 @@ public Iterator iterator() { return new RuntimeListIterator(elements); } + /** + * Check if this RuntimeList represents non-local control flow. + * Uses instanceof check which is optimized by JIT compiler. + * + * @return true if this is a RuntimeControlFlowList, false otherwise + */ + public boolean isNonLocalGoto() { + return this instanceof RuntimeControlFlowList; + } + + // ========== Control Flow Support ========== + /** * Inner class implementing the Iterator interface for RuntimeList. */ @@ -631,16 +638,4 @@ public RuntimeScalar next() { return currentIterator.next(); } } - - // ========== Control Flow Support ========== - - /** - * Check if this RuntimeList represents non-local control flow. - * Uses instanceof check which is optimized by JIT compiler. - * - * @return true if this is a RuntimeControlFlowList, false otherwise - */ - public boolean isNonLocalGoto() { - return this instanceof RuntimeControlFlowList; - } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimePosLvalue.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimePosLvalue.java index b49fd0c77..8e6fb0807 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimePosLvalue.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimePosLvalue.java @@ -68,6 +68,43 @@ private static void clearZeroLengthMatchTracking(RuntimeScalar perlVariable) { } } + /** + * Check if the last match at this position was zero-length with the given pattern. + * This is used to prevent infinite loops in global regex matches. + */ + public static boolean hadZeroLengthMatchAt(RuntimeScalar perlVariable, int position, String patternKey) { + CacheEntry cachedEntry = positionCache.get(perlVariable); + if (cachedEntry == null) { + return false; + } + return cachedEntry.lastMatchWasZeroLength && + cachedEntry.lastMatchPosition == position && + patternKey.equals(cachedEntry.lastMatchPattern); + } + + /** + * Record that a zero-length match occurred at the given position with the given pattern. + */ + public static void recordZeroLengthMatch(RuntimeScalar perlVariable, int position, String patternKey) { + CacheEntry cachedEntry = positionCache.get(perlVariable); + if (cachedEntry != null) { + cachedEntry.lastMatchWasZeroLength = true; + cachedEntry.lastMatchPosition = position; + cachedEntry.lastMatchPattern = patternKey; + } + } + + /** + * Clear the zero-length match tracking (called after successful non-zero-length match). + */ + public static void recordNonZeroLengthMatch(RuntimeScalar perlVariable) { + CacheEntry cachedEntry = positionCache.get(perlVariable); + if (cachedEntry != null) { + cachedEntry.lastMatchWasZeroLength = false; + cachedEntry.lastMatchPattern = null; + } + } + private static class PosLvalueScalar extends RuntimeScalar { private final RuntimeScalar target; @@ -113,43 +150,6 @@ public RuntimeScalar set(Object value) { } } - /** - * Check if the last match at this position was zero-length with the given pattern. - * This is used to prevent infinite loops in global regex matches. - */ - public static boolean hadZeroLengthMatchAt(RuntimeScalar perlVariable, int position, String patternKey) { - CacheEntry cachedEntry = positionCache.get(perlVariable); - if (cachedEntry == null) { - return false; - } - return cachedEntry.lastMatchWasZeroLength && - cachedEntry.lastMatchPosition == position && - patternKey.equals(cachedEntry.lastMatchPattern); - } - - /** - * Record that a zero-length match occurred at the given position with the given pattern. - */ - public static void recordZeroLengthMatch(RuntimeScalar perlVariable, int position, String patternKey) { - CacheEntry cachedEntry = positionCache.get(perlVariable); - if (cachedEntry != null) { - cachedEntry.lastMatchWasZeroLength = true; - cachedEntry.lastMatchPosition = position; - cachedEntry.lastMatchPattern = patternKey; - } - } - - /** - * Clear the zero-length match tracking (called after successful non-zero-length match). - */ - public static void recordNonZeroLengthMatch(RuntimeScalar perlVariable) { - CacheEntry cachedEntry = positionCache.get(perlVariable); - if (cachedEntry != null) { - cachedEntry.lastMatchWasZeroLength = false; - cachedEntry.lastMatchPattern = null; - } - } - /** * A cache entry that stores the hash of a {@code RuntimeScalar} value and its regex position. * This helps in determining if the cached position is still valid for the given scalar. diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index a184f569d..1a15178ed 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -1,8 +1,8 @@ package org.perlonjava.runtime.runtimetypes; +import org.perlonjava.frontend.parser.NumberParser; import org.perlonjava.runtime.mro.InheritanceResolver; import org.perlonjava.runtime.operators.StringOperators; -import org.perlonjava.frontend.parser.NumberParser; import org.perlonjava.runtime.regex.RuntimeRegex; import java.math.BigInteger; @@ -309,7 +309,7 @@ public RuntimeGlob globDerefPostfix(String packageName) { // Inlineable fast path for getInt() public int getInt() { - if (type == INTEGER ) { + if (type == INTEGER) { return (int) this.value; } return getIntLarge(); @@ -332,7 +332,7 @@ private int getIntLarge() { // Parse as long first so we can handle values outside 32-bit range // (Perl IV is commonly 64-bit). getInt() is used for array indices // and similar contexts, which should behave like (int)getLong(). - yield (int) Long.parseLong(t); + yield( int)Long.parseLong(t); } catch (NumberFormatException ignored) { // Fall through to full numification. } @@ -524,7 +524,7 @@ public long getLong() { // Inlineable fast path for getDouble() public double getDouble() { - if (type == INTEGER ) { + if (type == INTEGER) { return (int) this.value; } return getDoubleLarge(); @@ -752,7 +752,7 @@ public RuntimeArray setFromList(RuntimeList value) { @Override // Inlineable fast path for toString() - public String toString() { + public String toString() { if (type == STRING || type == BYTE_STRING) { return (String) this.value; } @@ -776,7 +776,7 @@ private String toStringLarge() { case CODE -> Overload.stringify(this).toString(); default -> { if (type == REGEX) yield value.toString(); - yield Overload.stringify(this).toString(); + yield Overload.stringify(this).toString(); } }; } @@ -788,32 +788,32 @@ public String toStringRef() { if (value == null) { yield "CODE(0x" + scalarUndef.hashCode() + ")"; } - yield ((RuntimeCode) value).toStringRef(); + yield((RuntimeCode) value).toStringRef(); } case GLOB -> { if (value == null) { yield "GLOB(0x" + scalarUndef.hashCode() + ")"; } - yield ((RuntimeGlob) value).toStringRef(); + yield((RuntimeGlob) value).toStringRef(); } case VSTRING -> "VSTRING(0x" + value.hashCode() + ")"; case ARRAYREFERENCE -> { if (value == null) { yield "ARRAY(0x" + scalarUndef.hashCode() + ")"; } - yield ((RuntimeArray) value).toStringRef(); + yield((RuntimeArray) value).toStringRef(); } case HASHREFERENCE -> { if (value == null) { yield "HASH(0x" + scalarUndef.hashCode() + ")"; } - yield ((RuntimeHash) value).toStringRef(); + yield((RuntimeHash) value).toStringRef(); } case GLOBREFERENCE -> { if (value == null) { yield "GLOB(0x" + scalarUndef.hashCode() + ")"; } - yield ((RuntimeBase) value).toStringRef(); + yield((RuntimeBase) value).toStringRef(); } case REFERENCE -> { // Determine the proper type name for the reference @@ -831,7 +831,7 @@ public String toStringRef() { } String refStr = typeName + "(0x" + Integer.toHexString(value.hashCode()) + ")"; // For REFERENCE type, the blessId is on the value, not on the reference itself - yield (valueBlessId == 0 ? refStr : NameNormalizer.getBlessStr(valueBlessId) + "=" + refStr); + yield(valueBlessId == 0 ? refStr : NameNormalizer.getBlessStr(valueBlessId) + "=" + refStr); } default -> "SCALAR(0x" + Integer.toHexString(value.hashCode()) + ")"; }; @@ -1297,7 +1297,7 @@ public RuntimeGlob globDeref() { tmp.setIO(io); yield tmp; } - yield (RuntimeGlob) value; + yield(RuntimeGlob) value; } case GLOB -> { // PVIO (like *STDOUT{IO}) is stored as type GLOB with a RuntimeIO value. @@ -1308,7 +1308,7 @@ public RuntimeGlob globDeref() { tmp.setIO(io); yield tmp; } - yield (RuntimeGlob) value; + yield(RuntimeGlob) value; } case STRING, BYTE_STRING -> throw new PerlCompilerException("Can't use string (\"" + this + "\") as a symbol ref while \"strict refs\" in use"); @@ -1341,7 +1341,7 @@ public RuntimeGlob globDerefNonStrict(String packageName) { tmp.setIO(io); yield tmp; } - yield (RuntimeGlob) value; + yield(RuntimeGlob) value; } case GLOB -> { // PVIO (like *STDOUT{IO}) is stored as type GLOB with a RuntimeIO value. @@ -1352,7 +1352,7 @@ public RuntimeGlob globDerefNonStrict(String packageName) { tmp.setIO(io); yield tmp; } - yield (RuntimeGlob) value; + yield(RuntimeGlob) value; } default -> { String varName = NameNormalizer.normalizeVariableName(this.toString(), packageName); @@ -1453,7 +1453,7 @@ public boolean getDefinedBoolean() { case DUALVAR -> ((DualVar) this.value).stringValue().getDefinedBoolean(); // 10 case FORMAT -> ((RuntimeFormat) value).getDefinedBoolean(); // 11 // Reference types (with REFERENCE_BIT) fall through to default - default -> type == CODE ? ((RuntimeCode) value).defined() : true; + default -> type != CODE || ((RuntimeCode) value).defined(); }; } @@ -1554,8 +1554,8 @@ public RuntimeScalar postAutoIncrement() { // Slow path for $v++ private RuntimeScalar postAutoIncrementLarge() { // For undef, the old value should be 0, not undef - RuntimeScalar old = this.type == RuntimeScalarType.UNDEF ? - new RuntimeScalar(0) : new RuntimeScalar(this); + RuntimeScalar old = this.type == RuntimeScalarType.UNDEF ? + new RuntimeScalar(0) : new RuntimeScalar(this); // Cases 0-11 are listed in order from RuntimeScalarType, and compile to fast tableswitch switch (type) { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarCache.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarCache.java index d20d561cd..69a1de319 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarCache.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarCache.java @@ -21,7 +21,6 @@ public class RuntimeScalarCache { private static final AtomicInteger nextByteStringIndex = new AtomicInteger(0); private static final ConcurrentHashMap byteStringToIndex = new ConcurrentHashMap<>(); private static final Object byteStringCacheLock = new Object(); - private static volatile RuntimeScalarReadOnly[] scalarByteString = new RuntimeScalarReadOnly[INITIAL_STRING_CACHE_SIZE]; // Cached RuntimeScalarReadOnly objects for common boolean and undefined values public static RuntimeScalarReadOnly scalarTrue; public static RuntimeScalarReadOnly scalarFalse; @@ -34,6 +33,7 @@ public class RuntimeScalarCache { static int maxInt = 100; // Array to store cached RuntimeScalarReadOnly objects for integers static RuntimeScalarReadOnly[] scalarInt = new RuntimeScalarReadOnly[maxInt - minInt + 1]; + private static volatile RuntimeScalarReadOnly[] scalarByteString = new RuntimeScalarReadOnly[INITIAL_STRING_CACHE_SIZE]; private static volatile RuntimeScalarReadOnly[] scalarString = new RuntimeScalarReadOnly[INITIAL_STRING_CACHE_SIZE]; // Static block to initialize the cache diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java index 21ea93079..6578e67a7 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java @@ -92,25 +92,25 @@ public RuntimeScalar get(String key) { if (!elements.containsKey(key)) { // Check if any slots exist for this glob name String fullKey = namespace + key; - + // Check if the variable exists by trying to get it and checking if it's defined RuntimeScalar var = GlobalVariable.getGlobalVariable(fullKey); boolean hasScalarSlot = var.getDefinedBoolean(); - + boolean hasArraySlot = GlobalVariable.existsGlobalArray(fullKey); boolean hasHashSlot = GlobalVariable.existsGlobalHash(fullKey); - + RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(fullKey); boolean hasCodeSlot = codeRef.type == RuntimeScalarType.CODE && codeRef.getDefinedBoolean(); - + RuntimeScalar ioRef = GlobalVariable.getGlobalIO(fullKey); boolean hasIOSlot = ioRef.type == RuntimeScalarType.GLOB && ioRef.value instanceof RuntimeIO; - + RuntimeScalar formatRef = GlobalVariable.getGlobalFormatRef(fullKey); boolean hasFormatSlot = formatRef.type == RuntimeScalarType.FORMAT && formatRef.getDefinedBoolean(); - + boolean hasSlots = hasScalarSlot || hasArraySlot || hasHashSlot || hasCodeSlot || hasIOSlot || hasFormatSlot; - + return new RuntimeStashEntry(namespace + key, hasSlots); } return new RuntimeStashEntry(namespace + key, true); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java index 4370479b7..13d9dfb08 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java @@ -4,7 +4,6 @@ import java.util.Stack; -import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.getScalarInt; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef; /** @@ -19,14 +18,9 @@ */ public class ScalarSpecialVariable extends RuntimeBaseProxy { - private record InputLineState(RuntimeIO lastHandle, int lastLineNumber, RuntimeScalar localValue) { - } - private static final Stack inputLineStateStack = new Stack<>(); - // The type of special variable, represented by an enum. final Id variableId; - // The position of the capture group, used only for CAPTURE type variables. final int position; @@ -53,6 +47,9 @@ public ScalarSpecialVariable(Id variableId, int position) { this.position = position; } + private record InputLineState(RuntimeIO lastHandle, int lastLineNumber, RuntimeScalar localValue) { + } + /** * Throws an exception as this variable represents a constant item * and cannot be modified. @@ -113,34 +110,34 @@ public RuntimeScalar getValueAsScalar() { RuntimeScalar result = switch (variableId) { case CAPTURE -> { String capture = RuntimeRegex.captureString(position); - yield capture != null ? new RuntimeScalar(capture) : scalarUndef; + yield capture !=null ? new RuntimeScalar(capture) : scalarUndef; } case MATCH -> { String match = RuntimeRegex.matchString(); - yield match != null ? new RuntimeScalar(match) : scalarUndef; + yield match !=null ? new RuntimeScalar(match) : scalarUndef; } case PREMATCH -> { String prematch = RuntimeRegex.preMatchString(); - yield prematch != null ? new RuntimeScalar(prematch) : scalarUndef; + yield prematch !=null ? new RuntimeScalar(prematch) : scalarUndef; } case POSTMATCH -> { String postmatch = RuntimeRegex.postMatchString(); - yield postmatch != null ? new RuntimeScalar(postmatch) : scalarUndef; + yield postmatch !=null ? new RuntimeScalar(postmatch) : scalarUndef; } case P_PREMATCH -> { if (!RuntimeRegex.lastMatchUsedPFlag) yield scalarUndef; String prematch = RuntimeRegex.preMatchString(); - yield prematch != null ? new RuntimeScalar(prematch) : scalarUndef; + yield prematch !=null ? new RuntimeScalar(prematch) : scalarUndef; } case P_MATCH -> { if (!RuntimeRegex.lastMatchUsedPFlag) yield scalarUndef; String match = RuntimeRegex.matchString(); - yield match != null ? new RuntimeScalar(match) : scalarUndef; + yield match !=null ? new RuntimeScalar(match) : scalarUndef; } case P_POSTMATCH -> { if (!RuntimeRegex.lastMatchUsedPFlag) yield scalarUndef; String postmatch = RuntimeRegex.postMatchString(); - yield postmatch != null ? new RuntimeScalar(postmatch) : scalarUndef; + yield postmatch !=null ? new RuntimeScalar(postmatch) : scalarUndef; } case LAST_FH -> { if (RuntimeIO.lastAccesseddHandle == null) { @@ -159,7 +156,7 @@ public RuntimeScalar getValueAsScalar() { packageName = "main"; name = globName; } - + // Get the stash and access the glob RuntimeHash stash = HashSpecialVariable.getStash(packageName); RuntimeScalar glob = stash.get(name); @@ -178,11 +175,11 @@ public RuntimeScalar getValueAsScalar() { } yield scalarUndef; } - yield getScalarInt(RuntimeIO.lastAccesseddHandle.currentLineNumber); + yield getScalarInt (RuntimeIO.lastAccesseddHandle.currentLineNumber); } case LAST_PAREN_MATCH -> { String lastCapture = RuntimeRegex.lastCaptureString(); - yield lastCapture != null ? new RuntimeScalar(lastCapture) : scalarUndef; + yield lastCapture !=null ? new RuntimeScalar(lastCapture) : scalarUndef; } case LAST_SUCCESSFUL_PATTERN -> new RuntimeScalar(RuntimeRegex.lastSuccessfulPattern); case LAST_REGEXP_CODE_RESULT -> { @@ -190,7 +187,7 @@ public RuntimeScalar getValueAsScalar() { // Get the last matched regex and retrieve its code block result if (RuntimeRegex.lastSuccessfulPattern != null) { RuntimeScalar codeBlockResult = RuntimeRegex.lastSuccessfulPattern.getLastCodeBlockResult(); - yield codeBlockResult != null ? codeBlockResult : scalarUndef; + yield codeBlockResult !=null ? codeBlockResult : scalarUndef; } yield scalarUndef; } diff --git a/src/main/java/org/perlonjava/runtime/util/Base64Util.java b/src/main/java/org/perlonjava/runtime/util/Base64Util.java index 0332eba40..18e59270e 100644 --- a/src/main/java/org/perlonjava/runtime/util/Base64Util.java +++ b/src/main/java/org/perlonjava/runtime/util/Base64Util.java @@ -14,13 +14,13 @@ public class Base64Util { // Lookup table for decoding private static final int[] DECODE_TABLE = new int[256]; - + static { // Initialize decode table for (int i = 0; i < 256; i++) { DECODE_TABLE[i] = -1; } - + // Fill valid characters for (int i = 0; i < BASE64_CHARS.length(); i++) { DECODE_TABLE[BASE64_CHARS.charAt(i)] = i; @@ -34,46 +34,46 @@ public static String encode(byte[] data) { if (data == null) { return ""; } - + int len = data.length; if (len == 0) { return ""; } - + StringBuilder result = new StringBuilder(); - + // Process 3 bytes at a time int i = 0; for (; i < len - 2; i += 3) { int byte1 = data[i] & MASK_8BITS; int byte2 = data[i + 1] & MASK_8BITS; int byte3 = data[i + 2] & MASK_8BITS; - + // Get 4 6-bit values int c1 = byte1 >>> 2; int c2 = ((byte1 & 0x03) << 4) | (byte2 >>> 4); int c3 = ((byte2 & 0x0f) << 2) | (byte3 >>> 6); int c4 = byte3 & MASK_6BITS; - + // Convert to Base64 characters result.append(BASE64_CHARS.charAt(c1)); result.append(BASE64_CHARS.charAt(c2)); result.append(BASE64_CHARS.charAt(c3)); result.append(BASE64_CHARS.charAt(c4)); } - + // Handle padding for remaining bytes if (i < len) { int byte1 = data[i] & MASK_8BITS; - + if (i + 1 < len) { // 2 bytes remaining int byte2 = data[i + 1] & MASK_8BITS; - + int c1 = byte1 >>> 2; int c2 = ((byte1 & 0x03) << 4) | (byte2 >>> 4); int c3 = (byte2 & 0x0f) << 2; - + result.append(BASE64_CHARS.charAt(c1)); result.append(BASE64_CHARS.charAt(c2)); result.append(BASE64_CHARS.charAt(c3)); @@ -82,16 +82,16 @@ public static String encode(byte[] data) { // 1 byte remaining int c1 = byte1 >>> 2; int c2 = (byte1 & 0x03) << 4; - + result.append(BASE64_CHARS.charAt(c1)); result.append(BASE64_CHARS.charAt(c2)); result.append(PAD).append(PAD); // Two padding characters } } - + return result.toString(); } - + /** * Decodes a Base64 string into binary data. * Follows Perl's MIME::Base64 behavior of ignoring invalid characters. @@ -100,7 +100,7 @@ public static byte[] decode(String base64) { if (base64 == null || base64.isEmpty()) { return new byte[0]; } - + // Filter out non-Base64 characters (like Perl's MIME::Base64) StringBuilder filtered = new StringBuilder(); for (int i = 0; i < base64.length(); i++) { @@ -109,51 +109,51 @@ public static byte[] decode(String base64) { filtered.append(c); } } - + String cleanBase64 = filtered.toString(); if (cleanBase64.isEmpty()) { return new byte[0]; } - + // Find the first padding character and truncate after it int padIndex = cleanBase64.indexOf(PAD); if (padIndex >= 0) { cleanBase64 = cleanBase64.substring(0, padIndex); } - + // Calculate output length (3 bytes for every 4 Base64 chars, rounded down) int len = cleanBase64.length(); int outputLen = (len * 3) / 4; byte[] result = new byte[outputLen]; - + int resultIndex = 0; int i = 0; - + // Process 4 characters at a time for (; i <= len - 4; i += 4) { int c1 = getValue(cleanBase64.charAt(i)); int c2 = getValue(cleanBase64.charAt(i + 1)); int c3 = getValue(cleanBase64.charAt(i + 2)); int c4 = getValue(cleanBase64.charAt(i + 3)); - + // Combine 4 6-bit values into 3 bytes result[resultIndex++] = (byte) ((c1 << 2) | (c2 >>> 4)); result[resultIndex++] = (byte) ((c2 << 4) | (c3 >>> 2)); result[resultIndex++] = (byte) ((c3 << 6) | c4); } - + // Handle remaining characters (shouldn't happen with proper padding) if (i < len) { // We need at least 2 characters to produce a byte if (i + 1 < len) { int c1 = getValue(cleanBase64.charAt(i)); int c2 = getValue(cleanBase64.charAt(i + 1)); - + // First byte: 6 bits from c1 and 2 bits from c2 if (resultIndex < result.length) { result[resultIndex++] = (byte) ((c1 << 2) | (c2 >>> 4)); } - + // If we have a third character, we can get another byte if (i + 2 < len) { int c3 = getValue(cleanBase64.charAt(i + 2)); @@ -169,14 +169,14 @@ else if (i + 2 == len) { } } } - + return result; } - + private static int getValue(char c) { return (c < 128) ? DECODE_TABLE[c] : -1; } - + /** * Encodes data with MIME line breaks (76 chars per line). */ @@ -185,11 +185,11 @@ public static String encodeMime(byte[] data, String lineSeparator) { if (lineSeparator == null || lineSeparator.isEmpty()) { return base64; } - + StringBuilder result = new StringBuilder(); int pos = 0; int len = base64.length(); - + while (pos < len) { int end = Math.min(pos + 76, len); if (pos > 0) { @@ -198,12 +198,12 @@ public static String encodeMime(byte[] data, String lineSeparator) { result.append(base64, pos, end); pos = end; } - + // Always add line separator at the end if not empty if (len > 0) { result.append(lineSeparator); } - + return result.toString(); } } From a43ceb03cd9fe43fd16629443656b893a902926d Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 6 Mar 2026 13:31:40 +0100 Subject: [PATCH 5/5] reformat code --- .../backend/bytecode/BytecodeCompiler.java | 82 +++++++------------ .../backend/bytecode/CompileAssignment.java | 73 ++++++----------- .../bytecode/CompileBinaryOperator.java | 28 ++----- .../backend/bytecode/CompileOperator.java | 60 +++++--------- .../backend/bytecode/EvalStringHandler.java | 3 +- .../backend/bytecode/InlineOpcodeHandler.java | 9 +- .../backend/bytecode/InterpretedCode.java | 6 +- .../backend/bytecode/InterpreterState.java | 17 +--- .../bytecode/VariableCaptureAnalyzer.java | 42 ++++------ .../bytecode/VariableCollectorVisitor.java | 3 +- .../org/perlonjava/backend/jvm/EmitRegex.java | 3 +- .../backend/jvm/EmitterMethodCreator.java | 3 +- .../perlonjava/backend/jvm/JavaClassInfo.java | 9 +- .../frontend/parser/CoreOperatorResolver.java | 3 +- .../frontend/parser/ParseBlock.java | 13 +-- .../frontend/parser/StatementParser.java | 4 +- .../frontend/parser/StatementResolver.java | 5 +- .../frontend/parser/StringDoubleQuoted.java | 3 +- .../frontend/parser/SubroutineParser.java | 8 +- .../runtime/operators/FileTestOperator.java | 46 +++++------ .../operators/FormatModifierValidator.java | 35 ++++---- .../runtime/operators/MathOperators.java | 2 +- .../runtime/operators/ModuleOperators.java | 9 +- .../runtime/operators/Operator.java | 2 +- .../runtime/operators/OperatorHandler.java | 18 ++-- .../perlonjava/runtime/operators/Pack.java | 3 +- .../operators/pack/PackGroupHandler.java | 3 +- .../sprintf/SprintfValidationResult.java | 2 +- .../runtime/perlmodule/Builtin.java | 2 +- .../runtime/perlmodule/CompressZlib.java | 9 +- .../perlonjava/runtime/regex/RegexFlags.java | 10 +-- .../runtime/runtimetypes/CallerStack.java | 2 +- .../runtime/runtimetypes/DualVar.java | 2 +- .../runtimetypes/ErrorMessageUtil.java | 6 +- .../runtimetypes/ExceptionFormatter.java | 18 ++-- .../runtimetypes/OutputAutoFlushVariable.java | 6 +- .../runtime/runtimetypes/RuntimeArray.java | 30 +++---- .../runtime/runtimetypes/RuntimeCode.java | 73 +++++++---------- .../runtime/runtimetypes/RuntimeGlob.java | 16 ++-- .../runtime/runtimetypes/RuntimeHash.java | 4 +- .../runtime/runtimetypes/RuntimeScalar.java | 22 ++--- .../runtimetypes/ScalarSpecialVariable.java | 27 +++--- 42 files changed, 288 insertions(+), 433 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 6cc79e053..d984d922b 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -88,6 +88,7 @@ public class BytecodeCompiler implements Visitor { // Closure support private RuntimeBase[] capturedVars; // Captured variable values private String[] capturedVarNames; // Parallel array of names + public BytecodeCompiler(String sourceName, int sourceLine, ErrorMessageUtil errorUtil) { this.sourceName = sourceName; this.sourceLine = sourceLine; @@ -684,10 +685,10 @@ private Set getLocalVariableNames(EmitterContext ctx) { private RuntimeBase getVariableValueFromContext(String varName, EmitterContext ctx) { // For eval STRING, runtime values are available via evalRuntimeContext ThreadLocal RuntimeCode.EvalRuntimeContext evalCtx = RuntimeCode.getEvalRuntimeContext(); - if (evalCtx != null && evalCtx.runtimeValues != null) { + if (evalCtx != null && evalCtx.runtimeValues() != null) { // Find variable in captured environment - String[] capturedEnv = evalCtx.capturedEnv; - Object[] runtimeValues = evalCtx.runtimeValues; + String[] capturedEnv = evalCtx.capturedEnv(); + Object[] runtimeValues = evalCtx.runtimeValues(); for (int i = 0; i < capturedEnv.length; i++) { if (capturedEnv[i].equals(varName)) { @@ -1097,11 +1098,10 @@ void handleArrayElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { } // Compile the index expression (right side) - if (!(node.right instanceof ArrayLiteralNode)) { + if (!(node.right instanceof ArrayLiteralNode indexNode)) { throwCompilerException("Array subscript requires ArrayLiteralNode"); return; } - ArrayLiteralNode indexNode = (ArrayLiteralNode) node.right; if (indexNode.elements.isEmpty()) { throwCompilerException("Array subscript requires at least one index"); return; @@ -1165,9 +1165,8 @@ void handleArraySlice(BinaryOperatorNode node, OperatorNode leftOp) { emitReg(arrayReg); emit(nameIdx); } - } else if (leftOp.operand instanceof OperatorNode) { + } else if (leftOp.operand instanceof OperatorNode derefOp) { // Array dereference slice: @$arrayref[indices] - OperatorNode derefOp = (OperatorNode) leftOp.operand; if (derefOp.operator.equals("$")) { // Compile the scalar reference derefOp.accept(this); @@ -1196,11 +1195,10 @@ void handleArraySlice(BinaryOperatorNode node, OperatorNode leftOp) { } // Compile the indices (right side) - this is an ArrayLiteralNode - if (!(node.right instanceof ArrayLiteralNode)) { + if (!(node.right instanceof ArrayLiteralNode indicesNode)) { throwCompilerException("Array slice requires ArrayLiteralNode"); return; } - ArrayLiteralNode indicesNode = (ArrayLiteralNode) node.right; // Compile the indices into a list // The ArrayLiteralNode might contain a range operator (..) or multiple elements @@ -1268,11 +1266,10 @@ void handleHashElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { } // Compile the key expression (right side) - if (!(node.right instanceof HashLiteralNode)) { + if (!(node.right instanceof HashLiteralNode keyNode)) { throwCompilerException("Hash subscript requires HashLiteralNode"); return; } - HashLiteralNode keyNode = (HashLiteralNode) node.right; if (keyNode.elements.isEmpty()) { throwCompilerException("Hash subscript requires at least one key"); return; @@ -1469,12 +1466,11 @@ void handleHashKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { } } - if (!(node.right instanceof HashLiteralNode)) { + if (!(node.right instanceof HashLiteralNode keysNode)) { throwCompilerException("Key/value slice requires HashLiteralNode"); return; } - HashLiteralNode keysNode = (HashLiteralNode) node.right; if (keysNode.elements.isEmpty()) { throwCompilerException("Key/value slice requires at least one key"); return; @@ -1538,8 +1534,7 @@ void handleCompoundAssignment(BinaryOperatorNode node) { boolean isGlobal = false; // Check if left side is a simple variable reference - if (node.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; + if (node.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) { // Simple scalar variable: $x += 5 @@ -1648,11 +1643,10 @@ void handleGeneralArrayAccess(BinaryOperatorNode node) { int baseReg = lastResultReg; // Compile the index expression (right side) - if (!(node.right instanceof ArrayLiteralNode)) { + if (!(node.right instanceof ArrayLiteralNode indexNode)) { throwCompilerException("Array subscript requires ArrayLiteralNode"); return; } - ArrayLiteralNode indexNode = (ArrayLiteralNode) node.right; if (indexNode.elements.isEmpty()) { throwCompilerException("Array subscript requires at least one index"); return; @@ -1707,11 +1701,10 @@ void handleGeneralHashAccess(BinaryOperatorNode node) { int baseReg = lastResultReg; // Compile the key expression (right side) - if (!(node.right instanceof HashLiteralNode)) { + if (!(node.right instanceof HashLiteralNode keyNode)) { throwCompilerException("Hash subscript requires HashLiteralNode"); return; } - HashLiteralNode keyNode = (HashLiteralNode) node.right; if (keyNode.elements.isEmpty()) { throwCompilerException("Hash subscript requires at least one key"); return; @@ -1798,8 +1791,7 @@ void handlePushUnshift(BinaryOperatorNode node) { // Get the array int arrayReg; - if (node.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; + if (node.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("@") && leftOp.operand instanceof IdentifierNode) { // Direct array: @array @@ -1886,8 +1878,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { if (op.equals("my") || op.equals("state")) { // my $x / my @x / my %x / state $x / state @x / state %x - variable declaration // The operand will be OperatorNode("$"/"@"/"%", IdentifierNode("x")) - if (node.operand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) node.operand; + if (node.operand instanceof OperatorNode sigilOp) { String sigil = sigilOp.operator; if (sigilOp.operand instanceof IdentifierNode) { @@ -1997,9 +1988,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { return; } } - } else if (node.operand instanceof ListNode) { + } else if (node.operand instanceof ListNode listNode) { // my ($x, $y, @rest) - list of variable declarations - ListNode listNode = (ListNode) node.operand; List varRegs = new ArrayList<>(); List wrapWithRef = new ArrayList<>(); @@ -2012,8 +2002,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { boolean foundBackslashInList = false; for (Node element : listNode.elements) { - if (element instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) element; + if (element instanceof OperatorNode sigilOp) { String sigil = sigilOp.operator; // Handle backslash operator (reference constructor): my (\$x) or my (\($x, $y)) @@ -2365,8 +2354,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { } else if (op.equals("our")) { // our $x / our @x / our %x - package variable declaration // The operand will be OperatorNode("$"/"@"/"%", IdentifierNode("x")) - if (node.operand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) node.operand; + if (node.operand instanceof OperatorNode sigilOp) { String sigil = sigilOp.operator; if (sigilOp.operand instanceof IdentifierNode) { @@ -2515,9 +2503,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { return; } } - } else if (node.operand instanceof ListNode) { + } else if (node.operand instanceof ListNode listNode) { // our ($x, $y) - list of package variable declarations - ListNode listNode = (ListNode) node.operand; List varRegs = new ArrayList<>(); // Check if this is a declared reference (our \($x, $y)) @@ -2529,8 +2516,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { boolean foundBackslashInList = false; for (Node element : listNode.elements) { - if (element instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) element; + if (element instanceof OperatorNode sigilOp) { String sigil = sigilOp.operator; // Parser may rewrite element-level declared refs like \$h/\@h/\%h into a scalar $h @@ -2803,8 +2789,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { } else if (op.equals("local")) { // local $x - temporarily localize a global variable // The operand will be OperatorNode("$", IdentifierNode("x")) - if (node.operand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) node.operand; + if (node.operand instanceof OperatorNode sigilOp) { String sigil = sigilOp.operator; if (sigil.equals("$") && sigilOp.operand instanceof IdentifierNode) { @@ -2983,9 +2968,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { lastResultReg = rd; return; } - } else if (node.operand instanceof ListNode) { + } else if (node.operand instanceof ListNode listNode) { // local ($x, $y) - list of localized global variables - ListNode listNode = (ListNode) node.operand; List varRegs = new ArrayList<>(); // Check if this is a declared reference @@ -2996,8 +2980,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { boolean foundBackslashInList = false; for (Node element : listNode.elements) { - if (element instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) element; + if (element instanceof OperatorNode sigilOp) { String sigil = sigilOp.operator; // Handle backslash operator: local (\$x) or local (\($x, $y)) @@ -3252,10 +3235,9 @@ void compileVariableReference(OperatorNode node, String op) { lastResultReg = rd; } - } else if (node.operand instanceof BlockNode) { + } else if (node.operand instanceof BlockNode block) { // Symbolic reference via block: ${label:expr} or ${expr} // Execute the block to get a variable name string, then load that variable - BlockNode block = (BlockNode) node.operand; // Check strict refs at compile time — mirrors JVM path in EmitVariable.java int savedCtx = currentCallContext; @@ -3359,9 +3341,8 @@ void compileVariableReference(OperatorNode node, String op) { } else { lastResultReg = arrayReg; } - } else if (node.operand instanceof OperatorNode) { + } else if (node.operand instanceof OperatorNode operandOp) { // Dereference: @$arrayref or @{$hashref} - OperatorNode operandOp = (OperatorNode) node.operand; // Compile the reference operandOp.accept(this); @@ -3387,10 +3368,9 @@ void compileVariableReference(OperatorNode node, String op) { // Note: We don't check scalar context here because dereferencing // should return the array itself. The slice or other operation // will handle scalar context conversion if needed. - } else if (node.operand instanceof BlockNode) { + } else if (node.operand instanceof BlockNode blockNode) { // @{ block } - evaluate block and dereference the result // The block should return an arrayref - BlockNode blockNode = (BlockNode) node.operand; int savedCtx = currentCallContext; currentCallContext = RuntimeContextType.SCALAR; blockNode.accept(this); @@ -3519,8 +3499,7 @@ void compileVariableReference(OperatorNode node, String op) { } } else if (op.equals("*")) { // Glob variable dereference: *x - if (node.operand instanceof IdentifierNode) { - IdentifierNode idNode = (IdentifierNode) node.operand; + if (node.operand instanceof IdentifierNode idNode) { String varName = idNode.name; // Add package prefix if not present @@ -3570,8 +3549,7 @@ void compileVariableReference(OperatorNode node, String op) { } else if (op.equals("&")) { // Code reference: &subname // Gets a reference to a named subroutine - if (node.operand instanceof IdentifierNode) { - IdentifierNode idNode = (IdentifierNode) node.operand; + if (node.operand instanceof IdentifierNode idNode) { String subName = idNode.name; // Use NameNormalizer to properly handle package prefixes @@ -4316,10 +4294,8 @@ public void visit(For1Node node) { enterScope(); // Step 5: If we have a named lexical loop variable, add it to the scope now - if (node.variable != null && node.variable instanceof OperatorNode) { - OperatorNode varOp2 = (OperatorNode) node.variable; - if (varOp2.operator.equals("my") && varOp2.operand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) varOp2.operand; + if (node.variable != null && node.variable instanceof OperatorNode varOp2) { + if (varOp2.operator.equals("my") && varOp2.operand instanceof OperatorNode sigilOp) { if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; registerVariable(varName, varReg); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 9c3f34bb2..f701df327 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -20,10 +20,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, int rhsContext = RuntimeContextType.LIST; // Default // Check if LHS is a scalar assignment (my $x = ... or our $x = ...) - if (node.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; - if ((leftOp.operator.equals("my") || leftOp.operator.equals("state") || leftOp.operator.equals("our")) && leftOp.operand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) leftOp.operand; + if (node.left instanceof OperatorNode leftOp) { + if ((leftOp.operator.equals("my") || leftOp.operator.equals("state") || leftOp.operator.equals("our")) && leftOp.operand instanceof OperatorNode sigilOp) { if (sigilOp.operator.equals("$")) { // Scalar assignment: use SCALAR context for RHS rhsContext = RuntimeContextType.SCALAR; @@ -41,15 +39,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.currentCallContext = rhsContext; // Special case: my $x = value - if (node.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; + if (node.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("my") || leftOp.operator.equals("state")) { // Extract variable name from "my"/"state" operand Node myOperand = leftOp.operand; // Handle my $x (where $x is OperatorNode("$", IdentifierNode("x"))) - if (myOperand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) myOperand; + if (myOperand instanceof OperatorNode sigilOp) { if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; @@ -240,8 +236,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Handle my ($x, $y, @rest) = ... - list declaration with assignment // Uses SET_FROM_LIST to match JVM backend's setFromList() semantics - if (myOperand instanceof ListNode) { - ListNode listNode = (ListNode) myOperand; + if (myOperand instanceof ListNode listNode) { // Compile RHS first node.right.accept(bytecodeCompiler); @@ -339,8 +334,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, Node localOperand = leftOp.operand; // Handle local $hash{key} = value (localizing hash element) - if (localOperand instanceof BinaryOperatorNode) { - BinaryOperatorNode hashAccess = (BinaryOperatorNode) localOperand; + if (localOperand instanceof BinaryOperatorNode hashAccess) { if (hashAccess.operator.equals("{")) { // Compile the hash access to get the hash element reference // This returns a RuntimeScalar that is aliased to the hash slot @@ -366,8 +360,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Handle local $x (where $x is OperatorNode("$", IdentifierNode("x"))) - if (localOperand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) localOperand; + if (localOperand instanceof OperatorNode sigilOp) { if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; @@ -548,15 +541,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } return; } - } else if (localOperand instanceof ListNode) { + } else if (localOperand instanceof ListNode listNode) { // Handle local($x) = value or local($x, $y) = (v1, v2) - ListNode listNode = (ListNode) localOperand; // Special case: single element list local($x) = value if (listNode.elements.size() == 1) { Node element = listNode.elements.get(0); - if (element instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) element; + if (element instanceof OperatorNode sigilOp) { if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; @@ -600,8 +591,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, for (int i = 0; i < listNode.elements.size(); i++) { Node element = listNode.elements.get(i); - if (element instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) element; + if (element instanceof OperatorNode sigilOp) { if (sigilOp.operator.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; @@ -647,16 +637,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Regular assignment: $x = value // OPTIMIZATION: Detect $x = $x + $y and emit ADD_ASSIGN instead of ADD_SCALAR + ALIAS - if (node.left instanceof OperatorNode && node.right instanceof BinaryOperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; - BinaryOperatorNode rightBin = (BinaryOperatorNode) node.right; + if (node.left instanceof OperatorNode leftOp && node.right instanceof BinaryOperatorNode rightBin) { if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode && rightBin.operator.equals("+") && - rightBin.left instanceof OperatorNode) { + rightBin.left instanceof OperatorNode rightLeftOp) { String leftVarName = "$" + ((IdentifierNode) leftOp.operand).name; - OperatorNode rightLeftOp = (OperatorNode) rightBin.left; if (rightLeftOp.operator.equals("$") && rightLeftOp.operand instanceof IdentifierNode) { String rightLeftVarName = "$" + ((IdentifierNode) rightLeftOp.operand).name; @@ -691,9 +678,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, if (node.left instanceof OperatorNode leftOp && leftOp.operator.equals("$")) { boolean strictRefsEnabled = bytecodeCompiler.isStrictRefsEnabled(); - if (leftOp.operand instanceof BlockNode) { + if (leftOp.operand instanceof BlockNode block) { // ${block} = value — mirrors JVM EmitVariable.java case "$" - BlockNode block = (BlockNode) leftOp.operand; block.accept(bytecodeCompiler); int nameReg = bytecodeCompiler.lastResultReg; @@ -755,8 +741,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, int valueReg = bytecodeCompiler.lastResultReg; // Assign to LHS - if (node.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; + if (node.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) leftOp.operand).name; @@ -900,8 +885,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // We need to determine which and use the appropriate assignment // Extract the sigil from our operand - if (leftOp.operand instanceof OperatorNode) { - OperatorNode sigilOp = (OperatorNode) leftOp.operand; + if (leftOp.operand instanceof OperatorNode sigilOp) { String sigil = sigilOp.operator; if (sigil.equals("$")) { @@ -920,10 +904,9 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(targetReg); bytecodeCompiler.emitReg(valueReg); } - } else if (leftOp.operand instanceof ListNode) { + } else if (leftOp.operand instanceof ListNode listNode) { // our ($a, $b) = ... - list declaration with assignment // Uses SET_FROM_LIST to match JVM backend's setFromList() semantics - ListNode listNode = (ListNode) leftOp.operand; // Convert RHS to list int rhsListReg = bytecodeCompiler.allocateRegister(); @@ -1035,10 +1018,9 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = valueReg; - } else if (leftOp.operator.equals("@") && leftOp.operand instanceof OperatorNode) { + } else if (leftOp.operator.equals("@") && leftOp.operand instanceof OperatorNode derefOp) { // 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 @@ -1130,12 +1112,10 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emit(nameIdx); bytecodeCompiler.lastResultReg = lvalueReg; } - } else if (node.left instanceof BinaryOperatorNode) { - BinaryOperatorNode leftBin = (BinaryOperatorNode) node.left; + } else if (node.left instanceof BinaryOperatorNode leftBin) { // Handle array slice assignment: @array[1, 3, 5] = (20, 30, 40) - if (leftBin.operator.equals("[") && leftBin.left instanceof OperatorNode) { - OperatorNode arrayOp = (OperatorNode) leftBin.left; + if (leftBin.operator.equals("[") && leftBin.left instanceof OperatorNode arrayOp) { // Must be @array (not $array) if (arrayOp.operator.equals("@") && arrayOp.operand instanceof IdentifierNode) { @@ -1208,8 +1188,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, int arrayReg; // Check if left side is a variable or multidimensional access - if (leftBin.left instanceof OperatorNode) { - OperatorNode arrayOp = (OperatorNode) leftBin.left; + if (leftBin.left instanceof OperatorNode arrayOp) { // Single element assignment: $array[index] = value if (arrayOp.operator.equals("$") && arrayOp.operand instanceof IdentifierNode) { @@ -1297,8 +1276,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // 1. Get hash variable (leftBin.left) int hashReg; - if (leftBin.left instanceof OperatorNode) { - OperatorNode hashOp = (OperatorNode) leftBin.left; + if (leftBin.left instanceof OperatorNode hashOp) { // Check for hash slice assignment: @hash{keys} = values if (hashOp.operator.equals("@")) { @@ -1348,11 +1326,10 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // Get the keys from HashLiteralNode - if (!(leftBin.right instanceof HashLiteralNode)) { + if (!(leftBin.right instanceof HashLiteralNode keysNode)) { bytecodeCompiler.throwCompilerException("Hash slice assignment requires HashLiteralNode"); return; } - HashLiteralNode keysNode = (HashLiteralNode) leftBin.right; if (keysNode.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("Hash slice assignment requires at least one key"); return; @@ -1472,11 +1449,10 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, } // 2. Compile key expression - if (!(leftBin.right instanceof HashLiteralNode)) { + if (!(leftBin.right instanceof HashLiteralNode keyNode)) { bytecodeCompiler.throwCompilerException("Hash assignment requires HashLiteralNode on right side"); return; } - HashLiteralNode keyNode = (HashLiteralNode) leftBin.right; if (keyNode.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("Hash key required for assignment"); return; @@ -1642,12 +1618,11 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rhsReg); bytecodeCompiler.lastResultReg = rhsReg; bytecodeCompiler.currentCallContext = savedContext; - } else if (node.left instanceof ListNode) { + } else if (node.left instanceof ListNode listNode) { // List assignment: ($a, $b) = ... or () = ... // In scalar context, returns the number of elements on RHS // In list context, returns the RHS list LValueVisitor.getContext(node.left); - ListNode listNode = (ListNode) node.left; // RHS was already compiled at the "regular assignment" fallthrough above (valueReg). // Reuse it instead of compiling again. diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java index 1ae0e0989..0149c0084 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java @@ -52,8 +52,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato // Use LIST context only for array/hash args so they expand; // scalar expressions keep current context to avoid wrapping in RuntimeList int argsListReg = bytecodeCompiler.allocateRegister(); - if (node.right instanceof ListNode) { - ListNode argsList = (ListNode) node.right; + if (node.right instanceof ListNode argsList) { int savedContext = bytecodeCompiler.currentCallContext; java.util.List argRegs = new java.util.ArrayList<>(); for (Node arg : argsList.elements) { @@ -135,7 +134,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato if (node.operator.equals("->")) { bytecodeCompiler.currentTokenIndex = node.getIndex(); // Track token for error reporting - if (node.right instanceof HashLiteralNode) { + if (node.right instanceof HashLiteralNode keyNode) { // Hashref dereference: $ref->{key} // left: scalar containing hash reference // right: HashLiteralNode containing key @@ -151,7 +150,6 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato bytecodeCompiler.emitReg(scalarRefReg); // Get the key - HashLiteralNode keyNode = (HashLiteralNode) node.right; if (keyNode.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("Hash dereference requires key"); } @@ -182,7 +180,7 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato bytecodeCompiler.lastResultReg = rd; return; - } else if (node.right instanceof ArrayLiteralNode) { + } else if (node.right instanceof ArrayLiteralNode indexNode) { // Arrayref dereference: $ref->[index] // left: scalar containing array reference // right: ArrayLiteralNode containing index @@ -198,7 +196,6 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato bytecodeCompiler.emitReg(scalarRefReg); // Get the index - ArrayLiteralNode indexNode = (ArrayLiteralNode) node.right; if (indexNode.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("Array dereference requires index"); } @@ -255,8 +252,7 @@ else if (node.right instanceof ListNode) { } // Method call: ->method() or ->$method() // right is BinaryOperatorNode with operator "(" - else if (node.right instanceof BinaryOperatorNode) { - BinaryOperatorNode rightCall = (BinaryOperatorNode) node.right; + else if (node.right instanceof BinaryOperatorNode rightCall) { if (rightCall.operator.equals("(")) { // object.call(method, arguments, context) Node invocantNode = node.left; @@ -270,8 +266,7 @@ else if (node.right instanceof BinaryOperatorNode) { } // Convert method name to string if needed - if (methodNode instanceof OperatorNode) { - OperatorNode methodOp = (OperatorNode) methodNode; + if (methodNode instanceof OperatorNode methodOp) { // &method is introduced by parser if method is predeclared if (methodOp.operator.equals("&")) { methodNode = methodOp.operand; @@ -330,8 +325,7 @@ else if (node.right instanceof BinaryOperatorNode) { bytecodeCompiler.currentTokenIndex = node.getIndex(); // Check if this is an array slice: @array[indices] - if (node.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; + if (node.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("@")) { // This is an array slice - handle it specially bytecodeCompiler.handleArraySlice(node, leftOp); @@ -363,8 +357,7 @@ else if (node.right instanceof BinaryOperatorNode) { bytecodeCompiler.currentTokenIndex = node.getIndex(); // Check if this is a hash slice: @hash{keys} or @$hashref{keys} - if (node.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) node.left; + if (node.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("@")) { // This is a hash slice - handle it specially bytecodeCompiler.handleHashSlice(node, leftOp); @@ -604,9 +597,8 @@ else if (node.right instanceof BinaryOperatorNode) { // 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 + && node.right instanceof OperatorNode rightOp) { + if (rightOp.operand instanceof ListNode originalList && !rightOp.operator.equals("quoteRegex")) { // Check if it's a regex operator (replaceRegex, matchRegex, tr, transliterate) if (rightOp.operator.equals("replaceRegex") @@ -614,8 +606,6 @@ else if (node.right instanceof BinaryOperatorNode) { || rightOp.operator.equals("tr") || rightOp.operator.equals("transliterate")) { - ListNode originalList = (ListNode) rightOp.operand; - // For !~, check for s///r and y///r which don't make sense (mirrors JVM handleNotBindRegex) if (node.operator.equals("!~")) { if ((rightOp.operator.equals("tr") || rightOp.operator.equals("transliterate")) diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index c862d2df5..bc88c1798 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -249,8 +249,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Compile the operand - if (node.operand instanceof ListNode) { - ListNode list = (ListNode) node.operand; + if (node.operand instanceof ListNode list) { if (list.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("ref requires an argument"); } @@ -278,8 +277,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Compile the operand (code reference or function name) - if (node.operand instanceof ListNode) { - ListNode list = (ListNode) node.operand; + if (node.operand instanceof ListNode list) { if (list.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("prototype requires an argument"); } @@ -576,8 +574,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } } else if (op.equals("index") || op.equals("rindex")) { // index(str, substr, pos?) or rindex(str, substr, pos?) - if (node.operand instanceof ListNode) { - ListNode args = (ListNode) node.operand; + if (node.operand instanceof ListNode args) { int savedContext = bytecodeCompiler.currentCallContext; bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; @@ -1285,13 +1282,11 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode Node arg = list.elements.get(0); // Handle hash access: $hash{key} - if (arg instanceof BinaryOperatorNode && ((BinaryOperatorNode) arg).operator.equals("{")) { - BinaryOperatorNode hashAccess = (BinaryOperatorNode) arg; + if (arg instanceof BinaryOperatorNode hashAccess && ((BinaryOperatorNode) arg).operator.equals("{")) { // Get hash register (need to handle $hash{key} -> %hash) int hashReg; - if (hashAccess.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) hashAccess.left; + if (hashAccess.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) { // Simple: exists $hash{key} -> get %hash String varName = ((IdentifierNode) leftOp.operand).name; @@ -1354,8 +1349,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Compile key (right side contains HashLiteralNode) int keyReg; - if (hashAccess.right instanceof HashLiteralNode) { - HashLiteralNode keyNode = (HashLiteralNode) hashAccess.right; + if (hashAccess.right instanceof HashLiteralNode keyNode) { if (!keyNode.elements.isEmpty()) { Node keyElement = keyNode.elements.get(0); if (keyElement instanceof IdentifierNode) { @@ -1388,8 +1382,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.lastResultReg = rd; - } else if (arg instanceof BinaryOperatorNode && ((BinaryOperatorNode) arg).operator.equals("[")) { - BinaryOperatorNode arrayAccess = (BinaryOperatorNode) arg; + } else if (arg instanceof BinaryOperatorNode arrayAccess && ((BinaryOperatorNode) arg).operator.equals("[")) { int arrayReg = compileArrayForExistsDelete(bytecodeCompiler, arrayAccess, node.getIndex()); int indexReg = compileArrayIndex(bytecodeCompiler, arrayAccess); @@ -1426,12 +1419,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode Node arg = list.elements.get(0); // Handle hash access: $hash{key} or hash slice delete: delete @hash{keys} - if (arg instanceof BinaryOperatorNode && ((BinaryOperatorNode) arg).operator.equals("{")) { - BinaryOperatorNode hashAccess = (BinaryOperatorNode) arg; + if (arg instanceof BinaryOperatorNode hashAccess && ((BinaryOperatorNode) arg).operator.equals("{")) { // Check if it's a hash slice delete: delete @hash{keys} - if (hashAccess.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) hashAccess.left; + if (hashAccess.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("@")) { // Hash slice delete: delete @hash{'key1', 'key2'} // Use SLOW_OP for slice delete @@ -1460,11 +1451,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Get keys from HashLiteralNode - if (!(hashAccess.right instanceof HashLiteralNode)) { + if (!(hashAccess.right instanceof HashLiteralNode keysNode)) { bytecodeCompiler.throwCompilerException("Hash slice delete requires HashLiteralNode"); return; } - HashLiteralNode keysNode = (HashLiteralNode) hashAccess.right; // Compile all keys List keyRegs = new ArrayList<>(); @@ -1507,8 +1497,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Single key delete: delete $hash{key} // Get hash register (need to handle $hash{key} -> %hash) int hashReg; - if (hashAccess.left instanceof OperatorNode) { - OperatorNode leftOp = (OperatorNode) hashAccess.left; + if (hashAccess.left instanceof OperatorNode leftOp) { if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) { // Simple: delete $hash{key} -> get %hash String varName = ((IdentifierNode) leftOp.operand).name; @@ -1571,8 +1560,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Compile key (right side contains HashLiteralNode) int keyReg; - if (hashAccess.right instanceof HashLiteralNode) { - HashLiteralNode keyNode = (HashLiteralNode) hashAccess.right; + if (hashAccess.right instanceof HashLiteralNode keyNode) { if (!keyNode.elements.isEmpty()) { Node keyElement = keyNode.elements.get(0); if (keyElement instanceof IdentifierNode) { @@ -1605,9 +1593,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.lastResultReg = rd; - } else if (arg instanceof BinaryOperatorNode && ((BinaryOperatorNode) arg).operator.equals("->")) { + } else if (arg instanceof BinaryOperatorNode arrowAccess && ((BinaryOperatorNode) arg).operator.equals("->")) { // Arrow dereference: delete $ref->{key} - BinaryOperatorNode arrowAccess = (BinaryOperatorNode) arg; // Compile the reference expression arrowAccess.left.accept(bytecodeCompiler); int scalarReg = bytecodeCompiler.lastResultReg; @@ -1652,8 +1639,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.lastResultReg = rd; - } else if (arg instanceof BinaryOperatorNode && ((BinaryOperatorNode) arg).operator.equals("[")) { - BinaryOperatorNode arrayAccess = (BinaryOperatorNode) arg; + } else if (arg instanceof BinaryOperatorNode arrayAccess && ((BinaryOperatorNode) arg).operator.equals("[")) { int arrayReg = compileArrayForExistsDelete(bytecodeCompiler, arrayAccess, node.getIndex()); int indexReg = compileArrayIndex(bytecodeCompiler, arrayAccess); @@ -1729,8 +1715,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } } else { Node actualOperand = node.operand; - if (actualOperand instanceof ListNode) { - ListNode list = (ListNode) actualOperand; + if (actualOperand instanceof ListNode list) { actualOperand = list.elements.get(0); } actualOperand.accept(bytecodeCompiler); @@ -1788,8 +1773,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode int arrayReg = -1; // Handle different operand types - if (node.operand instanceof OperatorNode) { - OperatorNode operandOp = (OperatorNode) node.operand; + if (node.operand instanceof OperatorNode operandOp) { if (operandOp.operator.equals("@") && operandOp.operand instanceof IdentifierNode) { // $#@array or $#array (both work) @@ -1876,8 +1860,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } // Compile the operand - if (node.operand instanceof ListNode) { - ListNode list = (ListNode) node.operand; + if (node.operand instanceof ListNode list) { if (list.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("length requires an argument"); } @@ -2175,8 +2158,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else { - if (node.operand instanceof ListNode) { - ListNode list = (ListNode) node.operand; + if (node.operand instanceof ListNode list) { if (!list.elements.isEmpty()) { list.elements.get(0).accept(bytecodeCompiler); } @@ -2211,8 +2193,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.lastResultReg = rd; } else if (op.equals("sprintf")) { // sprintf($format, @args) - SprintfOperator.sprintf - if (node.operand instanceof ListNode) { - ListNode list = (ListNode) node.operand; + if (node.operand instanceof ListNode list) { if (list.elements.isEmpty()) { bytecodeCompiler.throwCompilerException("sprintf requires a format argument"); } @@ -2584,8 +2565,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } else if (op.equals("atan2")) { // atan2($y, $x) - returns arctangent of y/x // Format: ATAN2 rd rs1 rs2 - if (node.operand instanceof ListNode) { - ListNode list = (ListNode) node.operand; + if (node.operand instanceof ListNode list) { if (list.elements.size() >= 2) { list.elements.get(0).accept(bytecodeCompiler); int rs1 = bytecodeCompiler.lastResultReg; diff --git a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java index f5625ebb4..c7cc5e8e9 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java @@ -201,9 +201,8 @@ public static RuntimeList evalStringList(String perlCode, // Only capture actual Perl variables: Scalar, Array, Hash, Code if (value == null) { // Null is fine - capture it - } else if (value instanceof RuntimeScalar) { + } else if (value instanceof RuntimeScalar scalar) { // Check if the scalar contains an Iterator (used by for loops) - RuntimeScalar scalar = (RuntimeScalar) value; if (scalar.value instanceof java.util.Iterator) { // Skip - this is a for loop iterator, not a user variable continue; diff --git a/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java index 8cfaff9ee..d35df3b79 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java @@ -390,11 +390,9 @@ public static int executeArrayGet(int[] bytecode, int pc, RuntimeBase[] register RuntimeBase arrayBase = registers[arrayReg]; RuntimeScalar idx = (RuntimeScalar) registers[indexReg]; - if (arrayBase instanceof RuntimeArray) { - RuntimeArray arr = (RuntimeArray) arrayBase; + if (arrayBase instanceof RuntimeArray arr) { registers[rd] = arr.get(idx.getInt()); - } else if (arrayBase instanceof RuntimeList) { - RuntimeList list = (RuntimeList) arrayBase; + } else if (arrayBase instanceof RuntimeList list) { int index = idx.getInt(); if (index < 0) index = list.elements.size() + index; registers[rd] = (index >= 0 && index < list.elements.size()) @@ -1084,8 +1082,7 @@ public static int executeDeref(int[] bytecode, int pc, RuntimeBase[] registers) int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - if (value instanceof RuntimeScalar) { - RuntimeScalar scalar = (RuntimeScalar) value; + if (value instanceof RuntimeScalar scalar) { if (scalar.type == RuntimeScalarType.REFERENCE) { registers[rd] = scalar.scalarDeref(); } else { diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index c871055c9..9a356763d 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -320,12 +320,10 @@ public String disassemble() { if (constants != null && constIdx < constants.length) { Object obj = constants[constIdx]; sb.append(" ("); - if (obj instanceof RuntimeScalar) { - RuntimeScalar scalar = (RuntimeScalar) obj; + if (obj instanceof RuntimeScalar scalar) { sb.append("RuntimeScalar{type=").append(scalar.type).append(", value=").append(scalar.value.getClass().getSimpleName()).append("}"); - } else if (obj instanceof PerlRange) { + } else if (obj instanceof PerlRange range) { // Special handling for PerlRange to avoid expanding large ranges - PerlRange range = (PerlRange) obj; sb.append("PerlRange{").append(range.getStart().toString()).append("..") .append(range.getEnd().toString()).append("}"); } else { diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java b/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java index 92200a4df..d2f9abc7b 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpreterState.java @@ -113,19 +113,10 @@ public static List getPcStack() { } /** - * Represents a single interpreter call frame. - * Contains minimal information needed for stack trace formatting. - */ - public static class InterpreterFrame { - public final InterpretedCode code; - public final String packageName; - public final String subroutineName; - - public InterpreterFrame(InterpretedCode code, String packageName, String subroutineName) { - this.code = code; - this.packageName = packageName; - this.subroutineName = subroutineName; - } + * Represents a single interpreter call frame. + * Contains minimal information needed for stack trace formatting. + */ + public record InterpreterFrame(InterpretedCode code, String packageName, String subroutineName) { } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java b/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java index f588a4b19..4684ac602 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java +++ b/src/main/java/org/perlonjava/backend/bytecode/VariableCaptureAnalyzer.java @@ -71,8 +71,7 @@ public static Set analyze(Node mainScript, Set outerScopeVars) { private static List findNamedSubroutines(Node node) { List subs = new ArrayList<>(); - if (node instanceof SubroutineNode) { - SubroutineNode sub = (SubroutineNode) node; + if (node instanceof SubroutineNode sub) { // Only include named subroutines (not anonymous closures) if (sub.name != null && !sub.name.isEmpty()) { subs.add(sub); @@ -84,27 +83,22 @@ private static List findNamedSubroutines(Node node) { for (Node child : ((BlockNode) node).elements) { subs.addAll(findNamedSubroutines(child)); } - } else if (node instanceof OperatorNode) { - OperatorNode op = (OperatorNode) node; + } else if (node instanceof OperatorNode op) { if (op.operand != null) { subs.addAll(findNamedSubroutines(op.operand)); } - } else if (node instanceof For1Node) { - For1Node forNode = (For1Node) node; + } else if (node instanceof For1Node forNode) { if (forNode.body != null) { subs.addAll(findNamedSubroutines(forNode.body)); } - } else if (node instanceof For3Node) { - For3Node forNode = (For3Node) node; + } else if (node instanceof For3Node forNode) { if (forNode.body != null) { subs.addAll(findNamedSubroutines(forNode.body)); } - } else if (node instanceof BinaryOperatorNode) { - BinaryOperatorNode bin = (BinaryOperatorNode) node; + } else if (node instanceof BinaryOperatorNode bin) { if (bin.left != null) subs.addAll(findNamedSubroutines(bin.left)); if (bin.right != null) subs.addAll(findNamedSubroutines(bin.right)); - } else if (node instanceof TernaryOperatorNode) { - TernaryOperatorNode tern = (TernaryOperatorNode) node; + } else if (node instanceof TernaryOperatorNode tern) { if (tern.condition != null) subs.addAll(findNamedSubroutines(tern.condition)); if (tern.trueExpr != null) subs.addAll(findNamedSubroutines(tern.trueExpr)); if (tern.falseExpr != null) subs.addAll(findNamedSubroutines(tern.falseExpr)); @@ -125,8 +119,7 @@ private static Set findVariableReferences(Node node) { } // Check if this node is a variable reference - if (node instanceof IdentifierNode) { - IdentifierNode id = (IdentifierNode) node; + if (node instanceof IdentifierNode id) { String name = id.name; // Only include lexical variables (not package variables with ::) if (!name.contains("::")) { @@ -139,40 +132,33 @@ private static Set findVariableReferences(Node node) { for (Node child : ((BlockNode) node).elements) { vars.addAll(findVariableReferences(child)); } - } else if (node instanceof OperatorNode) { - OperatorNode op = (OperatorNode) node; + } else if (node instanceof OperatorNode op) { if (op.operand != null) { vars.addAll(findVariableReferences(op.operand)); } - } else if (node instanceof SubroutineNode) { + } else if (node instanceof SubroutineNode sub) { // Don't recurse into nested subroutines - they have their own scope // We only care about variables in the immediate subroutine - SubroutineNode sub = (SubroutineNode) node; if (sub.block != null) { vars.addAll(findVariableReferences(sub.block)); } - } else if (node instanceof For1Node) { - For1Node forNode = (For1Node) node; + } else if (node instanceof For1Node forNode) { if (forNode.variable != null) vars.addAll(findVariableReferences(forNode.variable)); if (forNode.list != null) vars.addAll(findVariableReferences(forNode.list)); if (forNode.body != null) vars.addAll(findVariableReferences(forNode.body)); - } else if (node instanceof For3Node) { - For3Node forNode = (For3Node) node; + } else if (node instanceof For3Node forNode) { if (forNode.initialization != null) vars.addAll(findVariableReferences(forNode.initialization)); if (forNode.condition != null) vars.addAll(findVariableReferences(forNode.condition)); if (forNode.increment != null) vars.addAll(findVariableReferences(forNode.increment)); if (forNode.body != null) vars.addAll(findVariableReferences(forNode.body)); - } else if (node instanceof BinaryOperatorNode) { - BinaryOperatorNode bin = (BinaryOperatorNode) node; + } else if (node instanceof BinaryOperatorNode bin) { if (bin.left != null) vars.addAll(findVariableReferences(bin.left)); if (bin.right != null) vars.addAll(findVariableReferences(bin.right)); - } else if (node instanceof TernaryOperatorNode) { - TernaryOperatorNode tern = (TernaryOperatorNode) node; + } else if (node instanceof TernaryOperatorNode tern) { if (tern.condition != null) vars.addAll(findVariableReferences(tern.condition)); if (tern.trueExpr != null) vars.addAll(findVariableReferences(tern.trueExpr)); if (tern.falseExpr != null) vars.addAll(findVariableReferences(tern.falseExpr)); - } else if (node instanceof ListNode) { - ListNode list = (ListNode) node; + } else if (node instanceof ListNode list) { for (Node element : list.elements) { if (element != null) { vars.addAll(findVariableReferences(element)); diff --git a/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java b/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java index a232ffc7e..bcfca286a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java +++ b/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java @@ -37,9 +37,8 @@ public void visit(OperatorNode node) { // Check if this is a variable reference (sigil + identifier) String op = node.operator; if ((op.equals("$") || op.equals("@") || op.equals("%") || op.equals("&")) - && node.operand instanceof IdentifierNode) { + && node.operand instanceof IdentifierNode idNode) { // This is a variable reference - IdentifierNode idNode = (IdentifierNode) node.operand; String varName = op + idNode.name; variables.add(varName); } diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java b/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java index cea656d35..c17e59bdc 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java @@ -133,8 +133,7 @@ static void handleSystemCommand(EmitterVisitor emitterVisitor, OperatorNode node // Handle two cases: // 1. readpipe() with no args -> operand is OperatorNode for $_ // 2. readpipe($expr) or `cmd` -> operand is ListNode with command - if (node.operand instanceof ListNode) { - ListNode operand = (ListNode) node.operand; + if (node.operand instanceof ListNode operand) { commandNode = operand.elements.getFirst(); } else { // readpipe() with no arguments uses $_ diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java index 022791a80..373895c85 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java @@ -1420,8 +1420,7 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE )); // Add refactoring information if available - if (ast instanceof BlockNode) { - BlockNode blockNode = (BlockNode) ast; + if (ast instanceof BlockNode blockNode) { Object estimatedSize = blockNode.getAnnotation("estimatedBytecodeSize"); Object skipReason = blockNode.getAnnotation("refactorSkipReason"); diff --git a/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java b/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java index e6b538036..ef3d3329d 100644 --- a/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java +++ b/src/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java @@ -288,13 +288,6 @@ public String toString() { "}"; } - public static final class SpillRef { - public final int slot; - public final boolean pooled; - - public SpillRef(int slot, boolean pooled) { - this.slot = slot; - this.pooled = pooled; - } + public record SpillRef(int slot, boolean pooled) { } } diff --git a/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java b/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java index 454ddf8c2..0d20223e1 100644 --- a/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java +++ b/src/main/java/org/perlonjava/frontend/parser/CoreOperatorResolver.java @@ -33,8 +33,7 @@ public static Node parseCoreOperator(Parser parser, LexerToken token, int startI return switch (operatorName) { case "__LINE__" -> { handleEmptyParentheses(parser); - yield - new NumberNode(Integer.toString(parser.ctx.errorUtil.getLineNumber(parser.tokenIndex)), parser.tokenIndex); + yield new NumberNode(Integer.toString(parser.ctx.errorUtil.getLineNumber(parser.tokenIndex)), parser.tokenIndex); } case "__FILE__" -> { handleEmptyParentheses(parser); diff --git a/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java b/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java index d1d4cc82e..9e572eaf4 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java +++ b/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java @@ -149,15 +149,8 @@ private static String parseLabel(Parser parser, List statements, List { diff --git a/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java b/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java index 29195e7af..72920c5bc 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java +++ b/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java @@ -328,8 +328,7 @@ private Node createJoinNode(List nodes) { default -> { var listNode = new ListNode(parser.tokenIndex); listNode.elements.addAll(nodes); - yield - new BinaryOperatorNode("join", new StringNode("", parser.tokenIndex), listNode, parser.tokenIndex); + yield new BinaryOperatorNode("join", new StringNode("", parser.tokenIndex), listNode, parser.tokenIndex); } }; } diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java index 8147c9650..15d2bbd96 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java @@ -795,10 +795,8 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S EmitterMethodCreator.createRuntimeCode(newCtx, block, false); try { - if (runtimeCode instanceof CompiledCode) { + if (runtimeCode instanceof CompiledCode compiledCode) { // CompiledCode path - fill in the existing placeholder - CompiledCode compiledCode = - (CompiledCode) runtimeCode; Class generatedClass = compiledCode.generatedClass; // Prepare constructor with the captured variable types @@ -816,14 +814,12 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S Field field = placeholder.codeObject.getClass().getDeclaredField("__SUB__"); field.set(placeholder.codeObject, codeRef); - } else if (runtimeCode instanceof InterpretedCode) { + } else if (runtimeCode instanceof InterpretedCode interpretedCode) { // InterpretedCode path - update placeholder in-place (not replace codeRef.value) // This is critical: hash assignments copy RuntimeScalar but share the same // RuntimeCode value object. If we replace codeRef.value, hash copies won't see // the update. By setting methodHandle/codeObject on the placeholder, ALL // references (including hash copies) will see the compiled code. - InterpretedCode interpretedCode = - (InterpretedCode) runtimeCode; // Set captured variables if there are any if (!paramList.isEmpty()) { diff --git a/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java b/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java index 4699578cc..17a67d8d2 100644 --- a/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java @@ -165,7 +165,7 @@ private static RuntimeScalar fileTestFromLastStat(String operator) { case "-d" -> getScalarBoolean(lastBasicAttr.isDirectory()); case "-s" -> { long size = lastBasicAttr.size(); - yield size >0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; + yield size > 0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; } case "-z" -> getScalarBoolean(lastBasicAttr.size() == 0); case "-l" -> getScalarBoolean(lastBasicAttr.isSymbolicLink()); @@ -315,7 +315,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isReadable(path)); + yield getScalarBoolean(Files.isReadable(path)); } case "-w" -> { // Check if file is writable @@ -324,7 +324,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isWritable(path)); + yield getScalarBoolean(Files.isWritable(path)); } case "-x" -> { // Check if file is executable @@ -333,7 +333,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isExecutable(path)); + yield getScalarBoolean(Files.isExecutable(path)); } case "-e" -> { // Check if file exists @@ -352,7 +352,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.size(path) == 0); + yield getScalarBoolean(Files.size(path) == 0); } case "-s" -> { // Return file size if non-zero, otherwise return false @@ -360,7 +360,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } long size = lastBasicAttr.size(); - yield size >0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; + yield size > 0 ? new RuntimeScalar(size) : RuntimeScalarCache.scalarZero; } case "-f" -> { // Check if path is a regular file @@ -369,7 +369,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isRegularFile(path)); + yield getScalarBoolean(Files.isRegularFile(path)); } case "-d" -> { // Check if path is a directory @@ -378,14 +378,14 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isDirectory(path)); + yield getScalarBoolean(Files.isDirectory(path)); } case "-l" -> { // Check if path is a symbolic link if (!lastStatOk) { yield scalarUndef; } - yield getScalarBoolean (lastBasicAttr.isSymbolicLink()); + yield getScalarBoolean(lastBasicAttr.isSymbolicLink()); } case "-o" -> { // Check if file is owned by the effective user id (approximate with current user) @@ -397,7 +397,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) UserPrincipal owner = Files.getOwner(path); UserPrincipal currentUser = path.getFileSystem().getUserPrincipalLookupService() .lookupPrincipalByName(System.getProperty("user.name")); - yield getScalarBoolean (owner.equals(currentUser)); + yield getScalarBoolean(owner.equals(currentUser)); } case "-p" -> { // Approximate check for named pipe (FIFO) @@ -406,7 +406,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isRegularFile(path) && filename.endsWith(".fifo")); + yield getScalarBoolean(Files.isRegularFile(path) && filename.endsWith(".fifo")); } case "-S" -> { // Approximate check for socket @@ -415,7 +415,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isRegularFile(path) && filename.endsWith(".sock")); + yield getScalarBoolean(Files.isRegularFile(path) && filename.endsWith(".sock")); } case "-b" -> { // Approximate check for block special file @@ -424,7 +424,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isRegularFile(path) && filename.startsWith("/dev/")); + yield getScalarBoolean(Files.isRegularFile(path) && filename.startsWith("/dev/")); } case "-c" -> { // Approximate check for character special file @@ -433,7 +433,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isRegularFile(path) && filename.startsWith("/dev/")); + yield getScalarBoolean(Files.isRegularFile(path) && filename.startsWith("/dev/")); } case "-u" -> { // Check if setuid bit is set @@ -443,7 +443,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) } getGlobalVariable("main::!").set(0); // Clear error yield getScalarBoolean - ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OWNER_EXECUTE))); + ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OWNER_EXECUTE))); } case "-g" -> { // Check if setgid bit is set @@ -453,7 +453,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) } getGlobalVariable("main::!").set(0); // Clear error yield getScalarBoolean - ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.GROUP_EXECUTE))); + ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.GROUP_EXECUTE))); } case "-k" -> { // Approximate check for sticky bit (using others execute permission) @@ -463,7 +463,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) } getGlobalVariable("main::!").set(0); // Clear error yield getScalarBoolean - ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OTHERS_EXECUTE))); + ((Files.getPosixFilePermissions(path).contains(PosixFilePermission.OTHERS_EXECUTE))); } case "-T", "-B" -> { // Check if file is text (-T) or binary (-B) @@ -472,7 +472,7 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield isTextOrBinary (path, operator.equals("-T")) + yield isTextOrBinary(path, operator.equals("-T")); } case "-M", "-A", "-C" -> { // Get file time difference for modification (-M), access (-A), or creation (-C) time @@ -481,7 +481,7 @@ yield isTextOrBinary (path, operator.equals("-T")) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getFileTimeDifference (path, operator) + yield getFileTimeDifference(path, operator); } case "-R" -> { // Check if file is readable by the real user ID @@ -490,7 +490,7 @@ yield getFileTimeDifference (path, operator) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isReadable(path)); + yield getScalarBoolean(Files.isReadable(path)); } case "-W" -> { // Check if file is writable by the real user ID @@ -499,7 +499,7 @@ yield getFileTimeDifference (path, operator) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isWritable(path)); + yield getScalarBoolean(Files.isWritable(path)); } case "-X" -> { // Check if file is executable by the real user ID @@ -508,7 +508,7 @@ yield getFileTimeDifference (path, operator) yield scalarUndef; } getGlobalVariable("main::!").set(0); // Clear error - yield getScalarBoolean (Files.isExecutable(path)); + yield getScalarBoolean(Files.isExecutable(path)); } case "-O" -> { // Check if file is owned by the current user @@ -520,7 +520,7 @@ yield getFileTimeDifference (path, operator) UserPrincipal owner = Files.getOwner(path); UserPrincipal currentUser = path.getFileSystem().getUserPrincipalLookupService() .lookupPrincipalByName(System.getProperty("user.name")); - yield getScalarBoolean (owner.equals(currentUser)); + yield getScalarBoolean(owner.equals(currentUser)); } case "-t" -> { // -t on a string filename is an error in Perl (expects a filehandle) diff --git a/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java b/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java index 8ee50730a..dc4de5b37 100644 --- a/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java +++ b/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java @@ -1,6 +1,5 @@ package org.perlonjava.runtime.operators; -import org.perlonjava.runtime.operators.FormatModifierValidator.Modifier; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; import org.perlonjava.runtime.runtimetypes.RuntimeScalarCache; @@ -145,23 +144,6 @@ public static ValidationRule getValidationRule(char formatChar) { return VALIDATION_TABLE.get(formatChar); } - /** - * Validation rule for a format character - */ - public record ValidationRule(Set allowedModifiers, Set disallowedModifiers) { - public ValidationRule(Set < Modifier > allowedModifiers, Set < Modifier > disallowedModifiers) { - this.allowedModifiers = allowedModifiers != null ? allowedModifiers : Collections.emptySet(); - this.disallowedModifiers = disallowedModifiers != null ? disallowedModifiers : Collections.emptySet(); - } - - public boolean isModifierAllowed (Modifier modifier){ - if (!disallowedModifiers.isEmpty()) { - return !disallowedModifiers.contains(modifier); - } - return allowedModifiers.isEmpty() || allowedModifiers.contains(modifier); - } - } - /** * Enum for modifier types */ @@ -189,4 +171,21 @@ public char getSymbol() { return symbol; } } + + /** + * Validation rule for a format character + */ + public record ValidationRule(Set allowedModifiers, Set disallowedModifiers) { + public ValidationRule(Set allowedModifiers, Set disallowedModifiers) { + this.allowedModifiers = allowedModifiers != null ? allowedModifiers : Collections.emptySet(); + this.disallowedModifiers = disallowedModifiers != null ? disallowedModifiers : Collections.emptySet(); + } + + public boolean isModifierAllowed(Modifier modifier) { + if (!disallowedModifiers.isEmpty()) { + return !disallowedModifiers.contains(modifier); + } + return allowedModifiers.isEmpty() || allowedModifiers.contains(modifier); + } + } } diff --git a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java index 23dddac40..e0bb87701 100644 --- a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java @@ -726,7 +726,7 @@ public static RuntimeScalar not(RuntimeScalar runtimeScalar) { case DOUBLE -> getScalarBoolean((double) runtimeScalar.value == 0.0); case STRING, BYTE_STRING -> { String s = (String) runtimeScalar.value; - yield getScalarBoolean (s.isEmpty() || s.equals("0")); + yield getScalarBoolean(s.isEmpty() || s.equals("0")); } case BOOLEAN -> getScalarBoolean(!(boolean) runtimeScalar.value); case GLOB -> scalarFalse; diff --git a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java index 14b096be5..44bbb1925 100644 --- a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java @@ -132,8 +132,7 @@ private static RuntimeBase doFile(RuntimeScalar runtimeScalar, boolean setINC, b // ===== STEP 1: Handle ARRAY reference ===== // Array format: [coderef|filehandle, state...] if (runtimeScalar.type == RuntimeScalarType.ARRAYREFERENCE && - runtimeScalar.value instanceof RuntimeArray) { - RuntimeArray arr = (RuntimeArray) runtimeScalar.value; + runtimeScalar.value instanceof RuntimeArray arr) { if (arr.size() > 0) { RuntimeScalar firstElem = arr.get(0); @@ -486,8 +485,7 @@ else if (code == null) { incHookRef = dirScalar; break; } else if (hookResultScalar.type == RuntimeScalarType.ARRAYREFERENCE && - hookResultScalar.value instanceof RuntimeArray) { - RuntimeArray resultArray = (RuntimeArray) hookResultScalar.value; + hookResultScalar.value instanceof RuntimeArray resultArray) { if (resultArray.size() > 0) { RuntimeScalar firstElem = resultArray.get(0); if (firstElem.type == RuntimeScalarType.GLOB || @@ -848,8 +846,7 @@ else if (hook.type == RuntimeScalarType.REFERENCE && hook.value instanceof Runti codeRef = (RuntimeCode) hook.value; } // Case 4: ARRAY reference (not blessed) - call first element as coderef with array as $self - else if (hook.type == RuntimeScalarType.ARRAYREFERENCE && hook.value instanceof RuntimeArray) { - RuntimeArray arr = (RuntimeArray) hook.value; + else if (hook.type == RuntimeScalarType.ARRAYREFERENCE && hook.value instanceof RuntimeArray arr) { if (arr.size() > 0) { RuntimeScalar firstElem = arr.get(0); if (firstElem.type == RuntimeScalarType.CODE) { diff --git a/src/main/java/org/perlonjava/runtime/operators/Operator.java b/src/main/java/org/perlonjava/runtime/operators/Operator.java index 478b42baa..057789482 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Operator.java +++ b/src/main/java/org/perlonjava/runtime/operators/Operator.java @@ -381,7 +381,7 @@ public static RuntimeList splice(RuntimeArray runtimeArray, RuntimeList list) { } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield splice (runtimeArray, list)// Recursive call after vivification + yield splice(runtimeArray, list); // Recursive call after vivification } case TIED_ARRAY -> TieArray.tiedSplice(runtimeArray, list); default -> throw new IllegalStateException("Unknown array type: " + runtimeArray.type); diff --git a/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java b/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java index 557423c4c..5d63913ad 100644 --- a/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java @@ -349,7 +349,7 @@ public record OperatorHandler(String className, String methodName, int methodTyp * @param methodName The name of the method to associate with the operator. * @param className The name of the class containing the method. */ - static void put (String operator, String methodName, String className){ + static void put(String operator, String methodName, String className) { operatorHandlers.put(operator, new OperatorHandler(className, methodName, @@ -365,7 +365,7 @@ static void put (String operator, String methodName, String className){ * @param className The name of the class containing the method. * @param descriptor The JVM parameter descriptor */ - static void put (String operator, String methodName, String className, String descriptor){ + static void put(String operator, String methodName, String className, String descriptor) { operatorHandlers.put(operator, new OperatorHandler(className, methodName, @@ -379,7 +379,7 @@ static void put (String operator, String methodName, String className, String de * @param operator The operator symbol. * @return The OperatorHandler associated with the operator, or null if not found. */ - public static OperatorHandler get (String operator){ + public static OperatorHandler get(String operator) { return operatorHandlers.get(operator); } @@ -389,7 +389,7 @@ public static OperatorHandler get (String operator){ * @return The class name. */ @Override - public String className () { + public String className() { return className; } @@ -399,7 +399,7 @@ public String className () { * @return The method name. */ @Override - public String methodName () { + public String methodName() { return methodName; } @@ -409,7 +409,7 @@ public String methodName () { * @return The method type. */ @Override - public int methodType () { + public int methodType() { return methodType; } @@ -419,7 +419,7 @@ public int methodType () { * @return The method descriptor. */ @Override - public String descriptor () { + public String descriptor() { return descriptor; } @@ -428,7 +428,7 @@ public String descriptor () { * * @return The modified method descriptor. */ - public String getDescriptorWithIntParameter () { + public String getDescriptorWithIntParameter() { String descriptor = this.descriptor; // replace last argument with `I` return descriptor.replace("Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)", "I)"); @@ -440,7 +440,7 @@ public String getDescriptorWithIntParameter () { * @return The return type class name with semicolon (e.g., "RuntimeScalar;"), * or null if return type cannot be determined */ - public String getReturnTypeDescriptor () { + public String getReturnTypeDescriptor() { if (descriptor == null) { return null; } diff --git a/src/main/java/org/perlonjava/runtime/operators/Pack.java b/src/main/java/org/perlonjava/runtime/operators/Pack.java index 81971bc5f..c04f5230d 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Pack.java +++ b/src/main/java/org/perlonjava/runtime/operators/Pack.java @@ -522,6 +522,7 @@ public static PackResult packInto(String template, List values, i /** * Result of packing operation, containing final state. */ - public static record PackResult(int valueIndex, boolean byteMode, boolean byteModeUsed, boolean hasUnicodeInNormalMode) { + public record PackResult(int valueIndex, boolean byteMode, boolean byteModeUsed, + boolean hasUnicodeInNormalMode) { } } diff --git a/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java b/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java index 6edd005b4..fe8d91f43 100644 --- a/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/pack/PackGroupHandler.java @@ -399,6 +399,7 @@ public static GroupResult handleSlashConstruct(String template, int position, in /** * Result of processing a group, containing both the template position and updated value index. */ - public record GroupResult(int position, int valueIndex, boolean byteMode, boolean byteModeUsed, boolean hasUnicodeInNormalMode) { + public record GroupResult(int position, int valueIndex, boolean byteMode, boolean byteModeUsed, + boolean hasUnicodeInNormalMode) { } } diff --git a/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java b/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java index a29345cd8..2e62d33a9 100644 --- a/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java +++ b/src/main/java/org/perlonjava/runtime/operators/sprintf/SprintfValidationResult.java @@ -8,7 +8,7 @@ public SprintfValidationResult(Status status) { this(status, null); } - public boolean isValid () { + public boolean isValid() { return status == Status.VALID; } diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java b/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java index 6eeb8df37..e2ffc77a2 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Builtin.java @@ -156,7 +156,7 @@ yield switch (scalar.type) { case CODE -> "CODE"; case GLOB, GLOBREFERENCE -> "GLOB"; default -> "SCALAR"; - } + }; } yield "REF"; } diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java b/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java index 4d1bba305..796869357 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/CompressZlib.java @@ -124,14 +124,13 @@ public static RuntimeList inflateMethod(RuntimeArray args, int ctx) { RuntimeScalar inflaterScalar = self.get(INFLATER_KEY); if (inflaterScalar == null || inflaterScalar.type != RuntimeScalarType.JAVAOBJECT - || !(inflaterScalar.value instanceof Inflater)) { + || !(inflaterScalar.value instanceof Inflater inflater)) { RuntimeList result = new RuntimeList(); result.add(scalarUndef); result.add(new RuntimeScalar(-2)); return result; } - Inflater inflater = (Inflater) inflaterScalar.value; String dataStr = dataScalar.toString(); byte[] input = dataStr.getBytes(StandardCharsets.ISO_8859_1); inflater.setInput(input); @@ -179,11 +178,10 @@ public static RuntimeList deflateMethod(RuntimeArray args, int ctx) { RuntimeScalar deflaterScalar = self.get(DEFLATER_KEY); if (deflaterScalar == null || deflaterScalar.type != RuntimeScalarType.JAVAOBJECT - || !(deflaterScalar.value instanceof Deflater)) { + || !(deflaterScalar.value instanceof Deflater deflater)) { return scalarUndef.getList(); } - Deflater deflater = (Deflater) deflaterScalar.value; String dataStr = dataScalar.toString(); byte[] input = dataStr.getBytes(StandardCharsets.ISO_8859_1); deflater.setInput(input); @@ -210,11 +208,10 @@ public static RuntimeList flush(RuntimeArray args, int ctx) { RuntimeHash self = args.get(0).hashDeref(); RuntimeScalar deflaterScalar = self.get(DEFLATER_KEY); if (deflaterScalar == null || deflaterScalar.type != RuntimeScalarType.JAVAOBJECT - || !(deflaterScalar.value instanceof Deflater)) { + || !(deflaterScalar.value instanceof Deflater deflater)) { return scalarUndef.getList(); } - Deflater deflater = (Deflater) deflaterScalar.value; deflater.finish(); byte[] outputBuf = new byte[1024]; diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java b/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java index bc87b8aa9..5091c3c28 100644 --- a/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java +++ b/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java @@ -24,7 +24,7 @@ public record RegexFlags(boolean isGlobalMatch, boolean keepCurrentPosition, boo boolean isNonCapturing, boolean isOptimized, boolean isCaseInsensitive, boolean isMultiLine, boolean isDotAll, boolean isExtended, boolean preservesMatch) { - public static RegexFlags fromModifiers (String modifiers, String patternString){ + public static RegexFlags fromModifiers(String modifiers, String patternString) { return new RegexFlags( modifiers.contains("g"), modifiers.contains("c"), @@ -42,7 +42,7 @@ public static RegexFlags fromModifiers (String modifiers, String patternString){ ); } - public static void validateModifiers (String modifiers){ + public static void validateModifiers(String modifiers) { // Valid modifiers based on what's actually handled in fromModifiers String validModifiers = "gcr?noimsxpadeul"; // Add 'xx' handling separately, 'l' for locale @@ -61,7 +61,7 @@ public static void validateModifiers (String modifiers){ } } - public int toPatternFlags () { + public int toPatternFlags() { int flags = 0; if (isCaseInsensitive) { // For proper Unicode case-insensitive matching, we need both flags: @@ -82,7 +82,7 @@ public int toPatternFlags () { return flags; } - public RegexFlags with (String positiveFlags, String negativeFlags){ + public RegexFlags with(String positiveFlags, String negativeFlags) { boolean newFlagN = this.isNonCapturing; boolean newIsCaseInsensitive = this.isCaseInsensitive; boolean newIsMultiLine = this.isMultiLine; @@ -122,7 +122,7 @@ public RegexFlags with (String positiveFlags, String negativeFlags){ ); } - public String toFlagString () { + public String toFlagString() { StringBuilder flagString = new StringBuilder(); if (isGlobalMatch) flagString.append('g'); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java b/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java index 2760ca8df..ecef360de 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/CallerStack.java @@ -80,7 +80,7 @@ public record CallerInfo(String packageName, String filename, int line) { } @Override - public String toString () { + public String toString() { return String.format("(%s, %s, %d)", packageName, filename, line); } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java index da29893d6..7404e7749 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/DualVar.java @@ -7,7 +7,7 @@ public record DualVar(RuntimeScalar numericValue, RuntimeScalar stringValue) { @Override - public String toString () { + public String toString() { return stringValue.toString(); } } \ No newline at end of file diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java index b4ae4542c..8900ba6df 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java @@ -178,9 +178,6 @@ public static String stringifyException(Throwable t, int skipLevels) { return sb.toString(); } - public record SourceLocation(String fileName, int lineNumber) { - } - public String getFileName() { return fileName; } @@ -355,5 +352,8 @@ public SourceLocation getSourceLocationAccurate(int index) { return new SourceLocation(currentFileName, lineNumber); } + + public record SourceLocation(String fileName, int lineNumber) { + } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java index f9e2fc33f..ba36471a8 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java @@ -80,36 +80,36 @@ private static ArrayList> formatThrowable(Throwable t) { // in order correctly maps each JVM execute() frame to its Perl level. if (interpreterFrameIndex < interpreterFrames.size()) { var frame = interpreterFrames.get(interpreterFrameIndex); - if (frame != null && frame.code != null) { + if (frame != null && frame.code() != null) { // For the innermost frame (index 0), use the runtime current package // tracked by SET_PACKAGE/PUSH_PACKAGE opcodes, which reflects runtime // "package Foo;" declarations. Outer frames still use compile-time names. String pkg = (interpreterFrameIndex == 0) ? InterpreterState.currentPackage.get().toString() - : frame.packageName; + : frame.packageName(); int currentInterpreterFrameIndex = interpreterFrameIndex; interpreterFrameIndex++; - String subName = frame.subroutineName; + String subName = frame.subroutineName(); if (subName != null && !subName.isEmpty() && !subName.contains("::")) { subName = pkg + "::" + subName; } var entry = new ArrayList(); entry.add(pkg); - String filename = frame.code.sourceName; - String line = String.valueOf(frame.code.sourceLine); + String filename = frame.code().sourceName; + String line = String.valueOf(frame.code().sourceLine); if (currentInterpreterFrameIndex < interpreterPcs.size()) { Integer tokenIndex = null; int pc = interpreterPcs.get(currentInterpreterFrameIndex); - if (frame.code.pcToTokenIndex != null && !frame.code.pcToTokenIndex.isEmpty()) { - var entryPc = frame.code.pcToTokenIndex.floorEntry(pc); + if (frame.code().pcToTokenIndex != null && !frame.code().pcToTokenIndex.isEmpty()) { + var entryPc = frame.code().pcToTokenIndex.floorEntry(pc); if (entryPc != null) { tokenIndex = entryPc.getValue(); } } - if (tokenIndex != null && frame.code.errorUtil != null) { - ErrorMessageUtil.SourceLocation loc = frame.code.errorUtil.getSourceLocationAccurate(tokenIndex); + if (tokenIndex != null && frame.code().errorUtil != null) { + ErrorMessageUtil.SourceLocation loc = frame.code().errorUtil.getSourceLocationAccurate(tokenIndex); filename = loc.fileName(); line = String.valueOf(loc.lineNumber()); } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java index f9fab2ba0..eeef675e5 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/OutputAutoFlushVariable.java @@ -14,9 +14,6 @@ private static RuntimeIO currentHandle() { return handle != null ? handle : RuntimeIO.stdout; } - private record State(RuntimeIO handle, boolean autoFlush) { - } - @Override public RuntimeScalar set(RuntimeScalar value) { RuntimeIO handle = currentHandle(); @@ -97,4 +94,7 @@ public void dynamicRestoreState() { } } } + + private record State(RuntimeIO handle, boolean autoFlush) { + } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java index 36e90f840..084acde2b 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java @@ -98,7 +98,7 @@ public static RuntimeScalar pop(RuntimeArray runtimeArray) { } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield pop (runtimeArray); // Recursive call after vivification + yield pop(runtimeArray); // Recursive call after vivification } case TIED_ARRAY -> TieArray.tiedPop(runtimeArray); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -122,7 +122,7 @@ public static RuntimeScalar shift(RuntimeArray runtimeArray) { } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield shift (runtimeArray); // Recursive call after vivification + yield shift(runtimeArray); // Recursive call after vivification } case TIED_ARRAY -> TieArray.tiedShift(runtimeArray); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -154,11 +154,11 @@ public static RuntimeScalar push(RuntimeArray runtimeArray, RuntimeBase value) { return switch (runtimeArray.type) { case PLAIN_ARRAY -> { value.addToArray(runtimeArray); - yield getScalarInt (runtimeArray.elements.size()); + yield getScalarInt(runtimeArray.elements.size()); } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield push (runtimeArray, value) + yield push(runtimeArray, value); } case TIED_ARRAY -> TieArray.tiedPush(runtimeArray, value); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -180,11 +180,11 @@ public static RuntimeScalar unshift(RuntimeArray runtimeArray, RuntimeBase value RuntimeArray arr = new RuntimeArray(); RuntimeArray.push(arr, value); runtimeArray.elements.addAll(0, arr.elements); - yield getScalarInt (runtimeArray.elements.size()); + yield getScalarInt(runtimeArray.elements.size()); } case AUTOVIVIFY_ARRAY -> { AutovivificationArray.vivify(runtimeArray); - yield unshift (runtimeArray, value) + yield unshift(runtimeArray, value); } case TIED_ARRAY -> TieArray.tiedUnshift(runtimeArray, value); case READONLY_ARRAY -> throw new PerlCompilerException("Modification of a read-only value attempted"); @@ -300,7 +300,7 @@ public RuntimeScalar exists(int index) { } // Check if the element at index is null RuntimeScalar element = elements.get(index); - yield(element == null) ? scalarFalse : scalarTrue; + yield (element == null) ? scalarFalse : scalarTrue; } case AUTOVIVIFY_ARRAY -> scalarFalse; case TIED_ARRAY -> TieArray.tiedExists(this, getScalarInt(index)); @@ -312,7 +312,7 @@ public RuntimeScalar exists(int index) { yield scalarFalse; } RuntimeScalar element = elements.get(index); - yield(element == null) ? scalarFalse : scalarTrue; + yield (element == null) ? scalarFalse : scalarTrue; } default -> throw new IllegalStateException("Unknown array type: " + type); }; @@ -338,7 +338,7 @@ public RuntimeScalar delete(int index) { yield scalarUndef; } if (index == elements.size() - 1) { - yield pop (this); + yield pop(this); } RuntimeScalar previous = this.get(index); this.elements.set(index, null); @@ -612,22 +612,22 @@ public RuntimeScalar scalar() { case PLAIN_ARRAY -> { // If this array was created from hash assignment, use the original list size if (scalarContextSize != null) { - yield getScalarInt (scalarContextSize); + yield getScalarInt(scalarContextSize); } - yield getScalarInt (elements.size()); + yield getScalarInt(elements.size()); } case AUTOVIVIFY_ARRAY -> { if (this.strictAutovivify) { throw new PerlCompilerException("Can't use an undefined value as an ARRAY reference"); } - yield getScalarInt (0); + yield getScalarInt(0); } case TIED_ARRAY -> TieArray.tiedFetchSize(this); case READONLY_ARRAY -> { if (scalarContextSize != null) { - yield getScalarInt (scalarContextSize); + yield getScalarInt(scalarContextSize); } - yield getScalarInt (elements.size()); + yield getScalarInt(elements.size()); } default -> throw new IllegalStateException("Unknown array type: " + type); }; @@ -641,7 +641,7 @@ public int lastElementIndex() { if (this.strictAutovivify) { throw new PerlCompilerException("Can't use an undefined value as an ARRAY reference"); } - yield - 1; + yield -1; } case TIED_ARRAY -> TieArray.tiedFetchSize(this).getInt() - 1; case READONLY_ARRAY -> elements.size() - 1; diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java index bc7c7e594..ce06a675f 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java @@ -151,6 +151,7 @@ protected boolean removeEldestEntry(Map.Entry, MethodHandle> eldest) { public RuntimeList constantValue; // Field to hold the thread compiling this code public Supplier compilerSupplier; + /** * Constructs a RuntimeCode instance with the specified prototype and attributes. * @@ -161,6 +162,7 @@ public RuntimeCode(String prototype, List attributes) { this.prototype = prototype; this.attributes = attributes; } + public RuntimeCode(MethodHandle methodObject, Object codeObject, String prototype) { this.methodHandle = methodObject; this.codeObject = codeObject; @@ -405,7 +407,7 @@ public static Class evalStringHelper(RuntimeScalar code, String evalTag, Obje } else if (runtimeValue instanceof RuntimeScalar) { GlobalVariable.globalVariables.put(fullName, (RuntimeScalar) runtimeValue); } - evalAliasKeys.add(entry.name().substring(0, 1) + fullName); + evalAliasKeys.add(entry.name().charAt(0) + fullName); } } } @@ -490,14 +492,12 @@ public static Class evalStringHelper(RuntimeScalar code, String evalTag, Obje apply(sigHandler, args, RuntimeContextType.SCALAR); } catch (Throwable handlerException) { // If the handler dies, use its payload as the new error - if (handlerException instanceof RuntimeException && handlerException.getCause() instanceof PerlDieException) { - PerlDieException pde = (PerlDieException) handlerException.getCause(); + if (handlerException instanceof RuntimeException && handlerException.getCause() instanceof PerlDieException pde) { RuntimeBase handlerPayload = pde.getPayload(); if (handlerPayload != null) { err.set(handlerPayload.getFirst()); } - } else if (handlerException instanceof PerlDieException) { - PerlDieException pde = (PerlDieException) handlerException; + } else if (handlerException instanceof PerlDieException pde) { RuntimeBase handlerPayload = pde.getPayload(); if (handlerPayload != null) { err.set(handlerPayload.getFirst()); @@ -793,7 +793,7 @@ public static RuntimeList evalStringWithInterpreter( } else if (runtimeValue instanceof RuntimeScalar) { GlobalVariable.globalVariables.put(fullName, (RuntimeScalar) runtimeValue); } - evalAliasKeys.add(entry.name().substring(0, 1) + fullName); + evalAliasKeys.add(entry.name().charAt(0) + fullName); } } } @@ -920,14 +920,12 @@ public static RuntimeList evalStringWithInterpreter( apply(sigHandler, handlerArgs, RuntimeContextType.SCALAR); } catch (Throwable handlerException) { // If the handler dies, use its payload as the new error - if (handlerException instanceof RuntimeException && handlerException.getCause() instanceof PerlDieException) { - PerlDieException pde = (PerlDieException) handlerException.getCause(); + if (handlerException instanceof RuntimeException && handlerException.getCause() instanceof PerlDieException pde) { RuntimeBase handlerPayload = pde.getPayload(); if (handlerPayload != null) { err.set(handlerPayload.getFirst()); } - } else if (handlerException instanceof PerlDieException) { - PerlDieException pde = (PerlDieException) handlerException; + } else if (handlerException instanceof PerlDieException pde) { RuntimeBase handlerPayload = pde.getPayload(); if (handlerPayload != null) { err.set(handlerPayload.getFirst()); @@ -1965,43 +1963,34 @@ public void dynamicRestoreState() { } /** - * Container for runtime context during eval STRING compilation. - * Holds both the runtime values and variable names so SpecialBlockParser can - * match variables to their values. - */ - public static class EvalRuntimeContext { - public final Object[] runtimeValues; - public final String[] capturedEnv; - public final String evalTag; - - public EvalRuntimeContext(Object[] runtimeValues, String[] capturedEnv, String evalTag) { - this.runtimeValues = runtimeValues; - this.capturedEnv = capturedEnv; - this.evalTag = evalTag; - } + * Container for runtime context during eval STRING compilation. + * Holds both the runtime values and variable names so SpecialBlockParser can + * match variables to their values. + */ + public record EvalRuntimeContext(Object[] runtimeValues, String[] capturedEnv, String evalTag) { /** - * Get the runtime value for a variable by name. - *

    - * IMPORTANT: The capturedEnv array includes all variables (including 'this', '@_', 'wantarray'), - * but runtimeValues array skips the first skipVariables (currently 3). - * So if @imports is at capturedEnv[5], its value is at runtimeValues[5-3=2]. - * - * @param varName The variable name (e.g., "@imports", "$scalar") - * @return The runtime value, or null if not found - */ - public Object getRuntimeValue(String varName) { - int skipVariables = 3; // 'this', '@_', 'wantarray' - for (int i = skipVariables; i < capturedEnv.length; i++) { - if (varName.equals(capturedEnv[i])) { - int runtimeIndex = i - skipVariables; - if (runtimeIndex >= 0 && runtimeIndex < runtimeValues.length) { - return runtimeValues[runtimeIndex]; + * Get the runtime value for a variable by name. + *

    + * IMPORTANT: The capturedEnv array includes all variables (including 'this', '@_', 'wantarray'), + * but runtimeValues array skips the first skipVariables (currently 3). + * So if @imports is at capturedEnv[5], its value is at runtimeValues[5-3=2]. + * + * @param varName The variable name (e.g., "@imports", "$scalar") + * @return The runtime value, or null if not found + */ + public Object getRuntimeValue(String varName) { + int skipVariables = 3; // 'this', '@_', 'wantarray' + for (int i = skipVariables; i < capturedEnv.length; i++) { + if (varName.equals(capturedEnv[i])) { + int runtimeIndex = i - skipVariables; + if (runtimeIndex >= 0 && runtimeIndex < runtimeValues.length) { + return runtimeValues[runtimeIndex]; + } } } + return null; } - return null; } - } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java index 79fd271b4..bee6cf991 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java @@ -542,14 +542,6 @@ public RuntimeGlob undefine() { return this; } - private record GlobSlotSnapshot( - String globName, - RuntimeScalar scalar, - RuntimeArray array, - RuntimeHash hash, - RuntimeScalar code) { - } - @Override public void dynamicSaveState() { RuntimeScalar savedScalar = GlobalVariable.getGlobalVariable(this.globName); @@ -587,4 +579,12 @@ public void dynamicRestoreState() { GlobalVariable.getGlobalFormatRef(snap.globName).dynamicRestoreState(); } + + private record GlobSlotSnapshot( + String globName, + RuntimeScalar scalar, + RuntimeArray array, + RuntimeHash hash, + RuntimeScalar code) { + } } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java index 99160d60e..cc2a3a69a 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java @@ -383,7 +383,7 @@ public RuntimeScalar delete(RuntimeScalar key) { } case AUTOVIVIFY_HASH -> { AutovivificationHash.vivify(this); - yield delete (key); + yield delete(key); } case TIED_HASH -> TieHash.tiedDelete(this, key); default -> throw new IllegalStateException("Unknown array type: " + type); @@ -401,7 +401,7 @@ public RuntimeScalar delete(String key) { } case AUTOVIVIFY_HASH -> { AutovivificationHash.vivify(this); - yield delete (key); + yield delete(key); } case TIED_HASH -> TieHash.tiedDelete(this, new RuntimeScalar(key)); default -> throw new IllegalStateException("Unknown array type: " + type); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index 1a15178ed..3c81ed905 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -332,7 +332,7 @@ private int getIntLarge() { // Parse as long first so we can handle values outside 32-bit range // (Perl IV is commonly 64-bit). getInt() is used for array indices // and similar contexts, which should behave like (int)getLong(). - yield( int)Long.parseLong(t); + yield (int) Long.parseLong(t); } catch (NumberFormatException ignored) { // Fall through to full numification. } @@ -788,32 +788,32 @@ public String toStringRef() { if (value == null) { yield "CODE(0x" + scalarUndef.hashCode() + ")"; } - yield((RuntimeCode) value).toStringRef(); + yield ((RuntimeCode) value).toStringRef(); } case GLOB -> { if (value == null) { yield "GLOB(0x" + scalarUndef.hashCode() + ")"; } - yield((RuntimeGlob) value).toStringRef(); + yield ((RuntimeGlob) value).toStringRef(); } case VSTRING -> "VSTRING(0x" + value.hashCode() + ")"; case ARRAYREFERENCE -> { if (value == null) { yield "ARRAY(0x" + scalarUndef.hashCode() + ")"; } - yield((RuntimeArray) value).toStringRef(); + yield ((RuntimeArray) value).toStringRef(); } case HASHREFERENCE -> { if (value == null) { yield "HASH(0x" + scalarUndef.hashCode() + ")"; } - yield((RuntimeHash) value).toStringRef(); + yield ((RuntimeHash) value).toStringRef(); } case GLOBREFERENCE -> { if (value == null) { yield "GLOB(0x" + scalarUndef.hashCode() + ")"; } - yield((RuntimeBase) value).toStringRef(); + yield ((RuntimeBase) value).toStringRef(); } case REFERENCE -> { // Determine the proper type name for the reference @@ -831,7 +831,7 @@ public String toStringRef() { } String refStr = typeName + "(0x" + Integer.toHexString(value.hashCode()) + ")"; // For REFERENCE type, the blessId is on the value, not on the reference itself - yield(valueBlessId == 0 ? refStr : NameNormalizer.getBlessStr(valueBlessId) + "=" + refStr); + yield (valueBlessId == 0 ? refStr : NameNormalizer.getBlessStr(valueBlessId) + "=" + refStr); } default -> "SCALAR(0x" + Integer.toHexString(value.hashCode()) + ")"; }; @@ -1297,7 +1297,7 @@ public RuntimeGlob globDeref() { tmp.setIO(io); yield tmp; } - yield(RuntimeGlob) value; + yield (RuntimeGlob) value; } case GLOB -> { // PVIO (like *STDOUT{IO}) is stored as type GLOB with a RuntimeIO value. @@ -1308,7 +1308,7 @@ public RuntimeGlob globDeref() { tmp.setIO(io); yield tmp; } - yield(RuntimeGlob) value; + yield (RuntimeGlob) value; } case STRING, BYTE_STRING -> throw new PerlCompilerException("Can't use string (\"" + this + "\") as a symbol ref while \"strict refs\" in use"); @@ -1341,7 +1341,7 @@ public RuntimeGlob globDerefNonStrict(String packageName) { tmp.setIO(io); yield tmp; } - yield(RuntimeGlob) value; + yield (RuntimeGlob) value; } case GLOB -> { // PVIO (like *STDOUT{IO}) is stored as type GLOB with a RuntimeIO value. @@ -1352,7 +1352,7 @@ public RuntimeGlob globDerefNonStrict(String packageName) { tmp.setIO(io); yield tmp; } - yield(RuntimeGlob) value; + yield (RuntimeGlob) value; } default -> { String varName = NameNormalizer.normalizeVariableName(this.toString(), packageName); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java index 13d9dfb08..1ca7519d7 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java @@ -4,6 +4,7 @@ import java.util.Stack; +import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.getScalarInt; import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef; /** @@ -47,9 +48,6 @@ public ScalarSpecialVariable(Id variableId, int position) { this.position = position; } - private record InputLineState(RuntimeIO lastHandle, int lastLineNumber, RuntimeScalar localValue) { - } - /** * Throws an exception as this variable represents a constant item * and cannot be modified. @@ -110,34 +108,34 @@ public RuntimeScalar getValueAsScalar() { RuntimeScalar result = switch (variableId) { case CAPTURE -> { String capture = RuntimeRegex.captureString(position); - yield capture !=null ? new RuntimeScalar(capture) : scalarUndef; + yield capture != null ? new RuntimeScalar(capture) : scalarUndef; } case MATCH -> { String match = RuntimeRegex.matchString(); - yield match !=null ? new RuntimeScalar(match) : scalarUndef; + yield match != null ? new RuntimeScalar(match) : scalarUndef; } case PREMATCH -> { String prematch = RuntimeRegex.preMatchString(); - yield prematch !=null ? new RuntimeScalar(prematch) : scalarUndef; + yield prematch != null ? new RuntimeScalar(prematch) : scalarUndef; } case POSTMATCH -> { String postmatch = RuntimeRegex.postMatchString(); - yield postmatch !=null ? new RuntimeScalar(postmatch) : scalarUndef; + yield postmatch != null ? new RuntimeScalar(postmatch) : scalarUndef; } case P_PREMATCH -> { if (!RuntimeRegex.lastMatchUsedPFlag) yield scalarUndef; String prematch = RuntimeRegex.preMatchString(); - yield prematch !=null ? new RuntimeScalar(prematch) : scalarUndef; + yield prematch != null ? new RuntimeScalar(prematch) : scalarUndef; } case P_MATCH -> { if (!RuntimeRegex.lastMatchUsedPFlag) yield scalarUndef; String match = RuntimeRegex.matchString(); - yield match !=null ? new RuntimeScalar(match) : scalarUndef; + yield match != null ? new RuntimeScalar(match) : scalarUndef; } case P_POSTMATCH -> { if (!RuntimeRegex.lastMatchUsedPFlag) yield scalarUndef; String postmatch = RuntimeRegex.postMatchString(); - yield postmatch !=null ? new RuntimeScalar(postmatch) : scalarUndef; + yield postmatch != null ? new RuntimeScalar(postmatch) : scalarUndef; } case LAST_FH -> { if (RuntimeIO.lastAccesseddHandle == null) { @@ -175,11 +173,11 @@ public RuntimeScalar getValueAsScalar() { } yield scalarUndef; } - yield getScalarInt (RuntimeIO.lastAccesseddHandle.currentLineNumber); + yield getScalarInt(RuntimeIO.lastAccesseddHandle.currentLineNumber); } case LAST_PAREN_MATCH -> { String lastCapture = RuntimeRegex.lastCaptureString(); - yield lastCapture !=null ? new RuntimeScalar(lastCapture) : scalarUndef; + yield lastCapture != null ? new RuntimeScalar(lastCapture) : scalarUndef; } case LAST_SUCCESSFUL_PATTERN -> new RuntimeScalar(RuntimeRegex.lastSuccessfulPattern); case LAST_REGEXP_CODE_RESULT -> { @@ -187,7 +185,7 @@ public RuntimeScalar getValueAsScalar() { // Get the last matched regex and retrieve its code block result if (RuntimeRegex.lastSuccessfulPattern != null) { RuntimeScalar codeBlockResult = RuntimeRegex.lastSuccessfulPattern.getLastCodeBlockResult(); - yield codeBlockResult !=null ? codeBlockResult : scalarUndef; + yield codeBlockResult != null ? codeBlockResult : scalarUndef; } yield scalarUndef; } @@ -352,4 +350,7 @@ public enum Id { LAST_SUCCESSFUL_PATTERN, // ${^LAST_SUCCESSFUL_PATTERN} LAST_REGEXP_CODE_RESULT, // $^R - Result of last (?{...}) code block in regex } + + private record InputLineState(RuntimeIO lastHandle, int lastLineNumber, RuntimeScalar localValue) { + } }