Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4bba2f4
fix(JSON): shim compatibility for jcpan -t JSON
fglock Apr 23, 2026
324eb25
docs(JSON): plan to reach full parity with CPAN JSON test suite
fglock Apr 23, 2026
84ff602
refactor(JSON): delegate JSON to the bundled JSON::PP; drop Json.java…
fglock Apr 23, 2026
38571b3
fix(parser): package-literal barewords (Foo::Bar::) in bless and befo…
fglock Apr 23, 2026
1dd4774
fix(parser): disambiguate `{ "a", "b" }` as a hashref, not a block
fglock Apr 23, 2026
c277cd4
fix(unpack): `U*` reads code points in character mode, not UTF-8 bytes
fglock Apr 23, 2026
b9f8505
fix(regex): `[\c?]` inside a character class matches DEL, not U+001C
fglock Apr 23, 2026
1ef8038
fix(JSON): `-convert_blessed_universally` must use reftype, not ref
fglock Apr 23, 2026
7f3e0d1
fix(runtime): bless / isa canonicalise through stash aliases
fglock Apr 23, 2026
1919448
docs(JSON): update json_test_parity.md progress (66/68 passing)
fglock Apr 23, 2026
abec7df
feat(JSON): support PERL_JSON_BACKEND=JSON::backportPP — 0 failures o…
fglock Apr 23, 2026
dbcc871
docs(JSON): progress update — 0 failures on jcpan -t JSON
fglock Apr 23, 2026
26eb50e
fix(JSON): `JSON::backportPP` — require JSON::PP + delete %INC instea…
fglock Apr 23, 2026
af55778
fix(JSON::PP): IncrParser substr uses explicit UTF-8 byte handling
fglock Apr 23, 2026
94c5cff
docs(JSON): jcpan -t JSON now Result: PASS
fglock Apr 23, 2026
086c593
fix(parser): hashref disambiguation — only strings/numbers trigger, n…
fglock Apr 23, 2026
5a11e5e
fix(unpack): `U` format — real-Perl-compatible mode-aware handling
fglock Apr 23, 2026
3611e23
fix(import-perl5): refresh JSON/PP.pm patch with our fixes
fglock Apr 23, 2026
7058351
build: disable `make dev` — it bypassed the unit tests
fglock Apr 24, 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
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,14 @@ perl dev/tools/perl_test_runner.pl perl5_t/t/op/ > /tmp/test_output.txt 2>&1

| Command | What it does |
|---------|--------------|
| `make` | Build + run all unit tests (use before committing) |
| `make dev` | Build only, skip tests (for quick iteration during debugging) |
| `make` | Build + run all unit tests (always use this) |
| `make test-bundled-modules` | Run bundled CPAN module tests (XML::Parser, etc.) |

`make dev` has been disabled on purpose — it used to build without
running tests, which let regressions sneak into commits. Always use
`make`; if you truly need a no-test build, invoke Gradle directly
(`./gradlew shadowJar installDist`).

- For interpreter changes, test with both backends:
```bash
./jperl -e 'code' # JVM backend
Expand Down
29 changes: 22 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,28 @@ else
./gradlew classes testUnitParallel --parallel shadowJar
endif

# Development build - forces recompilation (use during active development)
dev: check-java-gradle
ifeq ($(OS),Windows_NT)
gradlew.bat clean compileJava shadowJar installDist
else
./gradlew clean compileJava shadowJar installDist
endif
# `make dev` is disabled on purpose.
#
# It used to be a "build without running tests" shortcut, but that is
# precisely what makes it dangerous: it lets changes land on a branch
# without having ever been exercised by the unit test suite. Agents
# (and humans in a hurry) reach for `make dev` to iterate faster and
# then forget to run `make` before pushing, so regressions sneak in.
#
# Use `make` (the default target) instead: it builds *and* runs the
# fast unit tests. If you really need a no-test build for a very
# specific reason, invoke Gradle directly (`./gradlew shadowJar`) and
# own the consequences.
dev:
@echo "ERROR: 'make dev' is disabled on purpose."
@echo ""
@echo " It skipped the unit tests, which caused regressions to slip"
@echo " into commits. Please use 'make' (which builds + tests) for"
@echo " everyday iteration."
@echo ""
@echo " If you truly need a no-test build, invoke Gradle directly:"
@echo " ./gradlew shadowJar installDist"
@exit 1

# Default test target - fast unit tests using perl_test_runner.pl
test: test-unit
Expand Down
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ dependencies {
implementation libs.asm // ByteCode manipulation
implementation libs.asm.util // ASM utilities
implementation libs.icu4j // Unicode support
implementation libs.fastjson2 // JSON processing
implementation libs.snakeyaml.engine // YAML processing
implementation libs.tomlj // TOML processing
implementation libs.commons.csv // CSV processing
Expand Down
8 changes: 4 additions & 4 deletions dev/custom_bytecode/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Micro-benchmark for interpreter performance on loop-heavy code.
./gradlew run -PmainClass=org.perlonjava.interpreter.ForLoopBenchmark

# Method 2: Direct Java execution
java -cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' -o -name 'fastjson*.jar' | tr '\n' ':')" \
java -cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' | tr '\n' ':')" \
org.perlonjava.interpreter.ForLoopBenchmark
```

Expand Down Expand Up @@ -120,7 +120,7 @@ See what methods the JVM is compiling and inlining:

```bash
java -XX:+PrintCompilation \
-cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' -o -name 'fastjson*.jar' | tr '\n' ':')" \
-cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' | tr '\n' ':')" \
org.perlonjava.interpreter.ForLoopBenchmark 2>&1 | grep -E "(BytecodeInterpreter|MathOperators|CompareOperators)"
```

Expand All @@ -130,7 +130,7 @@ See what the C2 compiler is inlining:

```bash
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining \
-cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' -o -name 'fastjson*.jar' | tr '\n' ':')" \
-cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' | tr '\n' ':')" \
org.perlonjava.interpreter.ForLoopBenchmark 2>&1 > /tmp/inline.txt

# Check if operators are being inlined into execute loop
Expand All @@ -144,7 +144,7 @@ View the generated interpreter bytecode:
```bash
# Enable DEBUG mode in ForLoopBenchmark.java, then:
make build
java -cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' -o -name 'fastjson*.jar' | tr '\n' ':')" \
java -cp "build/classes/java/main:$(find ~/.gradle/caches -name 'icu4j*.jar' -o -name 'asm*.jar' | tr '\n' ':')" \
org.perlonjava.interpreter.ForLoopBenchmark 2>&1 | head -30
```

Expand Down
1 change: 0 additions & 1 deletion dev/design/sbom.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ These are external libraries downloaded from Maven Central during build:
| org.ow2.asm:asm | 9.9.1 | BSD-3-Clause | JVM bytecode generation |
| org.ow2.asm:asm-util | 9.9.1 | BSD-3-Clause | ASM utilities |
| com.ibm.icu:icu4j | 78.2 | ICU License | Unicode support |
| com.alibaba.fastjson2:fastjson2 | 2.0.61 | Apache-2.0 | JSON processing |
| org.snakeyaml:snakeyaml-engine | 3.0.1 | Apache-2.0 | YAML processing |
| org.tomlj:tomlj | 1.1.1 | Apache-2.0 | TOML processing |
| org.apache.commons:commons-csv | 1.14.1 | Apache-2.0 | CSV processing |
Expand Down
57 changes: 54 additions & 3 deletions dev/import-perl5/patches/PP.pm.patch
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
--- perl5/cpan/JSON-PP/lib/JSON/PP.pm
+++ src/main/perl/lib/JSON/PP.pm
@@ -695,6 +695,9 @@ BEGIN {
--- perl5/cpan/JSON-PP/lib/JSON/PP.pm 2025-12-30 10:25:00
+++ src/main/perl/lib/JSON/PP.pm 2026-04-23 21:59:52
@@ -129,6 +129,17 @@


# Methods
+
+# Backend introspection — the CPAN JSON dispatcher and its test suite call
+# these as class methods on whichever backend module was loaded (JSON::XS,
+# JSON::PP, JSON::backportPP, or — in PerlOnJava — our `JSON` shim which
+# ISA JSON::PP). JSON::PP is the pure-Perl backend, so `is_pp == 1` and
+# `is_xs == 0`. Upstream CPAN JSON::PP doesn't define these (the real
+# dispatcher installs them), but we ship JSON::PP as our backend and add
+# them here so that tests which introspect `JSON::PP->is_pp` directly
+# work out of the box.
+sub is_xs { 0 }
+sub is_pp { 1 }

sub new {
my $class = shift;
@@ -695,6 +706,9 @@
last;
}
}
Expand All @@ -10,3 +28,36 @@
}

{ # PARSE
@@ -1544,8 +1558,30 @@

my ($obj, $offset) = $coder->PP_decode_json( $self->{incr_text}, 0x00000001 );
push @ret, $obj;
- use bytes;
- $self->{incr_text} = substr( $self->{incr_text}, $offset || 0 );
+ # PerlOnJava: the decoder advances its internal `$at` by the
+ # UTF-8 byte length of each multi-byte character (via
+ # `is_valid_utf8`), so `$offset` is in BYTES. CPAN
+ # JSON::PP papers over this with `use bytes; substr`,
+ # which in upstream Perl makes substr operate on the
+ # UTF-8 byte representation of the string. PerlOnJava's
+ # `use bytes` pragma does not yet redirect substr, so do
+ # the equivalent explicitly.
+ #
+ # When `get_utf8` is true, `$self->{incr_text}` is already
+ # a byte string (the user hands us UTF-8 bytes) and plain
+ # substr works correctly with a byte offset. When
+ # `get_utf8` is false, we decoded to chars above and need
+ # to encode-substr-decode to match the decoder's byte
+ # bookkeeping.
+ if ( $coder->get_utf8 ) {
+ $self->{incr_text} = substr( $self->{incr_text}, $offset || 0 );
+ } else {
+ my $bytes = $self->{incr_text};
+ utf8::encode($bytes);
+ my $remaining = substr($bytes, $offset || 0);
+ utf8::decode($remaining);
+ $self->{incr_text} = $remaining;
+ }
$self->{incr_pos} = 0;
$self->{incr_nest} = 0;
$self->{incr_mode} = 0;
2 changes: 1 addition & 1 deletion dev/modules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ PerlOnJava's XSLoader returns an error matching `/loadable object/` which these
For performance-critical modules, PerlOnJava can provide Java implementations:

- `DateTime` - Uses `java.time` APIs
- `JSON::XS` - Falls back to JSON::PP (or could use FASTJSON)
- `JSON::XS` - Falls back to the bundled `JSON::PP`
- `DBI` - Custom Java implementation with JDBC

See [xs_fallback.md](xs_fallback.md) for implementation details.
Expand Down
7 changes: 0 additions & 7 deletions dev/modules/dynamic_loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ The root build configurations that will build all subprojects:
<asm.version>9.7.1</asm.version>
<junit.version>5.11.4</junit.version>
<icu4j.version>76.1</icu4j.version>
<fastjson.version>2.0.54</fastjson.version>
<snakeyaml.version>2.9</snakeyaml.version>
</properties>

Expand All @@ -255,11 +254,6 @@ The root build configurations that will build all subprojects:
<artifactId>icu4j</artifactId>
<version>${icu4j.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.snakeyaml</groupId>
<artifactId>snakeyaml-engine</artifactId>
Expand Down Expand Up @@ -312,7 +306,6 @@ subprojects {
ext {
asmVersion = '9.7.1'
icu4jVersion = '76.1'
fastjsonVersion = '2.0.54'
snakeyamlVersion = '2.9'
junitVersion = '5.9.2'
}
Expand Down
Loading
Loading