diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index c99706e8d..c0880d0a7 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -396,8 +396,8 @@ boolean shouldBlockGlobalUnderStrictVars(String varName) { return false; } - // Allow regex capture variables ($1, $2, etc.) - if (ScalarUtils.isInteger(bareVarName)) { + // Allow regex capture variables ($1, $2, etc.) but not leading-zero variants ($01, $02) + if (ScalarUtils.isInteger(bareVarName) && !bareVarName.startsWith("0")) { return false; } @@ -423,6 +423,39 @@ boolean shouldBlockGlobalUnderStrictVars(String varName) { return false; } + // Allow variables that already exist in the global registry + // (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; + } + if (sigil.equals("@") && GlobalVariable.existsGlobalArray(normalizedName)) { + allowIfAlreadyExists = true; + } + if (sigil.equals("%") && !normalizedName.endsWith("::") && GlobalVariable.existsGlobalHash(normalizedName)) { + allowIfAlreadyExists = true; + } + + // Perl's strict 'vars' requires declaration for unqualified single-letter globals + // even if they were previously created under 'no strict'. + // This mirrors EmitVariable.java lines 349-359. + boolean isSpecialSortVar = sigil.equals("$") + && (bareVarName.equals("a") || bareVarName.equals("b")); + if (sigil.equals("$") + && bareVarName != null + && bareVarName.length() == 1 + && Character.isLetter(bareVarName.charAt(0)) + && !bareVarName.contains("::") + && !isSpecialSortVar) { + allowIfAlreadyExists = false; + } + + if (allowIfAlreadyExists) { + return false; + } + // BLOCK: Unqualified variable under strict vars return true; } diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java index ac3bd26e5..6e9fd6cac 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java @@ -361,7 +361,7 @@ static void handleVariableOperator(EmitterVisitor emitterVisitor, OperatorNode n // Compute createIfNotExists flag - determines if variable can be auto-vivified boolean createIfNotExists = name.contains("::") // Fully qualified: $Package::var - || ScalarUtils.isInteger(name) // Regex capture: $1, $2, etc. + || (ScalarUtils.isInteger(name) && !name.startsWith("0")) // Regex capture: $1, $2, etc. || isSpecialSortVar // Sort variables: $a, $b || isBuiltinSpecialLengthOneVar(sigil, name) // $%, $-, $[, $}, etc. || isBuiltinSpecialScalarVar(sigil, name) // ${^GLOBAL_PHASE}, $ARGV, $ENV, etc. diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java index faea5221a..3792ceb2a 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java @@ -912,7 +912,7 @@ private Set convertMode(String mode) { Set options = MODE_OPTIONS.get(mode); if (options == null) { - throw new PerlCompilerException("Unsupported file mode: " + mode); + throw new PerlCompilerException("Unknown open() mode '" + mode + "'"); } return new HashSet<>(options); } diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java index e3c9d0fb5..459364966 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java @@ -329,7 +329,8 @@ public RuntimeScalar scalar() { return scalarUndef; // Return undefined if empty } // XXX expand the last element - return elements.getLast().scalar(); + RuntimeBase last = elements.getLast(); + return (last == null) ? scalarUndef : last.scalar(); } /** @@ -464,8 +465,8 @@ public RuntimeArray setFromList(RuntimeList value) { rhsIndex++; } } else if (elem instanceof RuntimeScalar runtimeScalar) { - RuntimeScalar assigned = (rhsIndex < rhsSize) ? rhsElements.get(rhsIndex++) : new RuntimeScalar(); - runtimeScalar.set(assigned); + RuntimeScalar assigned = (rhsIndex < rhsSize) ? rhsElements.get(rhsIndex++) : null; + runtimeScalar.set(assigned != null ? assigned : new RuntimeScalar()); result.elements.add(runtimeScalar); // Add reference to the variable itself } else if (elem instanceof RuntimeArray runtimeArray) { List remaining = (rhsIndex < rhsSize) diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index e6a4471e2..a184f569d 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -640,6 +640,11 @@ public RuntimeScalar addToScalar(RuntimeScalar scalar) { // Setters public RuntimeScalar set(RuntimeScalar value) { + if (value == null) { + this.type = RuntimeScalarType.UNDEF; + this.value = null; + return this; + } if (value.type == TIED_SCALAR) { return set(value.tiedFetch()); }