Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c8ff50b
fix: %_ strict vars + use lib prepend ordering for Text::CSV support
fglock Apr 3, 2026
cccdf51
fix: @INC ordering + blib support for CPAN module testing
fglock Apr 3, 2026
60446a7
fix: bytecode compiler last/next/redo skips do-while to find true loop
fglock Apr 3, 2026
8817555
fix: add bytes:: functions and glob method dispatch for Text::CSV
fglock Apr 3, 2026
8eb0017
docs: update Text::CSV fix plan with Phase 3b/3c results
fglock Apr 3, 2026
2655e31
fix: bytecode HINT_BYTES parity and raw-bytes DATA section
fglock Apr 3, 2026
9b7de8a
fix: logical operator VOID context and PerlIO::get_layers NPE
fglock Apr 3, 2026
5c01be8
docs: update Text::CSV fix plan with Phase 4 results and next steps
fglock Apr 3, 2026
ff7ca0a
fix: local %hash now saves/restores globalHashes map entry
fglock Apr 3, 2026
4a0aa29
fix: readline now returns BYTE_STRING for handles without encoding la…
fglock Apr 3, 2026
b1dc97a
docs: update Text::CSV fix plan with Phase 5 results
fglock Apr 3, 2026
727de0f
fix: untie retains last FETCH value, fix UTF-16/32 encoding layer reads
fglock Apr 3, 2026
3ddfaae
fix: UTF-8 encode wide characters on binary handles, fix utf8::decode…
fglock Apr 3, 2026
91b3af5
fix: use bytes regex matching, Latin-1 source encoding detection
fglock Apr 3, 2026
c7c2d8a
fix: Wide character in print warning, utf8::upgrade preserves content
fglock Apr 3, 2026
ec44f96
fix: print reads internal ORS/OFS, not aliased $\ and $, variables
fglock Apr 4, 2026
fdbd4b8
fix: preserve gotoLabelPcs in InterpretedCode.withCapturedVars()
fglock Apr 4, 2026
48d2366
fix: preserve BYTE_STRING type through tr/// and substr operations
fglock Apr 4, 2026
41fa034
fix: comprehensive BYTE_STRING type preservation across string operat…
fglock Apr 4, 2026
9e543ae
fix: Encode::decode drops orphan trailing bytes for UTF-16/32
fglock Apr 4, 2026
aaeef89
docs: update Text::CSV fix plan — Phase 7 complete, 39/40 tests pass
fglock Apr 4, 2026
4db8610
fix: s/// preserves wide chars, :crlf read avoids over-consuming
fglock Apr 4, 2026
52c7066
docs: update Text::CSV fix plan — Phase 8 regression fixes complete
fglock Apr 4, 2026
a54352c
fix: Unicode property patterns now safe for Pattern.COMMENTS mode
fglock Apr 4, 2026
5256681
fix: resolve regressions in op/anonsub.t, comp/parser_run.t, re/pat_a…
fglock Apr 4, 2026
29638fc
feat: implement namespace::autoclean to actually clean imported funct…
fglock Apr 4, 2026
b037509
docs: update Text::CSV fix plan — Phase 9 regression fixes + namespac…
fglock Apr 4, 2026
5d58cec
fix: namespace::autoclean preserves companion package methods
fglock Apr 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
336 changes: 336 additions & 0 deletions dev/modules/text_csv_fix_plan.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions dev/tools/perl_test_runner.pl
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ sub run_single_test {
elsif ($test_file =~ m{^t/} && !-f 't/TestLib.pm') {
$local_test_dir = 't';
}
# For CPAN module tests with absolute paths (e.g., /path/to/Module-1.23/t/test.t)
# chdir to the module root so require "./t/util.pl" works
elsif ($test_file =~ m{^(/.*)/t/[^/]+\.t$}) {
$local_test_dir = $1;
}

chdir($local_test_dir) if $local_test_dir && -d $local_test_dir;

Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/perlonjava/app/cli/CompilerOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class CompilerOptions implements Cloneable {
public boolean processAndPrint = false; // For -p
public boolean inPlaceEdit = false; // New field for in-place editing
public String code = null;
public byte[] rawCodeBytes = null; // Raw file bytes (after BOM removal) for DATA section
public boolean codeHasEncoding = false;
public String fileName = null;
public String inPlaceExtension = null; // For -i
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ private static boolean isBuiltinSpecialContainerVar(String sigil, String name) {
|| name.equals("ENV")
|| name.equals("INC")
|| name.equals("+")
|| name.equals("-");
|| name.equals("-")
|| name.equals("_");
}
if ("@".equals(sigil)) {
return name.equals("ARGV")
Expand Down Expand Up @@ -400,6 +401,10 @@ boolean isStrictRefsEnabled() {
* @return true if access should be blocked under strict vars
*/

boolean isBytesEnabled() {
return getEffectiveSymbolTable().isStrictOptionEnabled(Strict.HINT_BYTES);
}

boolean isIntegerEnabled() {
return getEffectiveSymbolTable().isStrictOptionEnabled(Strict.HINT_INTEGER);
}
Expand Down Expand Up @@ -5786,9 +5791,13 @@ void handleLoopControlOperator(OperatorNode node, String op) {
// Find the target loop
LoopInfo targetLoop = null;
if (labelStr == null) {
// Unlabeled: find innermost loop
if (!loopStack.isEmpty()) {
targetLoop = loopStack.peek();
// Unlabeled: find innermost true loop (skip do-while/bare blocks)
for (int i = loopStack.size() - 1; i >= 0; i--) {
LoopInfo loop = loopStack.get(i);
if (loop.isTrueLoop) {
targetLoop = loop;
break;
}
}
} else {
// Labeled: search for matching label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2479,8 +2479,7 @@ private static int executeScopeOps(int opcode, int[] bytecode, int pc,
int nameIdx = bytecode[pc++];
String fullName = code.stringPool[nameIdx];

RuntimeHash hash = GlobalVariable.getGlobalHash(fullName);
DynamicVariableManager.pushLocalVariable(hash);
GlobalRuntimeHash.makeLocal(fullName);
registers[rd] = GlobalVariable.getGlobalHash(fullName);
return pc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitInt(0);

int rightCtx = bytecodeCompiler.currentCallContext == RuntimeContextType.VOID ? RuntimeContextType.SCALAR : bytecodeCompiler.currentCallContext;
int rightCtx = bytecodeCompiler.currentCallContext;
bytecodeCompiler.compileNode(node.right, rd, rightCtx);
int rs2 = bytecodeCompiler.lastResultReg;
if (rs2 >= 0) {
Expand All @@ -475,7 +475,7 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitInt(0);

int rightCtx = bytecodeCompiler.currentCallContext == RuntimeContextType.VOID ? RuntimeContextType.SCALAR : bytecodeCompiler.currentCallContext;
int rightCtx = bytecodeCompiler.currentCallContext;
bytecodeCompiler.compileNode(node.right, rd, rightCtx);
int rs2 = bytecodeCompiler.lastResultReg;
if (rs2 >= 0) {
Expand Down Expand Up @@ -506,7 +506,7 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
bytecodeCompiler.emitReg(definedReg);
bytecodeCompiler.emitInt(0);

int rightCtx = bytecodeCompiler.currentCallContext == RuntimeContextType.VOID ? RuntimeContextType.SCALAR : bytecodeCompiler.currentCallContext;
int rightCtx = bytecodeCompiler.currentCallContext;
bytecodeCompiler.compileNode(node.right, rd, rightCtx);
int rs2 = bytecodeCompiler.lastResultReg;
if (rs2 >= 0) {
Expand Down
24 changes: 16 additions & 8 deletions src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ private static void visitMatchRegex(BytecodeCompiler bc, OperatorNode node) {
} else {
stringReg = loadDefaultUnderscore(bc);
}
// When 'use bytes' is in effect, convert string to UTF-8 byte representation
if (bc.isBytesEnabled()) {
int bytesReg = bc.allocateRegister();
bc.emit(Opcodes.TO_BYTES_STRING);
bc.emitReg(bytesReg);
bc.emitReg(stringReg);
stringReg = bytesReg;
}
int rd = bc.allocateOutputRegister();
bc.emit(Opcodes.MATCH_REGEX);
bc.emitReg(rd);
Expand Down Expand Up @@ -666,20 +674,20 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
case "exp" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.EXP);
case "abs" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.ABS);
case "integerBitwiseNot" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.INTEGER_BITWISE_NOT);
case "ord" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.ORD);
case "ord" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, bytecodeCompiler.isBytesEnabled() ? Opcodes.ORD_BYTES : Opcodes.ORD);
case "ordBytes" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.ORD_BYTES);
case "oct" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.OCT);
case "hex" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.HEX);
case "srand" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.SRAND);
case "chr" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.CHR);
case "chr" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, bytecodeCompiler.isBytesEnabled() ? Opcodes.CHR_BYTES : Opcodes.CHR);
case "chrBytes" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.CHR_BYTES);
case "lengthBytes" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.LENGTH_BYTES);
case "quotemeta" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.QUOTEMETA);
case "fc" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.FC);
case "lc" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.LC);
case "lcfirst" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.LCFIRST);
case "uc" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.UC);
case "ucfirst" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.UCFIRST);
case "fc" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, bytecodeCompiler.isBytesEnabled() ? Opcodes.FC_BYTES : Opcodes.FC);
case "lc" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, bytecodeCompiler.isBytesEnabled() ? Opcodes.LC_BYTES : Opcodes.LC);
case "lcfirst" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, bytecodeCompiler.isBytesEnabled() ? Opcodes.LCFIRST_BYTES : Opcodes.LCFIRST);
case "uc" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, bytecodeCompiler.isBytesEnabled() ? Opcodes.UC_BYTES : Opcodes.UC);
case "ucfirst" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, bytecodeCompiler.isBytesEnabled() ? Opcodes.UCFIRST_BYTES : Opcodes.UCFIRST);
case "tell" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.TELL);
case "rmdir" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.RMDIR);
case "closedir" -> visitSimpleUnaryWithDefault(bytecodeCompiler, node, Opcodes.CLOSEDIR);
Expand Down Expand Up @@ -1274,7 +1282,7 @@ private static void visitLength(BytecodeCompiler bc, OperatorNode node) {
if (node.operand instanceof ListNode list) { if (list.elements.isEmpty()) bc.throwCompilerException("length requires an argument"); list.elements.get(0).accept(bc); }
else node.operand.accept(bc);
int stringReg = bc.lastResultReg;
int rd = bc.allocateOutputRegister(); bc.emit(Opcodes.LENGTH_OP); bc.emitReg(rd); bc.emitReg(stringReg);
int rd = bc.allocateOutputRegister(); bc.emit(bc.isBytesEnabled() ? Opcodes.LENGTH_BYTES : Opcodes.LENGTH_OP); bc.emitReg(rd); bc.emitReg(stringReg);
bc.lastResultReg = rd;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1499,10 +1499,16 @@ public static String disassemble(InterpretedCode interpretedCode) {
case Opcodes.LENGTH_BYTES:
case Opcodes.QUOTEMETA:
case Opcodes.FC:
case Opcodes.FC_BYTES:
case Opcodes.LC:
case Opcodes.LC_BYTES:
case Opcodes.LCFIRST:
case Opcodes.LCFIRST_BYTES:
case Opcodes.UC:
case Opcodes.UC_BYTES:
case Opcodes.UCFIRST:
case Opcodes.UCFIRST_BYTES:
case Opcodes.TO_BYTES_STRING:
case Opcodes.SLEEP:
case Opcodes.TELL:
case Opcodes.RMDIR:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ public InterpretedCode withCapturedVars(RuntimeBase[] capturedVars) {
copy.attributes = this.attributes;
copy.subName = this.subName;
copy.packageName = this.packageName;
// Preserve compiler-set fields that are not passed through the constructor
copy.gotoLabelPcs = this.gotoLabelPcs;
copy.usesLocalization = this.usesLocalization;
return copy;
}

Expand Down
32 changes: 32 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2157,6 +2157,38 @@ public class Opcodes {
*/
public static final short DEFINED_CODE = 454;

/**
* Fold case (bytes mode): rd = StringOperators.fcBytes(rs)
*/
public static final short FC_BYTES = 455;

/**
* Lowercase (bytes mode): rd = StringOperators.lcBytes(rs)
*/
public static final short LC_BYTES = 456;

/**
* Uppercase (bytes mode): rd = StringOperators.ucBytes(rs)
*/
public static final short UC_BYTES = 457;

/**
* Lowercase first (bytes mode): rd = StringOperators.lcfirstBytes(rs)
*/
public static final short LCFIRST_BYTES = 458;

/**
* Uppercase first (bytes mode): rd = StringOperators.ucfirstBytes(rs)
*/
public static final short UCFIRST_BYTES = 459;

/**
* Convert string to UTF-8 byte representation: rd = StringOperators.toBytesString(rs)
* Used when 'use bytes' is in effect before regex matching.
* Format: TO_BYTES_STRING rd rs
*/
public static final short TO_BYTES_STRING = 460;

private Opcodes() {
} // Utility class - no instantiation
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,16 @@ public static int execute(int opcode, int[] bytecode, int pc,
case Opcodes.LENGTH_BYTES -> StringOperators.lengthBytes((RuntimeScalar) registers[rs]);
case Opcodes.QUOTEMETA -> StringOperators.quotemeta((RuntimeScalar) registers[rs]);
case Opcodes.FC -> StringOperators.fc((RuntimeScalar) registers[rs]);
case Opcodes.FC_BYTES -> StringOperators.fcBytes((RuntimeScalar) registers[rs]);
case Opcodes.LC -> StringOperators.lc((RuntimeScalar) registers[rs]);
case Opcodes.LC_BYTES -> StringOperators.lcBytes((RuntimeScalar) registers[rs]);
case Opcodes.LCFIRST -> StringOperators.lcfirst((RuntimeScalar) registers[rs]);
case Opcodes.LCFIRST_BYTES -> StringOperators.lcfirstBytes((RuntimeScalar) registers[rs]);
case Opcodes.UC -> StringOperators.uc((RuntimeScalar) registers[rs]);
case Opcodes.UC_BYTES -> StringOperators.ucBytes((RuntimeScalar) registers[rs]);
case Opcodes.UCFIRST -> StringOperators.ucfirst((RuntimeScalar) registers[rs]);
case Opcodes.UCFIRST_BYTES -> StringOperators.ucfirstBytes((RuntimeScalar) registers[rs]);
case Opcodes.TO_BYTES_STRING -> StringOperators.toBytesString((RuntimeScalar) registers[rs]);
case Opcodes.SLEEP -> Time.sleep((RuntimeScalar) registers[rs]);
case Opcodes.TELL -> IOOperator.tell((RuntimeScalar) registers[rs]);
case Opcodes.RMDIR -> Directory.rmdir((RuntimeScalar) registers[rs]);
Expand Down Expand Up @@ -96,10 +102,22 @@ public static int disassemble(int opcode, int[] bytecode, int pc,
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.FC_BYTES ->
sb.append("FC_BYTES r").append(rd).append(" = fcBytes(r").append(rs).append(")\n");
case Opcodes.LC -> sb.append("LC r").append(rd).append(" = lc(r").append(rs).append(")\n");
case Opcodes.LC_BYTES ->
sb.append("LC_BYTES r").append(rd).append(" = lcBytes(r").append(rs).append(")\n");
case Opcodes.LCFIRST -> sb.append("LCFIRST r").append(rd).append(" = lcfirst(r").append(rs).append(")\n");
case Opcodes.LCFIRST_BYTES ->
sb.append("LCFIRST_BYTES r").append(rd).append(" = lcfirstBytes(r").append(rs).append(")\n");
case Opcodes.UC -> sb.append("UC r").append(rd).append(" = uc(r").append(rs).append(")\n");
case Opcodes.UC_BYTES ->
sb.append("UC_BYTES r").append(rd).append(" = ucBytes(r").append(rs).append(")\n");
case Opcodes.UCFIRST -> sb.append("UCFIRST r").append(rd).append(" = ucfirst(r").append(rs).append(")\n");
case Opcodes.UCFIRST_BYTES ->
sb.append("UCFIRST_BYTES r").append(rd).append(" = ucfirstBytes(r").append(rs).append(")\n");
case Opcodes.TO_BYTES_STRING ->
sb.append("TO_BYTES_STRING r").append(rd).append(" = toBytesString(r").append(rs).append(")\n");
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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ private static void emitLogicalOperatorSimple(EmitterVisitor emitterVisitor, Bin
Label endLabel = new Label();

if (emitterVisitor.ctx.contextType == RuntimeContextType.VOID) {
evalTrace("EmitLogicalOperatorSimple VOID op=" + node.operator + " emit LHS in SCALAR; RHS in SCALAR");
evalTrace("EmitLogicalOperatorSimple VOID op=" + node.operator + " emit LHS in SCALAR; RHS in VOID");

OperatorNode voidDeclaration = FindDeclarationVisitor.findOperator(node.right, "my");
String voidSavedOperator = null;
Expand All @@ -348,8 +348,7 @@ private static void emitLogicalOperatorSimple(EmitterVisitor emitterVisitor, Bin
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", getBoolean, "()Z", false);
mv.visitJumpInsn(compareOpcode, endLabel);

node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
mv.visitInsn(Opcodes.POP);
node.right.accept(emitterVisitor.with(RuntimeContextType.VOID));

mv.visitLabel(endLabel);
} finally {
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/org/perlonjava/backend/jvm/EmitOperatorLocal.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,40 @@ static void handleLocal(EmitterVisitor emitterVisitor, OperatorNode node) {
}
}

// Handle local %hash for global/our hashes.
// Uses GlobalRuntimeHash.makeLocal() to save/restore the globalHashes map entry,
// not just the hash contents. This is needed because `*glob = \%hash` replaces
// the map entry, and a simple save/restore of contents would lose the reference.
if (node.operand instanceof OperatorNode opNode && opNode.operator.equals("%")) {
if (opNode.operand instanceof IdentifierNode idNode) {
String varName = opNode.operator + idNode.name;
int varIndex = emitterVisitor.ctx.symbolTable.getVariableIndex(varName);
boolean isOurVariable = false;
if (varIndex != -1) {
var symbolEntry = emitterVisitor.ctx.symbolTable.getSymbolEntry(varName);
isOurVariable = symbolEntry != null && "our".equals(symbolEntry.decl());
}
if (varIndex == -1 || isOurVariable) {
String fullName = NameNormalizer.normalizeVariableName(idNode.name, emitterVisitor.ctx.symbolTable.getCurrentPackage());
mv.visitLdcInsn(fullName);
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/GlobalRuntimeHash",
"makeLocal",
"(Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeHash;",
false);
if (isDeclaredReference && emitterVisitor.ctx.contextType != RuntimeContextType.VOID) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeBase",
"createReference",
"()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
}
EmitOperator.handleVoidContext(emitterVisitor);
return;
}
}
}

// emit the lvalue
int lvalueContext = LValueVisitor.getContext(node.operand);

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/perlonjava/backend/jvm/EmitRegex.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.objectweb.asm.Opcodes;
import org.perlonjava.frontend.analysis.EmitterVisitor;
import org.perlonjava.frontend.astnode.*;
import org.perlonjava.runtime.perlmodule.Strict;
import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
import org.perlonjava.runtime.runtimetypes.RuntimeContextType;

Expand Down Expand Up @@ -312,8 +313,21 @@ static void handleMatchRegex(EmitterVisitor emitterVisitor, OperatorNode node) {
/**
* Helper method to emit bytecode for regex matching operations.
* Handles different context types (SCALAR, VOID) appropriately.
* When 'use bytes' is in effect, converts the input string to its
* UTF-8 byte representation before matching.
*/
private static void emitMatchRegex(EmitterVisitor emitterVisitor) {
// When 'use bytes' is in effect, convert the input string to byte representation
// so that regex character classes like [\x7f-\xa0] match against UTF-8 bytes
if (emitterVisitor.ctx.symbolTable != null &&
emitterVisitor.ctx.symbolTable.isStrictOptionEnabled(Strict.HINT_BYTES)) {
// Stack: regex, string (top) -> regex, bytesString (top)
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/operators/StringOperators", "toBytesString",
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
}

emitterVisitor.pushCallContext();
// Invoke the regex matching operation
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
Expand Down
Loading
Loading