Skip to content
Closed
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 @@ -3926,6 +3926,22 @@ void compileVariableDeclaration(OperatorNode node, String op) {
if (sigilOp.operand instanceof IdentifierNode) {
String varName = sigil + ((IdentifierNode) sigilOp.operand).name;

// Match the single-variable declaration path: a later
// list-form `our` under another package establishes a
// new lexical alias for the same bare name. Reusing the
// old entry would make subsequent reads keep loading the
// preceding package's global even though this declaration
// itself loaded the correct package variable.
if (hasVariable(varName) && isOurVariable(varName)) {
SymbolTable.SymbolEntry entry =
symbolTable.getSymbolEntry(varName);
if (entry != null
&& !getCurrentPackage().equals(
entry.perlPackage())) {
addVariable(varName, "our");
}
}

int reg;
// Check if already declared in current scope
if (hasVariable(varName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2096,14 +2096,15 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {

case Opcodes.MATCH_REGEX -> {
// Match regex
// Format: MATCH_REGEX rd stringReg regexReg ctx bytesMode targetNameIndex
// Format: MATCH_REGEX rd stringReg regexReg ctx bytesMode targetNameIndex packageNameIndex
pc = OpcodeHandlerExtended.executeMatchRegex(bytecode, pc, registers, code);
}

case Opcodes.MATCH_REGEX_NOT -> {
// Negated regex match
// Format: MATCH_REGEX_NOT rd stringReg regexReg ctx
pc = OpcodeHandlerExtended.executeMatchRegexNot(bytecode, pc, registers);
// Format: MATCH_REGEX_NOT rd stringReg regexReg ctx packageNameIndex
pc = OpcodeHandlerExtended.executeMatchRegexNot(
bytecode, pc, registers, code);
}

case Opcodes.CHOMP -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ private static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler
bytecodeCompiler.emit(bytecodeCompiler.currentCallContext);
bytecodeCompiler.emit(0);
bytecodeCompiler.emit(-1);
bytecodeCompiler.emit(bytecodeCompiler.addToStringPool(
bytecodeCompiler.getCurrentPackage()));
}
case "!~" -> {
// $string !~ /pattern/ - negated regex match
Expand All @@ -392,6 +394,8 @@ private static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
bytecodeCompiler.emit(bytecodeCompiler.currentCallContext);
bytecodeCompiler.emit(bytecodeCompiler.addToStringPool(
bytecodeCompiler.getCurrentPackage()));
}
case "&" -> {
// Numeric bitwise AND (default): rs1 & rs2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ private static void visitMatchRegex(BytecodeCompiler bc, OperatorNode node) {
bc.emit(bc.isBytesEnabled() ? 1 : 0);
bc.emit(regexTargetNameIndex(bc,
args.elements.size() > 2 ? args.elements.get(2) : null));
bc.emit(bc.addToStringPool(bc.getCurrentPackage()));
bc.lastResultReg = rd;
}

Expand Down Expand Up @@ -402,6 +403,7 @@ private static void visitReplaceRegex(BytecodeCompiler bc, OperatorNode node) {
bc.emit(bc.isBytesEnabled() ? 1 : 0);
bc.emit(regexTargetNameIndex(bc,
args.elements.size() > 3 ? args.elements.get(3) : null));
bc.emit(bc.addToStringPool(bc.getCurrentPackage()));
bc.lastResultReg = rd;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -845,14 +845,16 @@ public static String disassemble(InterpretedCode interpretedCode) {
int matchCtx = interpretedCode.bytecode[pc++];
int bytesMode = interpretedCode.bytecode[pc++];
int targetNameIndex = interpretedCode.bytecode[pc++];
sb.append("MATCH_REGEX r").append(rd).append(" = r").append(strReg).append(" =~ r").append(regReg).append(" (ctx=").append(matchCtx).append(", bytes=").append(bytesMode).append(", targetName=").append(targetNameIndex).append(")\n");
int matchPackageIndex = interpretedCode.bytecode[pc++];
sb.append("MATCH_REGEX r").append(rd).append(" = r").append(strReg).append(" =~ r").append(regReg).append(" (ctx=").append(matchCtx).append(", bytes=").append(bytesMode).append(", targetName=").append(targetNameIndex).append(", package=").append(interpretedCode.stringPool[matchPackageIndex]).append(")\n");
break;
case Opcodes.MATCH_REGEX_NOT:
rd = interpretedCode.bytecode[pc++];
strReg = interpretedCode.bytecode[pc++];
regReg = interpretedCode.bytecode[pc++];
matchCtx = interpretedCode.bytecode[pc++];
sb.append("MATCH_REGEX_NOT r").append(rd).append(" = r").append(strReg).append(" !~ r").append(regReg).append(" (ctx=").append(matchCtx).append(")\n");
matchPackageIndex = interpretedCode.bytecode[pc++];
sb.append("MATCH_REGEX_NOT r").append(rd).append(" = r").append(strReg).append(" !~ r").append(regReg).append(" (ctx=").append(matchCtx).append(", package=").append(interpretedCode.stringPool[matchPackageIndex]).append(")\n");
break;
case Opcodes.CHOMP:
rd = interpretedCode.bytecode[pc++];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@ public static int executeReadline(int[] bytecode, int pc, RuntimeBase[] register

/**
* Execute match regex operation.
* Format: MATCH_REGEX rd stringReg regexReg ctx bytesMode targetNameIndex
* Format: MATCH_REGEX rd stringReg regexReg ctx bytesMode targetNameIndex packageNameIndex
*/
public static int executeMatchRegex(int[] bytecode, int pc, RuntimeBase[] registers,
InterpretedCode code) {
Expand All @@ -918,6 +918,7 @@ public static int executeMatchRegex(int[] bytecode, int pc, RuntimeBase[] regist
int ctx = bytecode[pc++];
boolean bytesMode = bytecode[pc++] != 0;
int targetNameIndex = bytecode[pc++];
int packageNameIndex = bytecode[pc++];

RegexQuoteMeta.setMatchTargetName(targetNameIndex >= 0
&& targetNameIndex < code.stringPool.length
Expand All @@ -926,37 +927,47 @@ public static int executeMatchRegex(int[] bytecode, int pc, RuntimeBase[] regist
if (ctx == RuntimeContextType.RUNTIME) ctx = ((RuntimeScalar) registers[2]).getInt();
RuntimeScalar regex = registers[regexReg].scalar();
RuntimeScalar string = registers[stringReg].scalar();
if (bytesMode) {
registers[rd] = RuntimeRegex.matchRegexBytes(
regex,
string,
ctx);
} else {
registers[rd] = RuntimeRegex.matchRegex(
regex,
string,
ctx);
RuntimeScalar currentPackage = InterpreterState.currentPackage.get();
String savedPackage = currentPackage.toString();
currentPackage.set(code.stringPool[packageNameIndex]);
try {
if (bytesMode) {
registers[rd] = RuntimeRegex.matchRegexBytes(regex, string, ctx);
} else {
registers[rd] = RuntimeRegex.matchRegex(regex, string, ctx);
}
} finally {
currentPackage.set(savedPackage);
}
return pc;
}

/**
* Execute negated match regex operation.
* Format: MATCH_REGEX_NOT rd stringReg regexReg ctx
* Format: MATCH_REGEX_NOT rd stringReg regexReg ctx packageNameIndex
*/
public static int executeMatchRegexNot(int[] bytecode, int pc, RuntimeBase[] registers) {
public static int executeMatchRegexNot(int[] bytecode, int pc, RuntimeBase[] registers,
InterpretedCode code) {
int rd = bytecode[pc++];
int stringReg = bytecode[pc++];
int regexReg = bytecode[pc++];
int ctx = bytecode[pc++];
int packageNameIndex = bytecode[pc++];

RegexQuoteMeta.setMatchTargetName(null);
if (ctx == RuntimeContextType.RUNTIME) ctx = ((RuntimeScalar) registers[2]).getInt();
RuntimeBase matchResult = RuntimeRegex.matchRegex(
(RuntimeScalar) registers[regexReg],
(RuntimeScalar) registers[stringReg],
ctx
);
RuntimeScalar currentPackage = InterpreterState.currentPackage.get();
String savedPackage = currentPackage.toString();
RuntimeBase matchResult;
currentPackage.set(code.stringPool[packageNameIndex]);
try {
matchResult = RuntimeRegex.matchRegex(
(RuntimeScalar) registers[regexReg],
(RuntimeScalar) registers[stringReg],
ctx);
} finally {
currentPackage.set(savedPackage);
}
// Negate the boolean result
registers[rd] = new RuntimeScalar(matchResult.scalar().getBoolean() ? 0 : 1);
return pc;
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ public class Opcodes {

/**
* Match regex: rd = RuntimeRegex.matchRegex(string, regex, ctx)
* Format: MATCH_REGEX rd stringReg regexReg ctx bytesMode target_name_index
* Format: MATCH_REGEX rd stringReg regexReg ctx bytesMode target_name_index package_name_index
*/
public static final short MATCH_REGEX = 167;

Expand Down Expand Up @@ -1255,7 +1255,7 @@ public class Opcodes {

/**
* Match regex (negated): rd = !RuntimeRegex.matchRegex(string, regex, ctx)
* Format: MATCH_REGEX_NOT rd stringReg regexReg ctx
* Format: MATCH_REGEX_NOT rd stringReg regexReg ctx package_name_index
*/
public static final short MATCH_REGEX_NOT = 217;

Expand Down
17 changes: 5 additions & 12 deletions src/main/java/org/perlonjava/runtime/regex/JoniRegexPattern.java
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,6 @@ private static String translatePattern(String pattern, RegexFlags flags,
out.append(ch);
continue;
}
if (!inClass && pattern.startsWith("(*:", i)) {
// Perl's abbreviated MARK form is (*:NAME). Joni accepts the
// equivalent long spelling and publishes the mark normally.
out.append("(*MARK:");
i += 2;
continue;
}
if (inClass && flags.isExtendedWhitespace() && Character.isWhitespace(ch)) {
continue;
}
Expand Down Expand Up @@ -865,7 +858,6 @@ private boolean find(int option, boolean anchored) {
if (nextStart > regionEnd) {
matched = false;
committedLastClosedCapture = -1;
if (hasControlVerbState) RuntimeRegex.updateControlVerbVariables(null, null);
return false;
}
matcher = regex.matcher(bytes);
Expand All @@ -890,7 +882,7 @@ private boolean find(int option, boolean anchored) {
throw failure;
}
matched = result >= 0;
if (hasControlVerbState || matcher.hasEncounteredControlVerb()) {
if (matcher.hasEncounteredControlVerb() || (hasControlVerbState && matched)) {
RuntimeRegex.updateControlVerbVariables(
matcher.getControlMark(), matcher.getControlError());
}
Expand Down Expand Up @@ -1205,7 +1197,7 @@ private Evaluation evaluate(RuntimeRegexCallback callback, MatchView match) {
CaptureSnapshot priorDynamicView = previousDynamicView;
MatchView provisional = callback.kind == RuntimeRegexCallback.Kind.DYNAMIC
? dynamicCaptureView(match, priorDynamicView) : match;
publishProvisional(provisional);
publishProvisional(provisional, callback.lexicalPackage);
if (callback.kind == RuntimeRegexCallback.Kind.DYNAMIC) {
previousDynamicView = CaptureSnapshot.of(match);
}
Expand Down Expand Up @@ -1377,7 +1369,7 @@ private static void rejectEscapedControlFlow(RuntimeRegexCallback callback,
throw new PerlCompilerException(marker.buildErrorMessage() + ".\n");
}

private void publishProvisional(MatchView match) {
private void publishProvisional(MatchView match, String lexicalPackage) {
RuntimeRegexState state = PerlRuntime.current().regexState;
state.lastParenMatchOverrideActive = false;
state.lastParenMatchOverride = null;
Expand Down Expand Up @@ -1412,7 +1404,8 @@ private void publishProvisional(MatchView match) {
state.lastClosedCapture = lastClosed > 0 && lastClosed <= count
? state.lastCaptureGroups[lastClosed - 1] : null;
if (publishesControlVerbState || match.controlMark() != null) {
RuntimeRegex.updateControlVerbVariables(match.controlMark(), null);
RuntimeRegex.updateControlVerbVariables(
lexicalPackage, match.controlMark(), null);
}
}

Expand Down
21 changes: 9 additions & 12 deletions src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,22 +151,19 @@ private static RuntimeRegexState state() {
}

static void updateControlVerbVariables(String mark, String error) {
updateControlVerbVariables(
InterpreterState.currentPackage.get().toString(), mark, error);
}

static void updateControlVerbVariables(String packageName, String mark, String error) {
RuntimeScalar markValue = mark == null
? RuntimeScalarCache.scalarEmptyString : new RuntimeScalar(mark);
RuntimeScalar errorValue = error == null
? RuntimeScalarCache.scalarEmptyString : new RuntimeScalar(error);
// Perl activates these otherwise ordinary package variables through
// local(). The interpreter does not keep its runtime current-package
// facade synchronized with every lexical package statement, so use the
// localized scalar identities rather than guessing one package name.
for (Map.Entry<String, RuntimeScalar> entry
: DynamicVariableManager.activeLocalizedGlobalScalars().entrySet()) {
if (entry.getKey().endsWith("::REGMARK")) {
entry.getValue().set(markValue);
} else if (entry.getKey().endsWith("::REGERROR")) {
entry.getValue().set(errorValue);
}
}
String owner = packageName == null || packageName.isEmpty() ? "main" : packageName;
String separator = owner.endsWith("::") ? "" : "::";
GlobalVariable.getGlobalVariable(owner + separator + "REGMARK").set(markValue);
GlobalVariable.getGlobalVariable(owner + separator + "REGERROR").set(errorValue);
}
// Compiled regex pattern (for byte strings - ASCII-only \w, \d)
public Pattern pattern;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ void translatesPerlInlineModifierSemantics() {
}

@Test
void translatesAbbreviatedMarkControlVerb() {
void routesAbbreviatedMarkControlVerbWithoutRewriting() {
assertTrue(JoniRegexPattern.requiresJoniBackend("(*:B)A"));
assertEquals("(*MARK:B)A", JoniRegexPattern.translatePattern("(*:B)A"));
assertEquals("(*:B)A", JoniRegexPattern.translatePattern("(*:B)A"));
}

@Test
Expand Down
Loading