Skip to content

Commit 5fd2652

Browse files
committed
Modernize baseline to Java 21, adopt Java 21 features, remove NotCharacterPredicate, and add CHANGELOG
- Bump compiler release to 21 in pom.xml, update GitHub Actions CI matrix to Java 21, and remove legacy Travis CI section. - Adopt Java 21 switch expressions in Tuple2..Tuple9 and pattern matching for instanceof across tuples, character predicates, and grammar visitors. - Leverage Sequenced Collections (getFirst, getLast, removeLast) across CharacterPredicate, Analyzer, and GrammarDefinition. - Remove deprecated NotCharacterPredicate alias and associated test in favor of NotCharPredicate. - Add CHANGELOG.md covering highlights for 2.5.0 and all previous x.y.0 releases.
1 parent 6831368 commit 5fd2652

30 files changed

Lines changed: 228 additions & 288 deletions

‎.github/workflows/maven.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ jobs:
1919
runs-on: ubuntu-latest
2020
strategy:
2121
matrix:
22-
java: [ '11', '11.0.3', '14', '14.0.1' ]
23-
name: Java ${{ matrix.Java }}
22+
java: [ '21' ]
23+
name: Java ${{ matrix.java }}
2424
steps:
2525
- uses: actions/checkout@v7
2626
- uses: actions/setup-java@v5
2727
with:
2828
distribution: 'zulu'
29-
java-version: ${{ matrix.Java }}
29+
java-version: ${{ matrix.java }}
3030
- name: Build with Maven
3131
run: mvn -B package --file pom.xml

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
- Deliver code, tests, and documentation directly with zero introductory fluff or pleasantries.
66
- Strictly adhere to 2 spaces for indentation.
77
- Preserve zero external runtime dependencies for `petitparser-core` (rely exclusively on the Java standard library).
8-
- Target Java 11 bytecode compatibility (`<maven.compiler.release>11</maven.compiler.release>`).
8+
- Target Java 21 bytecode compatibility (`<maven.compiler.release>21</maven.compiler.release>`).
99

1010
---
1111

@@ -28,7 +28,7 @@
2828

2929
### Type System & Generics Guidelines
3030

31-
- **Dual-Nature Tuples**: Java 11 lacks language-level tuples/records. Implement `Tuple2<T1, T2>` through `Tuple9` extending `AbstractList<Object>`.
31+
- **Dual-Nature Tuples**: Implement `Tuple2<T1, T2>` through `Tuple9` extending `AbstractList<Object>`.
3232
- Enables strongly-typed accessors (`tuple.first()`, `tuple.second()`, etc.) without losing compatibility with APIs expecting `List<Object>`.
3333
- Seamlessly passes equality against standard lists (`tuple.equals(Arrays.asList(...)) == true`).
3434
- **Direct-Mapped Sequences**: Provide fluent `.then()` sequence chaining returning `SequenceParser2` through `SequenceParser9`, offering direct multi-argument mapping (`.map((a, b) -> ...)` via `BiFunction`, `Function3`, etc.) so users rarely need to inspect the underlying `Tuple`.

‎CHANGELOG.md‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Changelog
2+
3+
## 2.5.0
4+
5+
- Upgraded baseline to Java 21 LTS (`<maven.compiler.release>21</maven.compiler.release>`) and modernized GitHub Actions CI.
6+
- Added fluent `.then(...)` sequence chaining with strongly-typed `SequenceParser2` through `SequenceParser9`.
7+
- Added multi-arity `.map(...)` callbacks (`BiFunction`, `Function3` through `Function9`) for type-safe sequence unpacking.
8+
- Introduced dual-nature tuples (`Tuple2` through `Tuple9`) extending `AbstractList<Object>` with typed accessors and standard list compatibility.
9+
- Added type-safe grammar productions with `Production<T>` keys and `GrammarParser` typed lookup.
10+
- Modernized `ExpressionBuilder<T>` with full generic typing, builder-level primitive helpers, loopback access, optional operator groups, `foldLeft`/`foldRight`, and typed action callbacks.
11+
- Added zero-list repeating character parsers (`starString()`, `plusString()`, `timesString()`, `repeatString()`) with SIMD string matching.
12+
- Added full Unicode code point and surrogate pair parsing with `UnicodeCharacterParser`.
13+
- Added `CharacterPredicate` AST hierarchy with range merging and automatic lookup table optimization.
14+
- Added indentation-sensitive parsing engine (`Indent`) for indentation-aware grammars (`Indent.during`, `equal`, `greater`, etc.).
15+
- Added grammar `Analyzer` for computing reachability, cycles, nullability, FIRST sets, and FOLLOW sets.
16+
- Added grammar `Linter` with 13 automated rules detecting anti-patterns.
17+
- Added grammar `Optimizer` rule engine with choice flattening, character repeater rewriting, and delegate removal.
18+
- Added `Progress` visual execution debugger and `ProgressFrame` for inspecting parser backtracking and execution graphs.
19+
- Added lazy stream matching (`matchesAsStream()`) and offset-based searching on `Parser`.
20+
- Added zero-allocation fast parse (`fastParseOn`) across all parsers.
21+
- Added `skip(...)` and delimiter combinators, `where(...)` filtering combinator, `label(...)` parser, and `position()` / `newline()` primitives.
22+
- Comprehensive documentation rewrite and examples modernization in `README.md` and `ExamplesTest.java`.
23+
- Near 100% test coverage across all reactor modules.
24+
25+
## 2.4.0
26+
27+
- Added Java Platform Module System (JPMS) support with `module-info.java`.
28+
- Added configurable failure join strategy on `ChoiceParser` (`FailureJoiner`).
29+
- Improved character parser failure messages and XML grammar performance.
30+
- Migrated continuous integration pipelines to GitHub Actions.
31+
32+
## 2.3.0
33+
34+
- Introduced zero-allocation fast parse mode (`fastParseOn`).
35+
- Added parenthesis expression support to `ExpressionBuilder`.
36+
- Optimized XML and JSON grammars to take advantage of fast parsing.
37+
- Improved Java 11 (LTS) compatibility.
38+
39+
## 2.2.0
40+
41+
- Preserved parser generic types during `copy()`.
42+
- Cleaned up build configuration for Maven Central publication.
43+
- Code cleanups, static analysis fixes, and dependency upgrades.
44+
45+
## 2.1.0
46+
47+
- Introduced `GrammarDefinition` and `GrammarParser` following the Dart architecture.
48+
- Migrated XML and Smalltalk example grammars to grammar definitions.
49+
- Modernized internal collections, replacing legacy synchronized classes.
50+
51+
## 2.0.0
52+
53+
- Major rewrite modernizing PetitParser for Java 8+ with lambda expressions.
54+
- Package structure reorganization into `context`, `actions`, `combinators`, `primitive`, and `repeating`.
55+
- Rebuilt character predicate hierarchy with optimized range checks and bitmask lookups.
56+
- Added expression builder and composite grammar tools.

‎TASKS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PetitParser Java Parity Tasks
22

3-
Roadmap to bring `petitparser-core` to feature parity with the canonical Dart implementation while maintaining strict backward compatibility with existing code and adopting modern Java best practices (Java 11 baseline, standard functional interfaces, and stream idioms).
3+
Roadmap to bring `petitparser-core` to feature parity with the canonical Dart implementation while maintaining strict backward compatibility with existing code and adopting modern Java best practices (Java 21 baseline, standard functional interfaces, and stream idioms).
44

55
---
66

@@ -135,7 +135,7 @@ Roadmap to bring `petitparser-core` to feature parity with the canonical Dart im
135135
- [x] **Task 3.2: Multi-Arity Functional Interfaces**
136136
- Create standard functional interfaces in `org.petitparser.utils.functions`:
137137
- `Function3<T1, T2, T3, R>`, `Function4<T1, T2, T3, T4, R>`, ... `Function9<...>`.
138-
- Ensure compatibility with Java 11 `BiFunction` for arity-2 sequences.
138+
- Ensure compatibility with Java `BiFunction` for arity-2 sequences.
139139

140140
- [x] **Task 3.3: Typed Sequence Combinators (`SequenceParser2` to `SequenceParser9`)**
141141
- Implement `SequenceParser2<T1, T2>` through `SequenceParser9<...>` implementing `SequentialParser`.

‎petitparser-core/src/main/java/org/petitparser/parser/Parser.java‎

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -883,9 +883,8 @@ public <A, B> Parser map(Function<A, B> function) {
883883
public <T1, T2, R> Parser map(BiFunction<? super T1, ? super T2, ? extends R> function) {
884884
Objects.requireNonNull(function, "Undefined map function");
885885
return map(input -> {
886-
if (input instanceof Tuple2) {
887-
Tuple2<T1, T2> tuple = (Tuple2<T1, T2>) input;
888-
return function.apply(tuple.first(), tuple.second());
886+
if (input instanceof Tuple2<?, ?> tuple) {
887+
return function.apply((T1) tuple.first(), (T2) tuple.second());
889888
}
890889
List<?> list = (List<?>) input;
891890
return function.apply((T1) list.get(0), (T2) list.get(1));
@@ -899,9 +898,9 @@ public <T1, T2, R> Parser map(BiFunction<? super T1, ? super T2, ? extends R> fu
899898
public <T1, T2, T3, R> Parser map(Function3<? super T1, ? super T2, ? super T3, ? extends R> function) {
900899
Objects.requireNonNull(function, "Undefined map function");
901900
return map(input -> {
902-
if (input instanceof Tuple3) {
903-
Tuple3<T1, T2, T3> tuple = (Tuple3<T1, T2, T3>) input;
904-
return function.apply(tuple.first(), tuple.second(), tuple.third());
901+
if (input instanceof Tuple3<?, ?, ?> tuple) {
902+
return function.apply(
903+
(T1) tuple.first(), (T2) tuple.second(), (T3) tuple.third());
905904
}
906905
List<?> list = (List<?>) input;
907906
return function.apply((T1) list.get(0), (T2) list.get(1), (T3) list.get(2));
@@ -916,9 +915,9 @@ public <T1, T2, T3, T4, R> Parser map(
916915
Function4<? super T1, ? super T2, ? super T3, ? super T4, ? extends R> function) {
917916
Objects.requireNonNull(function, "Undefined map function");
918917
return map(input -> {
919-
if (input instanceof Tuple4) {
920-
Tuple4<T1, T2, T3, T4> tuple = (Tuple4<T1, T2, T3, T4>) input;
921-
return function.apply(tuple.first(), tuple.second(), tuple.third(), tuple.fourth());
918+
if (input instanceof Tuple4<?, ?, ?, ?> tuple) {
919+
return function.apply(
920+
(T1) tuple.first(), (T2) tuple.second(), (T3) tuple.third(), (T4) tuple.fourth());
922921
}
923922
List<?> list = (List<?>) input;
924923
return function.apply((T1) list.get(0), (T2) list.get(1), (T3) list.get(2), (T4) list.get(3));
@@ -933,10 +932,10 @@ public <T1, T2, T3, T4, T5, R> Parser map(
933932
Function5<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? extends R> function) {
934933
Objects.requireNonNull(function, "Undefined map function");
935934
return map(input -> {
936-
if (input instanceof Tuple5) {
937-
Tuple5<T1, T2, T3, T4, T5> tuple = (Tuple5<T1, T2, T3, T4, T5>) input;
935+
if (input instanceof Tuple5<?, ?, ?, ?, ?> tuple) {
938936
return function.apply(
939-
tuple.first(), tuple.second(), tuple.third(), tuple.fourth(), tuple.fifth());
937+
(T1) tuple.first(), (T2) tuple.second(), (T3) tuple.third(), (T4) tuple.fourth(),
938+
(T5) tuple.fifth());
940939
}
941940
List<?> list = (List<?>) input;
942941
return function.apply(
@@ -953,11 +952,10 @@ public <T1, T2, T3, T4, T5, T6, R> Parser map(
953952
Function6<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? super T6, ? extends R> function) {
954953
Objects.requireNonNull(function, "Undefined map function");
955954
return map(input -> {
956-
if (input instanceof Tuple6) {
957-
Tuple6<T1, T2, T3, T4, T5, T6> tuple = (Tuple6<T1, T2, T3, T4, T5, T6>) input;
955+
if (input instanceof Tuple6<?, ?, ?, ?, ?, ?> tuple) {
958956
return function.apply(
959-
tuple.first(), tuple.second(), tuple.third(), tuple.fourth(), tuple.fifth(),
960-
tuple.sixth());
957+
(T1) tuple.first(), (T2) tuple.second(), (T3) tuple.third(), (T4) tuple.fourth(),
958+
(T5) tuple.fifth(), (T6) tuple.sixth());
961959
}
962960
List<?> list = (List<?>) input;
963961
return function.apply(
@@ -974,11 +972,10 @@ public <T1, T2, T3, T4, T5, T6, T7, R> Parser map(
974972
Function7<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? super T6, ? super T7, ? extends R> function) {
975973
Objects.requireNonNull(function, "Undefined map function");
976974
return map(input -> {
977-
if (input instanceof Tuple7) {
978-
Tuple7<T1, T2, T3, T4, T5, T6, T7> tuple = (Tuple7<T1, T2, T3, T4, T5, T6, T7>) input;
975+
if (input instanceof Tuple7<?, ?, ?, ?, ?, ?, ?> tuple) {
979976
return function.apply(
980-
tuple.first(), tuple.second(), tuple.third(), tuple.fourth(), tuple.fifth(),
981-
tuple.sixth(), tuple.seventh());
977+
(T1) tuple.first(), (T2) tuple.second(), (T3) tuple.third(), (T4) tuple.fourth(),
978+
(T5) tuple.fifth(), (T6) tuple.sixth(), (T7) tuple.seventh());
982979
}
983980
List<?> list = (List<?>) input;
984981
return function.apply(
@@ -995,11 +992,10 @@ public <T1, T2, T3, T4, T5, T6, T7, T8, R> Parser map(
995992
Function8<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? super T6, ? super T7, ? super T8, ? extends R> function) {
996993
Objects.requireNonNull(function, "Undefined map function");
997994
return map(input -> {
998-
if (input instanceof Tuple8) {
999-
Tuple8<T1, T2, T3, T4, T5, T6, T7, T8> tuple = (Tuple8<T1, T2, T3, T4, T5, T6, T7, T8>) input;
995+
if (input instanceof Tuple8<?, ?, ?, ?, ?, ?, ?, ?> tuple) {
1000996
return function.apply(
1001-
tuple.first(), tuple.second(), tuple.third(), tuple.fourth(), tuple.fifth(),
1002-
tuple.sixth(), tuple.seventh(), tuple.eighth());
997+
(T1) tuple.first(), (T2) tuple.second(), (T3) tuple.third(), (T4) tuple.fourth(),
998+
(T5) tuple.fifth(), (T6) tuple.sixth(), (T7) tuple.seventh(), (T8) tuple.eighth());
1003999
}
10041000
List<?> list = (List<?>) input;
10051001
return function.apply(
@@ -1016,12 +1012,11 @@ public <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Parser map(
10161012
Function9<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? super T6, ? super T7, ? super T8, ? super T9, ? extends R> function) {
10171013
Objects.requireNonNull(function, "Undefined map function");
10181014
return map(input -> {
1019-
if (input instanceof Tuple9) {
1020-
Tuple9<T1, T2, T3, T4, T5, T6, T7, T8, T9> tuple =
1021-
(Tuple9<T1, T2, T3, T4, T5, T6, T7, T8, T9>) input;
1015+
if (input instanceof Tuple9<?, ?, ?, ?, ?, ?, ?, ?, ?> tuple) {
10221016
return function.apply(
1023-
tuple.first(), tuple.second(), tuple.third(), tuple.fourth(), tuple.fifth(),
1024-
tuple.sixth(), tuple.seventh(), tuple.eighth(), tuple.ninth());
1017+
(T1) tuple.first(), (T2) tuple.second(), (T3) tuple.third(), (T4) tuple.fourth(),
1018+
(T5) tuple.fifth(), (T6) tuple.sixth(), (T7) tuple.seventh(), (T8) tuple.eighth(),
1019+
(T9) tuple.ninth());
10251020
}
10261021
List<?> list = (List<?>) input;
10271022
return function.apply(

‎petitparser-core/src/main/java/org/petitparser/parser/actions/PermuteParser.java‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import org.petitparser.context.Result;
55
import org.petitparser.parser.Parser;
66
import org.petitparser.parser.combinators.DelegateParser;
7-
import org.petitparser.utils.tuples.Tuple;
8-
97
import java.util.ArrayList;
108
import java.util.Arrays;
119
import java.util.Collections;

‎petitparser-core/src/main/java/org/petitparser/parser/primitive/CharacterPredicate.java‎

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ static CharacterPredicate optimizedRanges(
202202
if (mergedRanges.isEmpty()) {
203203
mergedRanges.add(thisRange);
204204
} else {
205-
RangeCharPredicate lastRange = mergedRanges.get(mergedRanges.size() - 1);
205+
RangeCharPredicate lastRange = mergedRanges.getLast();
206206
if ((long) lastRange.getStop() + 1 >= thisRange.getStart()) {
207207
RangeCharPredicate merged = new RangeCharPredicate(
208208
lastRange.getStart(),
@@ -218,7 +218,7 @@ static CharacterPredicate optimizedRanges(
218218
if (mergedRanges.isEmpty()) {
219219
return ConstantCharPredicate.none();
220220
} else if (mergedRanges.size() == 1) {
221-
RangeCharPredicate range = mergedRanges.get(0);
221+
RangeCharPredicate range = mergedRanges.getFirst();
222222
if (range.getStart() <= 0 &&
223223
range.getStop() >= (unicode ? 0x10ffff : 0xffff)) {
224224
return ConstantCharPredicate.any();
@@ -228,8 +228,8 @@ static CharacterPredicate optimizedRanges(
228228
return range;
229229
}
230230
} else {
231-
int lookupBytes = (mergedRanges.get(mergedRanges.size() - 1).getStop()
232-
- mergedRanges.get(0).getStart() + 32) >> 3;
231+
int lookupBytes = (mergedRanges.getLast().getStop()
232+
- mergedRanges.getFirst().getStart() + 32) >> 3;
233233
int rangesBytes = mergedRanges.size() * 8;
234234
if (lookupBytes > 1024 && rangesBytes < (lookupBytes >> 3)) {
235235
return RangesCharPredicate.fromRanges(mergedRanges);
@@ -282,17 +282,4 @@ default CharacterPredicate not() {
282282
default boolean isEqualTo(CharacterPredicate other) {
283283
return equals(other);
284284
}
285-
286-
/**
287-
* Backward compatibility alias for {@link NotCharPredicate}.
288-
*
289-
* @deprecated Use {@link NotCharPredicate} or {@link CharacterPredicate#not()} instead.
290-
*/
291-
@Deprecated(since = "2.5.0")
292-
class NotCharacterPredicate extends NotCharPredicate {
293-
@Deprecated(since = "2.5.0")
294-
public NotCharacterPredicate(CharacterPredicate predicate) {
295-
super(predicate);
296-
}
297-
}
298285
}

‎petitparser-core/src/main/java/org/petitparser/parser/primitive/LookupCharPredicate.java‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,16 @@ public boolean test(int value) {
7777

7878
@Override
7979
public boolean isEqualTo(CharacterPredicate other) {
80-
return other instanceof LookupCharPredicate &&
81-
start == ((LookupCharPredicate) other).start &&
82-
stop == ((LookupCharPredicate) other).stop &&
83-
Arrays.equals(bits, ((LookupCharPredicate) other).bits);
80+
return other instanceof LookupCharPredicate that &&
81+
start == that.start &&
82+
stop == that.stop &&
83+
Arrays.equals(bits, that.bits);
8484
}
8585

8686
@Override
8787
public boolean equals(Object other) {
88-
return other instanceof CharacterPredicate &&
89-
isEqualTo((CharacterPredicate) other);
88+
return other instanceof CharacterPredicate that &&
89+
isEqualTo(that);
9090
}
9191

9292
@Override

‎petitparser-core/src/main/java/org/petitparser/parser/primitive/NotCharPredicate.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ public CharacterPredicate not() {
3434

3535
@Override
3636
public boolean isEqualTo(CharacterPredicate other) {
37-
return other instanceof NotCharPredicate &&
38-
predicate.isEqualTo(((NotCharPredicate) other).predicate);
37+
return other instanceof NotCharPredicate that &&
38+
predicate.isEqualTo(that.predicate);
3939
}
4040

4141
@Override
4242
public boolean equals(Object other) {
43-
return other instanceof CharacterPredicate &&
44-
isEqualTo((CharacterPredicate) other);
43+
return other instanceof CharacterPredicate that &&
44+
isEqualTo(that);
4545
}
4646

4747
@Override

‎petitparser-core/src/main/java/org/petitparser/parser/primitive/RangeCharPredicate.java‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,14 @@ public boolean test(int value) {
5151

5252
@Override
5353
public boolean isEqualTo(CharacterPredicate other) {
54-
return other instanceof RangeCharPredicate &&
55-
start == ((RangeCharPredicate) other).start &&
56-
stop == ((RangeCharPredicate) other).stop;
54+
return other instanceof RangeCharPredicate that &&
55+
start == that.start &&
56+
stop == that.stop;
5757
}
5858

5959
@Override
6060
public boolean equals(Object other) {
61-
return other instanceof CharacterPredicate &&
62-
isEqualTo((CharacterPredicate) other);
61+
return other instanceof CharacterPredicate that && isEqualTo(that);
6362
}
6463

6564
@Override

0 commit comments

Comments
 (0)