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
23 changes: 19 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
.PHONY: all clean test test-unit test-all test-gradle test-gradle-unit test-gradle-all test-gradle-parallel test-maven-parallel build run wrapper dev ci

# If FORCE_TESTS=1, Gradle test tasks will be executed with --rerun-tasks to avoid
# false positives from UP-TO-DATE/cached test results.
#
# Default is 1 because `make` is used frequently and should not give a false
# impression of correctness.
FORCE_TESTS ?= 1
RERUN_TASKS_FLAG := $(if $(filter 1,$(FORCE_TESTS)),--rerun-tasks,)

all: build

# CI build - optimized for CI/CD environments
Expand All @@ -16,9 +24,9 @@ wrapper:
# Standard build - incremental compilation with parallel tests (4 JVMs)
build: wrapper
ifeq ($(OS),Windows_NT)
gradlew.bat classes testUnitParallel --parallel shadowJar
gradlew.bat classes testUnitParallel --parallel $(RERUN_TASKS_FLAG) shadowJar
else
./gradlew classes testUnitParallel --parallel shadowJar
./gradlew classes testUnitParallel --parallel $(RERUN_TASKS_FLAG) shadowJar
endif

# Development build - forces recompilation (use during active development)
Expand All @@ -36,11 +44,18 @@ test: test-unit
# Uses Gradle's testUnitParallel (same as default make build)
test-unit: wrapper
ifeq ($(OS),Windows_NT)
gradlew.bat testUnitParallel --parallel
gradlew.bat testUnitParallel --parallel $(RERUN_TASKS_FLAG)
else
./gradlew testUnitParallel --parallel
./gradlew testUnitParallel --parallel $(RERUN_TASKS_FLAG)
endif

# Convenience targets to force tests regardless of FORCE_TESTS env var.
test-unit-force:
$(MAKE) FORCE_TESTS=1 test-unit

build-force:
$(MAKE) FORCE_TESTS=1 build

# Perl 5 core test suite (perl5_t/t/ directory)
# Run 'perl dev/import-perl5/sync.pl' first to populate perl5_t/
test-perl5:
Expand Down
5 changes: 0 additions & 5 deletions dev/import-perl5/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -382,11 +382,6 @@ imports:
target: src/main/perl/lib
type: directory

# Test::More.pm is protected - won't be overwritten if it exists (customized for PerlOnJava)
- source: perl5/cpan/Test-Simple/lib/Test/More.pm
target: src/main/perl/lib/Test/More.pm
protected: true

# Tests for distribution
- source: perl5/cpan/Test-Simple/t
target: perl5_t/Test-Simple
Expand Down
26 changes: 25 additions & 1 deletion src/main/java/org/perlonjava/codegen/EmitBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.List;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.ArrayList;

public class EmitBlock {

Expand Down Expand Up @@ -41,7 +42,6 @@ private static void collectStateDeclSigilNodes(Node node, Set<OperatorNode> out)
for (Node child : block.elements) {
collectStateDeclSigilNodes(child, out);
}
return;
}
if (node instanceof For1Node for1) {
collectStateDeclSigilNodes(for1.variable, out);
Expand Down Expand Up @@ -71,6 +71,14 @@ private static void collectStateDeclSigilNodes(Node node, Set<OperatorNode> out)
}
}

private static void collectStatementLabelNames(List<Node> elements, List<String> out) {
for (Node element : elements) {
if (element instanceof LabelNode labelNode) {
out.add(labelNode.label);
}
}
}

/**
* Emits bytecode for a block of statements.
*
Expand Down Expand Up @@ -116,6 +124,17 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
Label redoLabel = new Label();
Label nextLabel = new Label();

// Pre-register statement labels (e.g. `NEXT:`) in this block so `goto NEXT` can resolve
// even when the goto appears before the label (forward goto).
//
// We intentionally only register labels at this block's top level. Nested blocks get
// their own EmitBlock invocation and maintain proper scoping/shadowing via the stack.
List<String> statementLabelNames = new ArrayList<>();
collectStatementLabelNames(list, statementLabelNames);
for (String labelName : statementLabelNames) {
emitterVisitor.ctx.javaClassInfo.pushGotoLabels(labelName, new Label());
}

// Create labels used inside the block, like `{ L1: ... }`
for (int i = 0; i < node.labels.size(); i++) {
emitterVisitor.ctx.javaClassInfo.pushGotoLabels(node.labels.get(i), new Label());
Expand Down Expand Up @@ -220,6 +239,11 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
emitterVisitor.ctx.javaClassInfo.popGotoLabels();
}

// Pop statement labels registered for this block
for (int i = 0; i < statementLabelNames.size(); i++) {
emitterVisitor.ctx.javaClassInfo.popGotoLabels();
}

// Add 'next', 'last' label
mv.visitLabel(nextLabel);

Expand Down
33 changes: 33 additions & 0 deletions src/main/java/org/perlonjava/codegen/EmitForeach.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,35 @@ public class EmitForeach {

// Set to true to enable debug output for loop control flow
private static final boolean DEBUG_LOOP_CONTROL_FLOW = false;

private static void pushGotoLabelsForBlock(EmitterVisitor emitterVisitor, BlockNode blockNode) {
// Pre-register labels for forward/backward goto inside this block.
// BlockNode.labels contains labels visible in this block scope (including statement labels
// like `NEXT:` that can be goto targets).
for (int i = 0; i < blockNode.labels.size(); i++) {
emitterVisitor.ctx.javaClassInfo.pushGotoLabels(blockNode.labels.get(i), new Label());
}

// Some labels may appear as explicit LabelNode statements.
for (Node element : blockNode.elements) {
if (element instanceof LabelNode labelNode) {
emitterVisitor.ctx.javaClassInfo.pushGotoLabels(labelNode.label, new Label());
}
}
}

private static void popGotoLabelsForBlock(EmitterVisitor emitterVisitor, BlockNode blockNode) {
// Pop in reverse order of pushes above.
for (Node element : blockNode.elements) {
if (element instanceof LabelNode) {
emitterVisitor.ctx.javaClassInfo.popGotoLabels();
}
}
for (int i = 0; i < blockNode.labels.size(); i++) {
emitterVisitor.ctx.javaClassInfo.popGotoLabels();
}
}

public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) {
emitterVisitor.ctx.logDebug("FOR1 start");

Expand Down Expand Up @@ -439,6 +468,8 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) {
int bodyScopeIndex = emitterVisitor.ctx.symbolTable.enterScope();
Local.localRecord bodyLocalRecord = Local.localSetup(emitterVisitor.ctx, blockNode, mv);

pushGotoLabelsForBlock(emitterVisitor, blockNode);

java.util.List<Node> list = blockNode.elements;
int lastNonNullIndex = -1;
for (int i = list.size() - 1; i >= 0; i--) {
Expand All @@ -462,6 +493,8 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) {
emitRegistryCheck(mv, currentLoopLabels, redoLabel, continueLabel, loopEnd);
}

popGotoLabelsForBlock(emitterVisitor, blockNode);

Local.localTeardown(bodyLocalRecord, mv);
emitterVisitor.ctx.symbolTable.exitScope(bodyScopeIndex);
} else {
Expand Down
23 changes: 9 additions & 14 deletions src/main/java/org/perlonjava/regex/RuntimeRegex.java
Original file line number Diff line number Diff line change
Expand Up @@ -412,12 +412,13 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
int capture = 1;
int previousPos = startPos; // Track the previous position
int previousMatchEnd = -1; // Track end of previous match
// System.err.println("DEBUG: Resetting globalMatcher to null at start of matchRegex");
globalMatcher = null;
// Reset stored match information (but preserve last successful match info)
lastMatchedString = null;
lastMatchStart = -1;
lastMatchEnd = -1;
// NOTE: Do NOT clear global match variables here.
//
// Perl preserves $1, @-, @+, $&, etc. from the last *successful* match even if a
// subsequent regex operation fails. Test libraries (notably Test::Builder/Test2)
// frequently run internal regexes (some of which fail) between user assertions.
// Clearing these variables would incorrectly erase the previous successful capture
// state and break tests that rely on @-/@+.

while (matcher.find()) {
// If \G is used, ensure the match starts at the expected position
Expand Down Expand Up @@ -502,14 +503,8 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
posScalar.set(scalarUndef);
}

// Reset special variables on failed match (Perl behavior)
if (!found) {
lastSuccessfulPattern = null;
lastSuccessfulMatchedString = null;
lastSuccessfulMatchStart = -1;
lastSuccessfulMatchEnd = -1;
lastSuccessfulMatchString = null;
}
// If no match was found, keep lastSuccessful* state intact (Perl behavior).
// Do not clear lastSuccessfulPattern or lastSuccessfulMatch* values here.

if (found) {
regex.matched = true; // Counter for m?PAT?
Expand Down
Loading
Loading