diff --git a/lib/ch_usi_si_seart_treesitter_Language.cc b/lib/ch_usi_si_seart_treesitter_Language.cc index 06770807..ae556426 100644 --- a/lib/ch_usi_si_seart_treesitter_Language.cc +++ b/lib/ch_usi_si_seart_treesitter_Language.cc @@ -565,3 +565,13 @@ JNIEXPORT jobject JNICALL Java_ch_usi_si_seart_treesitter_Language_iterator( thisObject ); } + +JNIEXPORT jint JNICALL Java_ch_usi_si_seart_treesitter_Language_nextState( + JNIEnv* env, jclass self, jlong id, jint state, jint symbol) { + if (id == (jlong)ch_usi_si_seart_treesitter_Language_INVALID) return (jint)(-1); + return (jint)ts_language_next_state( + (const TSLanguage*)id, + (TSStateId)state, + (TSSymbol)symbol + ); +} diff --git a/lib/ch_usi_si_seart_treesitter_Language.h b/lib/ch_usi_si_seart_treesitter_Language.h index ac4c17e6..82d59748 100644 --- a/lib/ch_usi_si_seart_treesitter_Language.h +++ b/lib/ch_usi_si_seart_treesitter_Language.h @@ -505,6 +505,14 @@ JNIEXPORT jint JNICALL Java_ch_usi_si_seart_treesitter_Language_states JNIEXPORT jobject JNICALL Java_ch_usi_si_seart_treesitter_Language_iterator (JNIEnv *, jobject, jint); +/* + * Class: ch_usi_si_seart_treesitter_Language + * Method: nextState + * Signature: (JII)I + */ +JNIEXPORT jint JNICALL Java_ch_usi_si_seart_treesitter_Language_nextState + (JNIEnv *, jclass, jlong, jint, jint); + #ifdef __cplusplus } #endif diff --git a/src/main/java/ch/usi/si/seart/treesitter/Language.java b/src/main/java/ch/usi/si/seart/treesitter/Language.java index ba5c5fe9..a3466580 100644 --- a/src/main/java/ch/usi/si/seart/treesitter/Language.java +++ b/src/main/java/ch/usi/si/seart/treesitter/Language.java @@ -618,6 +618,32 @@ public static void validate(@NotNull Language language) { */ public native LookaheadIterator iterator(int state); + /** + * Obtain the next language parse state for a given {@link Node}. + *

+ * Combine this with lookahead iterators to generate completion + * suggestions or valid symbols in {@code ERROR} nodes. + * + * @param node the node + * @return the next parse state + * @throws NullPointerException if {@code node} is null + * @throws IllegalArgumentException if this language + * was not used to parse the node and its syntax tree + * @since 1.12.0 + */ + public int nextState(@NotNull Node node) { + Objects.requireNonNull(node, "Node must not be null!"); + Language language = node.getLanguage(); + if (!this.equals(language)) throw new IllegalArgumentException( + "Node language does not match the language of this instance!" + ); + int state = node.getParseState(); + Symbol symbol = node.getGrammarSymbol(); + return nextState(id, state, symbol.getId()); + } + + private static native int nextState(long id, int state, int symbol); + @Generated @SuppressWarnings("unused") public int getTotalSymbols() { diff --git a/src/test/java/ch/usi/si/seart/treesitter/LanguageTest.java b/src/test/java/ch/usi/si/seart/treesitter/LanguageTest.java index a7c71ca0..8948f5d1 100644 --- a/src/test/java/ch/usi/si/seart/treesitter/LanguageTest.java +++ b/src/test/java/ch/usi/si/seart/treesitter/LanguageTest.java @@ -1,6 +1,8 @@ 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.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -100,4 +102,21 @@ public Stream provideArguments(ExtensionContext extensionCo void testAssociatedWithThrows(Class throwableType, Path path) { Assertions.assertThrows(throwableType, () -> Language.associatedWith(path)); } + + @Test + void testNextState() { + Language language = Language.PYTHON; + @Cleanup Parser parser = Parser.getFor(language); + @Cleanup Tree tree = parser.parse("pass"); + Node root = tree.getRootNode(); + Assertions.assertEquals(0, language.nextState(root)); + } + + @Test + void testNextStateThrows() { + Assertions.assertThrows(NullPointerException.class, () -> Language.JAVA.nextState(null)); + Assertions.assertThrows(NullPointerException.class, () -> Language._INVALID_.nextState(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> Language.JAVA.nextState(empty)); + Assertions.assertThrows(IllegalArgumentException.class, () -> Language._INVALID_.nextState(empty)); + } }