diff --git a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java index 9736fcc9a..b12529d14 100644 --- a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java +++ b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java @@ -34,7 +34,7 @@ */ public class CompilerOptions implements Cloneable { public boolean debugEnabled = false; - public boolean disassembleEnabled = System.getenv("JPERL_DISASSEMBLE") != null; + public boolean disassembleEnabled = false; public boolean useInterpreter = false; public boolean tokenizeOnly = false; public boolean parseOnly = false; diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index c26d44f07..b8ce5c3fc 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -147,13 +147,10 @@ public BytecodeCompiler(String sourceName, int sourceLine, ErrorMessageUtil erro globalScope.put("wantarray", 2); if (parentRegistry != null) { - // Add parent scope variables to the global scope so hasVariable() finds them. - // A "my $x" inside the eval will call addVariable() which puts $x into the - // current (inner) scope, shadowing the global-scope entry — so lookups find - // the local my-declared register first (correct shadowing behaviour). + // Add parent scope variables (for eval STRING variable capture) globalScope.putAll(parentRegistry); - // Also mark them as captured so assignments use SET_SCALAR (not STORE_SCALAR) + // Mark parent scope variables as captured so assignments use SET_SCALAR capturedVarIndices = new HashMap<>(); for (Map.Entry entry : parentRegistry.entrySet()) { String varName = entry.getKey(); @@ -230,23 +227,12 @@ private void enterScope() { savedBaseRegister.push(baseRegisterForStatement); // Update base to protect all registers allocated before this scope baseRegisterForStatement = nextRegister; - // Mirror the JVM compiler: push a new pragma frame so that strict/feature/warning - // changes inside this block (e.g. `use strict`, `use 5.012`) are scoped to the block - // and do not leak into the surrounding code after the block exits. - if (emitterContext != null && emitterContext.symbolTable != null) { - savedSymbolTableScopes.push(emitterContext.symbolTable.enterScope()); - } else { - savedSymbolTableScopes.push(-1); // sentinel so stacks stay in sync - } } /** * Helper: Exit the current lexical scope. * Restores register allocation state to what it was before entering the scope. */ - // Saved symbol-table scope indices, parallel to savedNextRegister / savedBaseRegister. - private final Stack savedSymbolTableScopes = new Stack<>(); - private void exitScope() { if (variableScopes.size() > 1) { variableScopes.pop(); @@ -258,11 +244,6 @@ private void exitScope() { baseRegisterForStatement = savedBaseRegister.pop(); } } - // Restore the pragma frame (strict/feature/warning flags) for the scope we just exited. - if (emitterContext != null && emitterContext.symbolTable != null - && !savedSymbolTableScopes.isEmpty()) { - emitterContext.symbolTable.exitScope(savedSymbolTableScopes.pop()); - } } /** @@ -385,30 +366,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 at compile time. */ - boolean isStrictRefsEnabled() { - if (emitterContext == null || emitterContext.symbolTable == null) { - return false; - } - return emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_REFS); - } - - /** Returns the current strict options bitmask at this point in compilation. */ - int getCurrentStrictOptions() { - if (emitterContext == null || emitterContext.symbolTable == null) { - return 0; - } - return emitterContext.symbolTable.strictOptionsStack.peek(); - } - - /** Returns the current feature flags bitmask at this point in compilation. */ - int getCurrentFeatureFlags() { - if (emitterContext == null || emitterContext.symbolTable == null) { - return 0; - } - return emitterContext.symbolTable.featureFlagsStack.peek(); - } - boolean shouldBlockGlobalUnderStrictVars(String varName) { // Only check if strict vars is enabled if (emitterContext == null || emitterContext.symbolTable == null) { @@ -857,71 +814,72 @@ public void visit(IdentifierNode node) { // Variable reference String varName = node.name; + // Check if this is a captured variable (with sigil) + // Try common sigils: $, @, % String[] sigils = {"$", "@", "%"}; - - // Check local scope first (my declarations shadow captured vars from outer scope). - // This is critical for eval STRING: if the eval declares "my $x", that new $x - // must shadow any $x captured from the outer scope (which lives at a different - // register index from the outer code's register file). - if (hasVariable(varName)) { - lastResultReg = getVariableRegister(varName); - return; - } - for (String sigil : sigils) { - String varNameWithSigil = sigil + varName; - if (hasVariable(varNameWithSigil)) { - lastResultReg = getVariableRegister(varNameWithSigil); - return; - } - } - - // Not in local scope - check captured variables from outer eval scope. - // capturedVarIndices holds variables captured from the parent InterpretedCode - // (set up by EvalStringHandler). Only fall through to this if no local my - // declaration shadows the name. for (String sigil : sigils) { String varNameWithSigil = sigil + varName; if (capturedVarIndices != null && capturedVarIndices.containsKey(varNameWithSigil)) { + // Captured variable - use its pre-allocated register lastResultReg = capturedVarIndices.get(varNameWithSigil); return; } } - // Not a lexical variable - could be a global or a bareword - // Check for strict subs violation (bareword without sigil) - 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)) { - throwCompilerException("Bareword \"" + varName + "\" not allowed while \"strict subs\" in use"); + // Check if it's a lexical variable (may have sigil or not) + if (hasVariable(varName)) { + // Lexical variable - already has a register + lastResultReg = getVariableRegister(varName); + } else { + // Try with sigils + boolean found = false; + for (String sigil : sigils) { + String varNameWithSigil = sigil + varName; + if (hasVariable(varNameWithSigil)) { + lastResultReg = getVariableRegister(varNameWithSigil); + found = true; + break; + } } - // Not strict - treat bareword as string literal - int rd = allocateRegister(); - emit(Opcodes.LOAD_STRING); - emitReg(rd); - int strIdx = addToStringPool(varName); - emit(strIdx); - lastResultReg = rd; - return; - } - // Global variable - // Check strict vars before accessing - if (shouldBlockGlobalUnderStrictVars(varName)) { - throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name"); - } + if (!found) { + // Not a lexical variable - could be a global or a bareword + // Check for strict subs violation (bareword without sigil) + 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)) { + throwCompilerException("Bareword \"" + varName + "\" not allowed while \"strict subs\" in use"); + } + // Not strict - treat bareword as string literal + int rd = allocateRegister(); + emit(Opcodes.LOAD_STRING); + emitReg(rd); + int strIdx = addToStringPool(varName); + emit(strIdx); + lastResultReg = rd; + return; + } - // Strip sigil and normalize name (e.g., "$x" → "main::x") - String bareVarName = varName.substring(1); // Remove sigil - String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage()); - int rd = allocateRegister(); - int nameIdx = addToStringPool(normalizedName); + // Global variable + // Check strict vars before accessing + if (shouldBlockGlobalUnderStrictVars(varName)) { + throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name"); + } - emit(Opcodes.LOAD_GLOBAL_SCALAR); - emitReg(rd); - emit(nameIdx); + // Strip sigil and normalize name (e.g., "$x" → "main::x") + String bareVarName = varName.substring(1); // Remove sigil + String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage()); + int rd = allocateRegister(); + int nameIdx = addToStringPool(normalizedName); - lastResultReg = rd; + emit(Opcodes.LOAD_GLOBAL_SCALAR); + emitReg(rd); + emit(nameIdx); + + lastResultReg = rd; + } + } } /** @@ -2234,25 +2192,6 @@ void compileVariableDeclaration(OperatorNode node, String op) { OperatorNode sigilOp = (OperatorNode) node.operand; String sigil = sigilOp.operator; - if (sigil.equals("*") && sigilOp.operand instanceof IdentifierNode) { - // local *glob — save glob state and return same glob object - // Mirrors JVM path: load glob, call DynamicVariableManager.pushLocalVariable(RuntimeGlob) - String globName = NameNormalizer.normalizeVariableName(((IdentifierNode) sigilOp.operand).name, getCurrentPackage()); - int nameIdx = addToStringPool(globName); - - int globReg = allocateRegister(); - emitWithToken(Opcodes.LOAD_GLOB, node.getIndex()); - emitReg(globReg); - emit(nameIdx); - - // Push glob to local variable stack (saves state, returns same object) - emit(Opcodes.PUSH_LOCAL_VARIABLE); - emitReg(globReg); - - lastResultReg = globReg; - return; - } - if (sigil.equals("$") && sigilOp.operand instanceof IdentifierNode) { String varName = "$" + ((IdentifierNode) sigilOp.operand).name; @@ -2546,10 +2485,6 @@ void compileVariableReference(OperatorNode node, String op) { lastResultReg = getVariableRegister(varName); } else { // Global variable - load it - // Check strict vars before loading an undeclared global - if (shouldBlockGlobalUnderStrictVars(varName)) { - throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name"); - } // Use NameNormalizer to properly handle special variables (like $&) // which must always be in the "main" package String globalVarName = varName.substring(1); // Remove $ sigil first @@ -2576,14 +2511,9 @@ void compileVariableReference(OperatorNode node, String op) { block.accept(this); int blockResultReg = lastResultReg; - // Load via symbolic reference — use strict vs non-strict opcode. - // LOAD_SYMBOLIC_SCALAR throws "strict refs" for non-reference strings. - // LOAD_SYMBOLIC_SCALAR_NONSTRICT allows symbolic variable lookup. + // Load via symbolic reference int rd = allocateRegister(); - short symOp = isStrictRefsEnabled() - ? Opcodes.LOAD_SYMBOLIC_SCALAR - : Opcodes.LOAD_SYMBOLIC_SCALAR_NONSTRICT; - emitWithToken(symOp, node.getIndex()); + emitWithToken(Opcodes.LOAD_SYMBOLIC_SCALAR, node.getIndex()); emitReg(rd); emitReg(blockResultReg); @@ -2669,60 +2599,40 @@ void compileVariableReference(OperatorNode node, String op) { operandOp.accept(this); int refReg = lastResultReg; + // Dereference to get the array + // The reference should contain a RuntimeArray + // For @$scalar, we need to dereference it int rd = allocateRegister(); - if (isStrictRefsEnabled()) { - emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); - emitReg(rd); - emitReg(refReg); - } else { - int pkgIdx = addToStringPool(getCurrentPackage()); - emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); - emitReg(rd); - emitReg(refReg); - emit(pkgIdx); - } + emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + emitReg(rd); + emitReg(refReg); lastResultReg = rd; + // 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) { // @{ block } - evaluate block and dereference the result + // The block should return an arrayref BlockNode blockNode = (BlockNode) node.operand; blockNode.accept(this); int refReg = lastResultReg; + // Dereference to get the array int rd = allocateRegister(); - if (isStrictRefsEnabled()) { - emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); - emitReg(rd); - emitReg(refReg); - } else { - int pkgIdx = addToStringPool(getCurrentPackage()); - emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); - emitReg(rd); - emitReg(refReg); - emit(pkgIdx); - } + emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + emitReg(rd); + emitReg(refReg); lastResultReg = rd; } else if (node.operand instanceof StringNode strNode) { - // @{'name'} — symbolic array reference - int nameReg = allocateRegister(); - int strIdx = addToStringPool(strNode.value); - emit(Opcodes.LOAD_STRING); - emitReg(nameReg); - emit(strIdx); - + // Symbolic ref: @{'name'} or 'name'->@* — load global array by string name + String globalName = NameNormalizer.normalizeVariableName(strNode.value, getCurrentPackage()); + int nameIdx = addToStringPool(globalName); int rd = allocateRegister(); - if (isStrictRefsEnabled()) { - emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); - emitReg(rd); - emitReg(nameReg); - } else { - int pkgIdx = addToStringPool(getCurrentPackage()); - emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); - emitReg(rd); - emitReg(nameReg); - emit(pkgIdx); - } + emit(Opcodes.LOAD_GLOBAL_ARRAY); + emitReg(rd); + emit(nameIdx); lastResultReg = rd; } else { throwCompilerException("Unsupported @ operand: " + node.operand.getClass().getSimpleName()); @@ -2763,17 +2673,9 @@ void compileVariableReference(OperatorNode node, String op) { refOp.accept(this); int scalarReg = lastResultReg; int hashReg = allocateRegister(); - if (isStrictRefsEnabled()) { - emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); - emitReg(hashReg); - emitReg(scalarReg); - } else { - int pkgIdx = addToStringPool(getCurrentPackage()); - emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); - emitReg(hashReg); - emitReg(scalarReg); - emit(pkgIdx); - } + emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + emitReg(hashReg); + emitReg(scalarReg); if (currentCallContext == RuntimeContextType.SCALAR) { int rd = allocateRegister(); emit(Opcodes.ARRAY_SIZE); @@ -2788,44 +2690,24 @@ void compileVariableReference(OperatorNode node, String op) { blockNode.accept(this); int scalarReg = lastResultReg; int hashReg = allocateRegister(); - if (isStrictRefsEnabled()) { - emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); - emitReg(hashReg); - emitReg(scalarReg); - } else { - int pkgIdx = addToStringPool(getCurrentPackage()); - emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); - emitReg(hashReg); - emitReg(scalarReg); - emit(pkgIdx); - } + emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + emitReg(hashReg); + emitReg(scalarReg); lastResultReg = hashReg; } else if (node.operand instanceof StringNode strNode) { - // %{'name'} — symbolic hash reference - int nameReg = allocateRegister(); - int strIdx = addToStringPool(strNode.value); - emit(Opcodes.LOAD_STRING); - emitReg(nameReg); - emit(strIdx); - + // Symbolic ref: %{'name'} or 'name'->%* — load global hash by string name + String globalName = NameNormalizer.normalizeVariableName(strNode.value, getCurrentPackage()); + int nameIdx = addToStringPool(globalName); int hashReg = allocateRegister(); - if (isStrictRefsEnabled()) { - emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); - emitReg(hashReg); - emitReg(nameReg); - } else { - int pkgIdx = addToStringPool(getCurrentPackage()); - emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); - emitReg(hashReg); - emitReg(nameReg); - emit(pkgIdx); - } + emit(Opcodes.LOAD_GLOBAL_HASH); + emitReg(hashReg); + emit(nameIdx); lastResultReg = hashReg; } else { throwCompilerException("Unsupported % operand: " + node.operand.getClass().getSimpleName()); } } else if (op.equals("*")) { - // Glob variable dereference: *x or *{expr} + // Glob variable dereference: *x if (node.operand instanceof IdentifierNode) { IdentifierNode idNode = (IdentifierNode) node.operand; String varName = idNode.name; @@ -2845,27 +2727,28 @@ void compileVariableReference(OperatorNode node, String op) { emit(nameIdx); lastResultReg = rd; - } else if (node.operand instanceof BlockNode || node.operand instanceof StringNode) { - // *{expr} or *{'name'} — dynamic glob via symbolic reference - node.operand.accept(this); - int nameReg = lastResultReg; - + } else if (node.operand instanceof StringNode strNode) { + // Symbolic ref: *{'name'} or 'name'->** — load global glob by string name + String varName = strNode.value; + if (!varName.contains("::")) { + varName = getCurrentPackage() + "::" + varName; + } int rd = allocateRegister(); - emitWithToken(Opcodes.LOAD_SYMBOLIC_GLOB, node.getIndex()); + int nameIdx = addToStringPool(varName); + emitWithToken(Opcodes.LOAD_GLOB, node.getIndex()); emitReg(rd); - emitReg(nameReg); - + emit(nameIdx); lastResultReg = rd; } else if (node.operand instanceof OperatorNode) { - // *$ref or **postfix — dereference scalar as glob + // $ref->** — dereference a scalar as a glob (e.g. postfix deref) node.operand.accept(this); - int scalarReg = lastResultReg; - + int refReg = lastResultReg; int rd = allocateRegister(); + int pkgIdx = addToStringPool(getCurrentPackage()); // consumed but unused at runtime emitWithToken(Opcodes.DEREF_GLOB, node.getIndex()); emitReg(rd); - emitReg(scalarReg); - + emitReg(refReg); + emit(pkgIdx); lastResultReg = rd; } else { throwCompilerException("Unsupported * operand: " + node.operand.getClass().getSimpleName()); diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index d38b51a9d..485392a19 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1376,27 +1376,22 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.DEREF: { // Dereference: rd = $$rs (scalar reference dereference) - // Always call scalarDeref() — throws "Not a SCALAR reference" for - // non-reference types (IO, FORMAT, etc.), matching Perl semantics. + // 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 sv = (RuntimeScalar) value; - // Call scalarDeref() for scalar refs, undef, non-ref types (strings, globs, etc.) - // Pass through non-scalar reference types (array/hash/code/regex refs) — - // those are handled by the JVM compiler as non-scalar refs and should not - // throw here (decl-refs.t uses $$arrayref in no-strict context). - if (sv.type == RuntimeScalarType.ARRAYREFERENCE - || sv.type == RuntimeScalarType.HASHREFERENCE - || sv.type == RuntimeScalarType.CODE - || sv.type == RuntimeScalarType.REGEX) { - registers[rd] = sv; // pass through non-scalar refs + RuntimeScalar scalar = (RuntimeScalar) value; + if (scalar.type == RuntimeScalarType.REFERENCE) { + registers[rd] = scalar.scalarDeref(); } else { - registers[rd] = sv.scalarDeref(); + // Non-reference scalar, just copy + registers[rd] = value; } } else { + // RuntimeList or other types, pass through registers[rd] = value; } break; @@ -1774,16 +1769,12 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c pc = executeSystemOps(opcode, bytecode, pc, registers); break; - // Group 9: Special I/O (151-154) + // Group 9: Special I/O (151-154) and DEREF_GLOB case Opcodes.EVAL_STRING: case Opcodes.SELECT_OP: case Opcodes.LOAD_GLOB: case Opcodes.SLEEP_OP: - case Opcodes.LOAD_SYMBOLIC_GLOB: case Opcodes.DEREF_GLOB: - case Opcodes.DEREF_HASH_NONSTRICT: - case Opcodes.DEREF_ARRAY_NONSTRICT: - case Opcodes.DEREF_NONSTRICT: pc = executeSpecialIO(opcode, bytecode, pc, registers, code); break; @@ -1858,72 +1849,57 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; case Opcodes.STORE_SYMBOLIC_SCALAR: { - // Strict symbolic scalar store: throws for string refs, allows REFERENCE. + // 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]; - // scalarDeref() throws "strict refs" for STRING and acts as deref for REFERENCE - RuntimeScalar targetVar = nameScalar.scalarDeref(); - targetVar.set(registers[valueReg]); - break; - } - - case Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT: { - // Non-strict symbolic scalar store: allows string-keyed global variable store. - // Format: STORE_SYMBOLIC_SCALAR_NONSTRICT nameReg valueReg - int nameReg = bytecode[pc++]; - int valueReg = bytecode[pc++]; + String varName = nameScalar.toString(); - RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg]; + // 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 + ); - if (nameScalar.type == RuntimeScalarType.REFERENCE) { - // ${\ ref} = value — dereference then assign - nameScalar.scalarDeref().set(registers[valueReg]); - } else { - // ${"varname"} = value — symbolic reference store - String normalizedName = NameNormalizer.normalizeVariableName( - nameScalar.toString(), - code.compilePackage - ); - GlobalVariable.getGlobalVariable(normalizedName).set(registers[valueReg]); - } + // Get the global variable and set its value + RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName); + RuntimeBase value = registers[valueReg]; + globalVar.set(value); break; } case Opcodes.LOAD_SYMBOLIC_SCALAR: { - // Strict symbolic scalar load: rd = ${\ref} only. - // Throws "strict refs" for strings, matching Perl strict semantics. + // 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++]; - // scalarDeref() handles both REFERENCE (dereference) and STRING - // (throws "Can't use string ... as a SCALAR ref while strict refs in use") - registers[rd] = ((RuntimeScalar) registers[nameReg]).scalarDeref(); - break; - } - - case Opcodes.LOAD_SYMBOLIC_SCALAR_NONSTRICT: { - // Non-strict symbolic scalar load: rd = ${"varname"} or ${\ref}. - // Allows string-keyed global variable lookup (no strict refs). - // Format: LOAD_SYMBOLIC_SCALAR_NONSTRICT 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) { - // ${\ref} — dereference the scalar reference + // This is ${\ref} - dereference the reference registers[rd] = nameScalar.scalarDeref(); } else { - // ${"varname"} — symbolic reference: look up global by name + // 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, - code.compilePackage // Use compile-time package for name resolution + "main" // Use main package as default for symbolic references ); - registers[rd] = GlobalVariable.getGlobalVariable(normalizedName); + + // Get the global variable and load its value + RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName); + registers[rd] = globalVar; } break; } @@ -2075,7 +2051,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c default: // Unknown opcode - int opcodeInt = opcode & 0xFF; + int opcodeInt = opcode; throw new RuntimeException( "Unknown opcode: " + opcodeInt + " at pc=" + (pc - 1) + @@ -2244,19 +2220,17 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - // Call scalarDeref() for scalar refs, undef, non-ref types (strings, globs, etc.) - // Pass through non-scalar reference types (array/hash/code/regex refs). + // Only dereference if it's a RuntimeScalar with REFERENCE type if (value instanceof RuntimeScalar) { - RuntimeScalar sv = (RuntimeScalar) value; - if (sv.type == RuntimeScalarType.ARRAYREFERENCE - || sv.type == RuntimeScalarType.HASHREFERENCE - || sv.type == RuntimeScalarType.CODE - || sv.type == RuntimeScalarType.REGEX) { - registers[rd] = sv; // pass through non-scalar refs + RuntimeScalar scalar = (RuntimeScalar) value; + if (scalar.type == RuntimeScalarType.REFERENCE) { + registers[rd] = scalar.scalarDeref(); } else { - registers[rd] = sv.scalarDeref(); + // Non-reference scalar, just copy + registers[rd] = value; } } else { + // RuntimeList or other types, pass through registers[rd] = value; } return pc; @@ -3113,7 +3087,7 @@ private static int executeSystemOps(int opcode, int[] bytecode, int pc, /** * Execute special I/O operations (opcodes 151-154). - * Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP + * Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP, DEREF_GLOB */ private static int executeSpecialIO(int opcode, int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { @@ -3126,16 +3100,8 @@ private static int executeSpecialIO(int opcode, int[] bytecode, int pc, return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code); case Opcodes.SLEEP_OP: return SlowOpcodeHandler.executeSleep(bytecode, pc, registers); - case Opcodes.LOAD_SYMBOLIC_GLOB: - return SlowOpcodeHandler.executeLoadSymbolicGlob(bytecode, pc, registers); case Opcodes.DEREF_GLOB: - return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers); - case Opcodes.DEREF_HASH_NONSTRICT: - return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code); - case Opcodes.DEREF_ARRAY_NONSTRICT: - return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code); - case Opcodes.DEREF_NONSTRICT: - return SlowOpcodeHandler.executeDerefNonStrict(bytecode, pc, registers, code); + return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code); default: throw new RuntimeException("Unknown special I/O opcode: " + opcode); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 2657fc44e..3ee669681 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -651,11 +651,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, node.right.accept(bytecodeCompiler); int valueReg = bytecodeCompiler.lastResultReg; - // Use strict vs non-strict store opcode depending on current strict refs - short storeOp = bytecodeCompiler.isStrictRefsEnabled() - ? Opcodes.STORE_SYMBOLIC_SCALAR - : Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT; - bytecodeCompiler.emit(storeOp); + // Use STORE_SYMBOLIC_SCALAR to store via symbolic reference + bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR); bytecodeCompiler.emitReg(nameReg); bytecodeCompiler.emitReg(valueReg); @@ -671,11 +668,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, node.right.accept(bytecodeCompiler); int valueReg = bytecodeCompiler.lastResultReg; - // Use strict vs non-strict store opcode depending on current strict refs - short storeOp2 = bytecodeCompiler.isStrictRefsEnabled() - ? Opcodes.STORE_SYMBOLIC_SCALAR - : Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT; - bytecodeCompiler.emit(storeOp2); + // Use STORE_SYMBOLIC_SCALAR to store via symbolic reference + bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR); bytecodeCompiler.emitReg(nameReg); bytecodeCompiler.emitReg(valueReg); @@ -902,27 +896,6 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(globReg); bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = globReg; - } else if (leftOp.operator.equals("*") && - (leftOp.operand instanceof BlockNode || - leftOp.operand instanceof OperatorNode || - leftOp.operand instanceof StringNode)) { - // Dynamic typeglob assignment: *{"Pkg::name"} = value, *$ref = value, *{'name'} = value - // Evaluate the expression to get the glob name at runtime - leftOp.operand.accept(bytecodeCompiler); - int nameReg = bytecodeCompiler.lastResultReg; - - // Load the glob via symbolic reference - int globReg = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emitWithToken(Opcodes.LOAD_SYMBOLIC_GLOB, node.getIndex()); - bytecodeCompiler.emitReg(globReg); - bytecodeCompiler.emitReg(nameReg); - - // Store value to glob - bytecodeCompiler.emit(Opcodes.STORE_GLOB); - bytecodeCompiler.emitReg(globReg); - bytecodeCompiler.emitReg(valueReg); - bytecodeCompiler.lastResultReg = globReg; } else if (leftOp.operator.equals("pos")) { // pos($var) = value - lvalue assignment to regex position diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 1ed2a34e6..21a5ea90d 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -835,16 +835,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Allocate register for result int rd = bytecodeCompiler.allocateRegister(); - // Emit direct opcode EVAL_STRING with call-site strict/feature/warning flags - // so EvalStringHandler inherits the pragmas in effect at the eval call site - // (not just the end-of-compilation snapshot in InterpretedCode) - int callSiteStrictOptions = bytecodeCompiler.getCurrentStrictOptions(); - int callSiteFeatureFlags = bytecodeCompiler.getCurrentFeatureFlags(); + // Emit direct opcode EVAL_STRING bytecodeCompiler.emitWithToken(Opcodes.EVAL_STRING, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); - bytecodeCompiler.emitInt(callSiteStrictOptions); - bytecodeCompiler.emitInt(callSiteFeatureFlags); bytecodeCompiler.lastResultReg = rd; } else { @@ -1675,19 +1669,13 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.throwCompilerException("open requires arguments"); } - // Compile the filehandle argument (first arg) as an lvalue register - // We must NOT push it through ARRAY_PUSH (which copies via addToArray), - // because IOOperator.open needs to call fileHandle.set() on the actual lvalue. - argsList.elements.get(0).accept(bytecodeCompiler); - int fhReg = bytecodeCompiler.lastResultReg; - - // Compile remaining arguments into a list (mode, filename/ref, ...) + // Compile all arguments into a list int argsReg = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.NEW_ARRAY); bytecodeCompiler.emitReg(argsReg); - for (int i = 1; i < argsList.elements.size(); i++) { - argsList.elements.get(i).accept(bytecodeCompiler); + for (Node arg : argsList.elements) { + arg.accept(bytecodeCompiler); int elemReg = bytecodeCompiler.lastResultReg; bytecodeCompiler.emit(Opcodes.ARRAY_PUSH); @@ -1695,13 +1683,11 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode bytecodeCompiler.emitReg(elemReg); } - // Call open: OPEN rd ctx fhReg argsReg - // fhReg is the actual lvalue register for the filehandle (written back directly) + // Call open with context and args int rd = bytecodeCompiler.allocateRegister(); bytecodeCompiler.emit(Opcodes.OPEN); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emit(bytecodeCompiler.currentCallContext); - bytecodeCompiler.emitReg(fhReg); bytecodeCompiler.emitReg(argsReg); bytecodeCompiler.lastResultReg = rd; diff --git a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java index 862d0f633..13db3320e 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java @@ -49,9 +49,7 @@ public static RuntimeScalar evalString(String perlCode, InterpretedCode currentCode, RuntimeBase[] registers, String sourceName, - int sourceLine, - int callSiteStrictOptions, - int callSiteFeatureFlags) { + int sourceLine) { try { // Step 1: Clear $@ at start of eval GlobalVariable.getGlobalVariable("main::@").set(""); @@ -67,15 +65,13 @@ public static RuntimeScalar evalString(String perlCode, opts.fileName = sourceName + " (eval)"; ScopedSymbolTable symbolTable = new ScopedSymbolTable(); - // Inherit lexical pragma flags from the call site (not end-of-compilation snapshot) - // callSiteStrictOptions/callSiteFeatureFlags are embedded in the bytecode at the eval - // call site, capturing the exact pragmas in effect at that point (e.g. inside a - // "no strict 'refs'" block). Fall back to currentCode snapshot if not available. - symbolTable.strictOptionsStack.pop(); - symbolTable.strictOptionsStack.push(callSiteStrictOptions); - symbolTable.featureFlagsStack.pop(); - symbolTable.featureFlagsStack.push(callSiteFeatureFlags); + // Inherit lexical pragma flags from parent if available if (currentCode != null) { + // Replace default values with parent's flags + symbolTable.strictOptionsStack.pop(); + symbolTable.strictOptionsStack.push(currentCode.strictOptions); + symbolTable.featureFlagsStack.pop(); + symbolTable.featureFlagsStack.push(currentCode.featureFlags); symbolTable.warningFlagsStack.pop(); symbolTable.warningFlagsStack.push((java.util.BitSet) currentCode.warningFlags.clone()); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index 7e7177037..d3d5499f0 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -730,28 +730,6 @@ public String disassemble() { int keyReg = bytecode[pc++]; sb.append("GLOB_SLOT_GET r").append(rd).append(" = r").append(globReg2).append("{r").append(keyReg).append("}\n"); break; - case Opcodes.LOAD_SYMBOLIC_GLOB: - rd = bytecode[pc++]; - rs1 = bytecode[pc++]; - sb.append("LOAD_SYMBOLIC_GLOB r").append(rd).append(" = getGlobalIO(r").append(rs1).append(")\n"); - break; - case Opcodes.DEREF_GLOB: - rd = bytecode[pc++]; - rs1 = bytecode[pc++]; - sb.append("DEREF_GLOB r").append(rd).append(" = r").append(rs1).append(".globDeref()\n"); - break; - case Opcodes.DEREF_HASH_NONSTRICT: - rd = bytecode[pc++]; - rs1 = bytecode[pc++]; - rs2 = bytecode[pc++]; - sb.append("DEREF_HASH_NONSTRICT r").append(rd).append(" = r").append(rs1).append(".hashDerefNonStrict(pool[").append(rs2).append("])\n"); - break; - case Opcodes.DEREF_ARRAY_NONSTRICT: - rd = bytecode[pc++]; - rs1 = bytecode[pc++]; - rs2 = bytecode[pc++]; - sb.append("DEREF_ARRAY_NONSTRICT r").append(rd).append(" = r").append(rs1).append(".arrayDerefNonStrict(pool[").append(rs2).append("])\n"); - break; case Opcodes.SPRINTF: rd = bytecode[pc++]; int formatReg = bytecode[pc++]; @@ -788,9 +766,8 @@ public String disassemble() { case Opcodes.OPEN: rd = bytecode[pc++]; int openCtx = bytecode[pc++]; - int openFhReg = bytecode[pc++]; int openArgs = bytecode[pc++]; - sb.append("OPEN r").append(rd).append(" = open(ctx=").append(openCtx).append(", fh=r").append(openFhReg).append(", args=r").append(openArgs).append(")\n"); + sb.append("OPEN r").append(rd).append(" = open(ctx=").append(openCtx).append(", r").append(openArgs).append(")\n"); break; case Opcodes.READLINE: rd = bytecode[pc++]; @@ -886,24 +863,6 @@ public String disassemble() { rs = bytecode[pc++]; sb.append("DEREF r").append(rd).append(" = ${r").append(rs).append("}\n"); break; - case Opcodes.DEREF_NONSTRICT: { - rd = bytecode[pc++]; - rs = bytecode[pc++]; - int derefNsPkgIdx = bytecode[pc++]; - sb.append("DEREF_NONSTRICT r").append(rd).append(" = ${r").append(rs) - .append("} pkg=").append(stringPool[derefNsPkgIdx]).append("\n"); - break; - } - case Opcodes.LOAD_SYMBOLIC_SCALAR_NONSTRICT: - rd = bytecode[pc++]; - rs = bytecode[pc++]; - sb.append("LOAD_SYMBOLIC_SCALAR_NONSTRICT r").append(rd).append(" = ${r").append(rs).append("}\n"); - break; - case Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT: - rd = bytecode[pc++]; - rs = bytecode[pc++]; - sb.append("STORE_SYMBOLIC_SCALAR_NONSTRICT ${r").append(rd).append("} = r").append(rs).append("\n"); - break; case Opcodes.GET_TYPE: rd = bytecode[pc++]; rs = bytecode[pc++]; @@ -918,10 +877,8 @@ public String disassemble() { sb.append("WARN r").append(rs).append("\n"); break; case Opcodes.EVAL_TRY: { - // Read 4-byte absolute catch target - int high = bytecode[pc++] & 0xFFFF; - int low = bytecode[pc++] & 0xFFFF; - int catchPc = (high << 16) | low; + // Read catch target as single int slot (matches emitInt/readInt) + int catchPc = bytecode[pc++]; sb.append("EVAL_TRY catch_at=").append(catchPc).append("\n"); break; } @@ -1296,6 +1253,12 @@ public String disassemble() { rs = bytecode[pc++]; sb.append("SLEEP_OP r").append(rd).append(" = sleep(r").append(rs).append(")\n"); break; + case Opcodes.DEREF_GLOB: + rd = bytecode[pc++]; + rs = bytecode[pc++]; + nameIdx = bytecode[pc++]; + sb.append("DEREF_GLOB r").append(rd).append(" = *{r").append(rs).append("} pkg=").append(stringPool[nameIdx]).append("\n"); + break; case Opcodes.DEREF_ARRAY: rd = bytecode[pc++]; rs = bytecode[pc++]; @@ -1455,7 +1418,7 @@ public String disassemble() { // GENERATED_DISASM_END default: - sb.append("UNKNOWN(").append(opcode & 0xFF).append(")\n"); + sb.append("UNKNOWN(").append(opcode).append(")\n"); break; } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index efd8ab846..5399c907a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -369,10 +369,6 @@ public static int executeStringBitwiseXorAssign(int[] bytecode, int pc, RuntimeB /** * Execute bitwise AND binary operation. * Format: BITWISE_AND_BINARY rd rs1 rs2 - * - * Uses the context-sensitive bitwiseAnd() (not bitwiseAndBinary()) to match - * the JVM path: if operands are non-numeric strings, dispatches to string ops. - * bitwiseAndBinary() is only for the explicit "binary&" operator. */ public static int executeBitwiseAndBinary(int[] bytecode, int pc, RuntimeBase[] registers) { int rd = bytecode[pc++]; @@ -388,10 +384,6 @@ public static int executeBitwiseAndBinary(int[] bytecode, int pc, RuntimeBase[] /** * Execute bitwise OR binary operation. * Format: BITWISE_OR_BINARY rd rs1 rs2 - * - * Uses the context-sensitive bitwiseOr() (not bitwiseOrBinary()) to match - * the JVM path: if operands are non-numeric strings, dispatches to string ops. - * bitwiseOrBinary() is only for the explicit "binary|" operator. */ public static int executeBitwiseOrBinary(int[] bytecode, int pc, RuntimeBase[] registers) { int rd = bytecode[pc++]; @@ -407,10 +399,6 @@ public static int executeBitwiseOrBinary(int[] bytecode, int pc, RuntimeBase[] r /** * Execute bitwise XOR binary operation. * Format: BITWISE_XOR_BINARY rd rs1 rs2 - * - * Uses the context-sensitive bitwiseXor() (not bitwiseXorBinary()) to match - * the JVM path: if operands are non-numeric strings, dispatches to string ops. - * bitwiseXorBinary() is only for the explicit "binary^" operator. */ public static int executeBitwiseXorBinary(int[] bytecode, int pc, RuntimeBase[] registers) { int rd = bytecode[pc++]; @@ -713,28 +701,14 @@ public static int executePostAutoDecrement(int[] bytecode, int pc, RuntimeBase[] /** * Execute open operation. - * Format: OPEN rd ctx fhReg argsReg - * - * fhReg is the actual lvalue register for the filehandle. IOOperator.open calls - * fileHandle.set() on args[0], so we pass registers[fhReg] directly (not a copy - * from ARRAY_PUSH which would call addToArray -> new RuntimeScalar(this)). - * After the call, registers[fhReg] has been updated in place by set(). + * Format: OPEN rd ctx argsReg */ public static int executeOpen(int[] bytecode, int pc, RuntimeBase[] registers) { int rd = bytecode[pc++]; int ctx = bytecode[pc++]; - int fhReg = bytecode[pc++]; int argsReg = bytecode[pc++]; RuntimeArray argsArray = (RuntimeArray) registers[argsReg]; - - // Build varargs with the actual fh lvalue as args[0], then the rest - RuntimeBase fhLvalue = registers[fhReg]; - RuntimeBase[] argsVarargs = new RuntimeBase[argsArray.elements.size() + 1]; - argsVarargs[0] = fhLvalue; - for (int i = 0; i < argsArray.elements.size(); i++) { - argsVarargs[i + 1] = argsArray.elements.get(i); - } - + RuntimeBase[] argsVarargs = argsArray.elements.toArray(new RuntimeBase[0]); registers[rd] = IOOperator.open(ctx, argsVarargs); return pc; } diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index c5b359edd..d4741b087 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -1071,42 +1071,10 @@ public class Opcodes { * Effect: Restores previous packageName */ public static final short POP_PACKAGE = 308; - /** Load glob via symbolic reference: rd = GlobalVariable.getGlobalIO(nameReg.toString()) - * Format: LOAD_SYMBOLIC_GLOB rd nameReg */ - public static final short LOAD_SYMBOLIC_GLOB = 333; - - /** Dereference scalar as glob: rd = scalarReg.globDeref() - * Format: DEREF_GLOB rd scalarReg */ - public static final short DEREF_GLOB = 334; - - /** Dereference scalar as hash (no strict refs): rd = scalarReg.hashDerefNonStrict(pkg) - * Format: DEREF_HASH_NONSTRICT rd scalarReg packageIdx */ - public static final short DEREF_HASH_NONSTRICT = 335; - - /** Dereference scalar as array (no strict refs): rd = scalarReg.arrayDerefNonStrict(pkg) - * Format: DEREF_ARRAY_NONSTRICT rd scalarReg packageIdx */ - public static final short DEREF_ARRAY_NONSTRICT = 336; - - /** Dereference scalar (no strict refs): rd = scalarReg.scalarDerefNonStrict(pkg) - * Format: DEREF_NONSTRICT rd scalarReg packageIdx */ - public static final short DEREF_NONSTRICT = 337; - - /** Load via symbolic reference (strict refs): throws if nameReg is a string. - * Allows REFERENCE type (${\ ref}). Same format as LOAD_SYMBOLIC_SCALAR. - * Format: LOAD_SYMBOLIC_SCALAR rd nameReg (strict — already the default opcode 232) */ - // Note: LOAD_SYMBOLIC_SCALAR (232) is the strict variant. - - /** Load via symbolic reference (no strict refs): allows string-keyed global lookup. - * Format: LOAD_SYMBOLIC_SCALAR_NONSTRICT rd nameReg */ - public static final short LOAD_SYMBOLIC_SCALAR_NONSTRICT = 338; - - /** Store via symbolic reference (strict refs): throws if nameReg is a string. - * Format: STORE_SYMBOLIC_SCALAR rd nameReg */ - // Note: STORE_SYMBOLIC_SCALAR (231) is the strict variant. - - /** Store via symbolic reference (no strict refs): allows string-keyed global store. - * Format: STORE_SYMBOLIC_SCALAR_NONSTRICT nameReg valueReg */ - public static final short STORE_SYMBOLIC_SCALAR_NONSTRICT = 339; + /** Dereference a scalar as a glob: rd = rs.globDerefNonStrict(currentPackage) + * Used for $ref->** postfix glob deref + * Format: DEREF_GLOB rd rs nameIdx(currentPackage) */ + public static final short DEREF_GLOB = 333; private Opcodes() {} // Utility class - no instantiation } diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java index 98841ee3e..bea587b70 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -240,9 +240,6 @@ public static int executeEvalString( int rd = bytecode[pc++]; int stringReg = bytecode[pc++]; - // Read call-site strict/feature flags embedded by CompileOperator at the eval site - int callSiteStrictOptions = bytecode[pc++]; - int callSiteFeatureFlags = bytecode[pc++]; // Get the code string - handle both RuntimeScalar and RuntimeList (from string interpolation) RuntimeBase codeValue = registers[stringReg]; @@ -258,12 +255,10 @@ public static int executeEvalString( // Call EvalStringHandler to parse, compile, and execute RuntimeScalar result = EvalStringHandler.evalString( perlCode, - code, // Current InterpretedCode for context - registers, // Current registers for variable access + code, // Current InterpretedCode for context + registers, // Current registers for variable access code.sourceName, - code.sourceLine, - callSiteStrictOptions, // Strict flags at the eval call site - callSiteFeatureFlags // Feature flags at the eval call site + code.sourceLine ); registers[rd] = result; @@ -296,127 +291,57 @@ public static int executeSelect( } /** - * DEREF_HASH_NONSTRICT: rd = scalarReg.hashDerefNonStrict(pkg) - * Format: [DEREF_HASH_NONSTRICT] [rd] [scalarReg] [packageIdx] - * Effect: Dereferences a scalar as a hash with no-strict-refs semantics - */ - public static int executeDerefHashNonStrict( - int[] bytecode, - int pc, - RuntimeBase[] registers, - InterpretedCode code) { - - int rd = bytecode[pc++]; - int scalarReg = bytecode[pc++]; - int packageIdx = bytecode[pc++]; - - String packageName = code.stringPool[packageIdx]; - RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; - registers[rd] = scalar.hashDerefNonStrict(packageName); - return pc; - } - - /** - * DEREF_ARRAY_NONSTRICT: rd = scalarReg.arrayDerefNonStrict(pkg) - * Format: [DEREF_ARRAY_NONSTRICT] [rd] [scalarReg] [packageIdx] - * Effect: Dereferences a scalar as an array with no-strict-refs semantics + * SLOW_LOAD_GLOB: rd = getGlobalIO(name) + * Format: [SLOW_LOAD_GLOB] [rd] [name_idx] + * Effect: Loads a glob/filehandle from global variables */ - public static int executeDerefArrayNonStrict( + public static int executeLoadGlob( int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { int rd = bytecode[pc++]; - int scalarReg = bytecode[pc++]; - int packageIdx = bytecode[pc++]; - - String packageName = code.stringPool[packageIdx]; - RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; - registers[rd] = scalar.arrayDerefNonStrict(packageName); - return pc; - } + int nameIdx = bytecode[pc++]; - /** - * DEREF_NONSTRICT: rd = scalarReg.scalarDerefNonStrict(pkg) - * Format: [DEREF_NONSTRICT] [rd] [scalarReg] [packageIdx] - * Effect: Dereferences a scalar as a scalar with no-strict-refs semantics ($$ref or symbolic ref) - */ - public static int executeDerefNonStrict( - int[] bytecode, - int pc, - RuntimeBase[] registers, - InterpretedCode code) { + String globName = code.stringPool[nameIdx]; - int rd = bytecode[pc++]; - int scalarReg = bytecode[pc++]; - int packageIdx = bytecode[pc++]; + // Call GlobalVariable.getGlobalIO() to get the RuntimeGlob + RuntimeGlob glob = GlobalVariable.getGlobalIO(globName); - String packageName = code.stringPool[packageIdx]; - RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; - registers[rd] = scalar.scalarDerefNonStrict(packageName); + registers[rd] = glob; return pc; } /** - * DEREF_GLOB: rd = scalarReg.globDeref() - * Format: [DEREF_GLOB] [rd] [scalarReg] - * Effect: Dereferences a scalar as a glob (** postfix deref) + * DEREF_GLOB: rd = rs.globDerefNonStrict(currentPackage) + * Format: DEREF_GLOB rd rs nameIdx(currentPackage) + * Effect: Dereferences a scalar as a glob (for $ref->** postfix deref) */ public static int executeDerefGlob( - int[] bytecode, - int pc, - RuntimeBase[] registers) { - - int rd = bytecode[pc++]; - int scalarReg = bytecode[pc++]; - - RuntimeScalar scalar = (RuntimeScalar) registers[scalarReg]; - registers[rd] = scalar.globDeref(); - return pc; - } - - /** - * LOAD_SYMBOLIC_GLOB: rd = getGlobalIO(nameReg.toString()) - * Format: [LOAD_SYMBOLIC_GLOB] [rd] [nameReg] - * Effect: Loads a glob via a runtime string expression (e.g. *{"Pkg::name"}) - */ - public static int executeLoadSymbolicGlob( - int[] bytecode, - int pc, - RuntimeBase[] registers) { - - int rd = bytecode[pc++]; - int nameReg = bytecode[pc++]; - - // Normalize the name with the current package (e.g. "mysub" -> "main::mysub") - String rawName = registers[nameReg].toString(); - String pkg = InterpreterState.currentPackage.get().toString(); - String globName = NameNormalizer.normalizeVariableName(rawName, pkg); - registers[rd] = GlobalVariable.getGlobalIO(globName); - return pc; - } - - /** - * SLOW_LOAD_GLOB: rd = getGlobalIO(name) - * Format: [SLOW_LOAD_GLOB] [rd] [name_idx] - * Effect: Loads a glob/filehandle from global variables - */ - public static int executeLoadGlob( int[] bytecode, int pc, RuntimeBase[] registers, InterpretedCode code) { int rd = bytecode[pc++]; - int nameIdx = bytecode[pc++]; + int rs = bytecode[pc++]; + int nameIdx = bytecode[pc++]; // currentPackage (unused at runtime, consumed for alignment) - String globName = code.stringPool[nameIdx]; + RuntimeBase val = registers[rs]; - // Call GlobalVariable.getGlobalIO() to get the RuntimeGlob - RuntimeGlob glob = GlobalVariable.getGlobalIO(globName); + // PVIO case: *STDOUT{IO} returns RuntimeIO directly — wrap in a temporary glob + if (val instanceof RuntimeIO io) { + RuntimeGlob tmp = new RuntimeGlob("__ANON__"); + tmp.setIO(io); + registers[rd] = tmp; + return pc; + } - registers[rd] = glob; + // General case: use globDeref() — throws "Not a GLOB reference" for invalid refs. + // Matches JVM path (EmitVariable.java: globDeref()). + RuntimeScalar scalar = (RuntimeScalar) val; + registers[rd] = scalar.globDeref(); return pc; } @@ -884,7 +809,7 @@ public static int executeListSliceFrom( int rd = bytecode[pc++]; int listReg = bytecode[pc++]; - // Read startIndex as a single int (emitted by CompileAssignment via emitInt) + // Read startIndex as single int slot (emitInt writes one slot) int startIndex = bytecode[pc++]; RuntimeBase listBase = registers[listReg]; @@ -1028,18 +953,12 @@ public static int executeGlobSlotGet( // Use runtime current package — correct for both regular code and eval STRING String pkg = InterpreterState.currentPackage.get().toString(); - RuntimeScalar result; - if (globBase instanceof RuntimeGlob globObj) { - // Direct glob — access slot via a scalar wrapper that holds the glob reference - // RuntimeGlob.hashDerefGetNonStrict is not available directly; use scalar() to get - // a RuntimeScalar of type GLOB, then call hashDerefGetNonStrict on it. - // But scalar() on a RuntimeGlob returns a GLOB-typed scalar that delegates correctly. - result = globObj.scalar().hashDerefGetNonStrict(key, pkg); - } else { - // Already a scalar (e.g. from a variable holding a glob) - result = globBase.scalar().hashDerefGetNonStrict(key, pkg); - } - registers[rd] = result; + // Convert to scalar if needed + RuntimeScalar glob = globBase.scalar(); + + // Call hashDerefGetNonStrict which for RuntimeGlob accesses the slot directly + // without dereferencing the glob as a hash + registers[rd] = glob.hashDerefGetNonStrict(key, pkg); return pc; } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java index 098746844..47a556cd3 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java @@ -53,22 +53,25 @@ public PerlCompilerException(String message) { return; } - // Retrieve caller information: package name, file name, line number - RuntimeList caller = RuntimeCode.caller(new RuntimeList(getScalarInt(0)), RuntimeContextType.LIST); + // Retrieve caller information: package name, file name, line number. + // Guard against exceptions from caller() when interpreter state is mid-exception + // (e.g. PerlCompilerException thrown during interpreter eval STRING execution). + this.errorMessage = buildErrorMessage(message); + } - // Check if caller information is available - if (caller.size() < 3) { - this.errorMessage = message + "\n"; - return; + private static String buildErrorMessage(String message) { + try { + RuntimeList caller = RuntimeCode.caller(new RuntimeList(getScalarInt(0)), RuntimeContextType.LIST); + if (caller.size() < 3) { + return message + "\n"; + } + String fileName = caller.elements.get(1).toString(); + int line = ((RuntimeScalar) caller.elements.get(2)).getInt(); + return message + " at " + fileName + " line " + line + "\n"; + } catch (Throwable t) { + // caller() failed (e.g. mid-exception in interpreter) — use bare message + return message + "\n"; } - - // Extract caller information - String packageName = caller.elements.get(0).toString(); - String fileName = caller.elements.get(1).toString(); - int line = ((RuntimeScalar) caller.elements.get(2)).getInt(); - - // Construct the detailed error message with file and line information - this.errorMessage = message + " at " + fileName + " line " + line + "\n"; } /** diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index 374c60118..e3f201f33 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -1106,13 +1106,6 @@ public RuntimeScalar scalarDerefNonStrict(String packageName) { } return switch (type) { - case UNDEF -> { - // Autovivify: create a new scalar reference for undefined values (same as scalarDeref) - RuntimeScalar newScalar = new RuntimeScalar(); - this.value = newScalar; - this.type = RuntimeScalarType.REFERENCE; - yield newScalar; - } case REFERENCE -> (RuntimeScalar) value; case TIED_SCALAR -> tiedFetch().scalarDerefNonStrict(packageName); default -> {