From 82fa63f851201f89c98ab884b252e82f60233902 Mon Sep 17 00:00:00 2001 From: dabico Date: Wed, 14 Feb 2024 13:25:47 +0100 Subject: [PATCH 1/5] Add `Quantifier` enum --- .../usi/si/seart/treesitter/Quantifier.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/java/ch/usi/si/seart/treesitter/Quantifier.java diff --git a/src/main/java/ch/usi/si/seart/treesitter/Quantifier.java b/src/main/java/ch/usi/si/seart/treesitter/Quantifier.java new file mode 100644 index 00000000..717c9484 --- /dev/null +++ b/src/main/java/ch/usi/si/seart/treesitter/Quantifier.java @@ -0,0 +1,51 @@ +package ch.usi.si.seart.treesitter; + +/** + * Represents the {@link Capture} quantifier in a {@link Pattern}, + * i.e. the number of nodes that a capture should contain. Within a + * query, a capture can have different quantifiers for each pattern. + * + * @since 1.12.0 + * @author Ozren Dabić + */ +public enum Quantifier { + + /** + * The capture will not match any nodes, + * as said capture is not present in a + * specific pattern. + */ + ZERO, + /** + * The capture will match at most one node. + * Example: + *
{@code
+     * ((_)? @capture)
+     * }
+ */ + ZERO_OR_ONE, + /** + * The capture will match any number of nodes. + * Example: + *
{@code
+     * ((_)* @capture)
+     * }
+ */ + ZERO_OR_MORE, + /** + * The capture will match exactly one node. + * Example: + *
{@code
+     * ((_) @capture)
+     * }
+ */ + ONE, + /** + * The capture will match at least one node. + * Example: + *
{@code
+     * ((_)+ @capture)
+     * }
+ */ + ONE_OR_MORE +} From 4f945010d600bd2e29a740bfff46e7ec70df1bbd Mon Sep 17 00:00:00 2001 From: dabico Date: Wed, 14 Feb 2024 13:34:48 +0100 Subject: [PATCH 2/5] Create native method for retrieving `Quantifier` values Said method currently resides in `Query` due to the ease of which we can check all the preconditions. However, the method is package-private at this time, as I can not decide on where the `Quantifier` retrieval logic should reside. --- lib/ch_usi_si_seart_treesitter_Query.cc | 10 ++++++++++ lib/ch_usi_si_seart_treesitter_Query.h | 8 ++++++++ src/main/java/ch/usi/si/seart/treesitter/Query.java | 12 ++++++++++++ 3 files changed, 30 insertions(+) diff --git a/lib/ch_usi_si_seart_treesitter_Query.cc b/lib/ch_usi_si_seart_treesitter_Query.cc index 893c45c5..28a220d3 100644 --- a/lib/ch_usi_si_seart_treesitter_Query.cc +++ b/lib/ch_usi_si_seart_treesitter_Query.cc @@ -10,3 +10,13 @@ JNIEXPORT void JNICALL Java_ch_usi_si_seart_treesitter_Query_delete( ts_query_delete(query); __clearPointer(env, thisObject); } + +JNIEXPORT jint JNICALL Java_ch_usi_si_seart_treesitter_Query_getQuantifier( + JNIEnv* env, jobject thisObject, jint patternIndex, jint captureIndex) { + TSQuery* query = (TSQuery*)__getPointer(env, thisObject); + return (jint)ts_query_capture_quantifier_for_id( + query, + (uint32_t)patternIndex, + (uint32_t)captureIndex + ); +} diff --git a/lib/ch_usi_si_seart_treesitter_Query.h b/lib/ch_usi_si_seart_treesitter_Query.h index d391e513..ce1d8bd0 100644 --- a/lib/ch_usi_si_seart_treesitter_Query.h +++ b/lib/ch_usi_si_seart_treesitter_Query.h @@ -15,6 +15,14 @@ extern "C" { JNIEXPORT void JNICALL Java_ch_usi_si_seart_treesitter_Query_delete (JNIEnv *, jobject); +/* + * Class: ch_usi_si_seart_treesitter_Query + * Method: getQuantifier + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_ch_usi_si_seart_treesitter_Query_getQuantifier + (JNIEnv *, jobject, jint, jint); + #ifdef __cplusplus } #endif diff --git a/src/main/java/ch/usi/si/seart/treesitter/Query.java b/src/main/java/ch/usi/si/seart/treesitter/Query.java index a5cca927..fd4bb427 100644 --- a/src/main/java/ch/usi/si/seart/treesitter/Query.java +++ b/src/main/java/ch/usi/si/seart/treesitter/Query.java @@ -230,4 +230,16 @@ public String getPattern() { .map(Pattern::toString) .collect(Collectors.joining(" ")); } + + Quantifier getQuantifier(Pattern pattern, Capture capture) { + Objects.requireNonNull(pattern, "Pattern must not be null!"); + Objects.requireNonNull(capture, "Capture must not be null!"); + if (!patterns.contains(pattern)) throw new IllegalArgumentException("Pattern not present in query!"); + if (!captures.contains(capture)) throw new IllegalArgumentException("Capture not present in query!"); + Quantifier[] quantifiers = Quantifier.values(); + int ordinal = getQuantifier(pattern.getIndex(), capture.getIndex()); + return quantifiers[ordinal]; + } + + private native int getQuantifier(int patternIndex, int captureIndex); } From e29e7cdda96bd9101636eb5b1cdc8064227c8f85 Mon Sep 17 00:00:00 2001 From: dabico Date: Wed, 14 Feb 2024 13:45:48 +0100 Subject: [PATCH 3/5] Add instance methods to `Capture` for retrieving `Quantifiers` This includes a variant that returns a quantifier for one query pattern, as well as a variant that returns quantifiers for all query patterns. --- .../ch/usi/si/seart/treesitter/Capture.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/java/ch/usi/si/seart/treesitter/Capture.java b/src/main/java/ch/usi/si/seart/treesitter/Capture.java index d6779b4e..b27ed337 100644 --- a/src/main/java/ch/usi/si/seart/treesitter/Capture.java +++ b/src/main/java/ch/usi/si/seart/treesitter/Capture.java @@ -8,7 +8,9 @@ import lombok.experimental.NonFinal; import org.jetbrains.annotations.NotNull; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * Represents the named capture of a {@link Query}. Captures are used @@ -51,6 +53,33 @@ public class Capture { */ public native void disable(); + /** + * Get the capture quantifier for a given query {@link Pattern}. + * + * @param pattern the query pattern + * @return the quantifier + * @throws NullPointerException if pattern is {@code null} + * @throws IllegalArgumentException if the pattern is not present in the query + * @since 1.12.0 + */ + public Quantifier getQuantifier(Pattern pattern) { + return query.getQuantifier(pattern, this); + } + + /** + * Get the capture quantifiers for all {@link Query} patterns. + * The order of the quantifiers in the returned list corresponds + * to the {@link Pattern} order in the query. + * + * @return the quantifiers + * @since 1.12.0 + */ + public List getQuantifiers() { + return query.getPatterns().stream() + .map(this::getQuantifier) + .collect(Collectors.toUnmodifiableList()); + } + @Override @Generated public boolean equals(Object o) { From 7ebc02fb933158cdba2b1ca68e62c91041456f5b Mon Sep 17 00:00:00 2001 From: dabico Date: Wed, 14 Feb 2024 15:01:12 +0100 Subject: [PATCH 4/5] Add missing `@NotNull` annotations to method parameters --- src/main/java/ch/usi/si/seart/treesitter/Capture.java | 2 +- src/main/java/ch/usi/si/seart/treesitter/Query.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/usi/si/seart/treesitter/Capture.java b/src/main/java/ch/usi/si/seart/treesitter/Capture.java index b27ed337..29d58375 100644 --- a/src/main/java/ch/usi/si/seart/treesitter/Capture.java +++ b/src/main/java/ch/usi/si/seart/treesitter/Capture.java @@ -62,7 +62,7 @@ public class Capture { * @throws IllegalArgumentException if the pattern is not present in the query * @since 1.12.0 */ - public Quantifier getQuantifier(Pattern pattern) { + public Quantifier getQuantifier(@NotNull Pattern pattern) { return query.getQuantifier(pattern, this); } diff --git a/src/main/java/ch/usi/si/seart/treesitter/Query.java b/src/main/java/ch/usi/si/seart/treesitter/Query.java index fd4bb427..8427c43a 100644 --- a/src/main/java/ch/usi/si/seart/treesitter/Query.java +++ b/src/main/java/ch/usi/si/seart/treesitter/Query.java @@ -231,7 +231,7 @@ public String getPattern() { .collect(Collectors.joining(" ")); } - Quantifier getQuantifier(Pattern pattern, Capture capture) { + Quantifier getQuantifier(@NotNull Pattern pattern, @NotNull Capture capture) { Objects.requireNonNull(pattern, "Pattern must not be null!"); Objects.requireNonNull(capture, "Capture must not be null!"); if (!patterns.contains(pattern)) throw new IllegalArgumentException("Pattern not present in query!"); From 2ef629ddd1a9029b8dbd1a4dfd275b042bf137ac Mon Sep 17 00:00:00 2001 From: dabico Date: Wed, 14 Feb 2024 15:02:56 +0100 Subject: [PATCH 5/5] Add basic test cases for `Quantifier`-related APIs --- .../si/seart/treesitter/QuantifierTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/test/java/ch/usi/si/seart/treesitter/QuantifierTest.java diff --git a/src/test/java/ch/usi/si/seart/treesitter/QuantifierTest.java b/src/test/java/ch/usi/si/seart/treesitter/QuantifierTest.java new file mode 100644 index 00000000..bf054f6a --- /dev/null +++ b/src/test/java/ch/usi/si/seart/treesitter/QuantifierTest.java @@ -0,0 +1,84 @@ +package ch.usi.si.seart.treesitter; + +import lombok.Cleanup; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +class QuantifierTest extends BaseTest { + + private static final Language language = Language.JAVA; + + private static final Map symbols = Map.of( + Quantifier.ONE, "", + Quantifier.ONE_OR_MORE, "+", + Quantifier.ZERO_OR_ONE, "?", + Quantifier.ZERO_OR_MORE, "*" + ); + + private static final class QuantifierArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return symbols.entrySet().stream() + .map(entry -> { + Quantifier quantifier = entry.getKey(); + String symbol = entry.getValue(); + String pattern = "((_)" + symbol + " @capture)"; + Query query = Query.getFor(language, pattern); + List captures = query.getCaptures(); + Capture capture = captures.stream().findFirst().orElseThrow(); + return Arguments.of(quantifier, capture, query); + }); + } + } + + @ParameterizedTest(name = "[{index}] {0}") + @ArgumentsSource(QuantifierArgumentsProvider.class) + void testGetQuantifiers(Quantifier expected, Capture capture, Query query) { + List quantifiers = capture.getQuantifiers(); + Assertions.assertNotNull(quantifiers); + Assertions.assertFalse(quantifiers.isEmpty()); + Assertions.assertEquals(1, quantifiers.size()); + Quantifier actual = quantifiers.stream() + .findFirst() + .orElseGet(Assertions::fail); + Assertions.assertEquals(expected, actual); + query.close(); + } + + @Test + void testGetQuantifier() { + @Cleanup Query query = Query.builder() + .language(language) + .pattern("((_) @capture)") + .pattern("(identifier)") + .build(); + List captures = query.getCaptures(); + List patterns = query.getPatterns(); + Capture capture = captures.stream().findFirst().orElseThrow(); + Pattern pattern = patterns.stream().skip(1).findFirst().orElseThrow(); + Quantifier quantifier = capture.getQuantifier(pattern); + Assertions.assertEquals(Quantifier.ZERO, quantifier); + } + + @Test + void testGetQuantifierThrows() { + @Cleanup Query original = Query.getFor(language, "((_) @capture)"); + @Cleanup Query copy = Query.getFor(language, "((_) @capture)"); + List captures = original.getCaptures(); + Capture capture = captures.stream().findFirst().orElseThrow(); + List patterns = copy.getPatterns(); + Pattern pattern = patterns.stream().findFirst().orElseThrow(); + Assertions.assertThrows(NullPointerException.class, () -> capture.getQuantifier(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> capture.getQuantifier(pattern)); + } +}