From 07a8052953f2271cccbcb18855c244974c9e2369 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 12 Nov 2025 19:16:08 +0100 Subject: [PATCH 1/2] fix(query): fix multiple predicates being parsed incorrectly --- .../io/github/treesitter/jtreesitter/Query.java | 3 ++- .../treesitter/jtreesitter/QueryCursorTest.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/treesitter/jtreesitter/Query.java b/src/main/java/io/github/treesitter/jtreesitter/Query.java index 1a2c33f..b54366e 100644 --- a/src/main/java/io/github/treesitter/jtreesitter/Query.java +++ b/src/main/java/io/github/treesitter/jtreesitter/Query.java @@ -149,7 +149,8 @@ private void handlePredicates(String source, MemorySegment query, @Unsigned int if ((steps = count.get(C_INT, 0)) == 0) continue; int offset = ts_query_start_byte_for_pattern(query, i); long row = source.chars().limit(offset).filter(c -> c == '\n').count(); - for (long j = 0, nargs = 0; j < steps; ++j) { + for (long j = 0; j < steps; ++j) { + long nargs = 0; for (; ; ++nargs) { var t = TSQueryPredicateStep.asSlice(tokens, nargs); if (TSQueryPredicateStep.type(t) == TSQueryPredicateStepTypeDone()) break; diff --git a/src/test/java/io/github/treesitter/jtreesitter/QueryCursorTest.java b/src/test/java/io/github/treesitter/jtreesitter/QueryCursorTest.java index 7cb7bbf..e6dbe04 100644 --- a/src/test/java/io/github/treesitter/jtreesitter/QueryCursorTest.java +++ b/src/test/java/io/github/treesitter/jtreesitter/QueryCursorTest.java @@ -194,5 +194,18 @@ void findMatches() { matches.getFirst().captures().getFirst().node().getText()); }); } + + // Verify that multiple predicates with different number of args are handled correctly + try (var tree = parser.parse("int a, b;").orElseThrow()) { + var source = """ + ((_) @a (_) @b (#any-of? @a "a" "x") (#eq? @b "b")) + """; + assertCursor(source, cursor -> { + var matches = cursor.findMatches(tree.getRootNode()).toList(); + assertEquals(1, matches.size()); + assertEquals("a", matches.getFirst().findNodes("a").getFirst().getText()); + assertEquals("b", matches.getFirst().findNodes("b").getFirst().getText()); + }); + } } } From e355c2ffc7895dadba216bd5d7e117c41ee9f2b9 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 15 Nov 2025 01:39:22 +0100 Subject: [PATCH 2/2] refactor(query): move predicate handling to dedicated methods --- .../github/treesitter/jtreesitter/Query.java | 229 +++++++++++------- 1 file changed, 138 insertions(+), 91 deletions(-) diff --git a/src/main/java/io/github/treesitter/jtreesitter/Query.java b/src/main/java/io/github/treesitter/jtreesitter/Query.java index b54366e..27f64f6 100644 --- a/src/main/java/io/github/treesitter/jtreesitter/Query.java +++ b/src/main/java/io/github/treesitter/jtreesitter/Query.java @@ -139,7 +139,6 @@ private static boolean invalidPredicateChar(char c) { return !(Character.isLetterOrDigit(c) || c == '_' || c == '-' || c == '.' || c == '?' || c == '!'); } - @SuppressWarnings("DuplicatedCode") private void handlePredicates(String source, MemorySegment query, @Unsigned int patternCount) throws QueryError.Predicate { try (var alloc = Arena.ofConfined()) { @@ -149,6 +148,8 @@ private void handlePredicates(String source, MemorySegment query, @Unsigned int if ((steps = count.get(C_INT, 0)) == 0) continue; int offset = ts_query_start_byte_for_pattern(query, i); long row = source.chars().limit(offset).filter(c -> c == '\n').count(); + var predicates = this.predicates.get(i); + for (long j = 0; j < steps; ++j) { long nargs = 0; for (; ; ++nargs) { @@ -160,99 +161,20 @@ private void handlePredicates(String source, MemorySegment query, @Unsigned int var name = captureNames.get(TSQueryPredicateStep.value_id(t0)); throw new QueryError.Predicate(row, "@%s".formatted(name)); } + var predicate = stringValues.get(TSQueryPredicateStep.value_id(t0)); if (QueryPredicate.Eq.NAMES.contains(predicate)) { - if (nargs != 3) { - var error = "#%s expects 2 arguments, got %d"; - throw new QueryError.Predicate(row, error, predicate, nargs - 1); - } - var t1 = TSQueryPredicateStep.asSlice(tokens, 1); - if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeCapture()) { - var value = stringValues.get(TSQueryPredicateStep.value_id(t1)); - var error = "first argument to #%s must be a capture name, got \"%s\""; - throw new QueryError.Predicate(row, error, predicate, value); - } - var capture = captureNames.get(TSQueryPredicateStep.value_id(t1)); - var t2 = TSQueryPredicateStep.asSlice(tokens, 2); - var id = TSQueryPredicateStep.value_id(t2); - var isCapture = TSQueryPredicateStep.type(t2) == TSQueryPredicateStepTypeCapture(); - var value = isCapture ? captureNames.get(id) : stringValues.get(id); - predicates.get(i).add(new QueryPredicate.Eq(predicate, capture, value, isCapture)); + predicates.add(handlePredicateEq(predicate, tokens, nargs, row)); } else if (QueryPredicate.Match.NAMES.contains(predicate)) { - if (nargs != 3) { - var error = "#%s expects 2 arguments, got %d"; - throw new QueryError.Predicate(row, error, predicate, nargs - 1); - } - var t1 = TSQueryPredicateStep.asSlice(tokens, 1); - if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeCapture()) { - var value = stringValues.get(TSQueryPredicateStep.value_id(t1)); - var error = "first argument to #%s must be a capture name, got \"%s\""; - throw new QueryError.Predicate(row, error, predicate, value); - } - var t2 = TSQueryPredicateStep.asSlice(tokens, 2); - if (TSQueryPredicateStep.type(t2) != TSQueryPredicateStepTypeString()) { - var value = captureNames.get(TSQueryPredicateStep.value_id(t2)); - var error = "second argument to #%s must be a string literal, got @%s"; - throw new QueryError.Predicate(row, error, predicate, value); - } - try { - var capture = captureNames.get(TSQueryPredicateStep.value_id(t1)); - var pattern = Pattern.compile(stringValues.get(TSQueryPredicateStep.value_id(t2))); - predicates.get(i).add(new QueryPredicate.Match(predicate, capture, pattern)); - } catch (PatternSyntaxException e) { - throw new QueryError.Predicate(row, "pattern error", e); - } + predicates.add(handlePredicateMatch(predicate, tokens, nargs, row)); } else if (QueryPredicate.AnyOf.NAMES.contains(predicate)) { - if (nargs < 3) { - var error = "#%s expects at least 2 arguments, got %d"; - throw new QueryError.Predicate(row, error, predicate, nargs - 1); - } - var t1 = TSQueryPredicateStep.asSlice(tokens, 1); - if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeCapture()) { - var value = stringValues.get(TSQueryPredicateStep.value_id(t1)); - var error = "first argument to #%s must be a capture name, got \"%s\""; - throw new QueryError.Predicate(row, error, predicate, value); - } - List values = new ArrayList<>((int) nargs - 2); - for (long k = 2; k < nargs; ++k) { - var t = TSQueryPredicateStep.asSlice(tokens, k); - if (TSQueryPredicateStep.type(t) != TSQueryPredicateStepTypeString()) { - var value = captureNames.get(TSQueryPredicateStep.value_id(t)); - var error = "arguments to #%s must be string literals, got @%s"; - throw new QueryError.Predicate(row, error, predicate, value); - } - values.add(stringValues.get(TSQueryPredicateStep.value_id(t))); - } - var capture = captureNames.get(TSQueryPredicateStep.value_id(t1)); - predicates.get(i).add(new QueryPredicate.AnyOf(predicate, capture, values)); - } else if (predicate.equals("is?") || predicate.equals("is-not?") || predicate.equals("set!")) { - if (nargs == 1 || nargs > 3) { - var error = "#%s expects 1-2 arguments, got %d"; - throw new QueryError.Predicate(row, error, predicate, nargs - 1); - } - var t1 = TSQueryPredicateStep.asSlice(tokens, 1); - if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeString()) { - var value = captureNames.get(TSQueryPredicateStep.value_id(t1)); - var error = "first argument to #%s must be a string literal, got @%s"; - throw new QueryError.Predicate(row, error, predicate, value); - } - String key = stringValues.get(TSQueryPredicateStep.value_id(t1)), value = null; - if (nargs == 3) { - var t2 = TSQueryPredicateStep.asSlice(tokens, 2); - if (TSQueryPredicateStep.type(t2) != TSQueryPredicateStepTypeString()) { - var capture = captureNames.get(TSQueryPredicateStep.value_id(t2)); - var error = "second argument to #%s must be a string literal, got @%s"; - throw new QueryError.Predicate(row, error, predicate, capture); - } - value = stringValues.get(TSQueryPredicateStep.value_id(t2)); - } - if (predicate.equals("is?")) { - positiveAssertions.get(i).put(key, Optional.ofNullable(value)); - } else if (predicate.equals("is-not?")) { - negativeAssertions.get(i).put(key, Optional.ofNullable(value)); - } else { - settings.get(i).put(key, Optional.ofNullable(value)); - } + predicates.add(handlePredicateAnyOf(predicate, tokens, nargs, row)); + } else if (predicate.equals("is?") || predicate.equals("is-not?")) { + var assertions = (predicate.equals("is?") ? positiveAssertions : negativeAssertions).get(i); + handlePredicateAssertion(predicate, tokens, nargs, row, assertions); + } else if (predicate.equals("set!")) { + var settings = this.settings.get(i); + handleDirectiveSet(predicate, tokens, nargs, row, settings); } else { List values = new ArrayList<>((int) nargs - 1); for (long k = 1; k < nargs; ++k) { @@ -265,7 +187,7 @@ private void handlePredicates(String source, MemorySegment query, @Unsigned int values.add(new QueryPredicateArg.Capture(capture)); } } - predicates.get(i).add(new QueryPredicate(predicate, values)); + predicates.add(new QueryPredicate(predicate, values)); } j += nargs; tokens = TSQueryPredicateStep.asSlice(tokens, nargs + 1); @@ -274,6 +196,131 @@ private void handlePredicates(String source, MemorySegment query, @Unsigned int } } + /** {@code #eq?} predicate */ + private QueryPredicate handlePredicateEq(String name, MemorySegment tokens, long nargs, long row) { + if (nargs != 3) { + var error = "#%s expects 2 arguments, got %d"; + throw new QueryError.Predicate(row, error, name, nargs - 1); + } + var t1 = TSQueryPredicateStep.asSlice(tokens, 1); + if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeCapture()) { + var value = stringValues.get(TSQueryPredicateStep.value_id(t1)); + var error = "first argument to #%s must be a capture name, got \"%s\""; + throw new QueryError.Predicate(row, error, name, value); + } + var capture = captureNames.get(TSQueryPredicateStep.value_id(t1)); + var t2 = TSQueryPredicateStep.asSlice(tokens, 2); + var id = TSQueryPredicateStep.value_id(t2); + var isCapture = TSQueryPredicateStep.type(t2) == TSQueryPredicateStepTypeCapture(); + var value = isCapture ? captureNames.get(id) : stringValues.get(id); + return new QueryPredicate.Eq(name, capture, value, isCapture); + } + + /** {@code #match?} predicate */ + private QueryPredicate handlePredicateMatch(String name, MemorySegment tokens, long nargs, long row) { + if (nargs != 3) { + var error = "#%s expects 2 arguments, got %d"; + throw new QueryError.Predicate(row, error, name, nargs - 1); + } + var t1 = TSQueryPredicateStep.asSlice(tokens, 1); + if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeCapture()) { + var value = stringValues.get(TSQueryPredicateStep.value_id(t1)); + var error = "first argument to #%s must be a capture name, got \"%s\""; + throw new QueryError.Predicate(row, error, name, value); + } + var t2 = TSQueryPredicateStep.asSlice(tokens, 2); + if (TSQueryPredicateStep.type(t2) != TSQueryPredicateStepTypeString()) { + var value = captureNames.get(TSQueryPredicateStep.value_id(t2)); + var error = "second argument to #%s must be a string literal, got @%s"; + throw new QueryError.Predicate(row, error, name, value); + } + try { + var capture = captureNames.get(TSQueryPredicateStep.value_id(t1)); + var pattern = Pattern.compile(stringValues.get(TSQueryPredicateStep.value_id(t2))); + return new QueryPredicate.Match(name, capture, pattern); + } catch (PatternSyntaxException e) { + throw new QueryError.Predicate(row, "pattern error", e); + } + } + + /** {@code #any-of?} predicate */ + private QueryPredicate handlePredicateAnyOf(String name, MemorySegment tokens, long nargs, long row) { + if (nargs < 3) { + var error = "#%s expects at least 2 arguments, got %d"; + throw new QueryError.Predicate(row, error, name, nargs - 1); + } + var t1 = TSQueryPredicateStep.asSlice(tokens, 1); + if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeCapture()) { + var value = stringValues.get(TSQueryPredicateStep.value_id(t1)); + var error = "first argument to #%s must be a capture name, got \"%s\""; + throw new QueryError.Predicate(row, error, name, value); + } + List values = new ArrayList<>((int) nargs - 2); + for (long k = 2; k < nargs; ++k) { + var t = TSQueryPredicateStep.asSlice(tokens, k); + if (TSQueryPredicateStep.type(t) != TSQueryPredicateStepTypeString()) { + var value = captureNames.get(TSQueryPredicateStep.value_id(t)); + var error = "arguments to #%s must be string literals, got @%s"; + throw new QueryError.Predicate(row, error, name, value); + } + values.add(stringValues.get(TSQueryPredicateStep.value_id(t))); + } + var capture = captureNames.get(TSQueryPredicateStep.value_id(t1)); + return new QueryPredicate.AnyOf(name, capture, values); + } + + /** {@code #is?} predicate */ + private void handlePredicateAssertion( + String name, MemorySegment tokens, long nargs, long row, Map> assertions) { + if (nargs == 1 || nargs > 3) { + var error = "#%s expects 1-2 arguments, got %d"; + throw new QueryError.Predicate(row, error, name, nargs - 1); + } + var t1 = TSQueryPredicateStep.asSlice(tokens, 1); + if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeString()) { + var value = captureNames.get(TSQueryPredicateStep.value_id(t1)); + var error = "first argument to #%s must be a string literal, got @%s"; + throw new QueryError.Predicate(row, error, name, value); + } + String key = stringValues.get(TSQueryPredicateStep.value_id(t1)), value = null; + if (nargs == 3) { + var t2 = TSQueryPredicateStep.asSlice(tokens, 2); + if (TSQueryPredicateStep.type(t2) != TSQueryPredicateStepTypeString()) { + var capture = captureNames.get(TSQueryPredicateStep.value_id(t2)); + var error = "second argument to #%s must be a string literal, got @%s"; + throw new QueryError.Predicate(row, error, name, capture); + } + value = stringValues.get(TSQueryPredicateStep.value_id(t2)); + } + assertions.put(key, Optional.ofNullable(value)); + } + + /** {@code #set!} directive */ + private void handleDirectiveSet( + String name, MemorySegment tokens, long nargs, long row, Map> settings) { + if (nargs == 1 || nargs > 3) { + var error = "#%s expects 1-2 arguments, got %d"; + throw new QueryError.Predicate(row, error, name, nargs - 1); + } + var t1 = TSQueryPredicateStep.asSlice(tokens, 1); + if (TSQueryPredicateStep.type(t1) != TSQueryPredicateStepTypeString()) { + var value = captureNames.get(TSQueryPredicateStep.value_id(t1)); + var error = "first argument to #%s must be a string literal, got @%s"; + throw new QueryError.Predicate(row, error, name, value); + } + String key = stringValues.get(TSQueryPredicateStep.value_id(t1)), value = null; + if (nargs == 3) { + var t2 = TSQueryPredicateStep.asSlice(tokens, 2); + if (TSQueryPredicateStep.type(t2) != TSQueryPredicateStepTypeString()) { + var capture = captureNames.get(TSQueryPredicateStep.value_id(t2)); + var error = "second argument to #%s must be a string literal, got @%s"; + throw new QueryError.Predicate(row, error, name, capture); + } + value = stringValues.get(TSQueryPredicateStep.value_id(t2)); + } + settings.put(key, Optional.ofNullable(value)); + } + MemorySegment segment() { return self; }