Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
559403e
CI: Upload Maven test results on Windows to verify tests actually ran
fglock Jan 2, 2026
44b458a
CI: Add step to print Maven test summary on Windows for visibility
fglock Jan 2, 2026
60b3b51
Windows CI: Add -X flag to Maven for verbose/debug output
fglock Jan 2, 2026
b1a6d6d
Windows CI: Only compile, skip tests due to persistent Maven/Gradle i…
fglock Jan 2, 2026
d5ce65f
Windows CI: Build jperl and run unit tests directly to avoid Maven/Gr…
fglock Jan 2, 2026
3e710f5
Fix Makefile to use bash-compatible syntax for Windows CI test loop
fglock Jan 2, 2026
e6889b1
Windows CI: Add ls to debug jperl location and use jperl.bat with ./ …
fglock Jan 2, 2026
17a7f3e
Windows CI: Use perlonjava.bat instead of jperl.bat (correct executab…
fglock Jan 2, 2026
c6d0564
Windows CI: Use jperl.bat from repo root and build shadowJar instead …
fglock Jan 2, 2026
3d4256e
Windows CI: Run tests directly with Java using classpath to avoid JAR…
fglock Jan 2, 2026
1c9a013
Fix Windows JAR path issue: Don't convert JAR resource URLs to filesy…
fglock Jan 2, 2026
7f746b3
Windows CI: Revert to using jperl.bat with shadowJar now that JAR pat…
fglock Jan 2, 2026
423b281
CI: Remove obsolete Maven test summary step and update comments
fglock Jan 2, 2026
ad9c0bd
Merge pull request #86 from fglock/nonlocal-goto-wip
fglock Jan 2, 2026
1fa5dd2
cleanup "skip" workaround
fglock Jan 2, 2026
aea8113
cleanup "skip" workaround
fglock Jan 2, 2026
5a77d2c
Add skip_control_flow.t test to document scalar context control flow …
fglock Jan 5, 2026
7937c92
Fix last SKIP control flow in scalar context
fglock Jan 5, 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
4 changes: 2 additions & 2 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ jobs:
uses: gradle/actions/setup-gradle@v4

# Use Makefile's 'ci' target which handles platform differences
# Windows: builds without tests to avoid Gradle daemon socket errors
# Linux: full build with tests
# Windows: builds shadowJar and runs unit tests with jperl.bat
# Linux: full Gradle build with tests
- name: Build with Make (Windows)
if: runner.os == 'Windows'
shell: cmd
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ all: build
# CI build - optimized for CI/CD environments
ci: wrapper
ifeq ($(OS),Windows_NT)
mvn clean test -B
gradlew.bat clean compileJava shadowJar --no-daemon --stacktrace
@echo "Running unit tests with jperl.bat..."
@for test in src/test/resources/unit/*.t; do \
echo "Testing $$test" && \
./jperl.bat "$$test" || exit 1; \
done
else
./gradlew build --no-daemon --stacktrace
endif
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/org/perlonjava/codegen/EmitBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,44 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
element.accept(voidVisitor);
}

// Check for non-local control flow after each statement in labeled blocks
// Only for simple blocks to avoid ASM VerifyError
if (node.isLoop && node.labelName != null && i < list.size() - 1 && list.size() <= 3) {
// Check if block contains loop constructs (they handle their own control flow)
boolean hasLoopConstruct = false;
for (Node elem : list) {
if (elem instanceof For1Node || elem instanceof For3Node) {
hasLoopConstruct = true;
break;
}
}

if (!hasLoopConstruct) {
Label continueBlock = new Label();

// if (!RuntimeControlFlowRegistry.hasMarker()) continue
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/RuntimeControlFlowRegistry",
"hasMarker",
"()Z",
false);
mv.visitJumpInsn(Opcodes.IFEQ, continueBlock);

// Has marker: check if it matches this loop
mv.visitLdcInsn(node.labelName);
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/RuntimeControlFlowRegistry",
"checkLoopAndGetAction",
"(Ljava/lang/String;)I",
false);

// If action != 0, jump to nextLabel (exit block)
mv.visitJumpInsn(Opcodes.IFNE, nextLabel);

mv.visitLabel(continueBlock);
}
}

// NOTE: Registry checks are DISABLED in EmitBlock because:
// 1. They cause ASM frame computation errors in nested/refactored code
// 2. Bare labeled blocks (like TODO:) don't need non-local control flow
Expand Down
11 changes: 4 additions & 7 deletions src/main/java/org/perlonjava/operators/ModuleOperators.java
Original file line number Diff line number Diff line change
Expand Up @@ -519,13 +519,10 @@ else if (code == null) {
String resourcePath = "/lib/" + fileName;
URL resource = RuntimeScalar.class.getResource(resourcePath);
if (resource != null) {
String path = resource.getPath();
// Remove leading slash if on Windows
if (SystemUtils.osIsWindows() && path.startsWith("/")) {
path = path.substring(1);
}
fullName = Paths.get(path);
actualFileName = fullName.toString();
// For JAR resources, use the resource path directly instead of converting to filesystem path
// This avoids Windows path issues with colons in JAR URLs like "file:/D:/..."
actualFileName = resourcePath;
fullName = null; // No filesystem path for JAR resources

try (InputStream is = resource.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
Expand Down
3 changes: 0 additions & 3 deletions src/main/java/org/perlonjava/parser/StatementParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,6 @@ public static Node parseIfStatement(Parser parser) {
elseBranch = parseIfStatement(parser);
}

// Use a macro to emulate Test::More SKIP blocks
TestMoreHelper.handleSkipTest(parser, thenBranch);

return new IfNode(operator.text, condition, thenBranch, elseBranch, parser.tokenIndex);
}

Expand Down
5 changes: 0 additions & 5 deletions src/main/java/org/perlonjava/parser/StatementResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -572,11 +572,6 @@ yield dieWarnNode(parser, "die", new ListNode(List.of(

parser.ctx.symbolTable.exitScope(scopeIndex);

if (label != null && label.equals("SKIP")) {
// Use a macro to emulate Test::More SKIP blocks
TestMoreHelper.handleSkipTest(parser, block);
}

yield new For3Node(label,
true,
null, null,
Expand Down
52 changes: 0 additions & 52 deletions src/main/java/org/perlonjava/parser/TestMoreHelper.java

This file was deleted.

34 changes: 20 additions & 14 deletions src/main/perl/lib/Test/More.pm
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ our @EXPORT = qw(
pass fail diag note done_testing is_deeply subtest
use_ok require_ok BAIL_OUT
skip
skip_internal
eq_array eq_hash eq_set
);

Expand Down Expand Up @@ -287,20 +286,27 @@ sub BAIL_OUT {
}

sub skip {
die "Test::More::skip() is not implemented";
}

# Workaround to avoid non-local goto (last SKIP).
# The skip_internal subroutine is called from a macro in TestMoreHelper.java
#
sub skip_internal {
my ($name, $count) = @_;
for (1..$count) {
$Test_Count++;
my $result = "ok";
print "$Test_Indent$result $Test_Count # skip $name\n";
my $why = shift;
my $n = @_ ? shift : 1;
my $bad_swap;
my $both_zero;
{
local $^W = 0;
$bad_swap = $why > 0 && $n == 0;
$both_zero = $why == 0 && $n == 0;
}
if ($bad_swap || $both_zero || @_) {
my $arg = "'$why', '$n'";
if (@_) {
$arg .= join(", ", '', map { qq['$_'] } @_);
}
die qq[$0: expected skip(why, count), got skip($arg)\n];
}
for (1..$n) {
ok(1, "# skip $why");
}
return 1;
local $^W = 0;
last SKIP;
}

# Legacy comparison functions - simple implementations using is_deeply
Expand Down
54 changes: 54 additions & 0 deletions src/test/resources/unit/skip_control_flow.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env perl
use strict;
use warnings;

# Minimal TAP without Test::More (we need this to work even when skip()/TODO are broken)
my $t = 0;
sub ok_tap {
my ($cond, $name) = @_;
$t++;
print(($cond ? "ok" : "not ok"), " $t - $name\n");
}

# 1) Single frame
{
my $out = '';
sub skip_once { last SKIP }
SKIP: {
$out .= 'A';
skip_once();
$out .= 'B';
}
$out .= 'C';
ok_tap($out eq 'AC', 'last SKIP exits SKIP block (single frame)');
}

# 2) Two frames, scalar context
{
my $out = '';
sub inner2 { last SKIP }
sub outer2 { my $x = inner2(); return $x; }
SKIP: {
$out .= 'A';
my $r = outer2();
$out .= 'B';
}
$out .= 'C';
ok_tap($out eq 'AC', 'last SKIP exits SKIP block (2 frames, scalar context)');
}

# 3) Two frames, void context
{
my $out = '';
sub innerv { last SKIP }
sub outerv { innerv(); }
SKIP: {
$out .= 'A';
outerv();
$out .= 'B';
}
$out .= 'C';
ok_tap($out eq 'AC', 'last SKIP exits SKIP block (2 frames, void context)');
}

print "1..$t\n";