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 "" 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..d984d922b 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,101 +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; @@ -146,13 +109,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 +160,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 +300,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 +307,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 +320,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 +336,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 +393,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 +415,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 +482,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 +545,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 +583,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 +623,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 +640,10 @@ private void detectClosureVariables(Node ast, EmitterContext ctx) { capturedVars = outerValues.toArray(new RuntimeBase[0]); } + // ========================================================================= + // CLOSURE DETECTION + // ========================================================================= + /** * Collect all variable references in AST. * @@ -725,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)) { @@ -745,10 +705,6 @@ private RuntimeBase getVariableValueFromContext(String varName, EmitterContext c return new RuntimeScalar(); } - // ========================================================================= - // VISITOR METHODS - // ========================================================================= - /** * Compiles a block node, creating a new lexical scope. * @@ -879,6 +835,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 +1026,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 +1060,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 +1088,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); @@ -1142,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; @@ -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,17 +1157,16 @@ 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); 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); @@ -1236,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 @@ -1298,8 +1256,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); @@ -1308,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; @@ -1382,8 +1339,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); @@ -1509,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; @@ -1578,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 @@ -1594,7 +1549,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); @@ -1688,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; @@ -1747,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; @@ -1780,7 +1733,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 @@ -1838,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 @@ -1853,8 +1805,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); @@ -1926,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) { @@ -2037,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<>(); @@ -2052,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)) @@ -2113,7 +2062,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 +2096,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 +2134,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 +2258,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 +2282,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); @@ -2403,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) { @@ -2503,8 +2453,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); @@ -2553,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)) @@ -2567,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 @@ -2633,7 +2581,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 +2609,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 +2617,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 +2650,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 +2659,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 +2707,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 +2728,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); } } @@ -2840,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) { @@ -2925,8 +2873,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; @@ -3020,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 @@ -3033,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)) @@ -3047,8 +2993,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 +3044,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 +3074,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 +3222,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(); @@ -3289,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; @@ -3396,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); @@ -3424,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); @@ -3556,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 @@ -3607,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 @@ -3673,10 +3614,6 @@ public void visit(OperatorNode node) { CompileOperator.visitOperator(this, node); } - // ========================================================================= - // HELPER METHODS - // ========================================================================= - int allocateRegister() { int reg = nextRegister++; if (reg > 65535) { @@ -3690,6 +3627,10 @@ int allocateRegister() { return reg; } + // ========================================================================= + // HELPER METHODS + // ========================================================================= + TreeMap collectVisiblePerlVariables() { TreeMap closureVarsByReg = new TreeMap<>(); Map visible = symbolTable.getAllVisibleVariables(); @@ -3734,13 +3675,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 +3799,6 @@ private int[] toShortArray() { return result; } - // ========================================================================= - // UNIMPLEMENTED VISITOR METHODS (TODO) - // ========================================================================= - @Override public void visit(ArrayLiteralNode node) { // Array literal: [expr1, expr2, ...] @@ -3915,6 +3852,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 +3930,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 +4001,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 +4025,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 +4058,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 +4090,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 +4114,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 +4135,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) { @@ -4352,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); @@ -4889,10 +4829,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 +4837,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 +4869,6 @@ Map getCapturedVarIndices() { return capturedVarIndices; } - /** * Handle loop control operators: last, next, redo * Extracted for use by CompileOperator. @@ -4968,8 +4907,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 +4926,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 +4944,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/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 2281d18c2..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) @@ -104,2781 +105,1523 @@ 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) { - 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 - break; - - 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; - } + 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.GOTO: { - // Unconditional jump: pc = offset - int offset = readInt(bytecode, pc); - pc = offset; // Registers persist across jump (unlike stack-based!) - break; - } + case Opcodes.RETURN -> { + // Return from subroutine: return rd + int retReg = bytecode[pc++]; + RuntimeBase retVal = registers[retReg]; - case Opcodes.LAST: - case Opcodes.NEXT: - case Opcodes.REDO: { - // Loop control: jump to target PC - // Format: opcode, target (absolute PC as int) - int target = readInt(bytecode, pc); - pc = target; - break; - } + if (retVal == null) { + return new RuntimeList(); + } + RuntimeList retList = retVal.getList(); + RuntimeCode.materializeSpecialVarsInResult(retList); + return retList; + } - 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 -> { + // Unconditional jump: pc = offset + int offset = readInt(bytecode, pc); + pc = offset; // Registers persist across jump (unlike stack-based!) + } - // Convert to scalar if needed for boolean test - RuntimeBase condBase = registers[condReg]; - RuntimeScalar cond = (condBase instanceof RuntimeScalar) - ? (RuntimeScalar) condBase - : condBase.scalar(); + 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; + } - if (!cond.getBoolean()) { - pc = target; // Jump - all registers stay valid! - } - break; - } + 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_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; // Jump - all registers stay valid! + } + } - if (cond.getBoolean()) { - pc = target; - } - break; - } + case Opcodes.GOTO_IF_TRUE -> { + // Conditional jump: if (rs) pc = offset + int condReg = bytecode[pc++]; + int target = readInt(bytecode, pc); + pc += 1; - // ================================================================= - // 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; - break; - } + // Convert to scalar if needed for boolean test + RuntimeBase condBase = registers[condReg]; + RuntimeScalar cond = (condBase instanceof RuntimeScalar) + ? (RuntimeScalar) condBase + : condBase.scalar(); - 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; - } + if (cond.getBoolean()) { + pc = target; + } + } - 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); - break; - } + // ================================================================= + // 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_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); - } - break; - } + 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_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; - break; - } + 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_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.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.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.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_UNDEF: { - // Load undef: rd = new RuntimeScalar() - int rd = bytecode[pc++]; - registers[rd] = new RuntimeScalar(); - break; - } + 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.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.GLOB_OP -> { + pc = InlineOpcodeHandler.executeGlobOp(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; - break; - } + case Opcodes.LOAD_UNDEF -> { + // Load undef: rd = new RuntimeScalar() + int rd = bytecode[pc++]; + registers[rd] = new RuntimeScalar(); + } - // ================================================================= - // 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); - break; - } + case Opcodes.UNDEFINE_SCALAR -> { + pc = InlineOpcodeHandler.executeUndefineScalar(bytecode, pc, registers); + } - 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.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; + } - // Convert to scalar if needed - RuntimeBase value = registers[srcReg]; - RuntimeScalar scalarValue = (value instanceof RuntimeScalar) - ? (RuntimeScalar) value - : value.scalar(); + // ================================================================= + // 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); + } - GlobalVariable.getGlobalVariable(name).set(scalarValue); - break; - } + 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.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); - break; - } + // Convert to scalar if needed + RuntimeBase value = registers[srcReg]; + RuntimeScalar scalarValue = (value instanceof RuntimeScalar) + ? (RuntimeScalar) value + : value.scalar(); - 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; - } + GlobalVariable.getGlobalVariable(name).set(scalarValue); + } - case Opcodes.SAVE_REGEX_STATE: { - pc++; - regexStateStack.push(new RegexState()); - break; - } + 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.RESTORE_REGEX_STATE: { - pc++; - if (!regexStateStack.isEmpty()) { - regexStateStack.pop().restore(); - } - break; - } + 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.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(); - } - break; - } + case Opcodes.SAVE_REGEX_STATE -> { + pc++; + regexStateStack.push(new RegexState()); + } - 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.RESTORE_REGEX_STATE -> { + pc++; + if (!regexStateStack.isEmpty()) { + regexStateStack.pop().restore(); + } + } - RuntimeArray globalArray = GlobalVariable.getGlobalArray(name); - RuntimeBase value = registers[srcReg]; + 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(); + } + } - 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); - } + 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]; - // 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()); - } - break; - } + RuntimeArray globalArray = GlobalVariable.getGlobalArray(name); + RuntimeBase value = registers[srcReg]; - 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()); - } - break; - } + 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); + } - 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; - } + // 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.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.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_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.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.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); - break; - } + 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.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: { - // 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); - break; - } + 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); + } - // ================================================================= - // 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.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.SUB_SCALAR: { - // Subtraction: rd = rs1 - rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; + 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); + } - // 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(); + 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); + } - registers[rd] = MathOperators.subtract(s1, s2); - break; - } + // ================================================================= + // ARITHMETIC OPERATORS + // ================================================================= - case Opcodes.MUL_SCALAR: { - // Multiplication: rd = rs1 * rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; + case Opcodes.ADD_SCALAR -> { + pc = InlineOpcodeHandler.executeAddScalar(bytecode, pc, registers); + } - // 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(); + case Opcodes.SUB_SCALAR -> { + pc = InlineOpcodeHandler.executeSubScalar(bytecode, pc, registers); + } - registers[rd] = MathOperators.multiply(s1, s2); - break; - } + case Opcodes.MUL_SCALAR -> { + pc = InlineOpcodeHandler.executeMulScalar(bytecode, pc, registers); + } + + case Opcodes.DIV_SCALAR -> { + pc = InlineOpcodeHandler.executeDivScalar(bytecode, pc, registers); + } - case Opcodes.DIV_SCALAR: { - // Division: rd = rs1 / rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; + case Opcodes.MOD_SCALAR -> { + pc = InlineOpcodeHandler.executeModScalar(bytecode, pc, registers); + } - // 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(); + case Opcodes.POW_SCALAR -> { + pc = InlineOpcodeHandler.executePowScalar(bytecode, pc, registers); + } - registers[rd] = MathOperators.divide(s1, s2); - break; - } + case Opcodes.NEG_SCALAR -> { + pc = InlineOpcodeHandler.executeNegScalar(bytecode, pc, registers); + } - case Opcodes.MOD_SCALAR: { - // Modulus: rd = rs1 % rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; + // Specialized unboxed operations (rare optimizations) + case Opcodes.ADD_SCALAR_INT -> { + pc = InlineOpcodeHandler.executeAddScalarInt(bytecode, pc, registers); + } - // 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(); + // ================================================================= + // STRING OPERATORS + // ================================================================= - registers[rd] = MathOperators.modulus(s1, s2); - break; - } + case Opcodes.CONCAT -> { + pc = InlineOpcodeHandler.executeConcat(bytecode, pc, registers); + } - case Opcodes.POW_SCALAR: { - // Exponentiation: rd = rs1 ** rs2 - int rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; + case Opcodes.REPEAT -> { + pc = InlineOpcodeHandler.executeRepeat(bytecode, pc, registers); + } - // 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(); + case Opcodes.LENGTH -> { + pc = InlineOpcodeHandler.executeLength(bytecode, pc, registers); + } - registers[rd] = MathOperators.pow(s1, s2); - break; - } + // ================================================================= + // COMPARISON AND LOGICAL OPERATORS (opcodes 31-39) - Delegated + // ================================================================= - 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.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); + } - // 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; - } + // ================================================================= + // TYPE AND REFERENCE OPERATORS (opcodes 102-105) - Delegated + // ================================================================= - // ================================================================= - // 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.DEFINED, Opcodes.REF, Opcodes.BLESS, Opcodes.ISA, Opcodes.PROTOTYPE, + Opcodes.QUOTE_REGEX -> { + pc = executeTypeOps(opcode, bytecode, pc, registers, code); + } - 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; - } + // ================================================================= + // ITERATOR OPERATIONS - For efficient foreach loops + // ================================================================= - 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.ITERATOR_CREATE -> { + // Create iterator: rd = rs.iterator() + // Format: ITERATOR_CREATE rd rs + pc = OpcodeHandlerExtended.executeIteratorCreate(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: - 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: - pc = executeTypeOps(opcode, bytecode, pc, registers, code); - break; - - // ================================================================= - // 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); - break; - - 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: - // Get next element: rd = iterator.next() - // Format: ITERATOR_NEXT rd iterReg - pc = OpcodeHandlerExtended.executeIteratorNext(bytecode, pc, registers); - break; - - 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(); - } - break; - } + case Opcodes.ITERATOR_HAS_NEXT -> { + // Check iterator: rd = iterator.hasNext() + // Format: ITERATOR_HAS_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorHasNext(bytecode, pc, registers); + } - // ================================================================= - // 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); - break; - - case Opcodes.MULTIPLY_ASSIGN: - // Compound assignment: rd *= rs - // Format: MULTIPLY_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeMultiplyAssign(bytecode, pc, registers); - break; - - case Opcodes.DIVIDE_ASSIGN: - // Compound assignment: rd /= rs - // Format: DIVIDE_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeDivideAssign(bytecode, pc, registers); - break; - - case Opcodes.MODULUS_ASSIGN: - // Compound assignment: rd %= rs - // Format: MODULUS_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeModulusAssign(bytecode, pc, registers); - break; - - 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: - // Compound assignment: rd **= rs - // Format: POW_ASSIGN rd rs - pc = OpcodeHandlerExtended.executePowAssign(bytecode, pc, registers); - break; - - 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: - 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_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_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_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.ITERATOR_NEXT -> { + // Get next element: rd = iterator.next() + // Format: ITERATOR_NEXT rd iterReg + pc = OpcodeHandlerExtended.executeIteratorNext(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); - break; - - 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: - // 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.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.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; - } + // ================================================================= + // COMPOUND ASSIGNMENT OPERATORS (with overload support) + // ================================================================= - 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.SUBTRACT_ASSIGN -> { + // Compound assignment: rd -= rs + // Format: SUBTRACT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeSubtractAssign(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.MULTIPLY_ASSIGN -> { + // Compound assignment: rd *= rs + // Format: MULTIPLY_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeMultiplyAssign(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.DIVIDE_ASSIGN -> { + // Compound assignment: rd /= rs + // Format: DIVIDE_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeDivideAssign(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.MODULUS_ASSIGN -> { + // Compound assignment: rd %= rs + // Format: MODULUS_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeModulusAssign(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.REPEAT_ASSIGN -> { + // Compound assignment: rd x= rs + // Format: REPEAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeRepeatAssign(bytecode, pc, registers); + } - 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.POW_ASSIGN -> { + // Compound assignment: rd **= rs + // Format: POW_ASSIGN rd rs + pc = OpcodeHandlerExtended.executePowAssign(bytecode, pc, registers); + } - 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.LEFT_SHIFT_ASSIGN -> { + // Compound assignment: rd <<= rs + // Format: LEFT_SHIFT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLeftShiftAssign(bytecode, pc, registers); + } - 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.RIGHT_SHIFT_ASSIGN -> { + pc = OpcodeHandlerExtended.executeRightShiftAssign(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.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.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.LOGICAL_AND_ASSIGN -> { + // Compound assignment: rd &&= rs (short-circuit) + // Format: LOGICAL_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalAndAssign(bytecode, pc, registers); + } - 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.LOGICAL_OR_ASSIGN -> { + // Compound assignment: rd ||= rs (short-circuit) + // Format: LOGICAL_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeLogicalOrAssign(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.DEFINED_OR_ASSIGN -> { + // Compound assignment: rd //= rs (short-circuit) + // Format: DEFINED_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeDefinedOrAssign(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); - } + // ================================================================= + // SHIFT OPERATIONS + // ================================================================= - // Create reference (array literals always return references!) - registers[rd] = array.createReference(); - break; - } + case Opcodes.LEFT_SHIFT -> { + pc = InlineOpcodeHandler.executeLeftShift(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.RIGHT_SHIFT -> { + pc = InlineOpcodeHandler.executeRightShift(bytecode, pc, registers); + } - 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.INTEGER_LEFT_SHIFT -> { + pc = InlineOpcodeHandler.executeIntegerLeftShift(bytecode, pc, registers); + } - 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.INTEGER_RIGHT_SHIFT -> { + pc = InlineOpcodeHandler.executeIntegerRightShift(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.INTEGER_DIV -> { + pc = InlineOpcodeHandler.executeIntegerDiv(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.INTEGER_MOD -> { + pc = InlineOpcodeHandler.executeIntegerMod(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; - } + // ================================================================= + // ARRAY OPERATIONS + // ================================================================= - 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.ARRAY_GET -> { + pc = InlineOpcodeHandler.executeArrayGet(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_SET -> { + pc = InlineOpcodeHandler.executeArraySet(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); - } + case Opcodes.ARRAY_PUSH -> { + pc = InlineOpcodeHandler.executeArrayPush(bytecode, pc, registers); + } - RuntimeList result = RuntimeCode.apply(codeRef, "", callArgs, context); + case Opcodes.ARRAY_POP -> { + pc = InlineOpcodeHandler.executeArrayPop(bytecode, pc, registers); + } - // 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; - } + case Opcodes.ARRAY_SHIFT -> { + pc = InlineOpcodeHandler.executeArrayShift(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.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 -> { + pc = InlineOpcodeHandler.executeHashGet(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_DELETE -> { + pc = InlineOpcodeHandler.executeHashDelete(bytecode, pc, registers); + } + + case Opcodes.ARRAY_EXISTS -> { + pc = InlineOpcodeHandler.executeArrayExists(bytecode, pc, registers); + } + + case Opcodes.ARRAY_DELETE -> { + pc = InlineOpcodeHandler.executeArrayDelete(bytecode, pc, registers); + } - // 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(); + 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 -> { + // 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); + + // 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(); + } + 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; - } - } - break; - } - 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) + // ================================================================= + + case Opcodes.CREATE_LAST -> { + pc = InlineOpcodeHandler.executeCreateLast(bytecode, pc, registers, code); } - } - 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_NEXT -> { + pc = InlineOpcodeHandler.executeCreateNext(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_REDO -> { + pc = InlineOpcodeHandler.executeCreateRedo(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_GOTO -> { + pc = InlineOpcodeHandler.executeCreateGoto(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.IS_CONTROL_FLOW -> { + pc = InlineOpcodeHandler.executeIsControlFlow(bytecode, pc, registers); + } - 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; - } + // ================================================================= + // MISCELLANEOUS + // ================================================================= - // ================================================================= - // MISCELLANEOUS - // ================================================================= - - case Opcodes.PRINT: - // Print to filehandle - // Format: PRINT contentReg filehandleReg - pc = OpcodeHandlerExtended.executePrint(bytecode, pc, registers); - break; - - 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.PRINT -> { + // Print to filehandle + // Format: PRINT contentReg filehandleReg + pc = OpcodeHandlerExtended.executePrint(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.SAY -> { + // Say to filehandle + // Format: SAY contentReg filehandleReg + pc = OpcodeHandlerExtended.executeSay(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; - } + // ================================================================= + // SUPERINSTRUCTIONS - Eliminate ALIAS overhead + // ================================================================= - 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.INC_REG -> { + pc = InlineOpcodeHandler.executeIncReg(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); - break; - - case Opcodes.BITWISE_AND_ASSIGN: - // Bitwise AND assignment: rd &= rs - // Format: BITWISE_AND_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeBitwiseAndAssign(bytecode, pc, registers); - break; - - case Opcodes.BITWISE_OR_ASSIGN: - // Bitwise OR assignment: rd |= rs - // Format: BITWISE_OR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeBitwiseOrAssign(bytecode, pc, registers); - break; - - case Opcodes.BITWISE_XOR_ASSIGN: - // Bitwise XOR assignment: rd ^= rs - // Format: BITWISE_XOR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeBitwiseXorAssign(bytecode, pc, registers); - break; - - case Opcodes.STRING_BITWISE_AND_ASSIGN: - // String bitwise AND assignment: rd &.= rs - // Format: STRING_BITWISE_AND_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeStringBitwiseAndAssign(bytecode, pc, registers); - break; - - case Opcodes.STRING_BITWISE_OR_ASSIGN: - // String bitwise OR assignment: rd |.= rs - // Format: STRING_BITWISE_OR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeStringBitwiseOrAssign(bytecode, pc, registers); - break; - - case Opcodes.STRING_BITWISE_XOR_ASSIGN: - // String bitwise XOR assignment: rd ^.= rs - // Format: STRING_BITWISE_XOR_ASSIGN rd rs - pc = OpcodeHandlerExtended.executeStringBitwiseXorAssign(bytecode, pc, registers); - break; - - 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: - // 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: - // 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: - // 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: - // 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: - // 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.DEC_REG -> { + pc = InlineOpcodeHandler.executeDecReg(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); - break; - - 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: - pc = OpcodeHandlerExtended.executeStat(bytecode, pc, registers); - break; - - case Opcodes.LSTAT: - pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers); - break; - - case Opcodes.STAT_LASTHANDLE: - pc = OpcodeHandlerExtended.executeStatLastHandle(bytecode, pc, registers); - break; - - 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: - 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.ADD_ASSIGN -> { + pc = InlineOpcodeHandler.executeAddAssign(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.ADD_ASSIGN_INT -> { + pc = InlineOpcodeHandler.executeAddAssignInt(bytecode, pc, registers); + } - 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: - // Read line from filehandle - // Format: READLINE rd fhReg ctx - pc = OpcodeHandlerExtended.executeReadline(bytecode, pc, registers); - break; - - case Opcodes.MATCH_REGEX: - // Match regex - // Format: MATCH_REGEX rd stringReg regexReg ctx - pc = OpcodeHandlerExtended.executeMatchRegex(bytecode, pc, registers); - break; - - case Opcodes.MATCH_REGEX_NOT: - // Negated regex match - // Format: MATCH_REGEX_NOT rd stringReg regexReg ctx - pc = OpcodeHandlerExtended.executeMatchRegexNot(bytecode, pc, registers); - break; - - case Opcodes.CHOMP: - // Chomp: rd = rs.chomp() - // Format: CHOMP rd rs - pc = OpcodeHandlerExtended.executeChomp(bytecode, pc, registers); - break; - - case Opcodes.WANTARRAY: - // Get wantarray context - // Format: WANTARRAY rd wantarrayReg - pc = OpcodeHandlerExtended.executeWantarray(bytecode, pc, registers); - break; - - case Opcodes.REQUIRE: - // Require module or version - // Format: REQUIRE rd rs - pc = OpcodeHandlerExtended.executeRequire(bytecode, pc, registers); - break; - - case Opcodes.POS: - // Get regex position - // Format: POS rd rs - pc = OpcodeHandlerExtended.executePos(bytecode, pc, registers); - break; - - case Opcodes.INDEX: - // Find substring position - // Format: INDEX rd strReg substrReg posReg - pc = OpcodeHandlerExtended.executeIndex(bytecode, pc, registers); - break; - - case Opcodes.RINDEX: - // Find substring position from end - // Format: RINDEX rd strReg substrReg posReg - pc = OpcodeHandlerExtended.executeRindex(bytecode, pc, registers); - break; - - case Opcodes.PRE_AUTOINCREMENT: - // Pre-increment: ++rd - // Format: PRE_AUTOINCREMENT rd - pc = OpcodeHandlerExtended.executePreAutoIncrement(bytecode, pc, registers); - break; - - case Opcodes.POST_AUTOINCREMENT: - // Post-increment: rd = rs++ - // Format: POST_AUTOINCREMENT rd rs - pc = OpcodeHandlerExtended.executePostAutoIncrement(bytecode, pc, registers); - break; - - case Opcodes.PRE_AUTODECREMENT: - // Pre-decrement: --rd - // Format: PRE_AUTODECREMENT rd - pc = OpcodeHandlerExtended.executePreAutoDecrement(bytecode, pc, registers); - break; - - 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.STRING_CONCAT_ASSIGN -> { + // String concatenation and assign: rd .= rs + // Format: STRING_CONCAT_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringConcatAssign(bytecode, pc, registers); + } - 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]; + case Opcodes.BITWISE_AND_ASSIGN -> { + // Bitwise AND assignment: rd &= rs + // Format: BITWISE_AND_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseAndAssign(bytecode, pc, registers); + } - // Call WarnDie.warn() with precomputed location - WarnDie.warn(message, where, code.sourceName, code.sourceLine); - break; - } + case Opcodes.BITWISE_OR_ASSIGN -> { + // Bitwise OR assignment: rd |= rs + // Format: BITWISE_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseOrAssign(bytecode, pc, registers); + } - // ================================================================= - // 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.BITWISE_XOR_ASSIGN -> { + // Bitwise XOR assignment: rd ^= rs + // Format: BITWISE_XOR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeBitwiseXorAssign(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.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.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.STRING_BITWISE_OR_ASSIGN -> { + // String bitwise OR assignment: rd |.= rs + // Format: STRING_BITWISE_OR_ASSIGN rd rs + pc = OpcodeHandlerExtended.executeStringBitwiseOrAssign(bytecode, pc, registers); + } - // ================================================================= - // EVAL BLOCK SUPPORT - // ================================================================= + 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.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) + 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); + } - int catchPc = readInt(bytecode, pc); // Read 4-byte absolute address - pc += 1; // Skip the 2 shorts we just read + 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); + } - // Push catch PC onto eval stack - evalCatchStack.push(catchPc); + 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); + } - // Clear $@ at start of eval block - GlobalVariable.setGlobalVariable("main::@", ""); + case Opcodes.STRING_BITWISE_AND -> { + // String bitwise AND: rd = rs1 &. rs2 + // Format: STRING_BITWISE_AND rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseAnd(bytecode, pc, registers); + } - // Continue execution - if exception occurs, outer catch handler - // will check evalCatchStack and jump to catchPc - break; - } + 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.EVAL_END: { - // End of successful eval block - clear $@ and pop catch stack - GlobalVariable.setGlobalVariable("main::@", ""); + case Opcodes.STRING_BITWISE_XOR -> { + // String bitwise XOR: rd = rs1 ^. rs2 + // Format: STRING_BITWISE_XOR rd rs1 rs2 + pc = OpcodeHandlerExtended.executeStringBitwiseXor(bytecode, pc, registers); + } - // Pop the catch PC from eval stack (we didn't need it) - if (!evalCatchStack.isEmpty()) { - evalCatchStack.pop(); - } - break; - } + case Opcodes.XOR_LOGICAL -> { + pc = InlineOpcodeHandler.executeXorLogical(bytecode, pc, registers); + } - case Opcodes.EVAL_CATCH: { - // Exception handler for eval block - // Format: [EVAL_CATCH] [rd] - // This is only reached when an exception is caught + case Opcodes.BITWISE_NOT_BINARY -> { + // Numeric bitwise NOT: rd = binary~ rs + // Format: BITWISE_NOT_BINARY rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotBinary(bytecode, pc, registers); + } - int rd = bytecode[pc++]; + case Opcodes.BITWISE_NOT_STRING -> { + // String bitwise NOT: rd = ~. rs + // Format: BITWISE_NOT_STRING rd rs + pc = OpcodeHandlerExtended.executeBitwiseNotString(bytecode, pc, registers); + } - // WarnDie.catchEval() should have already been called to set $@ - // Just store undef as the eval result - registers[rd] = RuntimeScalarCache.scalarUndef; - break; - } + // File test and stat operations + case Opcodes.STAT -> { + pc = OpcodeHandlerExtended.executeStat(bytecode, pc, registers); + } - // ================================================================= - // LABELED BLOCK SUPPORT - // ================================================================= + case Opcodes.LSTAT -> { + pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers); + } - 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.STAT_LASTHANDLE -> { + pc = OpcodeHandlerExtended.executeStatLastHandle(bytecode, pc, registers); + } - case Opcodes.POP_LABELED_BLOCK: { - if (!labeledBlockStack.isEmpty()) { - labeledBlockStack.pop(); - } - break; - } + case Opcodes.LSTAT_LASTHANDLE -> { + pc = OpcodeHandlerExtended.executeLstatLastHandle(bytecode, pc, registers); + } - // ================================================================= - // 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; - } + // 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.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.PUSH_LOCAL_VARIABLE -> { + pc = InlineOpcodeHandler.executePushLocalVariable(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.STORE_GLOB -> { + pc = InlineOpcodeHandler.executeStoreGlob(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]); + case Opcodes.OPEN -> { + // Open file: rd = IOOperator.open(ctx, args...) + // Format: OPEN rd ctx argsReg + pc = OpcodeHandlerExtended.executeOpen(bytecode, pc, registers); } - registers[rd] = list; - } - break; - } + case Opcodes.READLINE -> { + // Read line from filehandle + // Format: READLINE rd fhReg ctx + pc = OpcodeHandlerExtended.executeReadline(bytecode, pc, registers); + } - // ================================================================= - // STRING OPERATIONS - // ================================================================= + case Opcodes.MATCH_REGEX -> { + // Match regex + // Format: MATCH_REGEX rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegex(bytecode, pc, registers); + } - case Opcodes.JOIN: { - // String join: rd = join(separator, list) - int rd = bytecode[pc++]; - int separatorReg = bytecode[pc++]; - int listReg = bytecode[pc++]; + case Opcodes.MATCH_REGEX_NOT -> { + // Negated regex match + // Format: MATCH_REGEX_NOT rd stringReg regexReg ctx + pc = OpcodeHandlerExtended.executeMatchRegexNot(bytecode, pc, registers); + } - // Separator should be scalar - convert if needed - RuntimeBase separatorBase = registers[separatorReg]; - RuntimeScalar separator = (separatorBase instanceof RuntimeScalar) - ? (RuntimeScalar) separatorBase - : separatorBase.scalar(); + case Opcodes.CHOMP -> { + // Chomp: rd = rs.chomp() + // Format: CHOMP rd rs + pc = OpcodeHandlerExtended.executeChomp(bytecode, pc, registers); + } - RuntimeBase list = registers[listReg]; + case Opcodes.WANTARRAY -> { + // Get wantarray context + // Format: WANTARRAY rd wantarrayReg + pc = OpcodeHandlerExtended.executeWantarray(bytecode, pc, registers); + } - // Call StringOperators.joinForInterpolation (doesn't warn on undef) - registers[rd] = StringOperators.joinForInterpolation(separator, list); - break; - } + case Opcodes.REQUIRE -> { + // Require module or version + // Format: REQUIRE rd rs + pc = OpcodeHandlerExtended.executeRequire(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.POS -> { + // Get regex position + // Format: POS rd rs + pc = OpcodeHandlerExtended.executePos(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++]; + case Opcodes.INDEX -> { + // Find substring position + // Format: INDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeIndex(bytecode, pc, registers); + } - RuntimeBase startBase = registers[startReg]; - RuntimeBase endBase = registers[endReg]; + case Opcodes.RINDEX -> { + // Find substring position from end + // Format: RINDEX rd strReg substrReg posReg + pc = OpcodeHandlerExtended.executeRindex(bytecode, pc, registers); + } - // 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(); + case Opcodes.PRE_AUTOINCREMENT -> { + // Pre-increment: ++rd + // Format: PRE_AUTOINCREMENT rd + pc = OpcodeHandlerExtended.executePreAutoIncrement(bytecode, pc, registers); + } - PerlRange range = PerlRange.createRange(start, end); - registers[rd] = range; - break; - } + case Opcodes.POST_AUTOINCREMENT -> { + // Post-increment: rd = rs++ + // Format: POST_AUTOINCREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoIncrement(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++]; + case Opcodes.PRE_AUTODECREMENT -> { + // Pre-decrement: --rd + // Format: PRE_AUTODECREMENT rd + pc = OpcodeHandlerExtended.executePreAutoDecrement(bytecode, pc, registers); + } - RuntimeBase list = registers[listReg]; - RuntimeHash hash = RuntimeHash.createHash(list); + case Opcodes.POST_AUTODECREMENT -> { + // Post-decrement: rd = rs-- + // Format: POST_AUTODECREMENT rd rs + pc = OpcodeHandlerExtended.executePostAutoDecrement(bytecode, pc, registers); + } - // Create reference (hash literals always return references!) - registers[rd] = hash.createReference(); - break; - } + // ================================================================= + // ERROR HANDLING + // ================================================================= - case Opcodes.RAND: { - // Random number: rd = Random.rand(max) - int rd = bytecode[pc++]; - int maxReg = bytecode[pc++]; + case Opcodes.DIE -> { + pc = InlineOpcodeHandler.executeDie(bytecode, pc, registers, code); + } - RuntimeScalar max = (RuntimeScalar) registers[maxReg]; - registers[rd] = Random.rand(max); - break; - } + case Opcodes.WARN -> { + pc = InlineOpcodeHandler.executeWarn(bytecode, pc, registers, code); + } - 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; - } + // ================================================================= + // REFERENCE OPERATIONS + // ================================================================= - 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.CREATE_REF -> { + pc = InlineOpcodeHandler.executeCreateRef(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.DEREF -> { + pc = InlineOpcodeHandler.executeDeref(bytecode, pc, registers); + } - case Opcodes.NEW_ARRAY: { - // Create empty array: rd = new RuntimeArray() - int rd = bytecode[pc++]; - registers[rd] = new RuntimeArray(); - break; - } + case Opcodes.GET_TYPE -> { + pc = InlineOpcodeHandler.executeGetType(bytecode, pc, registers); + } - case Opcodes.NEW_HASH: { - // Create empty hash: rd = new RuntimeHash() - int rd = bytecode[pc++]; - registers[rd] = new RuntimeHash(); - break; - } + // ================================================================= + // EVAL BLOCK SUPPORT + // ================================================================= - 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++]; + 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) - RuntimeArray array = (RuntimeArray) registers[arrayReg]; - RuntimeBase listBase = registers[listReg]; - RuntimeList list = listBase.getList(); + int catchPc = readInt(bytecode, pc); // Read 4-byte absolute address + pc += 1; // Skip the 2 shorts we just read - // setFromList clears and repopulates the array - array.setFromList(list); - break; - } + // Push catch PC onto eval stack + evalCatchStack.push(catchPc); - 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; - } + // Clear $@ at start of eval block + GlobalVariable.setGlobalVariable("main::@", ""); - 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++]; + // Continue execution - if exception occurs, outer catch handler + // will check evalCatchStack and jump to catchPc + } - RuntimeHash existingHash = (RuntimeHash) registers[hashReg]; - RuntimeBase listBase = registers[listReg]; + case Opcodes.EVAL_END -> { + // End of successful eval block - clear $@ and pop catch stack + GlobalVariable.setGlobalVariable("main::@", ""); - // Create new hash from list, then copy elements to existing hash - RuntimeHash newHash = RuntimeHash.createHash(listBase); - existingHash.elements = newHash.elements; - break; - } + // Pop the catch PC from eval stack (we didn't need it) + if (!evalCatchStack.isEmpty()) { + evalCatchStack.pop(); + } + } - // ================================================================= - // 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: - 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: - 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: - 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: - 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: - pc = executeSystemOps(opcode, bytecode, pc, registers); - break; - - // 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(); - 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: - pc = executeSpecialIO(opcode, bytecode, pc, registers, code); - break; - - // ================================================================= - // SLOW OPERATIONS (DEPRECATED) - // ================================================================= - - // 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_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: - 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: - pc = ScalarUnaryOpcodeHandler.execute(opcode, bytecode, pc, registers); - break; - // GENERATED_HANDLERS_END - - 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.EVAL_CATCH -> { + // Exception handler for eval block + // Format: [EVAL_CATCH] [rd] + // This is only reached when an exception is caught - 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]; - - // 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; - } + int rd = bytecode[pc++]; - 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; - } + // WarnDie.catchEval() should have already been called to set $@ + // Just store undef as the eval result + registers[rd] = RuntimeScalarCache.scalarUndef; + } - 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; - } + // ================================================================= + // LABELED BLOCK SUPPORT + // ================================================================= - case Opcodes.SPRINTF: - // sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) - // Format: SPRINTF rd formatReg argsListReg - pc = OpcodeHandlerExtended.executeSprintf(bytecode, pc, registers); - break; - - case Opcodes.CHOP: - // chop($x): rd = StringOperators.chopScalar(scalarReg) - // Format: CHOP rd scalarReg - pc = OpcodeHandlerExtended.executeChop(bytecode, pc, registers); - break; - - case Opcodes.GET_REPLACEMENT_REGEX: - // Get replacement regex: rd = RuntimeRegex.getReplacementRegex(pattern, replacement, flags) - // Format: GET_REPLACEMENT_REGEX rd pattern_reg replacement_reg flags_reg - pc = OpcodeHandlerExtended.executeGetReplacementRegex(bytecode, pc, registers); - break; - - case Opcodes.SUBSTR_VAR: - // substr with variable args: rd = Operator.substr(ctx, args...) - // Format: SUBSTR_VAR rd argsListReg ctx - pc = OpcodeHandlerExtended.executeSubstrVar(bytecode, pc, registers); - break; - - 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.PUSH_LABELED_BLOCK -> { + int labelIdx = bytecode[pc++]; + int exitPc = readInt(bytecode, pc); + pc += 1; + labeledBlockStack.push(new int[]{labelIdx, exitPc}); + } - 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.POP_LABELED_BLOCK -> { + if (!labeledBlockStack.isEmpty()) { + labeledBlockStack.pop(); + } + } - 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; - } + // ================================================================= + // LIST OPERATIONS + // ================================================================= - // 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: - pc = MiscOpcodeHandler.execute(opcode, bytecode, pc, registers); - break; - - 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.LIST_TO_COUNT -> { + pc = InlineOpcodeHandler.executeListToCount(bytecode, pc, registers); + } - 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.LIST_TO_SCALAR -> { + pc = InlineOpcodeHandler.executeListToScalar(bytecode, pc, registers); + } - 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.SCALAR_TO_LIST -> { + pc = InlineOpcodeHandler.executeScalarToList(bytecode, pc, registers); + } - 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.CREATE_LIST -> { + pc = InlineOpcodeHandler.executeCreateList(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; - } + // ================================================================= + // STRING OPERATIONS + // ================================================================= - 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.JOIN -> { + pc = InlineOpcodeHandler.executeJoin(bytecode, pc, registers); + } - default: - // Unknown opcode - int opcodeInt = opcode; - throw new RuntimeException( - "Unknown opcode: " + opcodeInt + - " at pc=" + (pc - 1) + - " in " + code.sourceName + ":" + code.sourceLine - ); - } - } + case Opcodes.SELECT -> { + pc = InlineOpcodeHandler.executeSelect(bytecode, pc, registers); + } - // Fell through end of bytecode - return empty list - return new RuntimeList(); + case Opcodes.RANGE -> { + pc = InlineOpcodeHandler.executeRange(bytecode, pc, registers); + } - } 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; - } + case Opcodes.CREATE_HASH -> { + pc = InlineOpcodeHandler.executeCreateHash(bytecode, pc, registers); + } - // Not in eval - show detailed error with bytecode context - int errorPc = Math.max(0, pc - 1); + case Opcodes.RAND -> { + pc = InlineOpcodeHandler.executeRand(bytecode, pc, registers); + } - // 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(" ]"); + case Opcodes.MAP -> { + pc = InlineOpcodeHandler.executeMap(bytecode, pc, registers); + } - 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 + case Opcodes.GREP -> { + pc = InlineOpcodeHandler.executeGrep(bytecode, pc, registers); + } - // Call WarnDie.catchEval() to set $@ - WarnDie.catchEval(e); + case Opcodes.SORT -> { + pc = InlineOpcodeHandler.executeSort(bytecode, pc, registers, code); + } - pc = catchPc; - continue outer; - } + case Opcodes.NEW_ARRAY -> { + pc = InlineOpcodeHandler.executeNewArray(bytecode, pc, registers); + } - // 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; - } + case Opcodes.NEW_HASH -> { + pc = InlineOpcodeHandler.executeNewHash(bytecode, pc, registers); + } - // 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; - } + case Opcodes.ARRAY_SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeArraySetFromList(bytecode, pc, registers); + } - // 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 - } finally { - DynamicVariableManager.popToLocalLevel(savedLocalLevel); - InterpreterState.currentPackage.get().set(savedPackage); - InterpreterState.pop(); - } - } + case Opcodes.SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeSetFromList(bytecode, pc, registers); + } + + case Opcodes.HASH_SET_FROM_LIST -> { + pc = InlineOpcodeHandler.executeHashSetFromList(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. + + // 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 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 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) + // ================================================================= + + // 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_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_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 - /** - * 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.TR_TRANSLITERATE -> { + pc = SlowOpcodeHandler.executeTransliterate(bytecode, pc, registers); + } - 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.STORE_SYMBOLIC_SCALAR -> { + pc = InlineOpcodeHandler.executeStoreSymbolicScalar(bytecode, pc, registers); + } - 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.LOAD_SYMBOLIC_SCALAR -> { + pc = InlineOpcodeHandler.executeLoadSymbolicScalar(bytecode, pc, registers); + } - 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.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.NEG_SCALAR: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = MathOperators.unaryMinus((RuntimeScalar) registers[rs]); - return pc; - } + 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.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.SPRINTF -> { + // sprintf($format, @args): rd = SprintfOperator.sprintf(formatReg, argsListReg) + // Format: SPRINTF rd formatReg argsListReg + pc = OpcodeHandlerExtended.executeSprintf(bytecode, pc, registers); + } - 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.CHOP -> { + // chop($x): rd = StringOperators.chopScalar(scalarReg) + // Format: CHOP rd scalarReg + pc = OpcodeHandlerExtended.executeChop(bytecode, pc, registers); + } - 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.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.LENGTH: { - int rd = bytecode[pc++]; - int rs = bytecode[pc++]; - registers[rd] = StringOperators.length((RuntimeScalar) registers[rs]); - return pc; - } + 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.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.TIE -> { + pc = InlineOpcodeHandler.executeTie(bytecode, pc, registers); + } - 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.UNTIE -> { + pc = InlineOpcodeHandler.executeUntie(bytecode, pc, registers); + } - 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.TIED -> { + pc = InlineOpcodeHandler.executeTied(bytecode, pc, registers); + } - 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; - } + // 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.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.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.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.PUSH_PACKAGE -> { + pc = InlineOpcodeHandler.executePushPackage(bytecode, pc, registers, code); + } - 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.FLIP_FLOP -> { + pc = InlineOpcodeHandler.executeFlipFlop(bytecode, pc, registers); + } - 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.LOCAL_GLOB -> { + pc = InlineOpcodeHandler.executeLocalGlob(bytecode, pc, registers, code); + } - 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.GET_LOCAL_LEVEL -> { + pc = InlineOpcodeHandler.executeGetLocalLevel(bytecode, pc, registers); + } - 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.POP_PACKAGE -> { + // Scoped package block exit — restore handled by POP_LOCAL_LEVEL. + } - 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.DO_FILE -> { + pc = InlineOpcodeHandler.executeDoFile(bytecode, pc, registers); + } - 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; - } + default -> { + int opcodeInt = opcode; + throw new RuntimeException( + "Unknown opcode: " + opcodeInt + + " at pc=" + (pc - 1) + + " in " + code.sourceName + ":" + code.sourceLine + ); + } + } + } - 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; - } + // Fell through end of bytecode - return empty list + return new RuntimeList(); - 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; - } + } 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; + } - 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; - } + // Not in eval - show detailed error with bytecode context + int errorPc = Math.max(0, pc - 1); - 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; - } + // 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(" ]"); - // 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; - } + 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 - 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; - } + // Call WarnDie.catchEval() to set $@ + WarnDie.catchEval(e); - 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; - } + 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; + } + + // 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; + } - default: - throw new RuntimeException("Unknown arithmetic opcode: " + opcode); + // 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 + // the current package, and pops the InterpreterState call stack. + DynamicVariableManager.popToLocalLevel(savedLocalLevel); + InterpreterState.currentPackage.get().set(savedPackage); + InterpreterState.pop(); } } @@ -2891,7 +1634,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 +1647,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 +1660,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 +1673,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 +1686,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 +1699,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 +1712,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 +1725,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 +1738,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 +1753,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,45 +1768,44 @@ 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) - ? (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; } - 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++]; 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; } - 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++]; 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); } } @@ -3074,7 +1816,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 +1824,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 +1839,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 +1848,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,15 +1859,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: - throw new RuntimeException("Unknown type opcode: " + opcode); + default -> throw new RuntimeException("Unknown type opcode: " + opcode); } } @@ -3135,33 +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: + case Opcodes.DEREF_ARRAY -> { return SlowOpcodeHandler.executeDerefArray(bytecode, pc, registers); - case Opcodes.DEREF_HASH: + } + case Opcodes.DEREF_HASH -> { return SlowOpcodeHandler.executeDerefHash(bytecode, pc, registers); - case Opcodes.DEREF_HASH_NONSTRICT: + } + case Opcodes.DEREF_HASH_NONSTRICT -> { return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code); - case Opcodes.DEREF_ARRAY_NONSTRICT: + } + case Opcodes.DEREF_ARRAY_NONSTRICT -> { return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code); - case Opcodes.ARRAY_SLICE: + } + case Opcodes.ARRAY_SLICE -> { return SlowOpcodeHandler.executeArraySlice(bytecode, pc, registers); - case Opcodes.ARRAY_SLICE_SET: + } + case Opcodes.ARRAY_SLICE_SET -> { return SlowOpcodeHandler.executeArraySliceSet(bytecode, pc, registers); - case Opcodes.HASH_SLICE: + } + case Opcodes.HASH_SLICE -> { return SlowOpcodeHandler.executeHashSlice(bytecode, pc, registers); - case Opcodes.HASH_SLICE_SET: + } + case Opcodes.HASH_SLICE_SET -> { return SlowOpcodeHandler.executeHashSliceSet(bytecode, pc, registers); - case Opcodes.HASH_SLICE_DELETE: + } + case Opcodes.HASH_SLICE_DELETE -> { return SlowOpcodeHandler.executeHashSliceDelete(bytecode, pc, registers); - case Opcodes.HASH_KEYVALUE_SLICE: + } + case Opcodes.HASH_KEYVALUE_SLICE -> { return SlowOpcodeHandler.executeHashKeyValueSlice(bytecode, pc, registers); - case Opcodes.LIST_SLICE_FROM: + } + case Opcodes.LIST_SLICE_FROM -> { return SlowOpcodeHandler.executeListSliceFrom(bytecode, pc, registers); - default: - throw new RuntimeException("Unknown slice opcode: " + opcode); + } + default -> throw new RuntimeException("Unknown slice opcode: " + opcode); } } @@ -3170,22 +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: + case Opcodes.SPLICE -> { return SlowOpcodeHandler.executeSplice(bytecode, pc, registers); - case Opcodes.REVERSE: + } + case Opcodes.REVERSE -> { return SlowOpcodeHandler.executeReverse(bytecode, pc, registers); - case Opcodes.SPLIT: + } + case Opcodes.SPLIT -> { return SlowOpcodeHandler.executeSplit(bytecode, pc, registers); - case Opcodes.LENGTH_OP: + } + case Opcodes.LENGTH_OP -> { return SlowOpcodeHandler.executeLength(bytecode, pc, registers); - case Opcodes.EXISTS: + } + case Opcodes.EXISTS -> { return SlowOpcodeHandler.executeExists(bytecode, pc, registers); - case Opcodes.DELETE: + } + case Opcodes.DELETE -> { return SlowOpcodeHandler.executeDelete(bytecode, pc, registers); - default: - throw new RuntimeException("Unknown array/string opcode: " + opcode); + } + default -> throw new RuntimeException("Unknown array/string opcode: " + opcode); } } @@ -3194,17 +1950,21 @@ 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: + case Opcodes.RETRIEVE_BEGIN_SCALAR -> { return SlowOpcodeHandler.executeRetrieveBeginScalar(bytecode, pc, registers, code); - case Opcodes.RETRIEVE_BEGIN_ARRAY: + } + case Opcodes.RETRIEVE_BEGIN_ARRAY -> { return SlowOpcodeHandler.executeRetrieveBeginArray(bytecode, pc, registers, code); - case Opcodes.RETRIEVE_BEGIN_HASH: + } + case Opcodes.RETRIEVE_BEGIN_HASH -> { return SlowOpcodeHandler.executeRetrieveBeginHash(bytecode, pc, registers, code); - case Opcodes.LOCAL_SCALAR: + } + case Opcodes.LOCAL_SCALAR -> { return SlowOpcodeHandler.executeLocalScalar(bytecode, pc, registers, code); - case Opcodes.LOCAL_ARRAY: { + } + case Opcodes.LOCAL_ARRAY -> { int rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; String fullName = code.stringPool[nameIdx]; @@ -3214,7 +1974,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,59 +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: + case Opcodes.CHOWN -> { return MiscOpcodeHandler.execute(Opcodes.CHOWN, bytecode, pc, registers); - case Opcodes.WAITPID: + } + case Opcodes.WAITPID -> { return MiscOpcodeHandler.execute(Opcodes.WAITPID, bytecode, pc, registers); - case Opcodes.FORK: + } + case Opcodes.FORK -> { return SlowOpcodeHandler.executeFork(bytecode, pc, registers); - case Opcodes.GETPPID: + } + case Opcodes.GETPPID -> { return SlowOpcodeHandler.executeGetppid(bytecode, pc, registers); - case Opcodes.GETPGRP: + } + case Opcodes.GETPGRP -> { return MiscOpcodeHandler.execute(Opcodes.GETPGRP, bytecode, pc, registers); - case Opcodes.SETPGRP: + } + case Opcodes.SETPGRP -> { return MiscOpcodeHandler.execute(Opcodes.SETPGRP, bytecode, pc, registers); - case Opcodes.GETPRIORITY: + } + case Opcodes.GETPRIORITY -> { return MiscOpcodeHandler.execute(Opcodes.GETPRIORITY, bytecode, pc, registers); - case Opcodes.SETPRIORITY: + } + case Opcodes.SETPRIORITY -> { return MiscOpcodeHandler.execute(Opcodes.SETPRIORITY, bytecode, pc, registers); - case Opcodes.GETSOCKOPT: + } + case Opcodes.GETSOCKOPT -> { return MiscOpcodeHandler.execute(Opcodes.GETSOCKOPT, bytecode, pc, registers); - case Opcodes.SETSOCKOPT: + } + case Opcodes.SETSOCKOPT -> { return MiscOpcodeHandler.execute(Opcodes.SETSOCKOPT, bytecode, pc, registers); - case Opcodes.SYSCALL: + } + case Opcodes.SYSCALL -> { return SlowOpcodeHandler.executeSyscall(bytecode, pc, registers); - case Opcodes.SEMGET: + } + case Opcodes.SEMGET -> { return SlowOpcodeHandler.executeSemget(bytecode, pc, registers); - case Opcodes.SEMOP: + } + case Opcodes.SEMOP -> { return SlowOpcodeHandler.executeSemop(bytecode, pc, registers); - case Opcodes.MSGGET: + } + case Opcodes.MSGGET -> { return SlowOpcodeHandler.executeMsgget(bytecode, pc, registers); - case Opcodes.MSGSND: + } + case Opcodes.MSGSND -> { return SlowOpcodeHandler.executeMsgsnd(bytecode, pc, registers); - case Opcodes.MSGRCV: + } + case Opcodes.MSGRCV -> { return SlowOpcodeHandler.executeMsgrcv(bytecode, pc, registers); - case Opcodes.SHMGET: + } + case Opcodes.SHMGET -> { return SlowOpcodeHandler.executeShmget(bytecode, pc, registers); - case Opcodes.SHMREAD: + } + case Opcodes.SHMREAD -> { return SlowOpcodeHandler.executeShmread(bytecode, pc, registers); - case Opcodes.SHMWRITE: + } + case Opcodes.SHMWRITE -> { return SlowOpcodeHandler.executeShmwrite(bytecode, pc, registers); - default: - throw new RuntimeException("Unknown system opcode: " + opcode); + } + default -> throw new RuntimeException("Unknown system opcode: " + opcode); } } @@ -3286,30 +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: + case Opcodes.EVAL_STRING -> { return SlowOpcodeHandler.executeEvalString(bytecode, pc, registers, code); - case Opcodes.SELECT_OP: + } + case Opcodes.SELECT_OP -> { return SlowOpcodeHandler.executeSelect(bytecode, pc, registers); - case Opcodes.LOAD_GLOB: + } + case Opcodes.LOAD_GLOB -> { return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code); - case Opcodes.SLEEP_OP: + } + case Opcodes.SLEEP_OP -> { return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); - case Opcodes.ALARM_OP: + } + case Opcodes.ALARM_OP -> { return SlowOpcodeHandler.executeAlarm(bytecode, pc, registers); - case Opcodes.DEREF_GLOB: + } + case Opcodes.DEREF_GLOB -> { return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code); - case Opcodes.DEREF_GLOB_NONSTRICT: + } + case Opcodes.DEREF_GLOB_NONSTRICT -> { return SlowOpcodeHandler.executeDerefGlobNonStrict(bytecode, pc, registers, code); - case Opcodes.LOAD_GLOB_DYNAMIC: + } + case Opcodes.LOAD_GLOB_DYNAMIC -> { return SlowOpcodeHandler.executeLoadGlobDynamic(bytecode, pc, registers, code); - case Opcodes.DEREF_SCALAR_STRICT: + } + case Opcodes.DEREF_SCALAR_STRICT -> { return SlowOpcodeHandler.executeDerefScalarStrict(bytecode, pc, registers); - case Opcodes.DEREF_SCALAR_NONSTRICT: + } + case Opcodes.DEREF_SCALAR_NONSTRICT -> { return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code); - default: - throw new RuntimeException("Unknown special I/O opcode: " + opcode); + } + default -> throw new RuntimeException("Unknown special I/O opcode: " + opcode); } } @@ -3342,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(); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 3c7fc133a..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; @@ -543,19 +536,18 @@ 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; } - } 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; @@ -599,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; @@ -646,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; @@ -690,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; @@ -754,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; @@ -899,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("$")) { @@ -919,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(); @@ -1034,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 @@ -1129,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) { @@ -1207,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) { @@ -1296,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("@")) { @@ -1347,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; @@ -1471,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; @@ -1641,13 +1618,11 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rhsReg); bytecodeCompiler.lastResultReg = rhsReg; bytecodeCompiler.currentCallContext = savedContext; - return; - } 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. @@ -1765,7 +1740,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..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; @@ -266,12 +262,11 @@ 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 - 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; @@ -279,7 +274,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 @@ -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")) @@ -807,11 +797,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..bc88c1798 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 @@ -254,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"); } @@ -283,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"); } @@ -581,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; @@ -921,10 +913,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()); @@ -1290,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; @@ -1359,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) { @@ -1393,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); @@ -1431,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 @@ -1465,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<>(); @@ -1512,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; @@ -1576,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) { @@ -1610,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; @@ -1657,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); @@ -1734,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); @@ -1793,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) @@ -1881,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"); } @@ -2180,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); } @@ -2216,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"); } @@ -2589,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; @@ -2630,23 +2605,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..c7cc5e8e9 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(); } @@ -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; @@ -283,8 +282,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 +294,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 +320,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 +337,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 +378,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 new file mode 100644 index 000000000..d35df3b79 --- /dev/null +++ b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java @@ -0,0 +1,1243 @@ +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 + : 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 arr) { + registers[rd] = arr.get(idx.getInt()); + } 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()) + ? 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 scalar) { + 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, + registers[rs1].scalar(), + 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 = 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..9a356763d 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) + + '}'; } /** @@ -312,14 +320,12 @@ 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("}"); + .append(range.getEnd().toString()).append("}"); } else { // For other objects, show class name and limit string length String objStr = obj.toString(); @@ -344,15 +350,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 +378,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 +1113,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 +1123,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 +1161,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 +1169,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 +1178,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 +1246,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 +1261,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 +1293,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 +1424,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 +1500,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 +1508,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 +1530,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 +1541,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 +1593,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; } @@ -1689,6 +1696,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; @@ -1697,14 +2305,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. */ @@ -1757,7 +2357,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..d2f9abc7b 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,11 @@ public static List getPcStack() { return new ArrayList<>(pcStack.get()); } + /** + * 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/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..4684ac602 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 */ @@ -68,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); @@ -81,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)); @@ -122,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("::")) { @@ -136,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 01c0a4483..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/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..c17e59bdc 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,18 +129,17 @@ 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 - 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 $_ 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..373895c85 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,23 @@ 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; + if (ast instanceof BlockNode blockNode) { 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 +1472,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 +1540,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 +1582,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 +1603,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 +1623,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 +1644,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 +1663,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..ef3d3329d 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,7 @@ public String toString() { " gotoLabelStack=" + gotoLabelStack + "\n" + "}"; } + + public record SpillRef(int slot, boolean 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/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..9e572eaf4 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,10 @@ 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..474fe0824 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,14 +861,14 @@ public static BlockNode parseOptionalPackageBlock(Parser parser, IdentifierNode BlockNode block; int blockScopeIndex; - + try { if (isClass) { // For classes, delay scope exit until after ClassTransformer runs // This allows methods to capture class-level lexical variables ParseBlock.BlockWithScope result = ParseBlock.parseBlock(parser, false); - block = result.block; - blockScopeIndex = result.scopeIndex; + block = result.block(); + blockScopeIndex = result.scopeIndex(); } else { // For packages, exit scope normally block = ParseBlock.parseBlock(parser); @@ -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..4fc823467 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java @@ -5,9 +5,9 @@ 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; @@ -96,9 +96,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 +208,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 +223,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 +234,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 +250,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 +272,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 +280,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 +292,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 +303,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 +333,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 +344,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 +354,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 +371,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 +393,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 +439,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 +450,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 +460,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,8 +554,8 @@ 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); } @@ -576,8 +576,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 +781,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/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..15d2bbd96 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,11 @@ 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) { + 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,20 +814,18 @@ 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()) { 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 +840,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..17a67d8d2 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; @@ -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) diff --git a/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java b/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java index 5afc720ed..dc4de5b37 100644 --- a/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java +++ b/src/main/java/org/perlonjava/runtime/operators/FormatModifierValidator.java @@ -84,8 +84,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); } @@ -173,19 +173,19 @@ public char getSymbol() { } /** - * 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(); - } + * 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); + 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..e0bb87701 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); diff --git a/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java b/src/main/java/org/perlonjava/runtime/operators/ModuleOperators.java index de6430c8b..44bbb1925 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,21 +122,20 @@ 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; // ===== 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); - + // Case 1a: Array with CODE reference [&coderef, state...] // Extract coderef and state parameters for later execution if (firstElem.type == RuntimeScalarType.CODE || @@ -154,7 +153,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 +170,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 +190,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 +229,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 +259,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 +267,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 +315,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 +334,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 +369,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 +400,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 +414,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 +445,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 @@ -486,17 +485,16 @@ 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 || - 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 +510,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 +598,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 +665,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 +675,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 +686,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 +695,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 +786,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); @@ -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) { @@ -859,24 +856,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..057789482 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); } @@ -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/Pack.java b/src/main/java/org/perlonjava/runtime/operators/Pack.java index f794f14eb..c04f5230d 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 @@ -524,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/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..fe8d91f43 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 @@ -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/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/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/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..796869357 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 { @@ -125,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); @@ -160,8 +158,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); @@ -181,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); @@ -198,8 +194,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(); @@ -213,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]; @@ -232,8 +226,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/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/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/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java index 55823abaa..8900ba6df 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. * @@ -356,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 a57c306eb..ba36471a8 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,42 +74,42 @@ 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. 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()); } @@ -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..eeef675e5 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() { @@ -97,4 +94,7 @@ public void dynamicRestoreState() { } } } + + private record State(RuntimeIO handle, boolean autoFlush) { + } } 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..084acde2b 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java @@ -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) { @@ -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(); @@ -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..ce06a675f 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; @@ -249,6 +169,43 @@ public RuntimeCode(MethodHandle methodObject, Object codeObject, String prototyp 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 +241,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 +288,277 @@ 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().charAt(0) + 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 pde) { + RuntimeBase handlerPayload = pde.getPayload(); + if (handlerPayload != null) { + err.set(handlerPayload.getFirst()); + } + } else if (handlerException instanceof PerlDieException pde) { + 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); } - } - return generatedClass; + // Cache the result (unless debugging is enabled) + if (!isDebugging) { + synchronized (evalCache) { + evalCache.put(cacheKey, 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; } @@ -838,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); } } } @@ -965,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()); @@ -1015,7 +968,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 +1022,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 +1292,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 +1302,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 +1348,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 +1436,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 +1448,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 +1479,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 +1494,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 +1526,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 +1538,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 +1617,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 +1631,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 +1641,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 +1695,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 +1841,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 +1962,35 @@ 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 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]; + } + } + } + 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..bee6cf991 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,15 +542,6 @@ 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) {} - @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 e6256093d..cc2a3a69a 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(); 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..3c81ed905 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(); @@ -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(); } }; } @@ -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..1ca7519d7 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java @@ -19,14 +19,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; @@ -159,7 +154,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); @@ -355,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) { + } } 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(); } }