diff --git a/lib/ch_usi_si_seart_treesitter_TreeCursor.cc b/lib/ch_usi_si_seart_treesitter_TreeCursor.cc index d6510fbf..8d306ae9 100644 --- a/lib/ch_usi_si_seart_treesitter_TreeCursor.cc +++ b/lib/ch_usi_si_seart_treesitter_TreeCursor.cc @@ -43,7 +43,7 @@ JNIEXPORT jobject JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_getCurrentT ); } -JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild( +JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild__( JNIEnv* env, jobject thisObject) { TSTreeCursor* cursor = (TSTreeCursor*)__getPointer(env, thisObject); bool result = ts_tree_cursor_goto_first_child(cursor); @@ -52,6 +52,61 @@ JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstC return result ? JNI_TRUE : JNI_FALSE; } +JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild__I( + JNIEnv* env, jobject thisObject, jint offset) { + if (offset < 0) { + __throwIAE(env, "Byte offset must not be negative!"); + return JNI_FALSE; + } + // Not sure why I need to multiply by two, again probably because of utf-16 + uint32_t childStart = (uint32_t)offset * 2; + TSTreeCursor* cursor = (TSTreeCursor*)__getPointer(env, thisObject); + TSNode node = ts_tree_cursor_current_node(cursor); + uint32_t nodeStart = ts_node_start_byte(node); + if (childStart < nodeStart) { + __throwIOB(env, offset); + return JNI_FALSE; + } + uint32_t nodeEnd = ts_node_end_byte(node); + if (childStart > nodeEnd) { + __throwIOB(env, offset); + return JNI_FALSE; + } + int64_t result = ts_tree_cursor_goto_first_child_for_byte(cursor, childStart); + env->SetIntField(thisObject, _treeCursorContext0Field, cursor->context[0]); + env->SetIntField(thisObject, _treeCursorContext1Field, cursor->context[1]); + return (result > -1) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild__Lch_usi_si_seart_treesitter_Point_2( + JNIEnv* env, jobject thisObject, jobject pointObject) { + if (pointObject == NULL) { + __throwNPE(env, "Point must not be null!"); + return JNI_FALSE; + } + TSPoint point = __unmarshalPoint(env, pointObject); + if (point.row < 0 || point.column < 0) { + __throwIAE(env, "Point can not have negative coordinates!"); + return JNI_FALSE; + } + TSTreeCursor* cursor = (TSTreeCursor*)__getPointer(env, thisObject); + TSNode node = ts_tree_cursor_current_node(cursor); + TSPoint lowerBound = ts_node_start_point(node); + if (__comparePoints(lowerBound, point) == GT) { + __throwIAE(env, "Point can not be outside of current node bounds!"); + return JNI_FALSE; + } + TSPoint upperBound = ts_node_end_point(node); + if (__comparePoints(point, upperBound) == GT) { + __throwIAE(env, "Point can not be outside of current node bounds!"); + return JNI_FALSE; + } + int64_t result = ts_tree_cursor_goto_first_child_for_point(cursor, point); + env->SetIntField(thisObject, _treeCursorContext0Field, cursor->context[0]); + env->SetIntField(thisObject, _treeCursorContext1Field, cursor->context[1]); + return (result > -1) ? JNI_TRUE : JNI_FALSE; +} + JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoNextSibling( JNIEnv* env, jobject thisObject) { TSTreeCursor* cursor = (TSTreeCursor*)__getPointer(env, thisObject); diff --git a/lib/ch_usi_si_seart_treesitter_TreeCursor.h b/lib/ch_usi_si_seart_treesitter_TreeCursor.h index 9d7cabfa..3ba4cc4a 100644 --- a/lib/ch_usi_si_seart_treesitter_TreeCursor.h +++ b/lib/ch_usi_si_seart_treesitter_TreeCursor.h @@ -44,9 +44,25 @@ JNIEXPORT jobject JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_getCurrentT * Method: gotoFirstChild * Signature: ()Z */ -JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild +JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild__ (JNIEnv *, jobject); +/* + * Class: ch_usi_si_seart_treesitter_TreeCursor + * Method: gotoFirstChild + * Signature: (I)Z + */ +JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild__I + (JNIEnv *, jobject, jint); + +/* + * Class: ch_usi_si_seart_treesitter_TreeCursor + * Method: gotoFirstChild + * Signature: (Lch/usi/si/seart/treesitter/Point;)Z + */ +JNIEXPORT jboolean JNICALL Java_ch_usi_si_seart_treesitter_TreeCursor_gotoFirstChild__Lch_usi_si_seart_treesitter_Point_2 + (JNIEnv *, jobject, jobject); + /* * Class: ch_usi_si_seart_treesitter_TreeCursor * Method: gotoNextSibling diff --git a/src/main/java/ch/usi/si/seart/treesitter/OffsetTreeCursor.java b/src/main/java/ch/usi/si/seart/treesitter/OffsetTreeCursor.java index 8dae6712..edba5700 100644 --- a/src/main/java/ch/usi/si/seart/treesitter/OffsetTreeCursor.java +++ b/src/main/java/ch/usi/si/seart/treesitter/OffsetTreeCursor.java @@ -65,6 +65,16 @@ public boolean gotoFirstChild() { return cursor.gotoFirstChild(); } + @Override + public boolean gotoFirstChild(int offset) { + throw new UnsupportedOperationException(UOE_MESSAGE_2); + } + + @Override + public boolean gotoFirstChild(@NotNull Point point) { + return cursor.gotoFirstChild(point.subtract(offset)); + } + @Override public boolean gotoNextSibling() { return cursor.gotoNextSibling(); diff --git a/src/main/java/ch/usi/si/seart/treesitter/TreeCursor.java b/src/main/java/ch/usi/si/seart/treesitter/TreeCursor.java index ed278c4b..8ed4d706 100644 --- a/src/main/java/ch/usi/si/seart/treesitter/TreeCursor.java +++ b/src/main/java/ch/usi/si/seart/treesitter/TreeCursor.java @@ -78,6 +78,34 @@ protected TreeCursor() { */ public native boolean gotoFirstChild(); + /** + * Move the cursor to the first child of its current node + * that extends beyond the given byte offset. + * + * @param offset the starting byte of the child + * @return true if the cursor successfully moved, + * and false if no such child was found + * @throws IllegalArgumentException if {@code offset} is negative + * @throws IndexOutOfBoundsException if {@code offset} is outside + * the current node's byte range + * @since 1.7.0 + */ + public native boolean gotoFirstChild(int offset); + + /** + * Move the cursor to the first child of its current node + * that extends beyond the given row-column offset. + * + * @param point the starting row-column position of the child + * @return true if the cursor successfully moved, + * and false if no such child was found + * @throws NullPointerException if {@code point} is null + * @throws IllegalArgumentException if {@code point} is + * outside the current node's positional span + * @since 1.7.0 + */ + public native boolean gotoFirstChild(@NotNull Point point); + /** * Move the cursor to the next sibling of its current node. * @@ -105,13 +133,11 @@ protected TreeCursor() { public void preorderTraversal(@NotNull Consumer callback) { Objects.requireNonNull(callback, "Callback must not be null!"); for (;;) { - callback.accept(this.getCurrentNode()); - if (this.gotoFirstChild() || this.gotoNextSibling()) - continue; + callback.accept(getCurrentNode()); + if (gotoFirstChild() || gotoNextSibling()) continue; do { - if (!this.gotoParent()) - return; - } while (!this.gotoNextSibling()); + if (!gotoParent()) return; + } while (!gotoNextSibling()); } } diff --git a/src/test/java/ch/usi/si/seart/treesitter/TreeCursorTest.java b/src/test/java/ch/usi/si/seart/treesitter/TreeCursorTest.java index c6d52080..2a1f106d 100644 --- a/src/test/java/ch/usi/si/seart/treesitter/TreeCursorTest.java +++ b/src/test/java/ch/usi/si/seart/treesitter/TreeCursorTest.java @@ -67,6 +67,54 @@ void testWalk() { Assertions.assertTrue(cursor.gotoFirstChild()); } + @Test + void testGotoFirstChildByteOffset() { + cursor.gotoFirstChild(); // function_definition + cursor.gotoFirstChild(4); + Assertions.assertEquals("identifier", cursor.getCurrentNode().getType()); + cursor.gotoParent(); + cursor.gotoFirstChild(21); + Assertions.assertEquals("block", cursor.getCurrentNode().getType()); + cursor.gotoFirstChild(34); + Assertions.assertEquals("expression_statement", cursor.getCurrentNode().getType()); + } + + @Test + void testGotoFirstChildByteOffsetThrows() { + cursor.gotoFirstChild(); // function_definition + cursor.gotoFirstChild(); // identifier + cursor.gotoNextSibling(); // parameters + Assertions.assertThrows(IllegalArgumentException.class, () -> cursor.gotoFirstChild(-1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> cursor.gotoFirstChild(0)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> cursor.gotoFirstChild(20)); + } + + @Test + void testGotoFirstChildPositionOffset() { + cursor.gotoFirstChild(); // function_definition + cursor.gotoFirstChild(new Point(0, 4)); + Assertions.assertEquals("identifier", cursor.getCurrentNode().getType()); + cursor.gotoParent(); + cursor.gotoFirstChild(new Point(1, 2)); + Assertions.assertEquals("block", cursor.getCurrentNode().getType()); + cursor.gotoFirstChild(new Point(2, 2)); + Assertions.assertEquals("expression_statement", cursor.getCurrentNode().getType()); + } + + @Test + void testGotoFirstChildPositionOffsetThrows() { + cursor.gotoFirstChild(); // function_definition + cursor.gotoFirstChild(); // identifier + cursor.gotoNextSibling(); // parameters + Point negative = new Point(0, -1); + Point illegal = new Point(1, 2); + Assertions.assertThrows(NullPointerException.class, () -> cursor.gotoFirstChild(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> cursor.gotoFirstChild(negative)); + Assertions.assertThrows(IllegalArgumentException.class, () -> cursor.gotoFirstChild(_0_0_)); + Assertions.assertThrows(IllegalArgumentException.class, () -> cursor.gotoFirstChild(illegal)); + + } + @Test void testPreorderTraversal() { AtomicInteger count = new AtomicInteger();