Skip to content

Commit 7b29a00

Browse files
committed
experimental Query / QueryCapture separation
1 parent c86f5ae commit 7b29a00

File tree

4 files changed

+310
-71
lines changed

4 files changed

+310
-71
lines changed

src/main/java/io/github/treesitter/jtreesitter/Query.java

Lines changed: 35 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@
88
import java.lang.foreign.SegmentAllocator;
99
import java.util.*;
1010
import java.util.function.BiPredicate;
11-
import java.util.function.Consumer;
1211
import java.util.function.Supplier;
1312
import java.util.regex.Pattern;
1413
import java.util.regex.PatternSyntaxException;
1514
import java.util.stream.Stream;
16-
import java.util.stream.StreamSupport;
15+
1716
import org.jspecify.annotations.NullMarked;
1817
import org.jspecify.annotations.Nullable;
1918

@@ -26,7 +25,7 @@
2625
@NullMarked
2726
public final class Query implements AutoCloseable {
2827
private final MemorySegment query;
29-
private final MemorySegment cursor;
28+
private final QueryCursorOptions cursorOptions = new QueryCursorOptions();
3029
private final Arena arena;
3130
private final Language language;
3231
private final String source;
@@ -86,7 +85,6 @@ public final class Query implements AutoCloseable {
8685
this.language = language;
8786
this.source = source;
8887
this.query = query.reinterpret(arena, TreeSitter::ts_query_delete);
89-
cursor = ts_query_cursor_new().reinterpret(arena, TreeSitter::ts_query_cursor_delete);
9088

9189
var captureCount = ts_query_capture_count(this.query);
9290
captureNames = new ArrayList<>(captureCount);
@@ -263,6 +261,10 @@ private static boolean invalidPredicateChar(char c) {
263261
return !(Character.isLetterOrDigit(c) || c == '_' || c == '-' || c == '.' || c == '?' || c == '!');
264262
}
265263

264+
MemorySegment self(){
265+
return query;
266+
}
267+
266268
/** Get the number of patterns in the query. */
267269
public @Unsigned int getPatternCount() {
268270
return ts_query_pattern_count(query);
@@ -273,13 +275,21 @@ private static boolean invalidPredicateChar(char c) {
273275
return ts_query_capture_count(query);
274276
}
275277

278+
public List<List<QueryPredicate>> getPredicates(){
279+
return predicates.stream().map(Collections::unmodifiableList).toList();
280+
}
281+
282+
public List<String> getCaptureNames(){
283+
return Collections.unmodifiableList(captureNames);
284+
}
285+
276286
/**
277287
* Get the maximum number of in-progress matches.
278288
*
279289
* @apiNote Defaults to {@code -1} (unlimited).
280290
*/
281291
public @Unsigned int getMatchLimit() {
282-
return ts_query_cursor_match_limit(cursor);
292+
return cursorOptions.getMatchLimit();
283293
}
284294

285295
/**
@@ -288,10 +298,7 @@ private static boolean invalidPredicateChar(char c) {
288298
* @throws IllegalArgumentException If {@code matchLimit == 0}.
289299
*/
290300
public Query setMatchLimit(@Unsigned int matchLimit) throws IllegalArgumentException {
291-
if (matchLimit == 0) {
292-
throw new IllegalArgumentException("The match limit cannot equal 0");
293-
}
294-
ts_query_cursor_set_match_limit(cursor, matchLimit);
301+
cursorOptions.setMatchLimit(matchLimit);
295302
return this;
296303
}
297304

@@ -303,7 +310,7 @@ public Query setMatchLimit(@Unsigned int matchLimit) throws IllegalArgumentExcep
303310
* @since 0.23.1
304311
*/
305312
public @Unsigned long getTimeoutMicros() {
306-
return ts_query_cursor_timeout_micros(cursor);
313+
return cursorOptions.getTimeoutMicros();
307314
}
308315

309316
/**
@@ -313,7 +320,7 @@ public Query setMatchLimit(@Unsigned int matchLimit) throws IllegalArgumentExcep
313320
* @since 0.23.1
314321
*/
315322
public Query setTimeoutMicros(@Unsigned long timeoutMicros) {
316-
ts_query_cursor_set_timeout_micros(cursor, timeoutMicros);
323+
cursorOptions.setTimeoutMicros(timeoutMicros);
317324
return this;
318325
}
319326

@@ -324,32 +331,24 @@ public Query setTimeoutMicros(@Unsigned long timeoutMicros) {
324331
* <br>Note that if a pattern includes many children, then they will still be checked.
325332
*/
326333
public Query setMaxStartDepth(@Unsigned int maxStartDepth) {
327-
ts_query_cursor_set_max_start_depth(cursor, maxStartDepth);
334+
cursorOptions.setMaxStartDepth(maxStartDepth);
328335
return this;
329336
}
330337

331338
/** Set the range of bytes in which the query will be executed. */
332339
public Query setByteRange(@Unsigned int startByte, @Unsigned int endByte) {
333-
ts_query_cursor_set_byte_range(cursor, startByte, endByte);
340+
cursorOptions.setStartByte(startByte);
341+
cursorOptions.setEndByte(endByte);
334342
return this;
335343
}
336344

337345
/** Set the range of points in which the query will be executed. */
338346
public Query setPointRange(Point startPoint, Point endPoint) {
339-
try (var alloc = Arena.ofConfined()) {
340-
MemorySegment start = startPoint.into(alloc), end = endPoint.into(alloc);
341-
ts_query_cursor_set_point_range(cursor, start, end);
342-
}
347+
cursorOptions.setStartPoint(startPoint);
348+
cursorOptions.setEndPoint(endPoint);
343349
return this;
344350
}
345351

346-
/**
347-
* Check if the query exceeded its maximum number of
348-
* in-progress matches during its last execution.
349-
*/
350-
public boolean didExceedMatchLimit() {
351-
return ts_query_cursor_did_exceed_match_limit(cursor);
352-
}
353352

354353
/**
355354
* Disable a certain pattern within a query.
@@ -478,6 +477,16 @@ public Map<String, Optional<String>> getPatternAssertions(@Unsigned int index, b
478477
return Collections.unmodifiableMap(assertions.get(index));
479478
}
480479

480+
481+
public QueryCursor execute(Node node){
482+
return new QueryCursor(this, node, cursorOptions);
483+
}
484+
485+
public QueryCursor execute(Node node, QueryCursorOptions options){
486+
return new QueryCursor(this, node, options);
487+
}
488+
489+
481490
/**
482491
* Iterate over all the matches in the order that they were found. The lifetime of the native memory of the returned
483492
* matches is bound to the lifetime of this query object.
@@ -521,10 +530,9 @@ public Stream<QueryMatch> findMatches(Node node, @Nullable BiPredicate<QueryPred
521530
* @param predicate A function that handles custom predicates.
522531
*/
523532
public Stream<QueryMatch> findMatches(Node node, SegmentAllocator allocator, @Nullable BiPredicate<QueryPredicate, QueryMatch> predicate) {
524-
try (var alloc = Arena.ofConfined()) {
525-
ts_query_cursor_exec(cursor, query, node.copy(alloc));
533+
try(QueryCursor cursor = this.execute(node)){
534+
return cursor.stream(allocator, predicate);
526535
}
527-
return StreamSupport.stream(new MatchesIterator(node.getTree(), predicate, allocator), false);
528536
}
529537

530538
@Override
@@ -537,12 +545,6 @@ public String toString() {
537545
return "Query{language=%s, source=%s}".formatted(language, source);
538546
}
539547

540-
private boolean matches(@Nullable BiPredicate<QueryPredicate, QueryMatch> predicate, QueryMatch match) {
541-
return predicates.get(match.patternIndex()).stream().allMatch(p -> {
542-
if (p.getClass() != QueryPredicate.class) return p.test(match);
543-
return predicate == null || predicate.test(p, match);
544-
});
545-
}
546548

547549
private void checkIndex(@Unsigned int index) throws IndexOutOfBoundsException {
548550
if (Integer.compareUnsigned(index, getPatternCount()) >= 0) {
@@ -551,40 +553,5 @@ private void checkIndex(@Unsigned int index) throws IndexOutOfBoundsException {
551553
}
552554
}
553555

554-
private final class MatchesIterator extends Spliterators.AbstractSpliterator<QueryMatch> {
555-
private final @Nullable BiPredicate<QueryPredicate, QueryMatch> predicate;
556-
private final Tree tree;
557-
private final SegmentAllocator allocator;
558556

559-
public MatchesIterator(Tree tree, @Nullable BiPredicate<QueryPredicate, QueryMatch> predicate, SegmentAllocator allocator) {
560-
super(Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL);
561-
this.predicate = predicate;
562-
this.tree = tree;
563-
this.allocator = allocator;
564-
}
565-
566-
@Override
567-
public boolean tryAdvance(Consumer<? super QueryMatch> action) {
568-
var hasNoText = tree.getText() == null;
569-
MemorySegment match = arena.allocate(TSQueryMatch.layout());
570-
while (ts_query_cursor_next_match(cursor, match)) {
571-
var count = Short.toUnsignedInt(TSQueryMatch.capture_count(match));
572-
var matchCaptures = TSQueryMatch.captures(match);
573-
var captureList = new ArrayList<QueryCapture>(count);
574-
for (int i = 0; i < count; ++i) {
575-
var capture = TSQueryCapture.asSlice(matchCaptures, i);
576-
var name = captureNames.get(TSQueryCapture.index(capture));
577-
var node = TSNode.allocate(allocator).copyFrom(TSQueryCapture.node(capture));
578-
captureList.add(new QueryCapture(name, new Node(node, tree)));
579-
}
580-
var patternIndex = TSQueryMatch.pattern_index(match);
581-
var result = new QueryMatch(patternIndex, captureList);
582-
if (hasNoText || matches(predicate, result)) {
583-
action.accept(result);
584-
return true;
585-
}
586-
}
587-
return false;
588-
}
589-
}
590557
}

0 commit comments

Comments
 (0)