Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -1427,8 +1427,8 @@ public void visit(StringNode node) {
: RuntimeScalarCache.getOrCreateStringIndex(node.value);
if (cacheIdx >= 0) {
RuntimeScalar cached = (opcode == Opcodes.LOAD_BYTE_STRING)
? RuntimeScalarCache.getScalarByteString(cacheIdx)
: RuntimeScalarCache.getScalarString(cacheIdx);
? RuntimeScalarCache.materializeByteStringLiteral(cacheIdx)
: RuntimeScalarCache.materializeStringLiteral(cacheIdx);
int constIdx = addToConstantPool(cached);
emit(Opcodes.LOAD_CONST);
emitReg(rd);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,9 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitInt(0);

int rightCtx = bytecodeCompiler.currentCallContext;
bytecodeCompiler.compileNode(node.right, rd, rightCtx);
// LHS is scalar for the boolean test; RHS uses this operator's context (LIST/SCALAR/
// VOID/RUNTIME) — perlop: context propagates to the right operand (//: EXPR2 in //).
bytecodeCompiler.compileNode(node.right, rd, bytecodeCompiler.currentCallContext);
int rs2 = bytecodeCompiler.lastResultReg;
if (rs2 >= 0) {
bytecodeCompiler.emitAliasWithTarget(rd, rs2);
Expand All @@ -499,8 +500,8 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitInt(0);

int rightCtx = bytecodeCompiler.currentCallContext;
bytecodeCompiler.compileNode(node.right, rd, rightCtx);
// LHS scalar for truth test; RHS in this operator's context — see && branch above.
bytecodeCompiler.compileNode(node.right, rd, bytecodeCompiler.currentCallContext);
int rs2 = bytecodeCompiler.lastResultReg;
if (rs2 >= 0) {
bytecodeCompiler.emitAliasWithTarget(rd, rs2);
Expand Down Expand Up @@ -530,8 +531,8 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
bytecodeCompiler.emitReg(definedReg);
bytecodeCompiler.emitInt(0);

int rightCtx = bytecodeCompiler.currentCallContext;
bytecodeCompiler.compileNode(node.right, rd, rightCtx);
// LHS scalar for definedness; RHS in this operator's context — see && branch above.
bytecodeCompiler.compileNode(node.right, rd, bytecodeCompiler.currentCallContext);
int rs2 = bytecodeCompiler.lastResultReg;
if (rs2 >= 0) {
bytecodeCompiler.emitAliasWithTarget(rd, rs2);
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/org/perlonjava/backend/jvm/EmitLiteral.java
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ public static void emitString(EmitterContext ctx, StringNode node) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeScalarCache",
"getScalarByteString",
"(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
"materializeByteStringLiteral",
"(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalarReadOnly;",
false);
return;
} else {
Expand Down Expand Up @@ -311,8 +311,8 @@ public static void emitString(EmitterContext ctx, StringNode node) {
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeScalarCache",
"getScalarString",
"(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
"materializeStringLiteral",
"(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalarReadOnly;",
false);
} else {
// String is too long for cache or null, create new object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,12 @@ static void emitLogicalOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod
// If true, jump to convert label
mv.visitJumpInsn(compareOpcode, convertLabel);

// LHS is false: evaluate RHS in LIST context
// LHS is false: RHS is evaluated in the same context as this &&/||///
// (perlop: context propagates to the right operand). For //, EXPR2 is explicitly
// in the context // itself; LHS stays scalar for the test above.
mv.visitInsn(Opcodes.POP); // Remove LHS
node.right.accept(emitterVisitor.with(RuntimeContextType.LIST));
// Stack: [RuntimeList]
// Stack: [RuntimeList] — LIST context emission matches flattening for aggregates
mv.visitJumpInsn(Opcodes.GOTO, endLabel);

// LHS is true: convert scalar to list
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/perlonjava/runtime/regex/RegexFlags.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ public record RegexFlags(boolean isGlobalMatch, boolean keepCurrentPosition, boo
boolean isAscii) {

public static RegexFlags fromModifiers(String modifiers, String patternString) {
// m?PAT? is encoded by StringParser as an extra trailing '?' on the modifier string
// (see parseRegexMatch). Do NOT use modifiers.contains("?"): '?' appears inside many
// ordinary patterns (e.g. (?:...) or ...?) and must not enable match-once mode for those.
boolean matchOnce = modifiers != null && modifiers.endsWith("?");
return new RegexFlags(
modifiers.contains("g"),
modifiers.contains("c"),
modifiers.contains("r"),
modifiers.contains("?"),
matchOnce,
patternString != null && patternString.contains("\\G"),
modifiers.contains("xx"),
modifiers.contains("n"),
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,9 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc

if (found) {
fixPerl16894AlternateCaptureInLookahead(regex, inputStr);
regex.matched = true; // Counter for m?PAT?
if (regex.regexFlags.isMatchExactlyOnce()) {
regex.matched = true; // m?PAT? — remember we consumed the one allowed match
}
lastMatchUsedPFlag = regex.hasPreservesMatch;
lastSuccessfulPattern = regex;
// Store last successful match information (persists across failed matches)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,21 @@ public static void invalidatePos(RuntimeScalar perlVariable) {
if (perlVariable == null) {
return;
}
// Remove the cache entry entirely so pos() returns undef
positionCache.remove(perlVariable);
// Reset the canonical pos lvalue in place. Removing the cache entry orphans the
// PosLvalueScalar that matchRegexDirect may already hold (local posScalar), breaking
// /g and \\G after (?{ }) or other mid-match assignments to the target scalar.
CacheEntry cachedEntry = positionCache.get(perlVariable);
if (cachedEntry != null) {
int code = perlVariable.value == null ? 0 : perlVariable.value.hashCode();
cachedEntry.valueHash = code;
RuntimeScalar pos = cachedEntry.regexPosition;
pos.type = RuntimeScalarType.UNDEF;
pos.value = null;
cachedEntry.lastMatchWasZeroLength = false;
cachedEntry.lastMatchPosition = -1;
cachedEntry.lastMatchPattern = null;
cachedEntry.hasUnicodeChars = null;
}
}

private static void clearZeroLengthMatchTracking(RuntimeScalar perlVariable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1040,11 +1040,27 @@ public void releaseOwnedScalarReferenceContents() {
// Types < TIED_SCALAR (0-8) never have REFERENCE_BIT (0x8000), so no
// reference check is needed here — all reference types route to setLarge().
public RuntimeScalar set(RuntimeScalar value) {
if (this.type < TIED_SCALAR & value.type < TIED_SCALAR) {
this.type = value.type;
this.value = value.value;
// Perl clears pos() when assigning from another SV ($x = $y), but preserves it for
// self-assignment ($x = $x). Hash/array element slots reuse one RuntimeScalar per key;
// $h{k} = $str must reset pos on that slot (Data::SExpression set_input / lexer \G).
// Invalidate only after the new value is stored so the pos cache records the correct
// string hash, and refresh in place so existing PosLvalueScalar handles stay valid.
if (value != null && this.type < TIED_SCALAR & value.type < TIED_SCALAR) {
if (this != value) {
this.type = value.type;
this.value = value.value;
RuntimePosLvalue.invalidatePos(this);
} else {
this.type = value.type;
this.value = value.value;
}
return this;
}
if (this != value) {
RuntimeScalar r = setLarge(value);
RuntimePosLvalue.invalidatePos(this);
return r;
}
return setLarge(value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,29 @@ public static RuntimeScalar getScalarString(int index) {
return scalarString[index];
}

/**
* Materialize a fresh read-only string scalar from a short-string cache entry.
* <p>Perl allocates a distinct SV for each string literal <em>occurrence</em>. Reusing the
* singleton from {@link #getScalarString(int)} breaks operations keyed by scalar identity,
* notably {@code pos()} / {@code \\G} regex state (see Data::SExpression folding tests).
*/
public static RuntimeScalarReadOnly materializeStringLiteral(int index) {
RuntimeScalarReadOnly template = scalarString[index];
RuntimeScalarReadOnly copy = new RuntimeScalarReadOnly(template.s);
copy.type = template.type;
return copy;
}

/**
* Same as {@link #materializeStringLiteral(int)} for octet-string literals.
*/
public static RuntimeScalarReadOnly materializeByteStringLiteral(int index) {
RuntimeScalarReadOnly template = scalarByteString[index];
RuntimeScalarReadOnly copy = new RuntimeScalarReadOnly(template.s);
copy.type = RuntimeScalarType.BYTE_STRING;
return copy;
}

/**
* Looks up an existing cache index for the specified byte string without creating a new entry.
*
Expand Down
Loading