From 49e63cb771fb0a24a4d006616088b73d1cdb6009 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Mon, 21 Oct 2024 20:15:43 +0300 Subject: [PATCH 1/5] feat!: upgrade to Kotlin 2 --- .github/scripts/build-jni.ps1 | 2 +- .github/scripts/build-jni.sh | 4 +- .github/workflows/ci.yml | 49 ++++------- .github/workflows/docs.yml | 24 +++--- .github/workflows/lint.yml | 2 +- .github/workflows/publish.yml | 40 ++++----- .gitignore | 1 + .idea/ktlint-plugin.xml | 5 +- build.gradle.kts | 2 +- gradle.properties | 10 ++- gradle/libs.versions.toml | 77 +++++------------- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +- gradlew.bat | 22 ++--- ktreesitter-plugin/build.gradle.kts | 1 - ktreesitter/CMakeLists.txt | 4 +- ktreesitter/build.gradle.kts | 25 +++--- .../ktreesitter/LookaheadIterator.kt | 2 +- .../io/github/treesitter/ktreesitter/Query.kt | 18 ++-- .../ktreesitter/LookaheadIterator.kt | 2 + .../treesitter/ktreesitter/QueryCapture.kt | 1 + ktreesitter/src/jni/query.c | 8 +- .../ktreesitter/LookaheadIterator.kt | 2 +- .../io/github/treesitter/ktreesitter/Query.kt | 16 ++-- .../ktreesitter/LookaheadIterator.kt | 2 +- languages/java/build.gradle.kts | 5 +- 27 files changed, 147 insertions(+), 186 deletions(-) diff --git a/.github/scripts/build-jni.ps1 b/.github/scripts/build-jni.ps1 index cc33042..5539ddb 100755 --- a/.github/scripts/build-jni.ps1 +++ b/.github/scripts/build-jni.ps1 @@ -2,7 +2,7 @@ & cmake -S ktreesitter -B ktreesitter/.cmake/build ` -DCMAKE_VERBOSE_MAKEFILE=ON ` - -DCMAKE_INSTALL_PREFIX=ktreesitter/src/jvmMain/resources ` + -DCMAKE_INSTALL_PREFIX=ktreesitter/src/jvmMain/resources ` -DCMAKE_INSTALL_BINDIR="$env:CMAKE_INSTALL_LIBDIR" & cmake --build ktreesitter/.cmake/build --config Debug & cmake --install ktreesitter/.cmake/build --config Debug diff --git a/.github/scripts/build-jni.sh b/.github/scripts/build-jni.sh index 89474b8..85df4b9 100755 --- a/.github/scripts/build-jni.sh +++ b/.github/scripts/build-jni.sh @@ -3,7 +3,7 @@ cmake -S ktreesitter -B ktreesitter/.cmake/build \ -DCMAKE_BUILD_TYPE=RelWithDebugInfo \ -DCMAKE_VERBOSE_MAKEFILE=ON \ - -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \ -DCMAKE_INSTALL_PREFIX=ktreesitter/src/jvmMain/resources \ -DCMAKE_INSTALL_LIBDIR="$CMAKE_INSTALL_LIBDIR" cmake --build ktreesitter/.cmake/build @@ -12,7 +12,7 @@ cmake --install ktreesitter/.cmake/build for dir in languages/*/; do cmake -S "${dir}build/generated" -B "${dir}.cmake/build" \ -DCMAKE_BUILD_TYPE=RelWithDebugInfo \ - -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' \ -DCMAKE_INSTALL_PREFIX="${dir}build/generated/src/jvmMain/resources" \ -DCMAKE_INSTALL_LIBDIR="$CMAKE_INSTALL_LIBDIR" cmake --build "${dir}.cmake/build" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52bd8e9..7103a2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,15 +42,12 @@ jobs: cache-dependency-path: | gradle/libs.versions.toml gradle/wrapper/gradle-wrapper.properties - - name: Cache Kotlin/Native prebuilt - uses: actions/cache@v4 + - name: Set up Kotlin/Native + uses: ObserverOfTime/setup-konan-action@v1 with: - path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-* - key: konan-${{runner.os}}-prebuilt-1.9 + kotlin_version: 2.1.10 - name: Generate files run: ./gradlew --no-daemon generateGrammarFiles - env: - KONAN_DATA_DIR: ${{runner.tool_cache}}/konan - name: Upload artifact uses: actions/upload-artifact@v4 with: @@ -113,14 +110,20 @@ jobs: cache-dependency-path: | gradle/libs.versions.toml gradle/wrapper/gradle-wrapper.properties + - name: Set up Kotlin/Native + uses: ObserverOfTime/setup-konan-action@v1 + with: + kotlin_version: 2.1.10 - name: Set up cross compilation - run: sudo apt-get install -qy {binutils,gcc}-aarch64-linux-gnu if: matrix.platform == 'Linux' - - name: Restore Kotlin/Native prebuilt - uses: actions/cache/restore@v4 - with: - path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-* - key: konan-${{runner.os}}-prebuilt-1.9 + run: |- + sudo apt-get update + sudo apt-get install -qy {binutils,gcc}-aarch64-linux-gnu + - name: Set up Ninja + if: matrix.platform == 'Android' + run: |- + sudo apt-get update + sudo apt-get install -qy ninja-build - name: Download generated files uses: actions/download-artifact@v4 with: @@ -131,30 +134,8 @@ jobs: run: .github/scripts/build-jni.${{matrix.os == 'windows-latest' && 'ps1' || 'sh'}} env: CMAKE_INSTALL_LIBDIR: lib/${{matrix.lib_platform}}/${{matrix.lib_arch}} - - name: Cache Kotlin/Native dependencies - id: cache-dependencies - uses: actions/cache@v4 - if: matrix.platform != 'JVM' && matrix.platform != 'Android' - with: - path: ${{runner.tool_cache}}/konan/dependencies - key: konan-${{runner.os}}-dependencies - - name: Download Kotlin/Native dependencies - if: matrix.platform == 'macOS/iOS' && steps.cache-dependencies.outputs.cache-hit != 'true' - run: |- - mkdir -p "$RUNNER_TOOL_CACHE/konan/dependencies" - curl -LSs https://download-cdn.jetbrains.com/kotlin/native/$DEP.tar.gz | \ - tar -xzf - -C "$RUNNER_TOOL_CACHE/konan/dependencies" - env: - DEP: apple-llvm-20200714-macos-aarch64-essentials - - name: Set up Ninja - if: matrix.platform == 'Android' - run: |- - sudo apt-get update - sudo apt-get install -y ninja-build - name: Run tests run: ./gradlew --no-daemon ${{matrix.targets}} - env: - KONAN_DATA_DIR: ${{runner.tool_cache}}/konan - name: Report test results uses: mikepenz/action-junit-report@v5 if: matrix.platform == 'JVM' && !cancelled() diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f013fb7..aaf5f24 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -44,24 +44,22 @@ jobs: cache-dependency-path: | gradle/libs.versions.toml gradle/wrapper/gradle-wrapper.properties - - name: Restore Kotlin/Native prebuilt - uses: actions/cache/restore@v4 + - name: Set up Kotlin/Native + uses: ObserverOfTime/setup-konan-action@v1 with: - path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-* - key: konan-${{runner.os}}-prebuilt-1.9 - - name: Restore Kotlin/Native dependencies - uses: actions/cache/restore@v4 - with: - path: ${{runner.tool_cache}}/konan/dependencies - key: konan-${{runner.os}}-dependencies + kotlin_version: 2.1.10 + - name: Set up cross compilation + if: matrix.platform == 'Linux' + run: |- + sudo apt-get update + sudo apt-get install -qy {binutils,gcc}-aarch64-linux-gnu - name: Set up Ninja + if: matrix.platform == 'Android' run: |- sudo apt-get update - sudo apt-get install -y ninja-build + sudo apt-get install -qy ninja-build - name: Build documentation - run: ./gradlew --no-daemon generateFiles :ktreesitter:dokkaHtml - env: - KONAN_DATA_DIR: ${{runner.tool_cache}}/konan + run: ./gradlew --no-daemon generateFiles :ktreesitter:dokkaGeneratePublicationHtml - name: Upload pages artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 36aea66..9f6ab89 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,7 +34,7 @@ jobs: java-version: 17 - name: Install ktlint run: |- - curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.3.1/ktlint + curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint chmod a+x ktlint && mv ktlint $RUNNER_TOOL_CACHE/ktlint - name: Run ktlint id: ktlint diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4b4e134..fdf027a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -132,20 +132,20 @@ jobs: cache-dependency-path: | gradle/libs.versions.toml gradle/wrapper/gradle-wrapper.properties - - name: Set up cross compilation - run: sudo apt-get install -qy {binutils,gcc}-aarch64-linux-gnu - if: matrix.platform == 'Linux' || matrix.platform == 'common' - - name: Restore Kotlin/Native prebuilt - uses: actions/cache/restore@v4 + - name: Set up Kotlin/Native + uses: ObserverOfTime/setup-konan-action@v1 with: - path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-* - key: konan-${{runner.os}}-prebuilt-1.9 - - name: Restore Kotlin/Native dependencies - uses: actions/cache/restore@v4 - if: matrix.platform != 'JVM' && matrix.platform != 'Android' - with: - path: ${{runner.tool_cache}}/konan/dependencies - key: konan-${{runner.os}}-dependencies + kotlin_version: 2.1.10 + - name: Set up cross compilation + if: matrix.platform == 'Linux' + run: |- + sudo apt-get update + sudo apt-get install -qy {binutils,gcc}-aarch64-linux-gnu + - name: Set up Ninja + if: matrix.platform == 'Android' + run: |- + sudo apt-get update + sudo apt-get install -qy ninja-build - name: Download library artifacts uses: actions/download-artifact@v4 if: matrix.platform == 'JVM' @@ -153,17 +153,11 @@ jobs: path: ktreesitter/src/jvmMain/resources/lib pattern: ktreesitter-lib-* merge-multiple: true - - name: Set up Ninja - if: matrix.platform == 'Android' - run: |- - sudo apt-get update - sudo apt-get install -y ninja-build - name: Build packages run: ./gradlew --no-daemon ${{matrix.targets}} env: SIGNING_KEY: ${{secrets.SIGNING_KEY}} SIGNING_PASSWORD: ${{secrets.SIGNING_PASSWORD}} - KONAN_DATA_DIR: ${{runner.tool_cache}}/konan - name: Upload artifacts uses: actions/upload-artifact@v4 with: @@ -235,14 +229,12 @@ jobs: cache-dependency-path: | gradle/libs.versions.toml gradle/wrapper/gradle-wrapper.properties - - name: Restore Kotlin/Native prebuilt - uses: actions/cache/restore@v4 + - name: Set up Kotlin/Native + uses: ObserverOfTime/setup-konan-action@v1 with: - path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-* - key: konan-${{runner.os}}-prebuilt-1.9 + kotlin_version: 2.1.10 - name: Publish Gradle plugin run: ./gradlew --no-daemon :ktreesitter-plugin:publishPlugins env: - KONAN_DATA_DIR: ${{runner.tool_cache}}/konan GRADLE_PUBLISH_KEY: ${{secrets.GRADLE_PUBLISH_KEY}} GRADLE_PUBLISH_SECRET: ${{secrets.GRADLE_PUBLISH_SECRET}} diff --git a/.gitignore b/.gitignore index cff9142..74ee895 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ ### Gradle ### .gradle/ build/ +.kotlin/ kotlin-js-store/ dependency-graph-reports/ local.properties diff --git a/.idea/ktlint-plugin.xml b/.idea/ktlint-plugin.xml index bee5678..e68ca2d 100644 --- a/.idea/ktlint-plugin.xml +++ b/.idea/ktlint-plugin.xml @@ -1,6 +1,7 @@ - + DISTRACT_FREE + DEFAULT - \ No newline at end of file + diff --git a/build.gradle.kts b/build.gradle.kts index ef9790e..7f3634f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,6 @@ subprojects { } tasks.wrapper { - gradleVersion = "8.7" + gradleVersion = "8.11" distributionType = Wrapper.DistributionType.BIN } diff --git a/gradle.properties b/gradle.properties index 2198304..ff8c42a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,11 +2,11 @@ project.version=0.24.1 # The Android SDK version that is used to compile the project. -sdk.version.compile=34 +sdk.version.compile=35 # The minimum supported Android SDK version. sdk.version.min=23 # The Android NDK version that is used to compile the project. -ndk.version=26.3.11579264 +ndk.version=27.2.12479018 # The CMake version that is used to compile the project. cmake.version=3.31.3 @@ -23,6 +23,8 @@ kotlin.code.style=official kotlin.mpp.androidSourceSetLayoutVersion=2 # Enable C interop sharing kotlin.mpp.enableCInteropCommonization=true +# Enable cross-compilation of klib artifacts +kotlin.native.enableKlibsCrossCompilation=true # Disable default stdlib dependency kotlin.stdlib.default.dependency=false # Ignore disabled Kotlin/Native targets @@ -32,3 +34,7 @@ kotlin.native.ignoreDisabledTargets=true android.useAndroidX=true # Disables automatically downloading the Android SDK. android.builder.sdkDownload=false + +# Enables the experimental Dokka V2 mode. +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled +org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6b9ff1..dc7a822 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,61 +1,24 @@ [versions] -kotlin-stdlib = "[1.9,2.0)" -#noinspection GradleDependency -android-gradle = {strictly = "8.2.0"} -kotest = "5.9.1" -dokka = "1.9.20" - -[libraries.kotlin-stdlib] -module = "org.jetbrains.kotlin:kotlin-stdlib" -version.ref = "kotlin-stdlib" - -[libraries.kotest-engine] -module = "io.kotest:kotest-framework-engine" -version.ref = "kotest" - -[libraries.kotest-assertions] -module = "io.kotest:kotest-assertions-core" -version.ref = "kotest" - -[libraries.kotest-junit-runner] -module = "io.kotest:kotest-runner-junit5" -version.ref = "kotest" - -[libraries.kotest-junit-reporter] -module = "io.kotest:kotest-extensions-junitxml" -version.ref = "kotest" - -[libraries.kotest-android-runner] -module = "br.com.colman:kotest-runner-android" -version = "1.1.1" - -[libraries.androidx-test-runner] -module = "androidx.test:runner" -version = "1.5.2" - -[libraries.dokka-base] -module = "org.jetbrains.dokka:dokka-base" -version.ref = "dokka" - -[plugins.kotlin-mpp] -id = "org.jetbrains.kotlin.multiplatform" -version.ref = "kotlin-stdlib" - -[plugins.android-library] -id = "com.android.library" -version.ref = "android-gradle" - -[plugins.kotest] -id = "io.kotest.multiplatform" -version.ref = "kotest" - -[plugins.dokka] -id = "org.jetbrains.dokka" -version.ref = "dokka" - -[plugins.gradle-publish] -id = "com.gradle.plugin-publish" -version = "1.2.1" +kotlin-stdlib = "2.1.10" +android-gradle = {strictly = "8.7.3"} +kotest = "6.0.0.M1" +dokka = "2.0.0" + +[libraries] +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-stdlib" } +kotest-engine = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" } +kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } +kotest-junit-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } +kotest-junit-reporter = { module = "io.kotest:kotest-extensions-junitxml", version.ref = "kotest" } +kotest-android-runner = { module = "br.com.colman:kotest-runner-android", version = "1.1.1" } +androidx-test-runner = { module = "androidx.test:runner", version = "1.6.2" } +dokka-base = { module = "org.jetbrains.dokka:dokka-base", version.ref = "dokka" } + +[plugins] +kotlin-mpp = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin-stdlib" } +android-library = { id = "com.android.library", version.ref = "android-gradle" } +kotest = { id = "io.kotest.multiplatform", version.ref = "kotest" } +dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } [bundles] kotest-core = [ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 34592 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 delta 34555 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23..94113f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 6689b85..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/ktreesitter-plugin/build.gradle.kts b/ktreesitter-plugin/build.gradle.kts index c22bc08..9282428 100644 --- a/ktreesitter-plugin/build.gradle.kts +++ b/ktreesitter-plugin/build.gradle.kts @@ -20,7 +20,6 @@ java { targetCompatibility = JavaVersion.VERSION_17 } -@Suppress("UnstableApiUsage") gradlePlugin { vcsUrl = "https://github.com/tree-sitter/kotlin-tree-sitter" website = "https://github.com/tree-sitter/kotlin-tree-sitter/tree/master/ktreesitter-plugin" diff --git a/ktreesitter/CMakeLists.txt b/ktreesitter/CMakeLists.txt index fdc786f..0728510 100644 --- a/ktreesitter/CMakeLists.txt +++ b/ktreesitter/CMakeLists.txt @@ -11,14 +11,14 @@ set(CMAKE_C_STANDARD 11) if(MSVC) add_compile_options(/W3 /wd4244) -else(MSVC) +else() set(CMAKE_C_VISIBILITY_PRESET hidden) add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror=incompatible-pointer-types -Werror=implicit-function-declaration) -endif(MSVC) +endif() include_directories(${JNI_INCLUDE_DIRS} ../tree-sitter/lib/src diff --git a/ktreesitter/build.gradle.kts b/ktreesitter/build.gradle.kts index b2acb3b..e223094 100644 --- a/ktreesitter/build.gradle.kts +++ b/ktreesitter/build.gradle.kts @@ -1,9 +1,7 @@ import java.io.OutputStream.nullOutputStream -import java.net.URI import org.gradle.internal.os.OperatingSystem import org.gradle.kotlin.dsl.support.useToRun -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.DokkaBaseConfiguration +import org.jetbrains.dokka.gradle.tasks.DokkaGeneratePublicationTask import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.CInteropProcess @@ -117,7 +115,6 @@ android { defaultConfig { minSdk = (property("sdk.version.min") as String).toInt() ndk { - moduleName = "ktreesitter" //noinspection ChromeOsAbiSupport abiFilters += setOf("x86_64", "arm64-v8a", "armeabi-v7a") } @@ -214,31 +211,33 @@ signing { sign(publishing.publications) } -tasks.dokkaHtml { +dokka { val tmpDir = layout.buildDirectory.get().dir("tmp") val ref = System.getenv("GITHUB_SHA")?.subSequence(0, 7) ?: "master" val url = "https://github.com/tree-sitter/kotlin-tree-sitter/blob/$ref/ktreesitter" - inputs.file(file("README.md")) - moduleName.set("KTreeSitter") - pluginConfiguration { + pluginsConfiguration.html { footerMessage = "© 2024 tree-sitter" homepageLink = "https://tree-sitter.github.io/tree-sitter/" - customAssets = listOf(rootDir.resolve("gradle/logo-icon.svg")) + customAssets.from(rootDir.resolve("gradle/logo-icon.svg")) } dokkaSourceSets.configureEach { jdkVersion.set(17) - noStdlibLink.set(true) includes.from(tmpDir.file("README.md")) - externalDocumentationLink("https://kotlinlang.org/api/core/") sourceLink { + remoteUrl(url) localDirectory.set(projectDir) - remoteUrl.set(URI.create(url).toURL()) } } +} + +tasks.withType { + val tmpDir = layout.buildDirectory.get().dir("tmp") + + inputs.file("README.md") doFirst { copy { @@ -264,6 +263,8 @@ tasks.withType().configureEach { } } +// TODO: replace with cmake +@Suppress("DEPRECATION") tasks.withType().configureEach { if (name.startsWith("cinteropTest")) return@configureEach diff --git a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt index afa9dfc..59c34f1 100644 --- a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt +++ b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt @@ -80,7 +80,7 @@ actual class LookaheadIterator @Throws(IllegalArgumentException::class) internal override fun close() = delete(self) - override fun computeNext() = if (nativeNext()) { + actual override fun computeNext() = if (nativeNext()) { setNext(Symbol(currentSymbol, currentSymbolName)) } else { done() diff --git a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Query.kt b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Query.kt index 2a1d173..8bc899d 100644 --- a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Query.kt +++ b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Query.kt @@ -33,20 +33,20 @@ actual class Query @Throws(QueryError::class) actual constructor( /** The number of patterns in the query. */ @get:JvmName("getPatternCount") actual val patternCount: UInt - @FastNative external get + get() = nativePatternCount().toUInt() /** The number of captures in the query. */ @get:JvmName("getCaptureCount") actual val captureCount: UInt - @FastNative external get + get() = nativeCaptureCount().toUInt() init { RefCleaner(this, CleanAction(self, cursor)) - predicates = List(patternCount.toInt()) { mutableListOf() } - settingList = List(patternCount.toInt()) { mutableMapOf() } - assertionList = List(patternCount.toInt()) { mutableMapOf() } - captureNames = MutableList(captureCount.toInt()) { + predicates = List(nativePatternCount()) { mutableListOf() } + settingList = List(nativePatternCount()) { mutableMapOf() } + assertionList = List(nativeCaptureCount()) { mutableMapOf() } + captureNames = MutableList(nativeCaptureCount()) { checkNotNull(captureNameForId(it)) { "Failed to get capture name at index $it" } @@ -530,6 +530,12 @@ actual class Query @Throws(QueryError::class) actual constructor( override fun close() = delete(self, cursor) + @FastNative + private external fun nativePatternCount(): Int + + @FastNative + private external fun nativeCaptureCount(): Int + @FastNative private external fun stringCount(): Int diff --git a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt index 46bb316..58b7339 100644 --- a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt +++ b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt @@ -42,6 +42,8 @@ expect class LookaheadIterator : AbstractIterator { /** Iterate over the symbol names. */ fun symbolNames(): Sequence + override fun computeNext() + /** A class that pairs a symbol ID with its name. */ class Symbol(id: UShort, name: String) { val id: UShort diff --git a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryCapture.kt b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryCapture.kt index a11578f..fb618ac 100644 --- a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryCapture.kt +++ b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryCapture.kt @@ -8,6 +8,7 @@ import kotlin.jvm.JvmName * @property node The captured node. * @property name The name of the capture. */ +@ConsistentCopyVisibility data class QueryCapture internal constructor( @get:JvmName("node") val node: Node, @get:JvmName("name") val name: String diff --git a/ktreesitter/src/jni/query.c b/ktreesitter/src/jni/query.c index 684a4d0..5f28593 100644 --- a/ktreesitter/src/jni/query.c +++ b/ktreesitter/src/jni/query.c @@ -111,12 +111,12 @@ void query_delete CRITICAL_ARGS(jlong query, jlong cursor) { ts_query_cursor_delete((TSQueryCursor *)cursor); } -jint query_get_pattern_count(JNIEnv *env, jobject this) { +jint query_native_pattern_count(JNIEnv *env, jobject this) { TSQuery *self = GET_POINTER(TSQuery, this, Query_self); return (jint)ts_query_pattern_count(self); } -jint query_get_capture_count(JNIEnv *env, jobject this) { +jint query_native_capture_count(JNIEnv *env, jobject this) { TSQuery *self = GET_POINTER(TSQuery, this, Query_self); return (jint)ts_query_capture_count(self); } @@ -347,8 +347,8 @@ const JNINativeMethod Query_methods[] = { {"init", "(JLjava/lang/String;)J", (void *)&query_init}, {"cursor", "()J", (void *)&query_cursor}, {"delete", "(JJ)V", (void *)&query_delete}, - {"getPatternCount", "()I", (void *)&query_get_pattern_count}, - {"getCaptureCount", "()I", (void *)&query_get_capture_count}, + {"nativePatternCount", "()I", (void *)&query_native_pattern_count}, + {"nativeCaptureCount", "()I", (void *)&query_native_capture_count}, {"getTimeoutMicros", "()J", (void *)&query_get_timeout_micros}, {"setTimeoutMicros", "(J)V", (void *)&query_set_timeout_micros}, {"getMatchLimit", "()I", (void *)&query_get_match_limit}, diff --git a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt index 496caf3..51692a2 100644 --- a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt +++ b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt @@ -71,7 +71,7 @@ actual class LookaheadIterator @Throws(IllegalArgumentException::class) internal } } - override fun computeNext() = if (nativeNext()) { + actual override fun computeNext() = if (nativeNext()) { setNext(Symbol(currentSymbol, currentSymbolName)) } else { done() diff --git a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Query.kt b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Query.kt index e073be7..e228bcd 100644 --- a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Query.kt +++ b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Query.kt @@ -27,20 +27,20 @@ actual class Query @Throws(QueryError::class) actual constructor( /** The number of patterns in the query. */ @get:JvmName("getPatternCount") actual val patternCount: UInt - external get + get() = nativePatternCount().toUInt() /** The number of captures in the query. */ @get:JvmName("getCaptureCount") actual val captureCount: UInt - external get + get() = nativeCaptureCount().toUInt() init { RefCleaner(this, CleanAction(self, cursor)) - predicates = List(patternCount.toInt()) { mutableListOf() } - settingList = List(patternCount.toInt()) { mutableMapOf() } - assertionList = List(patternCount.toInt()) { mutableMapOf() } - captureNames = MutableList(captureCount.toInt()) { + predicates = List(nativePatternCount()) { mutableListOf() } + settingList = List(nativePatternCount()) { mutableMapOf() } + assertionList = List(nativePatternCount()) { mutableMapOf() } + captureNames = MutableList(nativeCaptureCount()) { checkNotNull(captureNameForId(it)) { "Failed to get capture name at index $it" } @@ -514,6 +514,10 @@ actual class Query @Throws(QueryError::class) actual constructor( override fun toString() = "Query(language=$language, source=$source)" + private external fun nativePatternCount(): Int + + private external fun nativeCaptureCount(): Int + private external fun stringCount(): Int private external fun exec(node: Node) diff --git a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt index 1b1c8ec..b542d5e 100644 --- a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt +++ b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt @@ -79,7 +79,7 @@ actual class LookaheadIterator @Throws(IllegalArgumentException::class) internal } } - override fun computeNext() = if (ts_lookahead_iterator_next(self)) { + actual override fun computeNext() = if (ts_lookahead_iterator_next(self)) { val id = ts_lookahead_iterator_current_symbol(self) val name = ts_lookahead_iterator_current_symbol_name(self) setNext(Symbol(id, name!!.toKString())) diff --git a/languages/java/build.gradle.kts b/languages/java/build.gradle.kts index e90ae26..226deb7 100644 --- a/languages/java/build.gradle.kts +++ b/languages/java/build.gradle.kts @@ -60,7 +60,7 @@ kotlin { }.forEach { target -> target.compilations.configureEach { cinterops.create(grammar.interopName.get()) { - defFileProperty.set(generateTask.interopFile.asFile) + definitionFile.set(generateTask.interopFile) includeDirs.allHeaders(grammarDir.resolve("bindings/c")) extraOpts("-libraryPath", libsDir.dir(konanTarget.name)) tasks.getByName(interopProcessingTaskName).mustRunAfter(generateTask) @@ -102,7 +102,6 @@ android { defaultConfig { minSdk = (property("sdk.version.min") as String).toInt() ndk { - moduleName = grammar.libraryName.get() //noinspection ChromeOsAbiSupport abiFilters += setOf("x86_64", "arm64-v8a", "armeabi-v7a") } @@ -121,6 +120,8 @@ android { } } +// TODO: replace with cmake +@Suppress("DEPRECATION") tasks.withType().configureEach { if (name.startsWith("cinteropTest")) return@configureEach From ad6588ab896f8ca3e13dac92d04a5bb66fbc0ed6 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Tue, 18 Feb 2025 08:53:04 +0200 Subject: [PATCH 2/5] feat(plugin): infer source files --- ktreesitter-plugin/README.md | 5 ---- .../ktreesitter/plugin/GrammarFilesTask.java | 27 ++++++++----------- .../ktreesitter/plugin/GrammarPlugin.java | 1 - .../src/main/resources/CMakeLists.txt.in | 6 ++--- languages/java/build.gradle.kts | 13 +++++---- 5 files changed, 20 insertions(+), 32 deletions(-) diff --git a/ktreesitter-plugin/README.md b/ktreesitter-plugin/README.md index 0fdc9f3..21c6421 100644 --- a/ktreesitter-plugin/README.md +++ b/ktreesitter-plugin/README.md @@ -35,10 +35,5 @@ grammar { className = "TreeSitterJava" // The name of the package packageName = "io.github.treesitter.ktreesitter.java" - // The source files of the grammar - files = arrayOf( - baseDir.get().resolve("src/parser.c"), - // baseDir.get().resolve("src/scanner.c") - ) } ``` diff --git a/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarFilesTask.java b/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarFilesTask.java index bd35b28..e443c62 100644 --- a/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarFilesTask.java +++ b/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarFilesTask.java @@ -6,6 +6,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; @@ -25,8 +26,6 @@ public abstract class GrammarFilesTask extends DefaultTask { private String grammarName; - private File[] grammarFiles; - private String interopName; private String libraryName; @@ -63,18 +62,6 @@ public final void setGrammarName(String grammarName) { this.grammarName = grammarName; } - /** Get the source files of the grammar. */ - @InputFiles - @PathSensitive(PathSensitivity.RELATIVE) - public final File[] getGrammarFiles() { - return grammarFiles; - } - - /** Set the source files of the grammar. */ - public final void setGrammarFiles(File[] grammarFiles) { - this.grammarFiles = grammarFiles; - } - /** Get the name of the C interop def file. */ @Input public final String getInteropName() { @@ -281,9 +268,8 @@ jniPrefix, jniTransform(name), name private void generateCmakeLists(Path srcDir) throws GradleException { var jniBinding = srcDir.resolve("jni").resolve("binding.c"); - var files = Arrays.stream(grammarFiles).map(file -> relative(file.toPath()).toString()); var includeDir = relative(grammarDir.toPath().resolve("bindings/c")); - var sources = relative(jniBinding) + " " + String.join(" ", files.toList()); + var sources = String.format("%s %s", relative(jniBinding), srcFiles()); var template = readResource("CMakeLists.txt.in") .replace("@LIBRARY@", libraryName) .replace("@INCLUDE@", includeDir.toString()) @@ -305,6 +291,15 @@ private Path relative(Path file) { return getGeneratedSrc().get().getAsFile().toPath().getParent().relativize(file); } + private String srcFiles() { + var grammarSrcDir = grammarDir.toPath().resolve("src"); + var scannerFile = grammarSrcDir.resolve("scanner.c"); + if (!scannerFile.toFile().exists()) { + return grammarSrcDir.resolve("parser.c").toString(); + } + return String.format("%s %s", grammarSrcDir.resolve("parser.c"), scannerFile); + } + private String readResource(String file) throws GradleException { try (var stream = getClass().getResourceAsStream("/" + file)) { var bytes = Objects.requireNonNull(stream).readAllBytes(); diff --git a/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarPlugin.java b/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarPlugin.java index ef6953a..0bfc12f 100644 --- a/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarPlugin.java +++ b/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarPlugin.java @@ -30,7 +30,6 @@ public void apply(Project project) { project.getTasks().register("generateGrammarFiles", GrammarFilesTask.class, it -> { it.setGrammarDir(extension.getBaseDir().get()); it.setGrammarName(extension.getGrammarName().get()); - it.setGrammarFiles(extension.getFiles().get()); it.setInteropName(extension.getInteropName().get()); it.setLibraryName(extension.getLibraryName().get()); it.setPackageName(extension.getPackageName().get()); diff --git a/ktreesitter-plugin/src/main/resources/CMakeLists.txt.in b/ktreesitter-plugin/src/main/resources/CMakeLists.txt.in index 067086f..67e8422 100644 --- a/ktreesitter-plugin/src/main/resources/CMakeLists.txt.in +++ b/ktreesitter-plugin/src/main/resources/CMakeLists.txt.in @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.12.0) -project(ktreesitter-java LANGUAGES C) +project(@LIBRARY@ LANGUAGES C) find_package(JNI REQUIRED) @@ -10,12 +10,12 @@ set(CMAKE_C_STANDARD 11) if(MSVC) add_compile_options(/W3 /wd4244) -else(MSVC) +else() set(CMAKE_C_VISIBILITY_PRESET hidden) add_compile_options(-Wall -Wextra -Wno-unused-parameter -Werror=implicit-function-declaration) -endif(MSVC) +endif() include_directories(${JNI_INCLUDE_DIRS} @INCLUDE@) diff --git a/languages/java/build.gradle.kts b/languages/java/build.gradle.kts index 226deb7..15cd3ed 100644 --- a/languages/java/build.gradle.kts +++ b/languages/java/build.gradle.kts @@ -28,10 +28,6 @@ grammar { grammarName = project.name className = "TreeSitterJava" packageName = "io.github.treesitter.ktreesitter.java" - files = arrayOf( - // grammarDir.resolve("src/scanner.c"), - grammarDir.resolve("src/parser.c") - ) } val generateTask = tasks.generateGrammarFiles.get() @@ -125,13 +121,16 @@ android { tasks.withType().configureEach { if (name.startsWith("cinteropTest")) return@configureEach - val grammarFiles = grammar.files.get() + val srcDir = grammarDir.resolve("src") + val grammarFiles = + if (!srcDir.resolve("scanner.c").isFile) arrayOf(srcDir.resolve("parser.c")) + else arrayOf(srcDir.resolve("parser.c"), srcDir.resolve("scanner.c")) val grammarName = grammar.grammarName.get() val runKonan = File(konanHome.get()).resolve("bin") .resolve(if (os.isWindows) "run_konan.bat" else "run_konan").path val libFile = libsDir.dir(konanTarget.name).file("libtree-sitter-$grammarName.a").asFile val objectFiles = grammarFiles.map { - grammarDir.resolve(it.nameWithoutExtension + ".o").path + srcDir.resolve(it.nameWithoutExtension + ".o").path }.toTypedArray() val loader = PlatformManager(konanHome.get(), false, konanDataDir.orNull).loader(konanTarget) @@ -141,7 +140,7 @@ tasks.withType().configureEach { val argsFile = File.createTempFile("args", null) argsFile.deleteOnExit() argsFile.writer().useToRun { - write("-I" + grammarDir.resolve("src").unixPath + "\n") + write("-I" + srcDir.unixPath + "\n") write("-DTREE_SITTER_HIDE_SYMBOLS\n") write("-fvisibility=hidden\n") write("-std=c11\n") From 04eb3c9933fcc221f22e65a56b19957f698dc2ff Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Tue, 18 Feb 2025 08:53:45 +0200 Subject: [PATCH 3/5] build: fix native publication --- ktreesitter/build.gradle.kts | 50 +++++++++++++++------------------ languages/java/build.gradle.kts | 48 ++++++++++++++----------------- 2 files changed, 43 insertions(+), 55 deletions(-) diff --git a/ktreesitter/build.gradle.kts b/ktreesitter/build.gradle.kts index e223094..f7ee98c 100644 --- a/ktreesitter/build.gradle.kts +++ b/ktreesitter/build.gradle.kts @@ -4,13 +4,25 @@ import org.gradle.kotlin.dsl.support.useToRun import org.jetbrains.dokka.gradle.tasks.DokkaGeneratePublicationTask import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.tasks.CInteropProcess import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile -import org.jetbrains.kotlin.konan.target.PlatformManager inline val File.unixPath: String get() = if (!os.isWindows) path else path.replace("\\", "/") +fun KotlinNativeTarget.treesitter() { + compilations.configureEach { + cinterops.create("treesitter") { + val srcDir = treesitterDir.resolve("lib/src") + val includeDir = treesitterDir.resolve("lib/include") + includeDirs.allHeaders(srcDir, includeDir) + includeDirs.headerFilterOnly(includeDir) + extraOpts("-libraryPath", libsDir.dir(konanTarget.name)) + } + } +} + val os: OperatingSystem = OperatingSystem.current() val libsDir = layout.buildDirectory.get().dir("libs") val treesitterDir = rootDir.resolve("tree-sitter") @@ -40,30 +52,15 @@ kotlin { publishLibraryVariants("release") } - when { - os.isLinux -> listOf(linuxX64(), linuxArm64()) - os.isWindows -> listOf(mingwX64()) - os.isMacOsX -> listOf( - macosArm64(), - macosX64(), - iosArm64(), - iosSimulatorArm64() - ) - else -> { - val arch = System.getProperty("os.arch") - throw GradleException("Unsupported platform: $os ($arch)") - } - }.forEach { target -> - target.compilations.configureEach { - cinterops.create("treesitter") { - val srcDir = treesitterDir.resolve("lib/src") - val includeDir = treesitterDir.resolve("lib/include") - includeDirs.allHeaders(srcDir, includeDir) - includeDirs.headerFilterOnly(includeDir) - extraOpts("-libraryPath", libsDir.dir(konanTarget.name)) - } - } - } + linuxX64 { treesitter() } + linuxArm64 { treesitter() } + mingwX64 { treesitter() } + macosArm64 { treesitter() } + macosX64 { treesitter() } + iosArm64 { treesitter() } + iosSimulatorArm64 { treesitter() } + + applyDefaultHierarchyTemplate() jvmToolchain(17) @@ -274,11 +271,8 @@ tasks.withType().configureEach { "${konanTarget.family.staticPrefix}tree-sitter.${konanTarget.family.staticSuffix}" ).asFile val objectFile = treesitterDir.resolve("lib.o") - val loader = PlatformManager(konanHome.get(), false, konanDataDir.orNull).loader(konanTarget) doFirst { - if (!File(loader.absoluteTargetToolchain).isDirectory) loader.downloadDependencies() - val argsFile = File.createTempFile("args", null) argsFile.deleteOnExit() argsFile.writer().useToRun { diff --git a/languages/java/build.gradle.kts b/languages/java/build.gradle.kts index 15cd3ed..3243130 100644 --- a/languages/java/build.gradle.kts +++ b/languages/java/build.gradle.kts @@ -2,12 +2,23 @@ import java.io.OutputStream.nullOutputStream import org.gradle.internal.os.OperatingSystem import org.gradle.kotlin.dsl.support.useToRun import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.tasks.CInteropProcess -import org.jetbrains.kotlin.konan.target.PlatformManager inline val File.unixPath: String get() = if (!os.isWindows) path else path.replace("\\", "/") +fun KotlinNativeTarget.treesitterJava() { + compilations.configureEach { + cinterops.create("treesitterJava") { + definitionFile.set(generateTask.interopFile) + includeDirs.allHeaders(grammarDir.resolve("bindings/c")) + extraOpts("-libraryPath", libsDir.dir(konanTarget.name)) + tasks.getByName(interopProcessingTaskName).mustRunAfter(generateTask) + } + } +} + val os: OperatingSystem = OperatingSystem.current() val libsDir = layout.buildDirectory.get().dir("libs") val grammarDir = projectDir.resolve("tree-sitter-java") @@ -40,29 +51,15 @@ kotlin { publishLibraryVariants("release") } - when { - os.isLinux -> listOf(linuxX64(), linuxArm64()) - os.isWindows -> listOf(mingwX64()) - os.isMacOsX -> listOf( - macosArm64(), - macosX64(), - iosArm64(), - iosSimulatorArm64() - ) - else -> { - val arch = System.getProperty("os.arch") - throw GradleException("Unsupported platform: $os ($arch)") - } - }.forEach { target -> - target.compilations.configureEach { - cinterops.create(grammar.interopName.get()) { - definitionFile.set(generateTask.interopFile) - includeDirs.allHeaders(grammarDir.resolve("bindings/c")) - extraOpts("-libraryPath", libsDir.dir(konanTarget.name)) - tasks.getByName(interopProcessingTaskName).mustRunAfter(generateTask) - } - } - } + linuxX64 { treesitterJava() } + linuxArm64 { treesitterJava() } + mingwX64 { treesitterJava() } + macosArm64 { treesitterJava() } + macosX64 { treesitterJava() } + iosArm64 { treesitterJava() } + iosSimulatorArm64 { treesitterJava() } + + applyDefaultHierarchyTemplate() jvmToolchain(17) @@ -132,11 +129,8 @@ tasks.withType().configureEach { val objectFiles = grammarFiles.map { srcDir.resolve(it.nameWithoutExtension + ".o").path }.toTypedArray() - val loader = PlatformManager(konanHome.get(), false, konanDataDir.orNull).loader(konanTarget) doFirst { - if (!File(loader.absoluteTargetToolchain).isDirectory) loader.downloadDependencies() - val argsFile = File.createTempFile("args", null) argsFile.deleteOnExit() argsFile.writer().useToRun { From 5722cf0aaf742db23318193287294ba0aab4b0d4 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Tue, 18 Feb 2025 08:55:02 +0200 Subject: [PATCH 4/5] build: update submodules --- .gitmodules | 2 +- ktreesitter/CMakeLists.txt | 2 +- ktreesitter/build.gradle.kts | 2 ++ ktreesitter/src/nativeInterop/cinterop/treesitter.def | 2 +- languages/java/tree-sitter-java | 2 +- tree-sitter | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index c059394..581ed41 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "tree-sitter"] path = tree-sitter url = https://github.com/tree-sitter/tree-sitter - branch = release-0.24 + branch = release-0.25 [submodule "tree-sitter-java"] path = languages/java/tree-sitter-java diff --git a/ktreesitter/CMakeLists.txt b/ktreesitter/CMakeLists.txt index 0728510..b87d2b9 100644 --- a/ktreesitter/CMakeLists.txt +++ b/ktreesitter/CMakeLists.txt @@ -24,7 +24,7 @@ include_directories(${JNI_INCLUDE_DIRS} ../tree-sitter/lib/src ../tree-sitter/lib/include) -add_compile_definitions(TREE_SITTER_HIDE_SYMBOLS) +add_compile_definitions(TREE_SITTER_HIDE_SYMBOLS _DEFAULT_SOURCE _POSIX_C_SOURCE=200112L) add_library(ktreesitter SHARED ./src/jni/language.c diff --git a/ktreesitter/build.gradle.kts b/ktreesitter/build.gradle.kts index f7ee98c..85b1f3c 100644 --- a/ktreesitter/build.gradle.kts +++ b/ktreesitter/build.gradle.kts @@ -279,6 +279,8 @@ tasks.withType().configureEach { write("-I" + treesitterDir.resolve("lib/src").unixPath + "\n") write("-I" + treesitterDir.resolve("lib/include").unixPath + "\n") write("-DTREE_SITTER_HIDE_SYMBOLS\n") + write("-D_DEFAULT_SOURCE\n") + write("-D_POSIX_C_SOURCE=200112L\n") write("-fvisibility=hidden\n") write("-std=c11\n") write("-O2\n") diff --git a/ktreesitter/src/nativeInterop/cinterop/treesitter.def b/ktreesitter/src/nativeInterop/cinterop/treesitter.def index 3e2a4fc..7789be2 100644 --- a/ktreesitter/src/nativeInterop/cinterop/treesitter.def +++ b/ktreesitter/src/nativeInterop/cinterop/treesitter.def @@ -1,7 +1,7 @@ package = io.github.treesitter.ktreesitter.internal headers = tree_sitter/api.h alloc.h headerFilter = tree_sitter/api.h -compilerOpts = -DTREE_SITTER_HIDE_SYMBOLS +compilerOpts = -DTREE_SITTER_HIDE_SYMBOLS -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200112L staticLibraries = libtree-sitter.a strictEnums = \ TSLogType \ diff --git a/languages/java/tree-sitter-java b/languages/java/tree-sitter-java index 94703d5..a7db522 160000 --- a/languages/java/tree-sitter-java +++ b/languages/java/tree-sitter-java @@ -1 +1 @@ -Subproject commit 94703d5a6bed02b98e438d7cad1136c01a60ba2c +Subproject commit a7db5227ec40fcfe94489559d8c9bc7c8181e25a diff --git a/tree-sitter b/tree-sitter index 5e8760b..8b5c63b 160000 --- a/tree-sitter +++ b/tree-sitter @@ -1 +1 @@ -Subproject commit 5e8760bf462ce7b19b3d2396d5b7860f3906a297 +Subproject commit 8b5c63bffae4c1948106eb0a8d722bf2a9141ce5 From e3e6c908b01b3adf26be922acd6775205fc3b54e Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Sun, 23 Feb 2025 13:29:51 +0200 Subject: [PATCH 5/5] feat!: update API to 0.25 --- .../treesitter/ktreesitter/LanguageTest.kt | 30 +++-- .../github/treesitter/ktreesitter/NodeTest.kt | 15 +-- .../treesitter/ktreesitter/ParserTest.kt | 15 +-- .../treesitter/ktreesitter/InputEncoding.kt | 14 +++ .../github/treesitter/ktreesitter/Language.kt | 80 +++++++++++++- .../io/github/treesitter/ktreesitter/Node.kt | 28 +++-- .../github/treesitter/ktreesitter/Parser.kt | 23 ++-- .../treesitter/ktreesitter/InputEncoding.kt | 8 ++ .../github/treesitter/ktreesitter/Language.kt | 53 ++++++++- .../io/github/treesitter/ktreesitter/Node.kt | 23 ++-- .../github/treesitter/ktreesitter/Parser.kt | 43 +++++--- .../treesitter/ktreesitter/LanguageTest.kt | 30 +++-- .../github/treesitter/ktreesitter/NodeTest.kt | 15 +-- .../treesitter/ktreesitter/ParserTest.kt | 15 +-- .../github/treesitter/ktreesitter/TreeTest.kt | 4 +- ktreesitter/src/jni/language.c | 61 +++++++++- ktreesitter/src/jni/module.c | 23 ++++ ktreesitter/src/jni/node.c | 32 ++++-- ktreesitter/src/jni/parser.c | 84 +++++++++++--- ktreesitter/src/jni/utils.h | 14 ++- .../treesitter/ktreesitter/InputEncoding.kt | 14 +++ .../github/treesitter/ktreesitter/Language.kt | 79 ++++++++++++- .../io/github/treesitter/ktreesitter/Node.kt | 25 +++-- .../github/treesitter/ktreesitter/Parser.kt | 23 ++-- .../src/nativeInterop/cinterop/treesitter.def | 2 +- .../treesitter/ktreesitter/InputEncoding.kt | 16 +++ .../github/treesitter/ktreesitter/Language.kt | 104 +++++++++++++++--- .../io/github/treesitter/ktreesitter/Node.kt | 26 +++-- .../github/treesitter/ktreesitter/Parser.kt | 53 ++++++--- .../io/github/treesitter/ktreesitter/Utils.kt | 2 + 30 files changed, 763 insertions(+), 191 deletions(-) create mode 100644 ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt create mode 100644 ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt create mode 100644 ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt create mode 100644 ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt diff --git a/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt b/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt index 80672c8..289e2d7 100644 --- a/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt +++ b/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt @@ -2,9 +2,9 @@ package io.github.treesitter.ktreesitter import br.com.colman.kotest.KotestRunnerAndroid import io.github.treesitter.ktreesitter.java.TreeSitterJava -import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.* +import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.comparables.* import io.kotest.matchers.nulls.* import io.kotest.matchers.string.* @@ -15,8 +15,8 @@ import org.junit.runner.RunWith class LanguageTest : FunSpec({ val language = Language(TreeSitterJava.language()) - test("version") { - language.version shouldBe 14U + test("abiVersion") { + language.abiVersion shouldBe 14U } test("symbolCount") { @@ -31,6 +31,19 @@ class LanguageTest : FunSpec({ language.fieldCount shouldBeGreaterThan 1U } + test("name") { + language.name.shouldBeNull() + } + + test("metadata") { + language.metadata.shouldBeNull() + } + + @OptIn(ExperimentalUnsignedTypes::class) + test("supertypes") { + language.supertypes.shouldBeEmpty() + } + test("symbolName()") { language.symbolName(1U) shouldBe "identifier" } @@ -40,6 +53,11 @@ class LanguageTest : FunSpec({ language.symbolForName("program", true) shouldBeGreaterThan 0U } + @OptIn(ExperimentalUnsignedTypes::class) + test("subtypes") { + language.subtypes(1U).shouldBeEmpty() + } + test("isNamed()") { language.isNamed(1U) shouldBe true } @@ -72,10 +90,6 @@ class LanguageTest : FunSpec({ lookahead.language shouldBe language } - test("query()") { - shouldNotThrowAny { language.query("(program) @root") } - } - test("equals()") { Language(TreeSitterJava.language()) shouldBe language.copy() } @@ -85,6 +99,6 @@ class LanguageTest : FunSpec({ } test("toString()") { - language.toString() shouldMatch Regex("""Language\(id=0x[0-9a-f]+, version=14\)""") + language.toString() shouldMatch Regex("""Language\(id=0x[0-9a-f]+, abiVersion=14\)""") } }) diff --git a/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt b/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt index dbcd3bc..f08e1a5 100644 --- a/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt +++ b/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt @@ -149,6 +149,14 @@ class NodeTest : FunSpec({ shouldThrow { rootNode.namedChild(1U) } } + test("firstChildForByte()") { + rootNode.firstChildForByte(10U)!!.type shouldBe "class_declaration" + } + + test("firstNamedChildForByte()") { + rootNode.firstNamedChildForByte(10U)!!.type shouldBe "class_declaration" + } + test("childByFieldId()") { rootNode.childByFieldId(0U).shouldBeNull() } @@ -177,13 +185,6 @@ class NodeTest : FunSpec({ rootNode.child(0U)!!.fieldNameForNamedChild(2U).shouldBeNull() } - @Suppress("DEPRECATION") - test("childContainingDescendant()") { - val descendant = rootNode.child(0U)!!.child(0U)!! - val child = rootNode.childContainingDescendant(descendant) - child?.type shouldBe "class_declaration" - } - test("childWithDescendant()") { val descendant = rootNode.child(0U)!! val child = rootNode.childWithDescendant(descendant) diff --git a/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt b/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt index 995c139..a27e10e 100644 --- a/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt +++ b/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt @@ -30,12 +30,6 @@ class ParserTest : FunSpec({ parser.includedRanges shouldHaveSingleElement range } - test("timeoutMicros") { - parser.timeoutMicros shouldBe 0UL - parser.timeoutMicros = 10000UL - parser.timeoutMicros shouldBe 10000UL - } - test("logger") { shouldNotThrowAnyUnit { parser.logger = { _, _ -> @@ -61,12 +55,12 @@ class ParserTest : FunSpec({ } // UTF-16 - source = "var java = \"💩\"" - tree = parser.parse(source) - tree.text()?.subSequence(12, 14) shouldBe "\uD83D\uDCA9" + source = "\uFEFFvar java = \"💩\"" + tree = parser.parse(source, encoding = InputEncoding.UTF_16BE) + tree.text()?.subSequence(13, 15) shouldBe "\uD83D\uDCA9" } - test("parse(callback)") { + test("parse(readCallback)") { val source = "class Foo {}" val tree = parser.parse { byte, _ -> val end = minOf(byte.toInt() * 2, source.length) @@ -79,7 +73,6 @@ class ParserTest : FunSpec({ afterTest { (test, _) -> when (test.name.testName) { "includedRanges" -> parser.includedRanges = emptyList() - "timeoutMicros" -> parser.timeoutMicros = 0UL "logger" -> parser.logger = null } } diff --git a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt new file mode 100644 index 0000000..f927ec4 --- /dev/null +++ b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt @@ -0,0 +1,14 @@ +package io.github.treesitter.ktreesitter + +import java.nio.charset.Charset + +/** + * The encoding of the input text. + * + * @since 0.25.0 + */ +actual enum class InputEncoding(val charset: Charset) { + UTF_8(Charsets.UTF_8), + UTF_16LE(Charsets.UTF_16LE), + UTF_16BE(Charsets.UTF_16BE) +} diff --git a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Language.kt b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Language.kt index 4000182..2147011 100644 --- a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Language.kt +++ b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Language.kt @@ -6,12 +6,13 @@ import dalvik.annotation.optimization.FastNative /** * A class that defines how to parse a particular language. * - * When a [Language] is generated by the Tree-sitter CLI, it is assigned - * an ABI [version] number that corresponds to the current CLI version. + * When a [Language] is generated by the Tree-sitter CLI, it is assigned an + * [ABI version][abiVersion] number that corresponds to the current CLI version. * * @constructor Create a new instance from the given language pointer. * @param language A pointer to a `TSLanguage` cast to [Long]. - * @throws [IllegalArgumentException] If the pointer is invalid or the [version] is incompatible. + * @throws [IllegalArgumentException] + * If the pointer is invalid or the [version][abiVersion] is incompatible. */ actual class Language @Throws(IllegalArgumentException::class) actual constructor(language: Any) { @JvmField @@ -22,7 +23,17 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo checkVersion() } + /** + * The ABI version number for this language. + * + * @since 0.25.0 + */ + @get:JvmName("getAbiVersion") + actual val abiVersion: UInt + @FastNative external get + /** The ABI version number for this language. */ + @Deprecated("version is deprecated", ReplaceWith("abiVersion"), DeprecationLevel.ERROR) @get:JvmName("getVersion") actual val version: UInt @FastNative external get @@ -42,6 +53,32 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo actual val fieldCount: UInt @FastNative external get + /** + * The name of the language, if available. + * + * @since 0.25.0 + */ + actual val name: String? + @FastNative external get + + /** + * The metadata of the language, if available. + * + * @since 0.25.0 + */ + actual val metadata: Metadata? + @FastNative external get + + /** + * The supertype symbols of the language. + * + * @since 0.25.0 + */ + @OptIn(ExperimentalUnsignedTypes::class) + @get:JvmName("getSupertypes") + actual val supertypes: UShortArray + @FastNative external get + /** * Get another reference to the language. * @@ -59,6 +96,17 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo @JvmName("symbolForName") actual external fun symbolForName(name: String, isNamed: Boolean): UShort + /** + * Get the subtype symbols for the given supertype symbol + * + * @since 0.25.0 + * @see supertypes + */ + @FastNative + @JvmName("subtypes") + @OptIn(ExperimentalUnsignedTypes::class) + actual external fun subtypes(supertype: UShort): UShortArray + /** * Check if the node for the given numerical ID is named * @@ -119,11 +167,12 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo /** * Create a new [Query] from a string containing one or more S-expression - * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax). + * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html). * * @throws [QueryError] If any error occurred while creating the query. */ @Throws(QueryError::class) + @Deprecated("Use the Query constructor instead") actual fun query(source: String) = Query(this, source) actual override fun equals(other: Any?) = @@ -131,12 +180,33 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo actual override fun hashCode() = self.hashCode() - override fun toString() = "Language(id=0x${self.toString(16)}, version=$version)" + override fun toString() = "Language(id=0x${self.toString(16)}, abiVersion=$abiVersion)" @FastNative @Throws(IllegalArgumentException::class) private external fun checkVersion() + /** + * A class containing the [Language] metadata. + * + * @property semanticVersion The [Semantic Version](https://semver.org/) of the [Language]. + */ + @ConsistentCopyVisibility + actual data class Metadata internal actual constructor( + @get:JvmName("getSemanticVersion") + actual val semanticVersion: Triple + ) { + actual override fun toString() = buildString { + append("Metadata(semanticVersion=\"") + append(semanticVersion.first) + append('.') + append(semanticVersion.second) + append('.') + append(semanticVersion.third) + append("\")") + } + } + private companion object { @JvmStatic @CriticalNative diff --git a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Node.kt b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Node.kt index fe7f81a..8608780 100644 --- a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Node.kt +++ b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Node.kt @@ -207,6 +207,26 @@ actual class Node internal constructor( @Throws(IndexOutOfBoundsException::class) actual external fun namedChild(index: UInt): Node? + /** + * Get the node's first child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + @FastNative + @JvmName("firstChildForByte") + actual external fun firstChildForByte(byte: UInt): Node? + + /** + * Get the node's first _named_ child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + @FastNative + @JvmName("firstNamedChildForByte") + actual external fun firstNamedChildForByte(byte: UInt): Node? + /** * Get the node's child with the given field ID, if any. * @@ -249,14 +269,6 @@ actual class Node internal constructor( @Throws(IndexOutOfBoundsException::class) actual external fun fieldNameForNamedChild(index: UInt): String? - /** Get the child of the node that contains the given descendant, if any. */ - @FastNative - @Deprecated( - "This method will not return a direct descendant", - ReplaceWith("childWithDescendant(descendant)", "io.github.treesitter.ktreesitter.Node") - ) - actual external fun childContainingDescendant(descendant: Node): Node? - /** * Get the node that contains the given descendant, if any. * diff --git a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt index 9cf1b3f..54de309 100644 --- a/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt +++ b/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt @@ -50,6 +50,7 @@ actual class Parser actual constructor() : AutoCloseable { */ @get:JvmName("getTimeoutMicros") @set:JvmName("setTimeoutMicros") + @Deprecated("Use the progressCallback in parse()") actual var timeoutMicros: ULong @FastNative external get @@ -82,11 +83,10 @@ actual class Parser actual constructor() : AutoCloseable { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was cancelled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - actual external fun parse(source: String, oldTree: Tree?): Tree + actual external fun parse(source: String, encoding: InputEncoding, oldTree: Tree?): Tree /** * Parse source code from a callback and create a syntax tree. @@ -98,19 +98,22 @@ actual class Parser actual constructor() : AutoCloseable { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was cancelled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - actual external fun parse(oldTree: Tree?, callback: ParseCallback): Tree + actual external fun parse( + encoding: InputEncoding, + oldTree: Tree?, + progressCallback: ParseProgressCallback?, + readCallback: ParseReadCallback + ): Tree /** * Instruct the parser to start the next [parse] from the beginning. * - * If the parser previously failed because of a [timeout][timeoutMicros], - * then by default, it will resume where it left off. If you don't - * want to resume, and instead intend to use this parser to parse - * some other document, you must call this method first. + * If parsing was previously halted, then by default, it will resume where + * it left off. If you don't want to resume, and instead intend to use this + * parser to parse some other document, you must call this method first. */ @FastNative actual external fun reset() diff --git a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt new file mode 100644 index 0000000..4733b06 --- /dev/null +++ b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt @@ -0,0 +1,8 @@ +package io.github.treesitter.ktreesitter + +/** + * The encoding of the input text. + * + * @since 0.25.0 + */ +expect enum class InputEncoding { UTF_8, UTF_16LE, UTF_16BE } diff --git a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Language.kt b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Language.kt index 297e6f7..19ca63c 100644 --- a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Language.kt +++ b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Language.kt @@ -10,7 +10,15 @@ package io.github.treesitter.ktreesitter * @throws [IllegalArgumentException] If the pointer is invalid or the [version] is incompatible. */ expect class Language @Throws(IllegalArgumentException::class) constructor(language: Any) { + /** + * The ABI version number for this language. + * + * @since 0.25.0 + */ + val abiVersion: UInt + /** The ABI version number for this language. */ + @Deprecated("version is deprecated", ReplaceWith("abiVersion"), DeprecationLevel.ERROR) val version: UInt /** The number of distinct node types in this language. */ @@ -22,6 +30,28 @@ expect class Language @Throws(IllegalArgumentException::class) constructor(langu /** The number of distinct field names in this language. */ val fieldCount: UInt + /** + * The name of the language, if available. + * + * @since 0.25.0 + */ + val name: String? + + /** + * The metadata of the language, if available. + * + * @since 0.25.0 + */ + val metadata: Metadata? + + /** + * The supertype symbols of the language. + * + * @since 0.25.0 + */ + @OptIn(ExperimentalUnsignedTypes::class) + val supertypes: UShortArray + /** * Get another reference to the language. * @@ -35,6 +65,15 @@ expect class Language @Throws(IllegalArgumentException::class) constructor(langu /** Get the numerical ID for the given node type. */ fun symbolForName(name: String, isNamed: Boolean): UShort + /** + * Get the subtype symbols for the given supertype symbol + * + * @since 0.25.0 + * @see supertypes + */ + @OptIn(ExperimentalUnsignedTypes::class) + fun subtypes(supertype: UShort): UShortArray + /** * Check if the node for the given numerical ID is named * @@ -82,14 +121,26 @@ expect class Language @Throws(IllegalArgumentException::class) constructor(langu /** * Create a new [Query] from a string containing one or more S-expression - * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax). + * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html). * * @throws [QueryError] If any error occurred while creating the query. */ @Throws(QueryError::class) + @Deprecated("Use the Query constructor instead") fun query(source: String): Query override fun equals(other: Any?): Boolean override fun hashCode(): Int + + /** + * A class containing the [Language] metadata. + * + * @property semanticVersion The [Semantic Version](https://semver.org/) of the [Language]. + */ + class Metadata internal constructor(semanticVersion: Triple) { + val semanticVersion: Triple + + override fun toString(): String + } } diff --git a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Node.kt b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Node.kt index e1f1d3a..f8f37d4 100644 --- a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Node.kt +++ b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Node.kt @@ -154,6 +154,22 @@ expect class Node { @Throws(IndexOutOfBoundsException::class) fun namedChild(index: UInt): Node? + /** + * Get the node's first child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + fun firstChildForByte(byte: UInt): Node? + + /** + * Get the node's first _named_ child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + fun firstNamedChildForByte(byte: UInt): Node? + /** * Get the node's child with the given field ID, if any. * @@ -187,13 +203,6 @@ expect class Node { @Throws(IndexOutOfBoundsException::class) fun fieldNameForNamedChild(index: UInt): String? - /** Get the child of the node that contains the given descendant, if any. */ - @Deprecated( - "This method will not return a direct descendant", - ReplaceWith("childWithDescendant(descendant)", "io.github.treesitter.ktreesitter.Node") - ) - fun childContainingDescendant(descendant: Node): Node? - /** * Get the node that contains the given descendant, if any. * diff --git a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt index 02bf659..5b87bb9 100644 --- a/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt +++ b/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt @@ -5,7 +5,19 @@ package io.github.treesitter.ktreesitter * * The function should return `null` to indicate the end of the document. */ -typealias ParseCallback = (byte: UInt, point: Point) -> CharSequence? +typealias ParseReadCallback = (byte: UInt, point: Point) -> CharSequence? + +/** + * A function that is called during parsing. + * + * The first argument contains the current byte offset and the second + * argument indicates whether the parser has encountered an error. + * + * If the function returns `false`, parsing will halt early. + * + * @since 0.25.0 + */ +typealias ParseProgressCallback = (currentByteOffset: UInt, hasError: Boolean) -> Boolean /** * A function that logs parsing results. @@ -45,14 +57,13 @@ expect class Parser() { * The maximum duration in microseconds that parsing * should be allowed to take before halting. */ + @Deprecated("Use the progressCallback in parse()") var timeoutMicros: ULong /** The logger that the parser will use during parsing. */ @get:Deprecated("The logger can't be called directly.", level = DeprecationLevel.HIDDEN) var logger: LogFunction? - // TODO: add cancellationFlag - /** * Parse a source code string and create a syntax tree. * @@ -63,11 +74,14 @@ expect class Parser() { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was cancelled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - fun parse(source: String, oldTree: Tree? = null): Tree + fun parse( + source: String, + encoding: InputEncoding = InputEncoding.UTF_8, + oldTree: Tree? = null + ): Tree /** * Parse source code from a callback and create a syntax tree. @@ -79,19 +93,22 @@ expect class Parser() { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was cancelled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - fun parse(oldTree: Tree? = null, callback: ParseCallback): Tree + fun parse( + encoding: InputEncoding = InputEncoding.UTF_8, + oldTree: Tree? = null, + progressCallback: ParseProgressCallback? = null, + readCallback: ParseReadCallback + ): Tree /** * Instruct the parser to start the next [parse] from the beginning. * - * If the parser previously failed because of a [timeout][timeoutMicros], - * then by default, it will resume where it left off. If you don't - * want to resume, and instead intend to use this parser to parse - * some other document, you must call this method first. + * If parsing was previously halted, then by default, it will resume where + * it left off. If you don't want to resume, and instead intend to use this + * parser to parse some other document, you must call this method first. */ fun reset() diff --git a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt index dd36a8a..f98a076 100644 --- a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt +++ b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt @@ -1,9 +1,9 @@ package io.github.treesitter.ktreesitter import io.github.treesitter.ktreesitter.java.TreeSitterJava -import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.* +import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.comparables.* import io.kotest.matchers.nulls.* import io.kotest.matchers.string.* @@ -12,8 +12,8 @@ import io.kotest.matchers.types.* class LanguageTest : FunSpec({ val language = Language(TreeSitterJava.language()) - test("version") { - language.version shouldBe 14U + test("abiVersion") { + language.abiVersion shouldBe 14U } test("symbolCount") { @@ -28,6 +28,19 @@ class LanguageTest : FunSpec({ language.fieldCount shouldBeGreaterThan 1U } + test("name") { + language.name.shouldBeNull() + } + + test("metadata") { + language.metadata.shouldBeNull() + } + + @OptIn(ExperimentalUnsignedTypes::class) + test("supertypes") { + language.supertypes.shouldBeEmpty() + } + test("symbolName()") { language.symbolName(1U) shouldBe "identifier" } @@ -37,6 +50,11 @@ class LanguageTest : FunSpec({ language.symbolForName("program", true) shouldBeGreaterThan 0U } + @OptIn(ExperimentalUnsignedTypes::class) + test("subtypes") { + language.subtypes(1U).shouldBeEmpty() + } + test("isNamed()") { language.isNamed(1U) shouldBe true } @@ -69,10 +87,6 @@ class LanguageTest : FunSpec({ lookahead.language shouldBe language } - test("query()") { - shouldNotThrowAny { language.query("(program) @root") } - } - test("equals()") { Language(TreeSitterJava.language()) shouldBe language.copy() } @@ -82,6 +96,6 @@ class LanguageTest : FunSpec({ } test("toString()") { - language.toString() shouldMatch Regex("""Language\(id=0x[0-9a-f]+, version=14\)""") + language.toString() shouldMatch Regex("""Language\(id=0x[0-9a-f]+, abiVersion=14\)""") } }) diff --git a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt index e866bc5..01a401d 100644 --- a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt +++ b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt @@ -146,6 +146,14 @@ class NodeTest : FunSpec({ shouldThrow { rootNode.namedChild(1U) } } + test("firstChildForByte()") { + rootNode.firstChildForByte(10U)!!.type shouldBe "class_declaration" + } + + test("firstNamedChildForByte()") { + rootNode.firstNamedChildForByte(10U)!!.type shouldBe "class_declaration" + } + test("childByFieldId()") { rootNode.childByFieldId(0U).shouldBeNull() } @@ -174,13 +182,6 @@ class NodeTest : FunSpec({ rootNode.child(0U)!!.fieldNameForNamedChild(2U).shouldBeNull() } - @Suppress("DEPRECATION") - test("childContainingDescendant()") { - val descendant = rootNode.child(0U)!!.child(0U)!! - val child = rootNode.childContainingDescendant(descendant) - child?.type shouldBe "class_declaration" - } - test("childWithDescendant()") { val descendant = rootNode.child(0U)!! val child = rootNode.childWithDescendant(descendant) diff --git a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt index 4d668fb..d4f45bc 100644 --- a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt +++ b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt @@ -27,12 +27,6 @@ class ParserTest : FunSpec({ parser.includedRanges shouldHaveSingleElement range } - test("timeoutMicros") { - parser.timeoutMicros shouldBe 0UL - parser.timeoutMicros = 10000UL - parser.timeoutMicros shouldBe 10000UL - } - test("logger") { shouldNotThrowAnyUnit { parser.logger = { _, _ -> @@ -58,12 +52,12 @@ class ParserTest : FunSpec({ } // UTF-16 - source = "var java = \"💩\"" - tree = parser.parse(source) - tree.text()?.subSequence(12, 14) shouldBe "\uD83D\uDCA9" + source = "\uFEFFvar java = \"💩\"" + tree = parser.parse(source, encoding = InputEncoding.UTF_16BE) + tree.text()?.subSequence(13, 15) shouldBe "\uD83D\uDCA9" } - test("parse(callback)") { + test("parse(readCallback)") { val source = "class Foo {}" val tree = parser.parse { byte, _ -> val end = minOf(byte.toInt() * 2, source.length) @@ -76,7 +70,6 @@ class ParserTest : FunSpec({ afterTest { (test, _) -> when (test.name.testName) { "includedRanges" -> parser.includedRanges = emptyList() - "timeoutMicros" -> parser.timeoutMicros = 0UL "logger" -> parser.logger = null } } diff --git a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/TreeTest.kt b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/TreeTest.kt index 43cac43..4d7aeda 100644 --- a/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/TreeTest.kt +++ b/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/TreeTest.kt @@ -41,7 +41,7 @@ class TreeTest : FunSpec({ source = "class Foo2 {}" tree.edit(edit) tree.text().shouldBeNull() - tree = parser.parse(source, tree) + tree = parser.parse(source, oldTree = tree) tree.text() shouldBe source } @@ -53,7 +53,7 @@ class TreeTest : FunSpec({ test("changedRanges()") { val edit = InputEdit(0U, 0U, 7U, Point(0U, 0U), Point(0U, 0U), Point(0U, 7U)) tree.edit(edit) - val newTree = parser.parse("public $source", tree) + val newTree = parser.parse("public $source", oldTree = tree) tree.changedRanges(newTree).shouldHaveSingleElement { it.endByte == 7U && it.endPoint.column == 7U } diff --git a/ktreesitter/src/jni/language.c b/ktreesitter/src/jni/language.c index bc57e17..9783ff9 100644 --- a/ktreesitter/src/jni/language.c +++ b/ktreesitter/src/jni/language.c @@ -4,9 +4,14 @@ jlong JNICALL language_copy CRITICAL_ARGS(jlong self) { return (jlong)ts_language_copy((TSLanguage *)self); } +jint JNICALL language_get_abi_version(JNIEnv *env, jobject this) { + TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); + return (jint)ts_language_abi_version(self); +} + jint JNICALL language_get_version(JNIEnv *env, jobject this) { TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); - return (jint)ts_language_version(self); + return (jint)ts_language_abi_version(self); } jint JNICALL language_get_symbol_count(JNIEnv *env, jobject this) { @@ -24,10 +29,46 @@ jint JNICALL language_get_field_count(JNIEnv *env, jobject this) { return (jint)ts_language_field_count(self); } +jstring JNICALL language_get_name(JNIEnv *env, jobject this) { + TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); + const char *name = ts_language_name(self); + return name ? (*env)->NewStringUTF(env, name) : NULL; +} + +jobject JNICALL language_get_metadata(JNIEnv *env, jobject this) { + TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); + const TSLanguageMetadata *metadata = ts_language_metadata(self); + if (metadata == NULL) + return NULL; + + jobject major = (*env)->AllocObject(env, global_class_cache.UShort); + (*env)->SetShortField(env, major, global_field_cache.UShort_data, + (jshort)metadata->major_version); + jobject minor = (*env)->AllocObject(env, global_class_cache.UShort); + (*env)->SetShortField(env, minor, global_field_cache.UShort_data, + (jshort)metadata->minor_version); + jobject patch = (*env)->AllocObject(env, global_class_cache.UShort); + (*env)->SetShortField(env, patch, global_field_cache.UShort_data, + (jshort)metadata->patch_version); + jobject version = NEW_OBJECT(Triple, major, minor, patch); + return NEW_OBJECT(Language$Metadata, version); +} + +jshortArray JNICALL language_get_supertypes(JNIEnv *env, jobject this) { + TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); + uint32_t length; + const TSSymbol *supertypes = ts_language_supertypes(self, &length); + jshortArray result = (*env)->NewShortArray(env, length); + if (length > 0) { + (*env)->SetShortArrayRegion(env, result, 0, length, (const jshort *)supertypes); + } + return result; +} + jstring JNICALL language_symbol_name(JNIEnv *env, jobject this, jshort symbol) { TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); const char *name = ts_language_symbol_name(self, (uint16_t)symbol); - return (*env)->NewStringUTF(env, name); + return name ? (*env)->NewStringUTF(env, name) : NULL; } jshort JNICALL language_symbol_for_name(JNIEnv *env, jobject this, jstring name, @@ -40,6 +81,17 @@ jshort JNICALL language_symbol_for_name(JNIEnv *env, jobject this, jstring name, return (jshort)symbol; } +jshortArray JNICALL language_subtypes(JNIEnv *env, jobject this, jshort supertype) { + TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); + uint32_t length; + const TSSymbol *subtypes = ts_language_subtypes(self, supertype, &length); + jshortArray result = (*env)->NewShortArray(env, length); + if (length > 0) { + (*env)->SetShortArrayRegion(env, result, 0, length, (const jshort *)subtypes); + } + return result; +} + jboolean JNICALL language_is_named(JNIEnv *env, jobject this, jshort symbol) { TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self); TSSymbolType symbol_type = ts_language_symbol_type(self, symbol); @@ -93,12 +145,17 @@ void JNICALL language_check_version(JNIEnv *env, jobject this) { const JNINativeMethod Language_methods[] = { {"copy", "(J)J", (void *)&language_copy}, + {"getAbiVersion", "()I", (void *)&language_get_abi_version}, {"getVersion", "()I", (void *)&language_get_version}, {"getSymbolCount", "()I", (void *)&language_get_symbol_count}, {"getStateCount", "()I", (void *)&language_get_state_count}, {"getFieldCount", "()I", (void *)&language_get_field_count}, + {"getName", "()Ljava/lang/String;", (void *)&language_get_name}, + {"getMetadata", "()L" PACKAGE "Language$Metadata;", (void *)&language_get_metadata}, + {"getSupertypes", "()[S", (void *)&language_get_supertypes}, {"symbolName", "(S)Ljava/lang/String;", (void *)&language_symbol_name}, {"symbolForName", "(Ljava/lang/String;Z)S", (void *)&language_symbol_for_name}, + {"subtypes", "(S)[S", (void *)&language_subtypes}, {"isNamed", "(S)Z", (void *)&language_is_named}, {"isVisible", "(S)Z", (void *)&language_is_visible}, {"isSupertype", "(S)Z", (void *)&language_is_supertype}, diff --git a/ktreesitter/src/jni/module.c b/ktreesitter/src/jni/module.c index 1f5efde..d01b578 100644 --- a/ktreesitter/src/jni/module.c +++ b/ktreesitter/src/jni/module.c @@ -134,6 +134,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *_reserved) { CACHE_FIELD(InputEdit, oldEndPoint, "L" PACKAGE "Point;"); CACHE_FIELD(InputEdit, newEndPoint, "L" PACKAGE "Point;"); + CACHE_CLASS(PACKAGE, Language$Metadata); + CACHE_METHOD(Language$Metadata, init, "", "(Lkotlin/Triple;)V"); + CACHE_CLASS(PACKAGE, QueryCapture); CACHE_METHOD(QueryCapture, init, "", "(L" PACKAGE "Node;Ljava/lang/String;)V"); @@ -144,6 +147,11 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *_reserved) { CACHE_STATIC_FIELD(Parser$LogType, LEX, "L" PACKAGE "Parser$LogType;"); CACHE_STATIC_FIELD(Parser$LogType, PARSE, "L" PACKAGE "Parser$LogType;"); + CACHE_CLASS(PACKAGE, InputEncoding); + CACHE_STATIC_FIELD(InputEncoding, UTF_8, "L" PACKAGE "InputEncoding;"); + CACHE_STATIC_FIELD(InputEncoding, UTF_16LE, "L" PACKAGE "InputEncoding;"); + CACHE_STATIC_FIELD(InputEncoding, UTF_16BE, "L" PACKAGE "InputEncoding;"); + CACHE_CLASS(PACKAGE, QueryError$Capture); CACHE_METHOD(QueryError$Capture, init, "", "(IILjava/lang/String;)V"); @@ -159,6 +167,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *_reserved) { CACHE_CLASS(PACKAGE, QueryError$Structure); CACHE_METHOD(QueryError$Structure, init, "", "(II)V"); + CACHE_CLASS("java/lang/", Boolean); + CACHE_METHOD(Boolean, init, "", "(Z)V"); + CACHE_CLASS("java/lang/", CharSequence); CACHE_METHOD(CharSequence, toString, "toString", "()Ljava/lang/String;"); @@ -173,9 +184,16 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *_reserved) { CACHE_CLASS("kotlin/", Pair); CACHE_METHOD(Pair, init, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + CACHE_CLASS("kotlin/", Triple); + CACHE_METHOD(Triple, init, "", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"); + CACHE_CLASS("kotlin/", UInt); CACHE_FIELD(UInt, data, "I"); + CACHE_CLASS("kotlin/", UShort); + CACHE_FIELD(UShort, data, "S"); + CACHE_CLASS("kotlin/jvm/functions/", Function2); CACHE_METHOD(Function2, invoke, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); @@ -197,12 +215,15 @@ JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *_reserved) { return; (*env)->DeleteGlobalRef(env, global_class_cache.ArrayList); + (*env)->DeleteGlobalRef(env, global_class_cache.Boolean); (*env)->DeleteGlobalRef(env, global_class_cache.CharSequence); (*env)->DeleteGlobalRef(env, global_class_cache.IllegalArgumentException); (*env)->DeleteGlobalRef(env, global_class_cache.IllegalStateException); (*env)->DeleteGlobalRef(env, global_class_cache.IndexOutOfBoundsException); (*env)->DeleteGlobalRef(env, global_class_cache.InputEdit); + (*env)->DeleteGlobalRef(env, global_class_cache.InputEncoding); (*env)->DeleteGlobalRef(env, global_class_cache.Language); + (*env)->DeleteGlobalRef(env, global_class_cache.Language$Metadata); (*env)->DeleteGlobalRef(env, global_class_cache.List); (*env)->DeleteGlobalRef(env, global_class_cache.LookaheadIterator); (*env)->DeleteGlobalRef(env, global_class_cache.Node); @@ -220,5 +241,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *_reserved) { (*env)->DeleteGlobalRef(env, global_class_cache.Range); (*env)->DeleteGlobalRef(env, global_class_cache.Tree); (*env)->DeleteGlobalRef(env, global_class_cache.TreeCursor); + (*env)->DeleteGlobalRef(env, global_class_cache.Triple); (*env)->DeleteGlobalRef(env, global_class_cache.UInt); + (*env)->DeleteGlobalRef(env, global_class_cache.UShort); } diff --git a/ktreesitter/src/jni/node.c b/ktreesitter/src/jni/node.c index af42f7e..fef28df 100644 --- a/ktreesitter/src/jni/node.c +++ b/ktreesitter/src/jni/node.c @@ -247,6 +247,24 @@ jobject JNICALL node_children_by_field_id(JNIEnv *env, jobject this, jshort id) return children; } +jobject JNICALL node_first_child_for_byte(JNIEnv *env, jobject this, jint byte) { + TSNode self = unmarshal_node(env, this); + TSNode result = ts_node_first_child_for_byte(self, byte); + if (ts_node_is_null(result)) + return NULL; + jobject tree = GET_FIELD(Object, this, Node_tree); + return marshal_node(env, result, tree); +} + +jobject JNICALL node_first_named_child_for_byte(JNIEnv *env, jobject this, jint byte) { + TSNode self = unmarshal_node(env, this); + TSNode result = ts_node_first_named_child_for_byte(self, byte); + if (ts_node_is_null(result)) + return NULL; + jobject tree = GET_FIELD(Object, this, Node_tree); + return marshal_node(env, result, tree); +} + jstring JNICALL node_field_name_for_child(JNIEnv *env, jobject this, jint index) { TSNode self = unmarshal_node(env, this); if (ts_node_child_count(self) <= (uint32_t)index) { @@ -275,16 +293,6 @@ jstring JNICALL node_field_name_for_named_child(JNIEnv *env, jobject this, jint return field_name ? (*env)->NewStringUTF(env, field_name) : NULL; } -jobject JNICALL node_child_containing_descendant(JNIEnv *env, jobject this, jobject descendant) { - TSNode self = unmarshal_node(env, this); - TSNode other = unmarshal_node(env, descendant); - TSNode result = ts_node_child_containing_descendant(self, other); - if (ts_node_is_null(result)) - return NULL; - jobject tree = GET_FIELD(Object, this, Node_tree); - return marshal_node(env, result, tree); -} - jobject JNICALL node_child_with_descendant(JNIEnv *env, jobject this, jobject descendant) { TSNode self = unmarshal_node(env, this); TSNode other = unmarshal_node(env, descendant); @@ -393,14 +401,14 @@ const JNINativeMethod Node_methods[] = { {"getChildren", "()Ljava/util/List;", (void *)&node_get_children}, {"child", "(I)L" PACKAGE "Node;", (void *)&node_child}, {"namedChild", "(I)L" PACKAGE "Node;", (void *)&node_named_child}, + {"firstChildForByte", "(I)L" PACKAGE "Node;", (void *)&node_first_child_for_byte}, + {"firstNamedChildForByte", "(I)L" PACKAGE "Node;", (void *)&node_first_named_child_for_byte}, {"childByFieldId", "(S)L" PACKAGE "Node;", (void *)&node_child_by_field_id}, {"childByFieldName", "(Ljava/lang/String;)L" PACKAGE "Node;", (void *)&node_child_by_field_name}, {"childrenByFieldId", "(S)Ljava/util/List;", (void *)&node_children_by_field_id}, {"fieldNameForChild", "(I)Ljava/lang/String;", (void *)&node_field_name_for_child}, {"fieldNameForNamedChild", "(I)Ljava/lang/String;", (void *)&node_field_name_for_named_child}, - {"childContainingDescendant", "(L" PACKAGE "Node;)L" PACKAGE "Node;", - (void *)&node_child_containing_descendant}, {"childWithDescendant", "(L" PACKAGE "Node;)L" PACKAGE "Node;", (void *)&node_child_with_descendant}, {"descendant", "(II)L" PACKAGE "Node;", (void *)&node_descendant__bytes}, diff --git a/ktreesitter/src/jni/parser.c b/ktreesitter/src/jni/parser.c index c40cfdb..59132fe 100644 --- a/ktreesitter/src/jni/parser.c +++ b/ktreesitter/src/jni/parser.c @@ -9,7 +9,28 @@ typedef struct { jstring string; const char *chars; } last_result; -} ReadPayload; +} ParseReadPayload; + +typedef struct { + JNIEnv *env; + jobject callback; +} ParseProgressPayload; + +static inline TSInputEncoding get_encoding(JNIEnv *env, jobject encoding) { + jobject UTF_8 = GET_STATIC_FIELD(Object, InputEncoding, InputEncoding_UTF_8); + if (encoding == NULL || (*env)->IsSameObject(env, encoding, UTF_8)) { + return TSInputEncodingUTF8; + } + jobject UTF_16LE = GET_STATIC_FIELD(Object, InputEncoding, InputEncoding_UTF_16LE); + if ((*env)->IsSameObject(env, encoding, UTF_16LE)) { + return TSInputEncodingUTF16BE; + } + jobject UTF_16BE = GET_STATIC_FIELD(Object, InputEncoding, InputEncoding_UTF_16BE); + if ((*env)->IsSameObject(env, encoding, UTF_16BE)) { + return TSInputEncodingUTF16LE; + } + UNREACHABLE(); +} static void log_function(void *payload, TSLogType log_type, const char *buffer) { JNIEnv *env; @@ -38,9 +59,9 @@ static void log_function(void *payload, TSLogType log_type, const char *buffer) CALL_METHOD(Object, (jobject)payload, Function2_invoke, log_type_value, message); } -static const char *parse_callback(void *payload, uint32_t byte_index, TSPoint position, - uint32_t *bytes_read) { - ReadPayload *read_payload = (ReadPayload *)payload; +static const char *parse_read_callback(void *payload, uint32_t byte_index, TSPoint position, + uint32_t *bytes_read) { + ParseReadPayload *read_payload = (ParseReadPayload *)payload; JNIEnv *env = read_payload->env; jstring last_string = read_payload->last_result.string; const char *last_chars = read_payload->last_result.chars; @@ -72,6 +93,20 @@ static const char *parse_callback(void *payload, uint32_t byte_index, TSPoint po return result; } +static bool parse_progress_callback(TSParseState *state) { + ParseProgressPayload *progress_payload = (ParseProgressPayload *)state->payload; + JNIEnv *env = progress_payload->env; + jobject offset = (*env)->AllocObject(env, global_class_cache.UInt); + (*env)->SetIntField(env, offset, global_field_cache.UInt_data, + (jint)state->current_byte_offset); + jobject error = NEW_OBJECT(Boolean, (jboolean)state->has_error); + jboolean result = + CALL_METHOD(Boolean, progress_payload->callback, Function2_invoke, offset, error); + (*env)->DeleteLocalRef(env, offset); + (*env)->DeleteLocalRef(env, error); + return result; +} + jlong JNICALL parser_init CRITICAL_NO_ARGS() { return (jlong)ts_parser_new(); } void JNICALL parser_delete(JNIEnv *env, jclass _class, jlong self) { @@ -140,7 +175,8 @@ void JNICALL parser_set_logger(JNIEnv *env, jobject this, jobject value) { (*env)->SetObjectField(env, this, global_field_cache.Parser_logger, value); } -jobject JNICALL parser_parse__string(JNIEnv *env, jobject this, jstring source, jobject old_tree) { +jobject JNICALL parser_parse__string(JNIEnv *env, jobject this, jstring source, jobject encoding, + jobject old_tree) { TSParser *self = GET_POINTER(TSParser, this, Parser_self); jobject language = GET_FIELD(Object, this, Parser_language); if (language == NULL) { @@ -148,12 +184,14 @@ jobject JNICALL parser_parse__string(JNIEnv *env, jobject this, jstring source, (*env)->ThrowNew(env, global_class_cache.IllegalStateException, error); return NULL; } - TSTree *old_ts_tree = old_tree ? GET_POINTER(TSTree, old_tree, Tree_self) : NULL; + TSTree *old_ts_tree = old_tree ? GET_POINTER(TSTree, old_tree, Tree_self) : NULL; uint32_t length; const char *string = (*env)->GetStringUTFChars(env, source, NULL); length = (uint32_t)(*env)->GetStringUTFLength(env, source); - TSTree *ts_tree = ts_parser_parse_string(self, old_ts_tree, string, length); + TSInputEncoding input_encoding = get_encoding(env, encoding); + TSTree *ts_tree = + ts_parser_parse_string_encoding(self, old_ts_tree, string, length, input_encoding); (*env)->ReleaseStringUTFChars(env, source, string); if (ts_tree == NULL) { @@ -164,8 +202,9 @@ jobject JNICALL parser_parse__string(JNIEnv *env, jobject this, jstring source, return NEW_OBJECT(Tree, (jlong)ts_tree, source, language); } -jobject JNICALL parser_parse__function(JNIEnv *env, jobject this, jobject old_tree, - jobject callback) { +jobject JNICALL parser_parse__function(JNIEnv *env, jobject this, jobject encoding, + jobject old_tree, jobject progress_callback, + jobject read_callback) { TSParser *self = GET_POINTER(TSParser, this, Parser_self); jobject language = GET_FIELD(Object, this, Parser_language); if (language == NULL) { @@ -175,13 +214,24 @@ jobject JNICALL parser_parse__function(JNIEnv *env, jobject this, jobject old_tr } TSTree *old_ts_tree = old_tree ? GET_POINTER(TSTree, old_tree, Tree_self) : NULL; - ReadPayload payload = {.env = env, .callback = callback}; + ParseReadPayload read_payload = {.env = env, .callback = read_callback}; + TSInputEncoding input_encoding = get_encoding(env, encoding); TSInput input = { - .payload = (void *)&payload, - .read = parse_callback, - .encoding = TSInputEncodingUTF8, + .payload = (void *)&read_payload, + .read = parse_read_callback, + .encoding = input_encoding, }; - TSTree *ts_tree = ts_parser_parse(self, old_ts_tree, input); + TSTree *ts_tree; + if (progress_callback == NULL) { + ts_tree = ts_parser_parse(self, old_ts_tree, input); + } else { + ParseProgressPayload progress_payload = {.env = env, .callback = progress_callback}; + TSParseOptions options = { + .payload = (void *)&progress_payload, + .progress_callback = parse_progress_callback, + }; + ts_tree = ts_parser_parse_with_options(self, old_ts_tree, input, options); + } if ((*env)->ExceptionCheck(env)) { (*env)->Throw(env, (*env)->ExceptionOccurred(env)); @@ -208,9 +258,11 @@ const JNINativeMethod Parser_methods[] = { {"getTimeoutMicros", "()J", (void *)&parser_get_timeout_micros}, {"setTimeoutMicros", "(J)V", (void *)&parser_set_timeout_micros}, {"setLogger", "(Lkotlin/jvm/functions/Function2;)V", (void *)&parser_set_logger}, - {"parse", "(Ljava/lang/String;L" PACKAGE "Tree;)L" PACKAGE "Tree;", + {"parse", "(Ljava/lang/String;L" PACKAGE "InputEncoding;L" PACKAGE "Tree;)L" PACKAGE "Tree;", (void *)&parser_parse__string}, - {"parse", "(L" PACKAGE "Tree;Lkotlin/jvm/functions/Function2;)L" PACKAGE "Tree;", + {"parse", + "(L" PACKAGE "InputEncoding;L" PACKAGE "Tree;Lkotlin/jvm/functions/Function2;" + "Lkotlin/jvm/functions/Function2;)L" PACKAGE "Tree;", (void *)&parser_parse__function}, {"reset", "()V", (void *)&parser_reset}, }; diff --git a/ktreesitter/src/jni/utils.h b/ktreesitter/src/jni/utils.h index c6718cb..8ec2b3f 100644 --- a/ktreesitter/src/jni/utils.h +++ b/ktreesitter/src/jni/utils.h @@ -60,6 +60,9 @@ typedef struct { jfieldID InputEdit_oldEndPoint; jfieldID InputEdit_startByte; jfieldID InputEdit_startPoint; + jfieldID InputEncoding_UTF_8; + jfieldID InputEncoding_UTF_16LE; + jfieldID InputEncoding_UTF_16BE; jfieldID Language_self; jfieldID LookaheadIterator_self; jfieldID Node_context; @@ -93,14 +96,17 @@ typedef struct { jfieldID Tree_self; jfieldID Tree_source; jfieldID UInt_data; + jfieldID UShort_data; } FieldCache; typedef struct { jmethodID ArrayList_add; jmethodID ArrayList_init; + jmethodID Boolean_init; jmethodID CharSequence_toString; jmethodID Function2_invoke; jmethodID Language_init; + jmethodID Language$Metadata_init; jmethodID List_get; jmethodID List_size; jmethodID Node_init; @@ -115,19 +121,21 @@ typedef struct { jmethodID QueryMatch_init; jmethodID Range_init; jmethodID Tree_init; - jmethodID UInt_constructor; - jmethodID UInt_box; + jmethodID Triple_init; } MethodCache; typedef struct { jclass ArrayList; + jclass Boolean; jclass CharSequence; jclass Function2; jclass IllegalArgumentException; jclass IllegalStateException; jclass IndexOutOfBoundsException; jclass InputEdit; + jclass InputEncoding; jclass Language; + jclass Language$Metadata; jclass List; jclass LookaheadIterator; jclass Node; @@ -146,7 +154,9 @@ typedef struct { jclass Range; jclass Tree; jclass TreeCursor; + jclass Triple; jclass UInt; + jclass UShort; } ClassCache; extern FieldCache global_field_cache; diff --git a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt new file mode 100644 index 0000000..f927ec4 --- /dev/null +++ b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt @@ -0,0 +1,14 @@ +package io.github.treesitter.ktreesitter + +import java.nio.charset.Charset + +/** + * The encoding of the input text. + * + * @since 0.25.0 + */ +actual enum class InputEncoding(val charset: Charset) { + UTF_8(Charsets.UTF_8), + UTF_16LE(Charsets.UTF_16LE), + UTF_16BE(Charsets.UTF_16BE) +} diff --git a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Language.kt b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Language.kt index 4cc4037..539c13f 100644 --- a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Language.kt +++ b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Language.kt @@ -3,12 +3,13 @@ package io.github.treesitter.ktreesitter /** * A class that defines how to parse a particular language. * - * When a [Language] is generated by the Tree-sitter CLI, it is assigned - * an ABI [version] number that corresponds to the current CLI version. + * When a [Language] is generated by the Tree-sitter CLI, it is assigned an + * [ABI version][abiVersion] number that corresponds to the current CLI version. * * @constructor Create a new instance from the given language pointer. * @param language A pointer to a `TSLanguage` cast to [Long]. - * @throws [IllegalArgumentException] If the pointer is invalid or the [version] is incompatible. + * @throws [IllegalArgumentException] + * If the pointer is invalid or the [version][abiVersion] is incompatible. */ actual class Language @Throws(IllegalArgumentException::class) actual constructor(language: Any) { @JvmField @@ -19,7 +20,17 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo checkVersion() } + /** + * The ABI version number for this language. + * + * @since 0.25.0 + */ + @get:JvmName("getAbiVersion") + actual val abiVersion: UInt + external get + /** The ABI version number for this language. */ + @Deprecated("version is deprecated", ReplaceWith("abiVersion"), DeprecationLevel.ERROR) @get:JvmName("getVersion") actual val version: UInt external get @@ -39,6 +50,32 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo actual val fieldCount: UInt external get + /** + * The name of the language, if available. + * + * @since 0.25.0 + */ + actual val name: String? + external get + + /** + * The metadata of the language, if available. + * + * @since 0.25.0 + */ + actual val metadata: Metadata? + external get + + /** + * The supertype symbols of the language. + * + * @since 0.25.0 + */ + @OptIn(ExperimentalUnsignedTypes::class) + @get:JvmName("getSupertypes") + actual val supertypes: UShortArray + external get + /** * Get another reference to the language. * @@ -54,6 +91,16 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo @JvmName("symbolForName") actual external fun symbolForName(name: String, isNamed: Boolean): UShort + /** + * Get the subtype symbols for the given supertype symbol + * + * @since 0.25.0 + * @see supertypes + */ + @JvmName("subtypes") + @OptIn(ExperimentalUnsignedTypes::class) + actual external fun subtypes(supertype: UShort): UShortArray + /** * Check if the node for the given numerical ID is named * @@ -108,11 +155,12 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo /** * Create a new [Query] from a string containing one or more S-expression - * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax). + * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html). * * @throws [QueryError] If any error occurred while creating the query. */ @Throws(QueryError::class) + @Deprecated("Use the Query constructor instead") actual fun query(source: String) = Query(this, source) actual override fun equals(other: Any?) = @@ -120,11 +168,32 @@ actual class Language @Throws(IllegalArgumentException::class) actual constructo actual override fun hashCode() = self.hashCode() - override fun toString() = "Language(id=0x${self.toString(16)}, version=$version)" + override fun toString() = "Language(id=0x${self.toString(16)}, abiVersion=$abiVersion)" @Throws(IllegalArgumentException::class) private external fun checkVersion() + /** + * A class containing the [Language] metadata. + * + * @property semanticVersion The [Semantic Version](https://semver.org/) of the [Language]. + */ + @ConsistentCopyVisibility + actual data class Metadata internal actual constructor( + @get:JvmName("getSemanticVersion") + actual val semanticVersion: Triple + ) { + actual override fun toString() = buildString { + append("Metadata(semanticVersion=\"") + append(semanticVersion.first) + append('.') + append(semanticVersion.second) + append('.') + append(semanticVersion.third) + append("\")") + } + } + private companion object { @JvmStatic private external fun copy(self: Long): Long diff --git a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Node.kt b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Node.kt index 86e2b72..210af13 100644 --- a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Node.kt +++ b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Node.kt @@ -205,6 +205,24 @@ actual class Node internal constructor( @Throws(IndexOutOfBoundsException::class) actual external fun namedChild(index: UInt): Node? + /** + * Get the node's first child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + @JvmName("firstChildForByte") + actual external fun firstChildForByte(byte: UInt): Node? + + /** + * Get the node's first _named_ child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + @JvmName("firstNamedChildForByte") + actual external fun firstNamedChildForByte(byte: UInt): Node? + /** * Get the node's child with the given field ID, if any. * @@ -244,13 +262,6 @@ actual class Node internal constructor( @Throws(IndexOutOfBoundsException::class) actual external fun fieldNameForNamedChild(index: UInt): String? - /** Get the child of the node that contains the given descendant, if any. */ - @Deprecated( - "This method will not return a direct descendant", - ReplaceWith("childWithDescendant(descendant)", "io.github.treesitter.ktreesitter.Node") - ) - actual external fun childContainingDescendant(descendant: Node): Node? - /** * Get the node that contains the given descendant, if any. * diff --git a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt index fe32fe7..6ef25ef 100644 --- a/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt +++ b/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt @@ -44,6 +44,7 @@ actual class Parser actual constructor() { */ @get:JvmName("getTimeoutMicros") @set:JvmName("setTimeoutMicros") + @Deprecated("Use the progressCallback in parse()") actual var timeoutMicros: ULong external get external set @@ -84,11 +85,10 @@ actual class Parser actual constructor() { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was cancelled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - actual external fun parse(source: String, oldTree: Tree?): Tree + actual external fun parse(source: String, encoding: InputEncoding, oldTree: Tree?): Tree /** * Parse source code from a callback and create a syntax tree. @@ -100,19 +100,22 @@ actual class Parser actual constructor() { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was cancelled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - actual external fun parse(oldTree: Tree?, callback: ParseCallback): Tree + actual external fun parse( + encoding: InputEncoding, + oldTree: Tree?, + progressCallback: ParseProgressCallback?, + readCallback: ParseReadCallback + ): Tree /** * Instruct the parser to start the next [parse] from the beginning. * - * If the parser previously failed because of a [timeout][timeoutMicros], - * then by default, it will resume where it left off. If you don't - * want to resume, and instead intend to use this parser to parse - * some other document, you must call this method first. + * If parsing was previously halted, then by default, it will resume where + * it left off. If you don't want to resume, and instead intend to use this + * parser to parse some other document, you must call this method first. */ actual external fun reset() diff --git a/ktreesitter/src/nativeInterop/cinterop/treesitter.def b/ktreesitter/src/nativeInterop/cinterop/treesitter.def index 7789be2..c7f5608 100644 --- a/ktreesitter/src/nativeInterop/cinterop/treesitter.def +++ b/ktreesitter/src/nativeInterop/cinterop/treesitter.def @@ -4,11 +4,11 @@ headerFilter = tree_sitter/api.h compilerOpts = -DTREE_SITTER_HIDE_SYMBOLS -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200112L staticLibraries = libtree-sitter.a strictEnums = \ + TSInputEncoding \ TSLogType \ TSQuantifier \ TSQueryError nonStrictEnums = \ - TSInputEncoding \ TSQueryPredicateStepType \ TSSymbolType excludedFunctions = \ diff --git a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt new file mode 100644 index 0000000..15d1d02 --- /dev/null +++ b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/InputEncoding.kt @@ -0,0 +1,16 @@ +package io.github.treesitter.ktreesitter + +import io.github.treesitter.ktreesitter.internal.TSInputEncoding +import kotlinx.cinterop.ExperimentalForeignApi + +/** + * The encoding of the input text. + * + * @since 0.25.0 + */ +@OptIn(ExperimentalForeignApi::class) +actual enum class InputEncoding(internal val value: TSInputEncoding) { + UTF_8(TSInputEncoding.TSInputEncodingUTF8), + UTF_16LE(TSInputEncoding.TSInputEncodingUTF16LE), + UTF_16BE(TSInputEncoding.TSInputEncodingUTF16BE) +} diff --git a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Language.kt b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Language.kt index 56b7595..9663142 100644 --- a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Language.kt +++ b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Language.kt @@ -7,8 +7,8 @@ import kotlinx.cinterop.* /** * A class that defines how to parse a particular language. * - * When a [Language] is generated by the Tree-sitter CLI, it is assigned - * an ABI [version] number that corresponds to the current CLI version. + * When a [Language] is generated by the Tree-sitter CLI, it is assigned an + * [ABI version][abiVersion] number that corresponds to the current CLI version. */ @OptIn(ExperimentalForeignApi::class) actual class Language internal constructor( internal val self: CPointer @@ -18,7 +18,7 @@ import kotlinx.cinterop.* * * @param language A [CPointer] to a `TSLanguage`. * @throws [IllegalArgumentException] - * If the pointer is invalid or the [version] is incompatible. + * If the pointer is invalid or the [version][abiVersion] is incompatible. */ @Throws(IllegalArgumentException::class) actual constructor(language: Any) : this( @@ -26,16 +26,24 @@ import kotlinx.cinterop.* ?: throw IllegalArgumentException("Invalid language: $language") ) - /** The ABI version number for this language. */ - actual val version: UInt = ts_language_version(self) + /** + * The ABI version number for this language. + * + * @since 0.25.0 + */ + actual val abiVersion: UInt = ts_language_abi_version(self) init { - require(version in MIN_COMPATIBLE_LANGUAGE_VERSION..LANGUAGE_VERSION) { - "Incompatible language version $version. " + + require(abiVersion in MIN_COMPATIBLE_LANGUAGE_VERSION..LANGUAGE_VERSION) { + "Incompatible language version $abiVersion. " + "Must be between $MIN_COMPATIBLE_LANGUAGE_VERSION and $LANGUAGE_VERSION." } } + /** The ABI version number for this language. */ + @Deprecated("version is deprecated", ReplaceWith("abiVersion"), DeprecationLevel.ERROR) + actual val version = abiVersion + /** The number of distinct node types in this language. */ actual val symbolCount: UInt = ts_language_symbol_count(self) @@ -45,6 +53,42 @@ import kotlinx.cinterop.* /** The number of distinct field names in this language. */ actual val fieldCount: UInt = ts_language_field_count(self) + /** + * The name of the language, if available. + * + * @since 0.25.0 + */ + actual val name: String? = ts_language_name(self)?.toKString() + + /** + * The metadata of the language, if available. + * + * @since 0.25.0 + */ + actual val metadata: Metadata? + get() = ts_language_metadata(self)?.pointed?.run { + Metadata( + Triple( + major_version.convert(), + minor_version.convert(), + patch_version.convert() + ) + ) + } + + /** + * The supertype symbols of the language. + * + * @since 0.25.0 + */ + @OptIn(ExperimentalUnsignedTypes::class) + actual val supertypes: UShortArray + get() = memScoped { + val length = alloc() + val supertypes = ts_language_supertypes(self, length.ptr) ?: return ushortArrayOf() + return UShortArray(length.value.convert()) { supertypes[it] } + } + /** * Get another reference to the language. * @@ -59,6 +103,19 @@ import kotlinx.cinterop.* actual fun symbolForName(name: String, isNamed: Boolean): UShort = ts_language_symbol_for_name(self, name, name.length.convert(), isNamed) + /** + * Get the subtype symbols for the given supertype symbol + * + * @since 0.25.0 + * @see supertypes + */ + @OptIn(ExperimentalUnsignedTypes::class) + actual fun subtypes(supertype: UShort): UShortArray = memScoped { + val length = alloc() + val supertypes = ts_language_subtypes(self, supertype, length.ptr) ?: return ushortArrayOf() + return UShortArray(length.value.convert()) { supertypes[it] } + } + /** * Check if the node for the given numerical ID is named * @@ -106,22 +163,43 @@ import kotlinx.cinterop.* * * @throws [IllegalArgumentException] If the state is invalid for this language. */ - @Throws( - IllegalArgumentException::class - ) actual fun lookaheadIterator(state: UShort) = LookaheadIterator(this, state) + @Throws(IllegalArgumentException::class) + actual fun lookaheadIterator(state: UShort) = LookaheadIterator(this, state) /** * Create a new [Query] from a string containing one or more S-expression - * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax). + * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html). * * @throws [QueryError] If any error occurred while creating the query. */ - @Throws(QueryError::class) actual fun query(source: String) = Query(this, source) + @Throws(QueryError::class) + @Deprecated("Use the Query constructor instead") + actual fun query(source: String) = Query(this, source) actual override fun equals(other: Any?) = this === other || (other is Language && self == other.self) actual override fun hashCode() = self.hashCode() - override fun toString() = "Language(id=${self.rawValue}, version=$version)" + override fun toString() = "Language(id=${self.rawValue}, abiVersion=$abiVersion)" + + /** + * A class containing the [Language] metadata. + * + * @property semanticVersion The [Semantic Version](https://semver.org/) of the [Language]. + */ + @ConsistentCopyVisibility + actual data class Metadata internal actual constructor( + actual val semanticVersion: Triple + ) { + actual override fun toString() = buildString { + append("Metadata(semanticVersion=\"") + append(semanticVersion.first) + append('.') + append(semanticVersion.second) + append('.') + append(semanticVersion.third) + append("\")") + } + } } diff --git a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Node.kt b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Node.kt index 9300596..c5b5f8a 100644 --- a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Node.kt +++ b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Node.kt @@ -212,6 +212,24 @@ actual class Node internal constructor( throw IndexOutOfBoundsException("Child index $index is out of bounds") } + /** + * Get the node's first child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + actual fun firstChildForByte(byte: UInt) = + ts_node_first_child_for_byte(self, byte).convert(tree) + + /** + * Get the node's first _named_ child that contains + * or starts after the given byte offset. + * + * @since 0.25.0 + */ + actual fun firstNamedChildForByte(byte: UInt) = + ts_node_first_named_child_for_byte(self, byte).convert(tree) + /** * Get the node's child with the given field ID, if any. * @@ -268,14 +286,6 @@ actual class Node internal constructor( throw IndexOutOfBoundsException("Child index $index is out of bounds") } - /** Get the child of the node that contains the given descendant, if any. */ - @Deprecated( - "This method will not return a direct descendant", - ReplaceWith("childWithDescendant(descendant)", "io.github.treesitter.ktreesitter.Node") - ) - actual fun childContainingDescendant(descendant: Node) = - ts_node_child_containing_descendant(self, descendant.self).convert(tree) - /** * Get the node that contains the given descendant, if any. * diff --git a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt index 654707b..c2fdd04 100644 --- a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt +++ b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt @@ -69,6 +69,7 @@ actual class Parser actual constructor() { * The maximum duration in microseconds that parsing * should be allowed to take before halting. */ + @Deprecated("Use the progressCallback in parse()") actual var timeoutMicros: ULong get() = ts_parser_timeout_micros(self) set(value) = ts_parser_set_timeout_micros(self, value) @@ -106,19 +107,19 @@ actual class Parser actual constructor() { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was canceled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - actual fun parse(source: String, oldTree: Tree?): Tree { + actual fun parse(source: String, encoding: InputEncoding, oldTree: Tree?): Tree { val language = checkNotNull(language) { "The parser has no language assigned" } - val tree = ts_parser_parse_string( + val tree = ts_parser_parse_string_encoding( self, oldTree?.self, source, - source.length.convert() + source.length.convert(), + encoding.value ) checkNotNull(tree) { "Parsing failed" } return Tree(tree, source, language) @@ -134,19 +135,23 @@ actual class Parser actual constructor() { * [Tree.edit] method in a way that exactly matches the source code changes. * * @throws [IllegalStateException] - * If the parser does not have a [language] assigned or - * if parsing was cancelled due to a [timeout][timeoutMicros]. + * If the parser does not have a [language] assigned or if parsing was halted. */ @Throws(IllegalStateException::class) - actual fun parse(oldTree: Tree?, callback: ParseCallback): Tree { + actual fun parse( + encoding: InputEncoding, + oldTree: Tree?, + progressCallback: ParseProgressCallback?, + readCallback: ParseReadCallback + ): Tree { val language = checkNotNull(language) { "The parser has no language assigned" } val arena = Arena() - val payloadRef = StableRef.create(ParsePayload(arena, callback)) + val payloadRef = StableRef.create(ParsePayload(arena, readCallback)) val input = cValue { payload = payloadRef.asCPointer() - encoding = TSInputEncodingUTF8 + this.encoding = encoding.value read = staticCFunction { payload, index, point, bytes -> val data = payload!!.asStableRef().get() val result = data.callback(index, point.useContents { convert() }) @@ -154,9 +159,24 @@ actual class Parser actual constructor() { result?.toString()?.cstr?.getPointer(data.memScope) } } - val tree = ts_parser_parse(self, oldTree?.self, input) + var progressRef: StableRef? = null + val tree = if (progressCallback == null) { + ts_parser_parse(self, oldTree?.self, input) + } else { + progressRef = StableRef.create(progressCallback) + val options = cValue { + payload = progressRef.asCPointer() + progress_callback = staticCFunction { state -> + val callback = state!!.pointed.payload!! + .asStableRef().get() + callback(state.pointed.current_byte_offset, state.pointed.has_error) + } + } + ts_parser_parse_with_options(self, oldTree?.self, input, options) + } arena.clear() payloadRef.dispose() + progressRef?.dispose() checkNotNull(tree) { "Parsing failed" } return Tree(tree, null, language) } @@ -164,10 +184,9 @@ actual class Parser actual constructor() { /** * Instruct the parser to start the next [parse] from the beginning. * - * If the parser previously failed because of a [timeout][timeoutMicros], - * then by default, it will resume where it left off. If you don't - * want to resume, and instead intend to use this parser to parse - * some other document, you must call this method first. + * If parsing was previously halted, then by default, it will resume where + * it left off. If you don't want to resume, and instead intend to use this + * parser to parse some other document, you must call this method first. */ actual fun reset() = ts_parser_reset(self) @@ -178,11 +197,11 @@ actual class Parser actual constructor() { private class ParsePayload( val memScope: AutofreeScope, - val callback: ParseCallback + val callback: ParseReadCallback ) private companion object { - private inline fun freeLogger(logger: CValue) { + private fun freeLogger(logger: CValue) { val arena = Arena() interpretNullablePointed( arena.alloc(logger.size, logger.align).rawPtr diff --git a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Utils.kt b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Utils.kt index 44e3fc7..79d640b 100644 --- a/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Utils.kt +++ b/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Utils.kt @@ -1,3 +1,5 @@ +@file:Suppress("NOTHING_TO_INLINE") + package io.github.treesitter.ktreesitter import io.github.treesitter.ktreesitter.internal.*