├── .editorconfig
├── .gitattributes
├── .github
├── CONTRIBUTING.md
├── dependabot.yml
├── detekt.yml
├── scripts
│ ├── build-jni.ps1
│ └── build-jni.sh
└── workflows
│ ├── ci.yml
│ ├── docs.yml
│ ├── lint.yml
│ └── publish.yml
├── .gitignore
├── .gitmodules
├── .idea
├── conventionalCommit.json
├── conventionalCommit.xml
├── detekt.xml
├── ktlint-plugin.xml
└── vcs.xml
├── LICENSE
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
├── logo-icon.svg
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ktreesitter-plugin
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── java
│ └── io
│ │ └── github
│ │ └── treesitter
│ │ └── ktreesitter
│ │ └── plugin
│ │ ├── GrammarExtension.java
│ │ ├── GrammarFilesTask.java
│ │ └── GrammarPlugin.java
│ └── resources
│ ├── CMakeLists.txt.in
│ ├── android.kt.in
│ ├── common.kt.in
│ ├── interop.def.in
│ ├── jni.c.in
│ ├── jvm.kt.in
│ └── native.kt.in
├── ktreesitter
├── .clang-format
├── CMakeLists.txt
├── README.md
├── build.gradle.kts
└── src
│ ├── androidInstrumentedTest
│ ├── kotlin
│ │ └── io
│ │ │ └── github
│ │ │ └── treesitter
│ │ │ └── ktreesitter
│ │ │ ├── LanguageTest.kt
│ │ │ ├── LookaheadIteratorTest.kt
│ │ │ ├── NodeTest.kt
│ │ │ ├── ParserTest.kt
│ │ │ ├── QueryTest.kt
│ │ │ ├── TreeCursorTest.kt
│ │ │ └── TreeTest.kt
│ └── resources
│ │ └── kotest.properties
│ ├── androidMain
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── treesitter
│ │ └── ktreesitter
│ │ ├── Language.kt
│ │ ├── LookaheadIterator.kt
│ │ ├── Node.kt
│ │ ├── Parser.kt
│ │ ├── Query.kt
│ │ ├── RefCleaner.kt
│ │ ├── Tree.kt
│ │ └── TreeCursor.kt
│ ├── commonMain
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── treesitter
│ │ └── ktreesitter
│ │ ├── InputEdit.kt
│ │ ├── Language.kt
│ │ ├── LookaheadIterator.kt
│ │ ├── Node.kt
│ │ ├── Parser.kt
│ │ ├── Point.kt
│ │ ├── Query.kt
│ │ ├── QueryCapture.kt
│ │ ├── QueryError.kt
│ │ ├── QueryMatch.kt
│ │ ├── QueryPredicate.kt
│ │ ├── QueryPredicateArg.kt
│ │ ├── Range.kt
│ │ ├── Tree.kt
│ │ ├── TreeCursor.kt
│ │ └── TreeSitter.kt
│ ├── commonTest
│ ├── kotlin
│ │ └── io
│ │ │ └── github
│ │ │ └── treesitter
│ │ │ └── ktreesitter
│ │ │ ├── LanguageTest.kt
│ │ │ ├── LookaheadIteratorTest.kt
│ │ │ ├── NodeTest.kt
│ │ │ ├── ParserTest.kt
│ │ │ ├── QueryTest.kt
│ │ │ ├── TreeCursorTest.kt
│ │ │ └── TreeTest.kt
│ └── resources
│ │ └── kotest.properties
│ ├── jni
│ ├── language.c
│ ├── lookahead_iterator.c
│ ├── module.c
│ ├── node.c
│ ├── parser.c
│ ├── query.c
│ ├── tree.c
│ ├── tree_cursor.c
│ └── utils.h
│ ├── jvmMain
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── treesitter
│ │ └── ktreesitter
│ │ ├── Language.kt
│ │ ├── LookaheadIterator.kt
│ │ ├── NativeUtils.kt
│ │ ├── Node.kt
│ │ ├── Parser.kt
│ │ ├── Query.kt
│ │ ├── RefCleaner.kt
│ │ ├── Tree.kt
│ │ └── TreeCursor.kt
│ ├── nativeInterop
│ └── cinterop
│ │ └── treesitter.def
│ └── nativeMain
│ └── kotlin
│ └── io
│ └── github
│ └── treesitter
│ └── ktreesitter
│ ├── Language.kt
│ ├── LookaheadIterator.kt
│ ├── Node.kt
│ ├── Parser.kt
│ ├── Query.kt
│ ├── Tree.kt
│ ├── TreeCursor.kt
│ └── Utils.kt
├── languages
└── java
│ └── build.gradle.kts
└── settings.gradle.kts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | max_line_length = 100
11 |
12 | [LICENSE]
13 | max_line_length = off
14 |
15 | [README.md]
16 | indent_size = 2
17 | max_line_length = off
18 |
19 | [.editorconfig]
20 | max_line_length = off
21 |
22 | [*.{json,yml,toml,xml}]
23 | indent_size = 2
24 |
25 | [*.{bat,ps1}]
26 | end_of_line = crlf
27 |
28 | # noinspection EditorConfigKeyCorrectness
29 | [*.{kt,kts}]
30 | ktlint_code_style = android_studio
31 | ktlint_standard_filename = disabled
32 | ktlint_standard_annotation = disabled
33 | ktlint_standard_class-signature = disabled
34 | ktlint_standard_function-naming = disabled
35 | ktlint_standard_multiline-if-else = disabled
36 | ij_formatter_tags_enabled = true
37 | ij_kotlin_imports_layout = *
38 | ij_kotlin_allow_trailing_comma = false
39 | ij_kotlin_allow_trailing_comma_on_call_site = false
40 | ij_kotlin_name_count_to_use_star_import = 10
41 | ij_kotlin_name_count_to_use_star_import_for_members = 10
42 | ij_kotlin_packages_to_use_import_on_demand = kotlinx.cinterop.*,io.kotest.matchers.**,io.github.treesitter.ktreesitter.internal.*
43 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | /gradlew linguist-generated
4 | /gradlew.bat linguist-generated eol=crlf
5 | /.github/scripts/build-jni.ps1 eol=crlf
6 | /gradle/wrapper/gradle-wrapper.jar binary
7 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Commits
4 |
5 | Commits must follow the [Conventional Commits] specification.
6 | If you're developing on IntelliJ IDEA or Android Studio,
7 | you can use the [Conventional Commit plugin].
8 |
9 | [Conventional Commits]: https://www.conventionalcommits.org/en/v1.0.0/
10 | [Conventional Commit plugin]: https://plugins.jetbrains.com/plugin/13389-conventional-commit
11 |
12 | ## Prerequisites
13 |
14 | - JDK 17+
15 | - Android SDK & NDK
16 | - CMake
17 | - C compiler
18 |
19 | ## Building
20 |
21 | ### Clone the repository
22 |
23 | ```shell
24 | git clone https://github.com/tree-sitter/kotlin-tree-sitter
25 | cd kotlin-tree-sitter
26 | ```
27 |
28 | ### Build the JNI libraries
29 |
30 | *This step is only necessary for JVM or Android development.*
31 |
32 | Generate the grammar files:
33 |
34 | ```shell
35 | ./gradlew generateGrammarFiles
36 | ```
37 |
38 | Set the `CMAKE_INSTALL_LIBDIR` environment variable.
39 | The value depends on your system:
40 |
41 | - `lib/linux/x64`
42 | - `lib/linux/aarch64`
43 | - `lib/macos/x64`
44 | - `lib/macos/aarch64`
45 | - `lib/windows/x64`
46 |
47 | Build the libraries (change `.sh` to `.ps1` on Windows):
48 |
49 | ```shell
50 | ./.github/scripts/build-jni.sh
51 | ```
52 |
53 | ### Run the tests
54 |
55 | #### JVM
56 |
57 | ```shell
58 | ./gradlew :ktreesitter:jvmTest
59 | ```
60 |
61 | #### Android
62 |
63 | *Requires a connected device.*
64 |
65 | ```shell
66 | ./gradlew :ktreesitter:connectedDebugAndroidTest
67 | ```
68 |
69 | #### Native
70 |
71 | Linux:
72 |
73 | ```shell
74 | ./gradlew :ktreesitter:linuxX64Test
75 | ```
76 |
77 | macOS:
78 |
79 | ```shell
80 | # x64
81 | ./gradlew :ktreesitter:macosX64Test
82 | # arm64
83 | ./gradlew :ktreesitter:macosArm64Test
84 | ```
85 |
86 | Windows:
87 |
88 | ```shell
89 | ./gradlew :ktreesitter:mingwX64Test
90 | ```
91 |
92 | ## Linting
93 |
94 | Code linting is performed using [Ktlint] and [detekt].
95 | Configuration for IntelliJ IDEA and Android Studio is included
96 | (requires the [Ktlint][Ktlint plugin] and [detekt][detekt plugin] plugins).
97 |
98 | [Ktlint]: https://pinterest.github.io/ktlint/latest/
99 | [detekt]: https://detekt.dev/
100 | [Ktlint plugin]: https://plugins.jetbrains.com/plugin/15057-ktlint
101 | [detekt plugin]: https://plugins.jetbrains.com/plugin/10761-detekt
102 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: weekly
7 | day: sunday
8 | commit-message:
9 | prefix: ci
10 | labels: [dependencies]
11 | open-pull-requests-limit: 1
12 | groups:
13 | actions:
14 | patterns: ["*"]
15 |
16 | - package-ecosystem: gitsubmodule
17 | directory: /
18 | schedule:
19 | interval: weekly
20 | day: friday
21 | commit-message:
22 | prefix: build
23 | labels: [dependencies]
24 | open-pull-requests-limit: 1
25 | groups:
26 | submodules:
27 | patterns: ["*"]
28 |
29 | - package-ecosystem: gradle
30 | directory: /
31 | schedule:
32 | interval: weekly
33 | day: saturday
34 | commit-message:
35 | prefix: build
36 | labels: [dependencies]
37 | open-pull-requests-limit: 1
38 | groups:
39 | gradle:
40 | patterns: ["*"]
41 | exclude-patterns:
42 | - android-gradle
43 |
--------------------------------------------------------------------------------
/.github/detekt.yml:
--------------------------------------------------------------------------------
1 | complexity:
2 | TooManyFunctions:
3 | active: false
4 | LongParameterList:
5 | functionThreshold: 7
6 |
7 | performance:
8 | SpreadOperator:
9 | excludes:
10 | - "**/*.kts"
11 |
12 | style:
13 | WildcardImport:
14 | active: false
15 | ForbiddenComment:
16 | active: false
17 | UnusedPrivateProperty:
18 | excludes:
19 | - "**/commonMain/**"
20 | MagicNumber:
21 | ignoreNumbers:
22 | - "-1"
23 | - "0"
24 | - "1"
25 | - "2"
26 | - "3"
27 | - "10"
28 | - "16"
29 | - "0x7F"
30 | ReturnCount:
31 | max: 3
32 |
--------------------------------------------------------------------------------
/.github/scripts/build-jni.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | & cmake -S ktreesitter -B ktreesitter/.cmake/build `
4 | -DCMAKE_VERBOSE_MAKEFILE=ON `
5 | -DCMAKE_INSTALL_PREFIX=ktreesitter/src/jvmMain/resources `
6 | -DCMAKE_INSTALL_BINDIR="$env:CMAKE_INSTALL_LIBDIR"
7 | & cmake --build ktreesitter/.cmake/build --config Debug
8 | & cmake --install ktreesitter/.cmake/build --config Debug
9 |
10 | foreach ($dir in Get-ChildItem -Directory -Path languages) {
11 | & cmake -S "$dir/build/generated" -B "$dir/.cmake/build" `
12 | -DCMAKE_INSTALL_PREFIX="$dir/build/generated/src/jvmMain/resources" `
13 | -DCMAKE_INSTALL_BINDIR="$env:CMAKE_INSTALL_LIBDIR"
14 | & cmake --build "$dir/.cmake/build" --config Debug
15 | & cmake --install "$dir/.cmake/build" --config Debug
16 | }
17 |
--------------------------------------------------------------------------------
/.github/scripts/build-jni.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eu
2 |
3 | cmake -S ktreesitter -B ktreesitter/.cmake/build \
4 | -DCMAKE_BUILD_TYPE=RelWithDebugInfo \
5 | -DCMAKE_VERBOSE_MAKEFILE=ON \
6 | -DCMAKE_OSX_ARCHITECTURES=arm64 \
7 | -DCMAKE_INSTALL_PREFIX=ktreesitter/src/jvmMain/resources \
8 | -DCMAKE_INSTALL_LIBDIR="$CMAKE_INSTALL_LIBDIR"
9 | cmake --build ktreesitter/.cmake/build
10 | cmake --install ktreesitter/.cmake/build
11 |
12 | for dir in languages/*/; do
13 | cmake -S "${dir}build/generated" -B "${dir}.cmake/build" \
14 | -DCMAKE_BUILD_TYPE=RelWithDebugInfo \
15 | -DCMAKE_OSX_ARCHITECTURES=arm64 \
16 | -DCMAKE_INSTALL_PREFIX="${dir}build/generated/src/jvmMain/resources" \
17 | -DCMAKE_INSTALL_LIBDIR="$CMAKE_INSTALL_LIBDIR"
18 | cmake --build "${dir}.cmake/build"
19 | cmake --install "${dir}.cmake/build"
20 | done
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | paths:
7 | - "**/*.kt"
8 | - "**/*.kts"
9 | - "**/jni/*"
10 | - gradle/**
11 | - gradle.properties
12 | pull_request:
13 | paths:
14 | - "/*.kt"
15 | - "**/*.kts"
16 | - "**/jni/*"
17 | - gradle/**
18 | - gradle.properties
19 |
20 | concurrency:
21 | cancel-in-progress: true
22 | group: ${{github.workflow}}-${{github.ref_name}}
23 |
24 | permissions:
25 | contents: write
26 |
27 | jobs:
28 | generate:
29 | runs-on: ubuntu-latest
30 | name: Generate grammar files
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@v4
34 | with:
35 | submodules: true
36 | - name: Set up Java
37 | uses: actions/setup-java@v4
38 | with:
39 | distribution: temurin
40 | java-version: 17
41 | cache: gradle
42 | cache-dependency-path: |
43 | gradle/libs.versions.toml
44 | gradle/wrapper/gradle-wrapper.properties
45 | - name: Cache Kotlin/Native prebuilt
46 | uses: actions/cache@v4
47 | with:
48 | path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-*
49 | key: konan-${{runner.os}}-prebuilt-1.9
50 | - name: Generate files
51 | run: ./gradlew --no-daemon generateGrammarFiles
52 | env:
53 | KONAN_DATA_DIR: ${{runner.tool_cache}}/konan
54 | - name: Upload artifact
55 | uses: actions/upload-artifact@v4
56 | with:
57 | name: generated-files
58 | path: languages/*/build/generated/**
59 | retention-days: 1
60 | test:
61 | runs-on: ${{matrix.os}}
62 | name: >-
63 | Test ${{matrix.platform}} platform
64 | ${{matrix.lib_platform && format('({0}-{1})', matrix.lib_platform, matrix.lib_arch)}}
65 | needs: [generate]
66 | strategy:
67 | fail-fast: false
68 | matrix:
69 | include:
70 | - os: ubuntu-latest
71 | platform: JVM
72 | targets: :ktreesitter:jvmTest
73 | lib_platform: linux
74 | lib_arch: x64
75 | - os: windows-latest
76 | platform: JVM
77 | targets: :ktreesitter:jvmTest
78 | lib_platform: windows
79 | lib_arch: x64
80 | - os: macos-latest
81 | platform: JVM
82 | targets: :ktreesitter:jvmTest
83 | lib_platform: macos
84 | lib_arch: aarch64
85 | - os: ubuntu-latest
86 | platform: Android
87 | targets: :ktreesitter:assembleDebug
88 | - os: ubuntu-latest
89 | platform: Linux
90 | targets: >-
91 | :ktreesitter:compileKotlinLinuxArm64
92 | :ktreesitter:linuxX64Test
93 | - os: windows-latest
94 | platform: Windows
95 | targets: :ktreesitter:mingwX64Test
96 | - os: macos-latest
97 | platform: macOS/iOS
98 | targets: >-
99 | :ktreesitter:macosX64Test
100 | :ktreesitter:macosArm64Test
101 | :ktreesitter:iosSimulatorArm64Test
102 | steps:
103 | - name: Checkout repository
104 | uses: actions/checkout@v4
105 | with:
106 | submodules: true
107 | - name: Set up Java
108 | uses: actions/setup-java@v4
109 | with:
110 | distribution: temurin
111 | java-version: 17
112 | cache: gradle
113 | cache-dependency-path: |
114 | gradle/libs.versions.toml
115 | gradle/wrapper/gradle-wrapper.properties
116 | - name: Set up cross compilation
117 | run: sudo apt-get install -qy {binutils,gcc}-aarch64-linux-gnu
118 | if: matrix.platform == 'Linux'
119 | - name: Restore Kotlin/Native prebuilt
120 | uses: actions/cache/restore@v4
121 | with:
122 | path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-*
123 | key: konan-${{runner.os}}-prebuilt-1.9
124 | - name: Download generated files
125 | uses: actions/download-artifact@v4
126 | with:
127 | path: languages
128 | name: generated-files
129 | - name: Build JNI libraries
130 | if: matrix.platform == 'JVM'
131 | run: .github/scripts/build-jni.${{matrix.os == 'windows-latest' && 'ps1' || 'sh'}}
132 | env:
133 | CMAKE_INSTALL_LIBDIR: lib/${{matrix.lib_platform}}/${{matrix.lib_arch}}
134 | - name: Cache Kotlin/Native dependencies
135 | id: cache-dependencies
136 | uses: actions/cache@v4
137 | if: matrix.platform != 'JVM' && matrix.platform != 'Android'
138 | with:
139 | path: ${{runner.tool_cache}}/konan/dependencies
140 | key: konan-${{runner.os}}-dependencies
141 | - name: Download Kotlin/Native dependencies
142 | if: matrix.platform == 'macOS/iOS' && steps.cache-dependencies.outputs.cache-hit != 'true'
143 | run: |-
144 | mkdir -p "$RUNNER_TOOL_CACHE/konan/dependencies"
145 | curl -LSs https://download-cdn.jetbrains.com/kotlin/native/$DEP.tar.gz | \
146 | tar -xzf - -C "$RUNNER_TOOL_CACHE/konan/dependencies"
147 | env:
148 | DEP: apple-llvm-20200714-macos-aarch64-essentials
149 | - name: Set up Ninja
150 | if: matrix.platform == 'Android'
151 | run: |-
152 | sudo apt-get update
153 | sudo apt-get install -y ninja-build
154 | - name: Run tests
155 | run: ./gradlew --no-daemon ${{matrix.targets}}
156 | env:
157 | KONAN_DATA_DIR: ${{runner.tool_cache}}/konan
158 | - name: Report test results
159 | uses: mikepenz/action-junit-report@v5
160 | if: matrix.platform == 'JVM' && !cancelled()
161 | with:
162 | annotate_only: true
163 | detailed_summary: true
164 | report_paths: ktreesitter/build/reports/xml/*
165 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 |
3 | run-name: Update API docs
4 |
5 | on:
6 | workflow_run:
7 | workflows: [CI]
8 | types: [completed]
9 | branches: [master]
10 | push:
11 | branches: [master]
12 | paths:
13 | - ktreesitter/README.md
14 |
15 | concurrency:
16 | cancel-in-progress: true
17 | group: ${{github.workflow}}-${{github.ref_name}}
18 |
19 | permissions:
20 | pages: write
21 | id-token: write
22 |
23 | jobs:
24 | docs:
25 | runs-on: ubuntu-latest
26 | name: Publish docs on GitHub pages
27 | if: >-
28 | github.event_name == 'push' ||
29 | github.event.workflow_run.conclusion == 'success'
30 | environment:
31 | name: github-pages
32 | url: ${{steps.deployment.outputs.page_url}}
33 | steps:
34 | - name: Checkout repository
35 | uses: actions/checkout@v4
36 | with:
37 | submodules: true
38 | - name: Set up Java
39 | uses: actions/setup-java@v4
40 | with:
41 | distribution: temurin
42 | java-version: 17
43 | cache: gradle
44 | cache-dependency-path: |
45 | gradle/libs.versions.toml
46 | gradle/wrapper/gradle-wrapper.properties
47 | - name: Restore Kotlin/Native prebuilt
48 | uses: actions/cache/restore@v4
49 | with:
50 | path: ${{runner.tool_cache}}/konan/kotlin-native-prebuilt-*
51 | key: konan-${{runner.os}}-prebuilt-1.9
52 | - name: Restore Kotlin/Native dependencies
53 | uses: actions/cache/restore@v4
54 | with:
55 | path: ${{runner.tool_cache}}/konan/dependencies
56 | key: konan-${{runner.os}}-dependencies
57 | - name: Set up Ninja
58 | run: |-
59 | sudo apt-get update
60 | sudo apt-get install -y ninja-build
61 | - name: Build documentation
62 | run: ./gradlew --no-daemon generateFiles :ktreesitter:dokkaHtml
63 | env:
64 | KONAN_DATA_DIR: ${{runner.tool_cache}}/konan
65 | - name: Upload pages artifact
66 | uses: actions/upload-pages-artifact@v3
67 | with:
68 | path: ktreesitter/build/dokka/html
69 | - name: Deploy to GitHub Pages
70 | id: deployment
71 | uses: actions/deploy-pages@v4
72 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | paths:
7 | - "**/*.kt"
8 | - "**.kts"
9 | - .github/workflows/lint.yml
10 | pull_request:
11 | paths:
12 | - "**/*.kt"
13 | - "**.kts"
14 | - .github/workflows/lint.yml
15 |
16 | concurrency:
17 | cancel-in-progress: true
18 | group: ${{github.workflow}}-${{github.ref_name}}
19 |
20 | permissions:
21 | contents: read
22 | security-events: write
23 |
24 | jobs:
25 | ktlint:
26 | runs-on: ubuntu-latest
27 | steps:
28 | - name: Checkout repository
29 | uses: actions/checkout@v4
30 | - name: Set up Java
31 | uses: actions/setup-java@v4
32 | with:
33 | distribution: temurin
34 | java-version: 17
35 | - name: Install ktlint
36 | run: |-
37 | curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.3.1/ktlint
38 | chmod a+x ktlint && mv ktlint $RUNNER_TOOL_CACHE/ktlint
39 | - name: Run ktlint
40 | id: ktlint
41 | run: $RUNNER_TOOL_CACHE/ktlint --reporter=sarif,output=build/reports/ktlint.sarif
42 | - name: Upload report
43 | uses: github/codeql-action/upload-sarif@v3
44 | if: "!cancelled() && steps.ktlint.outcome == 'failure'"
45 | with:
46 | sarif_file: build/reports/ktlint.sarif
47 | # FIXME: github/codeql-action#2215
48 | checkout_path: ${{env.HOME}}
49 | category: ktlint
50 | detekt:
51 | runs-on: ubuntu-latest
52 | steps:
53 | - name: Checkout repository
54 | uses: actions/checkout@v4
55 | - name: Set up Java
56 | uses: actions/setup-java@v4
57 | with:
58 | distribution: temurin
59 | java-version: 17
60 | - name: Install detekt
61 | run: |-
62 | curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7-all.jar
63 | mv detekt-cli-1.23.7-all.jar $RUNNER_TOOL_CACHE/detekt-cli.jar
64 | - name: Run detekt
65 | id: detekt
66 | run: >-
67 | java -jar $RUNNER_TOOL_CACHE/detekt-cli.jar --build-upon-default-config
68 | --jvm-target 17 -c .github/detekt.yml -r sarif:build/reports/detekt.sarif
69 | - name: Upload report
70 | uses: github/codeql-action/upload-sarif@v3
71 | if: "!cancelled() && steps.detekt.outcome == 'failure'"
72 | with:
73 | sarif_file: build/reports/detekt.sarif
74 | category: detekt
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Gradle ###
2 | .gradle/
3 | build/
4 | kotlin-js-store/
5 | dependency-graph-reports/
6 | local.properties
7 |
8 | ### C ###
9 | .cache/
10 | .cmake/
11 | *.a
12 | *.lib
13 | *.so
14 | *.dylib
15 | *.dll
16 |
17 | ### JetBrains ###
18 | .idea/*
19 | out/
20 | *.iws
21 | *.iml
22 | *.ipr
23 | *.trace
24 | *.log
25 |
26 | !.idea/conventionalCommit.json
27 | !.idea/conventionalCommit.xml
28 | !.idea/detekt.xml
29 | !.idea/ktlint-plugin.xml
30 | !.idea/vcs.xml
31 |
32 | ### Vim ###
33 | [._]*.s[a-v][a-z]
34 | [._]*.sw[a-p]
35 | [._]s[a-rt-v][a-z]
36 | [._]ss[a-gi-z]
37 | [._]sw[a-p]
38 | Session.vim
39 | .nvimrc
40 | tags
41 | *~
42 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "tree-sitter"]
2 | path = tree-sitter
3 | url = https://github.com/tree-sitter/tree-sitter
4 | branch = release-0.24
5 |
6 | [submodule "tree-sitter-java"]
7 | path = languages/java/tree-sitter-java
8 | url = https://github.com/tree-sitter/tree-sitter-java
9 | shallow = true
10 |
--------------------------------------------------------------------------------
/.idea/conventionalCommit.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://github.com/lppedd/idea-conventional-commit/raw/master/src/main/resources/defaults/conventionalcommit.schema.json",
3 | "types": {
4 | "refactor": {
5 | "description": "Changes which neither fix a bug nor add a feature"
6 | },
7 | "fix": {
8 | "description": "Changes which patch a bug"
9 | },
10 | "feat": {
11 | "description": "Changes which introduce a new feature"
12 | },
13 | "build": {
14 | "description": "Changes which affect the build system or external dependencies"
15 | },
16 | "chore": {
17 | "description": "Changes which are not user-facing"
18 | },
19 | "test": {
20 | "description": "Changes which add missing tests or fix existing ones"
21 | },
22 | "docs": {
23 | "description": "Changes which affect documentation"
24 | },
25 | "ci": {
26 | "description": "Changes which affect CI configuration files and scripts"
27 | },
28 | "revert": {
29 | "description": "Changes which revert a previous commit"
30 | }
31 | },
32 | "commonScopes": {
33 | "android": {},
34 | "jvm": {},
35 | "native": {},
36 | "jni": {},
37 | "plugin": {},
38 | "languages": {}
39 | },
40 | "footerTypes": [
41 | {
42 | "name": "Closes",
43 | "description": "The commit closes issues or pull requests"
44 | },
45 | {
46 | "name": "Co-authored-by",
47 | "description": "The specified person co-authored the commit's changes"
48 | },
49 | {
50 | "name": "Refs",
51 | "description": "The commit references another commit by its hash"
52 | }
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/.idea/conventionalCommit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/detekt.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/ktlint-plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DISTRACT_FREE
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2024 tree-sitter contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kotlin Tree-sitter
2 |
3 | [![CI][ci]](https://github.com/tree-sitter/kotlin-tree-sitter/actions/workflows/ci.yml)
4 | [![docs][docs]](https://tree-sitter.github.io/kotlin-tree-sitter/)
5 |
6 | Kotlin bindings to the [tree-sitter] parsing library.
7 |
8 | ## Modules
9 |
10 | ### ktreesitter
11 |
12 | [![central][central]](https://central.sonatype.com/artifact/io.github.tree-sitter/ktreesitter)
13 |
14 | The KTreeSitter library module.
15 |
16 | ### ktreesitter-plugin
17 |
18 | [![portal][portal]](https://plugins.gradle.org/plugin/io.github.tree-sitter.ktreesitter-plugin)
19 |
20 | The plugin responsible for generating source files for languages.
21 |
22 | ### languages
23 |
24 | Some bundled languages that are relevant to Kotlin development.
25 |
26 | [tree-sitter]: https://tree-sitter.github.io/tree-sitter/
27 | [ci]: https://img.shields.io/github/actions/workflow/status/tree-sitter/kotlin-tree-sitter/ci.yml?logo=github&label=CI
28 | [central]: https://img.shields.io/maven-central/v/io.github.tree-sitter/ktreesitter?logo=sonatype&label=Maven%20Central
29 | [portal]: https://img.shields.io/gradle-plugin-portal/v/io.github.tree-sitter.ktreesitter-plugin?logo=gradle&label=Gradle%20Plugin%20Portal
30 | [docs]: https://img.shields.io/github/deployments/tree-sitter/kotlin-tree-sitter/github-pages?logo=kotlin&label=API%20Docs
31 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.kotlin.mpp) apply false
3 | alias(libs.plugins.android.library) apply false
4 | alias(libs.plugins.kotest) apply false
5 | alias(libs.plugins.dokka) apply false
6 | }
7 |
8 | allprojects {
9 | repositories {
10 | google()
11 | mavenCentral()
12 | }
13 | }
14 |
15 | subprojects {
16 | group = "io.github.tree-sitter"
17 | }
18 |
19 | tasks.wrapper {
20 | gradleVersion = "8.7"
21 | distributionType = Wrapper.DistributionType.BIN
22 | }
23 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # The version of the project.
2 | project.version=0.24.1
3 |
4 | # The Android SDK version that is used to compile the project.
5 | sdk.version.compile=34
6 | # The minimum supported Android SDK version.
7 | sdk.version.min=23
8 | # The Android NDK version that is used to compile the project.
9 | ndk.version=26.3.11579264
10 | # The CMake version that is used to compile the project.
11 | cmake.version=3.31.3
12 |
13 | # Specifies the JVM arguments used for the daemon process.
14 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
15 | # Disables deprecation warnings.
16 | org.gradle.warning.mode=none
17 | # Hides the welcome message.
18 | org.gradle.welcome=never
19 |
20 | # Specifies the Kotlin code style for this project.
21 | kotlin.code.style=official
22 | # Enables the new Android multiplatform sourceset layout.
23 | kotlin.mpp.androidSourceSetLayoutVersion=2
24 | # Enable C interop sharing
25 | kotlin.mpp.enableCInteropCommonization=true
26 | # Disable default stdlib dependency
27 | kotlin.stdlib.default.dependency=false
28 | # Ignore disabled Kotlin/Native targets
29 | kotlin.native.ignoreDisabledTargets=true
30 |
31 | # Enables the AndroidX package structure.
32 | android.useAndroidX=true
33 | # Disables automatically downloading the Android SDK.
34 | android.builder.sdkDownload=false
35 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | kotlin-stdlib = "[1.9,2.0)"
3 | #noinspection GradleDependency
4 | android-gradle = {strictly = "8.2.0"}
5 | kotest = "5.9.1"
6 | dokka = "1.9.20"
7 |
8 | [libraries.kotlin-stdlib]
9 | module = "org.jetbrains.kotlin:kotlin-stdlib"
10 | version.ref = "kotlin-stdlib"
11 |
12 | [libraries.kotest-engine]
13 | module = "io.kotest:kotest-framework-engine"
14 | version.ref = "kotest"
15 |
16 | [libraries.kotest-assertions]
17 | module = "io.kotest:kotest-assertions-core"
18 | version.ref = "kotest"
19 |
20 | [libraries.kotest-junit-runner]
21 | module = "io.kotest:kotest-runner-junit5"
22 | version.ref = "kotest"
23 |
24 | [libraries.kotest-junit-reporter]
25 | module = "io.kotest:kotest-extensions-junitxml"
26 | version.ref = "kotest"
27 |
28 | [libraries.kotest-android-runner]
29 | module = "br.com.colman:kotest-runner-android"
30 | version = "1.1.1"
31 |
32 | [libraries.androidx-test-runner]
33 | module = "androidx.test:runner"
34 | version = "1.5.2"
35 |
36 | [libraries.dokka-base]
37 | module = "org.jetbrains.dokka:dokka-base"
38 | version.ref = "dokka"
39 |
40 | [plugins.kotlin-mpp]
41 | id = "org.jetbrains.kotlin.multiplatform"
42 | version.ref = "kotlin-stdlib"
43 |
44 | [plugins.android-library]
45 | id = "com.android.library"
46 | version.ref = "android-gradle"
47 |
48 | [plugins.kotest]
49 | id = "io.kotest.multiplatform"
50 | version.ref = "kotest"
51 |
52 | [plugins.dokka]
53 | id = "org.jetbrains.dokka"
54 | version.ref = "dokka"
55 |
56 | [plugins.gradle-publish]
57 | id = "com.gradle.plugin-publish"
58 | version = "1.2.1"
59 |
60 | [bundles]
61 | kotest-core = [
62 | "kotest-engine",
63 | "kotest-assertions",
64 | ]
65 | kotest-junit = [
66 | "kotest-junit-runner",
67 | "kotest-junit-reporter",
68 | ]
69 | kotest-android = [
70 | "kotest-android-runner",
71 | "androidx-test-runner",
72 | ]
73 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tree-sitter/kotlin-tree-sitter/a1a9e0e589af3bc16a9b6d0046732e9f396111bc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/README.md:
--------------------------------------------------------------------------------
1 | # KTreeSitter plugin
2 |
3 | A plugin that generates code for KTreeSitter grammar packages.
4 |
5 | ## Installation
6 |
7 | ```groovy
8 | buildscript {
9 | repositories {
10 | gradlePluginPortal()
11 | }
12 | }
13 |
14 | plugins {
15 | id("io.github.tree-sitter.ktreesitter-plugin")
16 | }
17 | ```
18 |
19 | ## Configuration
20 |
21 | ```groovy
22 | grammar {
23 | /* Default options */
24 | // The base directory of the grammar
25 | baseDir = projectDir.parentFile.parentFile
26 | // The name of the C interop def file
27 | interopName = "grammar"
28 | // The name of the JNI library
29 | libraryName = "ktreesitter-$grammarName"
30 |
31 | /* Required options */
32 | // The name of the grammar
33 | grammarName = "java"
34 | // The name of the class
35 | className = "TreeSitterJava"
36 | // The name of the package
37 | packageName = "io.github.treesitter.ktreesitter.java"
38 | // The source files of the grammar
39 | files = arrayOf(
40 | baseDir.get().resolve("src/parser.c"),
41 | // baseDir.get().resolve("src/scanner.c")
42 | )
43 | }
44 | ```
45 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.util.Properties
2 |
3 | group = "io.github.tree-sitter"
4 | version = with(Properties()) {
5 | file("../gradle.properties").reader().use(::load)
6 | getProperty("project.version")
7 | }
8 |
9 | plugins {
10 | `java-gradle-plugin`
11 | id("com.gradle.plugin-publish") version "1.2.1"
12 | }
13 |
14 | repositories {
15 | mavenCentral()
16 | }
17 |
18 | java {
19 | sourceCompatibility = JavaVersion.VERSION_17
20 | targetCompatibility = JavaVersion.VERSION_17
21 | }
22 |
23 | @Suppress("UnstableApiUsage")
24 | gradlePlugin {
25 | vcsUrl = "https://github.com/tree-sitter/kotlin-tree-sitter"
26 | website = "https://github.com/tree-sitter/kotlin-tree-sitter/tree/master/ktreesitter-plugin"
27 | plugins.create("ktreesitter") {
28 | id = "$group.${project.name}"
29 | displayName = "KTreeSitter grammar plugin"
30 | description = "A plugin that generates code for KTreeSitter grammar packages"
31 | implementationClass = "io.github.treesitter.ktreesitter.plugin.GrammarPlugin"
32 | tags = listOf("tree-sitter", "code", "generator")
33 | }
34 | }
35 |
36 | tasks.javadoc {
37 | (options as CoreJavadocOptions).addBooleanOption("Xdoclint:all,-missing", true)
38 | }
39 |
40 | tasks.validatePlugins {
41 | enableStricterValidation.set(true)
42 | }
43 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarExtension.java:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter.plugin;
2 |
3 | import java.io.File;
4 | import org.gradle.api.provider.MapProperty;
5 | import org.gradle.api.provider.Property;
6 |
7 | /** The grammar configuration extension. */
8 | public interface GrammarExtension {
9 | /**
10 | * The base directory of the grammar.
11 | *
12 | *
Default: {@code ../..}
13 | */
14 | Property getBaseDir();
15 | /**
16 | * The name of the grammar.
17 | *
18 | * Required
19 | */
20 | Property getGrammarName();
21 | /**
22 | * The source files of the grammar.
23 | *
24 | * Required
25 | */
26 | Property getFiles();
27 | /**
28 | * The name of the C interop def file.
29 | *
30 | * Default: {@code grammar}
31 | */
32 | Property getInteropName();
33 | /**
34 | * The name of the JNI library.
35 | *
36 | * Default: {@code ktreesitter-${grammarName}}
37 | */
38 | Property getLibraryName();
39 | /**
40 | * The name of the package.
41 | *
42 | * Required
43 | */
44 | Property getPackageName();
45 | /**
46 | * The name of the class.
47 | *
48 | * Required
49 | */
50 | Property getClassName();
51 | /**
52 | * A map of Java methods to C functions.
53 | *
54 | * Default: {@code language -> tree_sitter_${grammarName}}
55 | */
56 | MapProperty getLanguageMethods();
57 | }
58 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/java/io/github/treesitter/ktreesitter/plugin/GrammarPlugin.java:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter.plugin;
2 |
3 | import java.util.Map;
4 |
5 | import org.gradle.api.NonNullApi;
6 | import org.gradle.api.Plugin;
7 | import org.gradle.api.Project;
8 |
9 | /**
10 | * A plugin that generates code for
11 | * KTreeSitter
12 | * grammar packages.
13 | */
14 | @NonNullApi
15 | public abstract class GrammarPlugin implements Plugin {
16 | @Override
17 | public void apply(Project project) {
18 | var extension = project.getExtensions().create("grammar", GrammarExtension.class);
19 | extension.getInteropName().convention("grammar");
20 | extension.getBaseDir().convention(
21 | project.getProjectDir().getParentFile().getParentFile()
22 | );
23 | extension.getLibraryName().convention(
24 | extension.getGrammarName().map(name -> "ktreesitter-" + name)
25 | );
26 | extension.getLanguageMethods().convention(
27 | extension.getGrammarName().map(name -> Map.of("language", "tree_sitter_" + name))
28 | );
29 |
30 | project.getTasks().register("generateGrammarFiles", GrammarFilesTask.class, it -> {
31 | it.setGrammarDir(extension.getBaseDir().get());
32 | it.setGrammarName(extension.getGrammarName().get());
33 | it.setGrammarFiles(extension.getFiles().get());
34 | it.setInteropName(extension.getInteropName().get());
35 | it.setLibraryName(extension.getLibraryName().get());
36 | it.setPackageName(extension.getPackageName().get());
37 | it.setClassName(extension.getClassName().get());
38 | it.setLanguageMethods(extension.getLanguageMethods().get());
39 |
40 | var generatedDir = project.getLayout().getBuildDirectory().get().dir("generated");
41 | it.getGeneratedSrc().set(generatedDir.dir("src"));
42 | it.getCmakeListsFile().set(generatedDir.file("CMakeLists.txt"));
43 | it.getInteropFile().set(
44 | generatedDir.dir("src").dir("nativeInterop").file(it.getInteropName() + ".def")
45 | );
46 |
47 | it.getOutputs().dir(it.getGeneratedSrc());
48 | it.getOutputs().files(it.getCmakeListsFile(), it.getInteropFile());
49 | });
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/resources/CMakeLists.txt.in:
--------------------------------------------------------------------------------
1 | # Automatically generated file. DO NOT MODIFY
2 |
3 | cmake_minimum_required(VERSION 3.12.0)
4 |
5 | project(ktreesitter-java LANGUAGES C)
6 |
7 | find_package(JNI REQUIRED)
8 |
9 | set(CMAKE_C_STANDARD 11)
10 |
11 | if(MSVC)
12 | add_compile_options(/W3 /wd4244)
13 | else(MSVC)
14 | set(CMAKE_C_VISIBILITY_PRESET hidden)
15 | add_compile_options(-Wall -Wextra
16 | -Wno-unused-parameter
17 | -Werror=implicit-function-declaration)
18 | endif(MSVC)
19 |
20 | include_directories(${JNI_INCLUDE_DIRS} @INCLUDE@)
21 |
22 | add_compile_definitions(TREE_SITTER_HIDE_SYMBOLS)
23 |
24 | add_library(@LIBRARY@ SHARED @SOURCES@)
25 |
26 | set_target_properties(@LIBRARY@ PROPERTIES DEFINE_SYMBOL "")
27 |
28 | install(TARGETS @LIBRARY@ ARCHIVE EXCLUDE_FROM_ALL)
29 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/resources/android.kt.in:
--------------------------------------------------------------------------------
1 | // Automatically generated file. DO NOT MODIFY
2 |
3 | package @PACKAGE@
4 |
5 | import dalvik.annotation.optimization.CriticalNative
6 | import javax.annotation.processing.Generated
7 |
8 | @Suppress("FunctionName")
9 | @Generated("io.github.treesitter.ktreesitter-plugin")
10 | actual object @CLASS@ {
11 | init {
12 | System.loadLibrary("@LIBRARY@")
13 | }
14 |
15 | @METHODS@
16 | }
17 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/resources/common.kt.in:
--------------------------------------------------------------------------------
1 | // Automatically generated file. DO NOT MODIFY
2 |
3 | package @PACKAGE@
4 |
5 | expect object @CLASS@ {
6 | @METHODS@
7 | }
8 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/resources/interop.def.in:
--------------------------------------------------------------------------------
1 | # Automatically generated file. DO NOT MODIFY
2 |
3 | package = @PACKAGE@.internal
4 | headers = tree-sitter-@GRAMMAR@.h
5 | compilerOpts = -DTREE_SITTER_HIDE_SYMBOLS
6 | staticLibraries = libtree-sitter-@GRAMMAR@.a
7 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/resources/jni.c.in:
--------------------------------------------------------------------------------
1 | // Automatically generated file. DO NOT MODIFY
2 |
3 | #include
4 | #include
5 |
6 | #ifndef __ANDROID__
7 | #define NATIVE_FUNCTION(name) JNIEXPORT jlong JNICALL name(JNIEnv * _env, jclass _class)
8 | #else
9 | #define NATIVE_FUNCTION(name) JNIEXPORT jlong JNICALL name()
10 | #endif
11 |
12 | @FUNCTIONS@
13 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/resources/jvm.kt.in:
--------------------------------------------------------------------------------
1 | // Automatically generated file. DO NOT MODIFY
2 |
3 | package @PACKAGE@
4 |
5 | import java.io.File.createTempFile
6 | import javax.annotation.processing.Generated
7 |
8 | @Suppress("FunctionName")
9 | @Generated("io.github.treesitter.ktreesitter-plugin")
10 | actual object @CLASS@ {
11 | private const val LIB_NAME = "@LIBRARY@"
12 |
13 | init {
14 | try {
15 | System.loadLibrary(LIB_NAME)
16 | } catch (ex: UnsatisfiedLinkError) {
17 | @Suppress("UnsafeDynamicallyLoadedCode")
18 | System.load(libPath() ?: throw ex)
19 | }
20 | }
21 |
22 | @METHODS@
23 |
24 | @JvmStatic
25 | @Suppress("ConvertToStringTemplate")
26 | @Throws(UnsupportedOperationException::class)
27 | internal fun libPath(): String? {
28 | val osName = System.getProperty("os.name")!!.lowercase()
29 | val archName = System.getProperty("os.arch")!!.lowercase()
30 | val ext: String
31 | val os: String
32 | val prefix: String
33 | when {
34 | "windows" in osName -> {
35 | ext = "dll"
36 | os = "windows"
37 | prefix = ""
38 | }
39 | "linux" in osName -> {
40 | ext = "so"
41 | os = "linux"
42 | prefix = "lib"
43 | }
44 | "mac" in osName -> {
45 | ext = "dylib"
46 | os = "macos"
47 | prefix = "lib"
48 | }
49 | else -> {
50 | throw UnsupportedOperationException("Unsupported operating system: " + osName)
51 | }
52 | }
53 | val arch = when {
54 | "amd64" in archName || "x86_64" in archName -> "x64"
55 | "aarch64" in archName || "arm64" in archName -> "aarch64"
56 | else -> throw UnsupportedOperationException("Unsupported architecture: " + archName)
57 | }
58 | val libPath = "/lib/" + os + "/" + arch + "/" + prefix + LIB_NAME + "." + ext
59 | val libUrl = javaClass.getResource(libPath) ?: return null
60 | return createTempFile(prefix + LIB_NAME, "." + ext).apply {
61 | writeBytes(libUrl.openStream().use { it.readAllBytes() })
62 | deleteOnExit()
63 | }.path
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/ktreesitter-plugin/src/main/resources/native.kt.in:
--------------------------------------------------------------------------------
1 | // Automatically generated file. DO NOT MODIFY
2 |
3 | package @PACKAGE@
4 |
5 | @IMPORTS@
6 | import kotlinx.cinterop.ExperimentalForeignApi
7 |
8 | @OptIn(ExperimentalForeignApi::class)
9 | actual object @CLASS@ {
10 | @METHODS@
11 | }
12 |
--------------------------------------------------------------------------------
/ktreesitter/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: LLVM
2 | AlignEscapedNewlinesLeft: false
3 | DerivePointerAlignment: false
4 | PointerAlignment: Right
5 | IndentWidth: 4
6 | ColumnLimit: 100
7 | IncludeBlocks: Preserve
8 | BinPackArguments: true
9 | IndentCaseLabels: true
10 |
--------------------------------------------------------------------------------
/ktreesitter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.12.0)
2 |
3 | file(READ ../gradle.properties GRADLE_PROPERTIES)
4 | string(REGEX MATCH "project[.]version=([0-9.]+)" _ ${GRADLE_PROPERTIES})
5 |
6 | project(ktreesitter VERSION ${CMAKE_MATCH_1} LANGUAGES C)
7 |
8 | find_package(JNI REQUIRED)
9 |
10 | set(CMAKE_C_STANDARD 11)
11 |
12 | if(MSVC)
13 | add_compile_options(/W3 /wd4244)
14 | else(MSVC)
15 | set(CMAKE_C_VISIBILITY_PRESET hidden)
16 | add_compile_options(-Wall -Wextra
17 | -Wno-unused-parameter
18 | -Wno-cast-function-type
19 | -Werror=incompatible-pointer-types
20 | -Werror=implicit-function-declaration)
21 | endif(MSVC)
22 |
23 | include_directories(${JNI_INCLUDE_DIRS}
24 | ../tree-sitter/lib/src
25 | ../tree-sitter/lib/include)
26 |
27 | add_compile_definitions(TREE_SITTER_HIDE_SYMBOLS)
28 |
29 | add_library(ktreesitter SHARED
30 | ./src/jni/language.c
31 | ./src/jni/lookahead_iterator.c
32 | ./src/jni/node.c
33 | ./src/jni/parser.c
34 | ./src/jni/query.c
35 | ./src/jni/tree.c
36 | ./src/jni/tree_cursor.c
37 | ./src/jni/module.c
38 | ../tree-sitter/lib/src/lib.c)
39 |
40 | set_target_properties(ktreesitter PROPERTIES DEFINE_SYMBOL "")
41 |
42 | install(TARGETS ktreesitter ARCHIVE EXCLUDE_FROM_ALL)
43 |
--------------------------------------------------------------------------------
/ktreesitter/README.md:
--------------------------------------------------------------------------------
1 | # KTreeSitter
2 |
3 | Kotlin bindings to the [tree-sitter] parsing library.
4 |
5 | ## Supported platforms
6 |
7 | - [x] JVM
8 | - [x] Android
9 | - [x] Native
10 | - [ ] WASI
11 |
12 | *JS and WasmJS will not be supported.*
13 |
14 | ## Installation
15 |
16 | ```kotlin
17 | dependencies {
18 | implementation("io.github.tree-sitter:ktreesitter") version $ktreesitterVersion
19 | }
20 |
21 | repositories {
22 | mavenCentral()
23 | }
24 | ```
25 |
26 | ## Basic usage
27 |
28 | ```kotlin
29 | val language = Language(TreeSitterKotlin.language())
30 | val parser = Parser(language)
31 | val tree = parser.parse("fun main() {}")
32 | val rootNode = tree.rootNode
33 |
34 | assert(rootNode.type == "source_file")
35 | assert(rootNode.startPoint.column == 0)
36 | assert(rootNode.endPoint.column == 13)
37 | ```
38 |
39 | [tree-sitter]: https://tree-sitter.github.io/tree-sitter/
40 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import br.com.colman.kotest.KotestRunnerAndroid
4 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
5 | import io.kotest.assertions.throwables.shouldNotThrowAny
6 | import io.kotest.core.spec.style.FunSpec
7 | import io.kotest.matchers.*
8 | import io.kotest.matchers.comparables.*
9 | import io.kotest.matchers.nulls.*
10 | import io.kotest.matchers.string.*
11 | import io.kotest.matchers.types.*
12 | import org.junit.runner.RunWith
13 |
14 | @RunWith(KotestRunnerAndroid::class)
15 | class LanguageTest : FunSpec({
16 | val language = Language(TreeSitterJava.language())
17 |
18 | test("version") {
19 | language.version shouldBe 14U
20 | }
21 |
22 | test("symbolCount") {
23 | language.symbolCount shouldBeGreaterThan 1U
24 | }
25 |
26 | test("stateCount") {
27 | language.stateCount shouldBeGreaterThan 1U
28 | }
29 |
30 | test("fieldCount") {
31 | language.fieldCount shouldBeGreaterThan 1U
32 | }
33 |
34 | test("symbolName()") {
35 | language.symbolName(1U) shouldBe "identifier"
36 | }
37 |
38 | test("symbolForName()") {
39 | language.symbolForName(";", false) shouldBeGreaterThan 0U
40 | language.symbolForName("program", true) shouldBeGreaterThan 0U
41 | }
42 |
43 | test("isNamed()") {
44 | language.isNamed(1U) shouldBe true
45 | }
46 |
47 | test("isVisible()") {
48 | language.isVisible(1U) shouldBe true
49 | }
50 |
51 | test("isSupertype()") {
52 | language.isSupertype(1U) shouldBe false
53 | }
54 |
55 | test("fieldNameForId()") {
56 | language.fieldNameForId(1U).shouldNotBeNull()
57 | }
58 |
59 | test("fieldIdForName()") {
60 | language.fieldIdForName("body") shouldBeGreaterThan 0U
61 | }
62 |
63 | test("nextState()") {
64 | val program = language.symbolForName("program", true)
65 | language.nextState(1U, program) shouldBeGreaterThan 0U
66 | }
67 |
68 | test("lookaheadIterator()") {
69 | val program = language.symbolForName("program", true)
70 | val state = language.nextState(1U, program)
71 | val lookahead = language.lookaheadIterator(state)
72 | lookahead.language shouldBe language
73 | }
74 |
75 | test("query()") {
76 | shouldNotThrowAny { language.query("(program) @root") }
77 | }
78 |
79 | test("equals()") {
80 | Language(TreeSitterJava.language()) shouldBe language.copy()
81 | }
82 |
83 | test("hashCode()") {
84 | language shouldHaveSameHashCodeAs TreeSitterJava.language()
85 | }
86 |
87 | test("toString()") {
88 | language.toString() shouldMatch Regex("""Language\(id=0x[0-9a-f]+, version=14\)""")
89 | }
90 | })
91 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/LookaheadIteratorTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import br.com.colman.kotest.KotestRunnerAndroid
4 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
5 | import io.kotest.core.spec.style.FunSpec
6 | import io.kotest.matchers.*
7 | import io.kotest.matchers.collections.*
8 | import org.junit.runner.RunWith
9 |
10 | @RunWith(KotestRunnerAndroid::class)
11 | class LookaheadIteratorTest : FunSpec({
12 | val language = Language(TreeSitterJava.language())
13 | val state = language.nextState(1U, 138U)
14 | val lookahead = language.lookaheadIterator(state)
15 |
16 | test("currentSymbol") {
17 | lookahead.currentSymbol shouldBe UShort.MAX_VALUE
18 | lookahead.currentSymbolName shouldBe "ERROR"
19 | }
20 |
21 | test("next()") {
22 | lookahead.next() shouldBe LookaheadIterator.Symbol(0U, "end")
23 | }
24 |
25 | test("reset()") {
26 | lookahead.reset(state) shouldBe true
27 | lookahead.reset(state, language) shouldBe true
28 | }
29 |
30 | test("symbols()") {
31 | lookahead.symbols().shouldBeStrictlyIncreasing()
32 | }
33 |
34 | test("names()") {
35 | val names = lookahead.symbolNames().toList()
36 | names shouldContainExactly listOf("end", "line_comment", "block_comment")
37 | }
38 |
39 | test("iterator") {
40 | for ((symbol, name) in lookahead) {
41 | symbol shouldBe lookahead.currentSymbol
42 | name shouldBe lookahead.currentSymbolName
43 | }
44 | }
45 | })
46 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import br.com.colman.kotest.KotestRunnerAndroid
4 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
5 | import io.kotest.assertions.throwables.shouldThrow
6 | import io.kotest.core.spec.style.FunSpec
7 | import io.kotest.inspectors.forSingle
8 | import io.kotest.matchers.*
9 | import io.kotest.matchers.collections.*
10 | import io.kotest.matchers.nulls.*
11 | import io.kotest.matchers.types.*
12 | import org.junit.runner.RunWith
13 |
14 | @RunWith(KotestRunnerAndroid::class)
15 | class NodeTest : FunSpec({
16 | val language = Language(TreeSitterJava.language())
17 | val parser = Parser(language)
18 | val source = "class Foo {}"
19 | val tree = parser.parse(source)
20 | val rootNode = tree.rootNode
21 |
22 | test("id") {
23 | rootNode.id shouldNotBe 0UL
24 | }
25 |
26 | test("symbol") {
27 | rootNode.symbol shouldNotBe UShort.MIN_VALUE
28 | }
29 |
30 | test("grammarSymbol") {
31 | rootNode.grammarSymbol shouldBe rootNode.symbol
32 | }
33 |
34 | test("type") {
35 | rootNode.type shouldBe "program"
36 | }
37 |
38 | test("grammarType") {
39 | rootNode.grammarType shouldBe rootNode.type
40 | }
41 |
42 | test("isNamed") {
43 | rootNode.isNamed shouldBe true
44 | }
45 |
46 | test("isExtra") {
47 | rootNode.isExtra shouldBe false
48 | }
49 |
50 | test("isError") {
51 | rootNode.isError shouldBe false
52 | }
53 |
54 | test("isMissing") {
55 | rootNode.isMissing shouldBe false
56 | }
57 |
58 | test("hasChanges") {
59 | rootNode.hasChanges shouldBe false
60 | }
61 |
62 | test("hasError") {
63 | rootNode.hasError shouldBe false
64 | }
65 |
66 | test("parseState") {
67 | rootNode.parseState shouldBe 0U
68 | }
69 |
70 | test("nextParseState") {
71 | rootNode.nextParseState shouldBe 0U
72 | }
73 |
74 | test("startByte") {
75 | rootNode.startByte shouldBe 0U
76 | }
77 |
78 | test("endByte") {
79 | rootNode.endByte shouldBe 12U
80 | }
81 |
82 | test("byteRange") {
83 | rootNode.byteRange shouldBe 0U..12U
84 | }
85 |
86 | test("range") {
87 | rootNode.range shouldBe Range(Point(0U, 0U), Point(0U, 12U), 0U, 12U)
88 | }
89 |
90 | test("startPoint") {
91 | rootNode.startPoint shouldBe Point(0U, 0U)
92 | }
93 |
94 | test("endPoint") {
95 | rootNode.endPoint shouldBe Point(0U, 12U)
96 | }
97 |
98 | test("childCount") {
99 | rootNode.childCount shouldBe 1U
100 | }
101 |
102 | test("namedChildCount") {
103 | rootNode.namedChildCount shouldBe 1U
104 | }
105 |
106 | test("descendantCount") {
107 | rootNode.descendantCount shouldBe 7U
108 | }
109 |
110 | test("parent") {
111 | rootNode.parent.shouldBeNull()
112 | }
113 |
114 | test("nextSibling") {
115 | rootNode.nextSibling.shouldBeNull()
116 | }
117 |
118 | test("prevSibling") {
119 | rootNode.prevSibling.shouldBeNull()
120 | }
121 |
122 | test("nextNamedSibling") {
123 | rootNode.nextNamedSibling.shouldBeNull()
124 | }
125 |
126 | test("prevNamedSibling") {
127 | rootNode.prevNamedSibling.shouldBeNull()
128 | }
129 |
130 | test("children") {
131 | val children = rootNode.children
132 | children.forSingle { it.type shouldBe "class_declaration" }
133 | rootNode.children shouldBeSameInstanceAs children
134 | }
135 |
136 | test("namedChildren") {
137 | val children = rootNode.namedChildren
138 | children shouldContainExactly rootNode.children
139 | }
140 |
141 | test("child()") {
142 | val node = rootNode.child(0U)
143 | node?.type shouldBe "class_declaration"
144 | shouldThrow { rootNode.child(1U) }
145 | }
146 |
147 | test("namedChild()") {
148 | rootNode.namedChild(0U) shouldBe rootNode.child(0U)
149 | shouldThrow { rootNode.namedChild(1U) }
150 | }
151 |
152 | test("childByFieldId()") {
153 | rootNode.childByFieldId(0U).shouldBeNull()
154 | }
155 |
156 | test("childByFieldName()") {
157 | val child = rootNode.child(0U)!!.childByFieldName("body")
158 | child?.type shouldBe "class_body"
159 | }
160 |
161 | test("childrenByFieldId()") {
162 | val id = language.fieldIdForName("name")
163 | val children = rootNode.child(0U)!!.childrenByFieldId(id)
164 | children.forSingle { it.type shouldBe "identifier" }
165 | }
166 |
167 | test("childrenByFieldName()") {
168 | val children = rootNode.child(0U)!!.childrenByFieldName("name")
169 | children.forSingle { it.type shouldBe "identifier" }
170 | }
171 |
172 | test("fieldNameForChild()") {
173 | rootNode.child(0U)!!.fieldNameForChild(2U) shouldBe "body"
174 | }
175 |
176 | test("fieldNameForNamedChild()") {
177 | rootNode.child(0U)!!.fieldNameForNamedChild(2U).shouldBeNull()
178 | }
179 |
180 | @Suppress("DEPRECATION")
181 | test("childContainingDescendant()") {
182 | val descendant = rootNode.child(0U)!!.child(0U)!!
183 | val child = rootNode.childContainingDescendant(descendant)
184 | child?.type shouldBe "class_declaration"
185 | }
186 |
187 | test("childWithDescendant()") {
188 | val descendant = rootNode.child(0U)!!
189 | val child = rootNode.childWithDescendant(descendant)
190 | child?.type shouldBe "class_declaration"
191 | }
192 |
193 | test("descendant()") {
194 | rootNode.descendant(0U, 5U)?.type shouldBe "class"
195 | rootNode.descendant(Point(0U, 10U), Point(0U, 12U))?.type shouldBe "class_body"
196 | }
197 |
198 | test("namedDescendant()") {
199 | rootNode.namedDescendant(0U, 5U)?.type shouldBe "class_declaration"
200 | rootNode.namedDescendant(Point(0U, 6U), Point(0U, 9U))?.type shouldBe "identifier"
201 | }
202 |
203 | test("walk()") {
204 | val cursor = rootNode.walk()
205 | cursor.currentNode shouldBeSameInstanceAs rootNode
206 | }
207 |
208 | test("copy()") {
209 | val copy = tree.copy()
210 | copy shouldNotBeSameInstanceAs tree
211 | copy.text() shouldBe tree.text()
212 | }
213 |
214 | @Suppress("UnnecessaryOptInAnnotation")
215 | @OptIn(ExperimentalMultiplatform::class)
216 | test("edit()") {
217 | val edit = InputEdit(0U, 12U, 10U, Point(0U, 0U), Point(0U, 12U), Point(0U, 10U))
218 | val copy = tree.copy()
219 | val node = copy.rootNode
220 | copy.edit(edit)
221 | node.edit(edit)
222 | node.hasChanges shouldBe true
223 | }
224 |
225 | test("text()") {
226 | rootNode.descendant(6U, 9U)?.text() shouldBe "Foo"
227 | }
228 |
229 | test("sexp()") {
230 | rootNode.child(0U)!!.sexp() shouldBe
231 | "(class_declaration name: (identifier) body: (class_body))"
232 | }
233 |
234 | test("equals()") {
235 | rootNode shouldNotBe rootNode.child(0U)
236 | }
237 |
238 | test("hashCode()") {
239 | rootNode.hashCode() shouldBe rootNode.id.toInt()
240 | }
241 |
242 | test("toString()") {
243 | rootNode.toString() shouldBe "Node(type=program, startByte=0, endByte=12)"
244 | }
245 | })
246 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import br.com.colman.kotest.KotestRunnerAndroid
4 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
5 | import io.kotest.assertions.throwables.shouldNotThrowAnyUnit
6 | import io.kotest.core.spec.style.FunSpec
7 | import io.kotest.inspectors.forSome
8 | import io.kotest.matchers.*
9 | import io.kotest.matchers.collections.*
10 | import io.kotest.matchers.nulls.*
11 | import io.kotest.matchers.string.*
12 | import io.kotest.matchers.types.*
13 | import org.junit.runner.RunWith
14 |
15 | @RunWith(KotestRunnerAndroid::class)
16 | class ParserTest : FunSpec({
17 | val parser = Parser()
18 |
19 | test("language") {
20 | val language = Language(TreeSitterJava.language())
21 | parser.language.shouldBeNull()
22 | parser.language = language
23 | parser.language shouldBeSameInstanceAs language
24 | }
25 |
26 | test("includedRanges") {
27 | val range = Range(Point(0U, 0U), Point(0U, 1U), 0U, 1U)
28 | parser.includedRanges.shouldBeEmpty()
29 | parser.includedRanges = listOf(range)
30 | parser.includedRanges shouldHaveSingleElement range
31 | }
32 |
33 | test("timeoutMicros") {
34 | parser.timeoutMicros shouldBe 0UL
35 | parser.timeoutMicros = 10000UL
36 | parser.timeoutMicros shouldBe 10000UL
37 | }
38 |
39 | test("logger") {
40 | shouldNotThrowAnyUnit {
41 | parser.logger = { _, _ ->
42 | throw UnsupportedOperationException()
43 | }
44 | }
45 | }
46 |
47 | test("parse(source)") {
48 | // UTF-8
49 | var source = "class Foo {}"
50 | var tree = parser.parse(source)
51 | tree.text()?.get(5) shouldBe ' '
52 |
53 | // logging
54 | val logs = mutableListOf()
55 | parser.logger = { type, msg -> logs += "$type - $msg" }
56 | parser.reset()
57 | parser.parse(source)
58 | parser.logger = null
59 | logs.shouldNotBeEmpty().forSome {
60 | it shouldStartWith "LEX"
61 | }
62 |
63 | // UTF-16
64 | source = "var java = \"💩\""
65 | tree = parser.parse(source)
66 | tree.text()?.subSequence(12, 14) shouldBe "\uD83D\uDCA9"
67 | }
68 |
69 | test("parse(callback)") {
70 | val source = "class Foo {}"
71 | val tree = parser.parse { byte, _ ->
72 | val end = minOf(byte.toInt() * 2, source.length)
73 | source.subSequence(byte.toInt(), end).ifEmpty { null }
74 | }
75 | tree.text().shouldBeNull()
76 | tree.rootNode.type shouldBe "program"
77 | }
78 |
79 | afterTest { (test, _) ->
80 | when (test.name.testName) {
81 | "includedRanges" -> parser.includedRanges = emptyList()
82 | "timeoutMicros" -> parser.timeoutMicros = 0UL
83 | "logger" -> parser.logger = null
84 | }
85 | }
86 | })
87 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/TreeCursorTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import br.com.colman.kotest.KotestRunnerAndroid
4 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
5 | import io.kotest.core.spec.style.FunSpec
6 | import io.kotest.matchers.*
7 | import io.kotest.matchers.nulls.*
8 | import io.kotest.matchers.types.*
9 | import org.junit.runner.RunWith
10 |
11 | @RunWith(KotestRunnerAndroid::class)
12 | class TreeCursorTest : FunSpec({
13 | val language = Language(TreeSitterJava.language())
14 | val parser = Parser(language)
15 | val source = "class Foo {}"
16 | val tree = parser.parse(source)
17 | val cursor = tree.walk()
18 | val rootNode = tree.rootNode
19 |
20 | test("currentNode") {
21 | val node = cursor.currentNode
22 | node shouldBe rootNode
23 | node shouldBeSameInstanceAs cursor.currentNode
24 | }
25 |
26 | test("currentDepth") {
27 | cursor.currentDepth shouldBe 0U
28 | }
29 |
30 | test("currentFieldId") {
31 | cursor.currentFieldId shouldBe 0U
32 | }
33 |
34 | test("currentFieldName") {
35 | cursor.currentFieldName.shouldBeNull()
36 | }
37 |
38 | test("currentDescendantIndex") {
39 | cursor.currentDescendantIndex shouldBe 0U
40 | }
41 |
42 | test("copy()") {
43 | val copy = cursor.copy()
44 | copy shouldNotBeSameInstanceAs cursor
45 | cursor.reset(copy)
46 | copy.currentNode shouldBe cursor.currentNode
47 | }
48 |
49 | test("gotoFirstChild()") {
50 | cursor.gotoFirstChild() shouldBe true
51 | cursor.currentNode.type shouldBe "class_declaration"
52 | }
53 |
54 | test("gotoLastChild()") {
55 | cursor.gotoLastChild() shouldBe true
56 | cursor.currentFieldName shouldBe "body"
57 | }
58 |
59 | test("gotoParent()") {
60 | cursor.gotoParent() shouldBe true
61 | cursor.currentNode.type shouldBe "class_declaration"
62 | }
63 |
64 | test("gotoNextSibling()") {
65 | cursor.gotoNextSibling() shouldBe false
66 | }
67 |
68 | test("gotoPreviousSibling()") {
69 | cursor.gotoPreviousSibling() shouldBe false
70 | }
71 |
72 | test("gotoDescendant()") {
73 | cursor.gotoDescendant(2U)
74 | cursor.currentDescendantIndex shouldBe 2U
75 | cursor.reset(rootNode)
76 | }
77 |
78 | test("gotoFirstChildForByte()") {
79 | cursor.gotoFirstChildForByte(1U) shouldBe 0U
80 | cursor.currentNode.type shouldBe "class_declaration"
81 | }
82 |
83 | test("gotoFirstChildForPoint()") {
84 | cursor.gotoFirstChildForPoint(Point(0U, 7U)) shouldBe 1U
85 | cursor.currentFieldName shouldBe "name"
86 | }
87 | })
88 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidInstrumentedTest/kotlin/io/github/treesitter/ktreesitter/TreeTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import br.com.colman.kotest.KotestRunnerAndroid
4 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
5 | import io.kotest.core.spec.style.FunSpec
6 | import io.kotest.matchers.*
7 | import io.kotest.matchers.collections.*
8 | import io.kotest.matchers.nulls.*
9 | import io.kotest.matchers.types.*
10 | import org.junit.runner.RunWith
11 |
12 | @RunWith(KotestRunnerAndroid::class)
13 | class TreeTest : FunSpec({
14 | val language = Language(TreeSitterJava.language())
15 | val parser = Parser(language)
16 | var source = "class Foo {}"
17 | var tree = parser.parse(source)
18 |
19 | test("language") {
20 | tree.language shouldBeSameInstanceAs language
21 | }
22 |
23 | test("rootNode") {
24 | tree.rootNode.endByte shouldBe source.length.toUInt()
25 | }
26 |
27 | test("includedRanges") {
28 | tree.includedRanges.shouldHaveSingleElement {
29 | it.startByte == 0U && it.endByte == UInt.MAX_VALUE
30 | }
31 | }
32 |
33 | test("rootNodeWithOffset()") {
34 | val node = tree.rootNodeWithOffset(6U, Point(0U, 6U))
35 | node?.text() shouldBe "Foo {}"
36 | }
37 |
38 | test("text()") {
39 | tree.text() shouldBe source
40 | }
41 |
42 | test("edit()") {
43 | val edit = InputEdit(9U, 9U, 10U, Point(0U, 9U), Point(0U, 9U), Point(0U, 10U))
44 | source = "class Foo2 {}"
45 | tree.edit(edit)
46 | tree.text().shouldBeNull()
47 | tree = parser.parse(source, tree)
48 | tree.text() shouldBe source
49 | }
50 |
51 | test("walk()") {
52 | val cursor = tree.walk()
53 | cursor.tree shouldBeSameInstanceAs tree
54 | }
55 |
56 | test("changedRanges()") {
57 | val edit = InputEdit(0U, 0U, 7U, Point(0U, 0U), Point(0U, 0U), Point(0U, 7U))
58 | tree.edit(edit)
59 | val newTree = parser.parse("public $source", tree)
60 | tree.changedRanges(newTree).shouldHaveSingleElement {
61 | it.endByte == 7U && it.endPoint.column == 7U
62 | }
63 | }
64 | })
65 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidInstrumentedTest/resources/kotest.properties:
--------------------------------------------------------------------------------
1 | # suppress inspection "UnusedProperty" for whole file
2 | kotest.framework.classpath.scanning.config.disable=true
3 | kotest.framework.classpath.scanning.autoscan.disable=true
4 | kotest.framework.assertion.globalassertsoftly=true
5 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Language.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import dalvik.annotation.optimization.CriticalNative
4 | import dalvik.annotation.optimization.FastNative
5 |
6 | /**
7 | * A class that defines how to parse a particular language.
8 | *
9 | * When a [Language] is generated by the Tree-sitter CLI, it is assigned
10 | * an ABI [version] number that corresponds to the current CLI version.
11 | *
12 | * @constructor Create a new instance from the given language pointer.
13 | * @param language A pointer to a `TSLanguage` cast to [Long].
14 | * @throws [IllegalArgumentException] If the pointer is invalid or the [version] is incompatible.
15 | */
16 | actual class Language @Throws(IllegalArgumentException::class) actual constructor(language: Any) {
17 | @JvmField
18 | internal val self: Long = (language as? Long)?.takeIf { it > 0L }
19 | ?: throw IllegalArgumentException("Invalid language: $language")
20 |
21 | init {
22 | checkVersion()
23 | }
24 |
25 | /** The ABI version number for this language. */
26 | @get:JvmName("getVersion")
27 | actual val version: UInt
28 | @FastNative external get
29 |
30 | /** The number of distinct node types in this language. */
31 | @get:JvmName("getSymbolCount")
32 | actual val symbolCount: UInt
33 | @FastNative external get
34 |
35 | /** The number of valid states in this language. */
36 | @get:JvmName("getStateCount")
37 | actual val stateCount: UInt
38 | @FastNative external get
39 |
40 | /** The number of distinct field names in this language. */
41 | @get:JvmName("getFieldCount")
42 | actual val fieldCount: UInt
43 | @FastNative external get
44 |
45 | /**
46 | * Get another reference to the language.
47 | *
48 | * @since 0.24.0
49 | */
50 | actual fun copy() = Language(copy(self))
51 |
52 | /** Get the node type for the given numerical ID. */
53 | @FastNative
54 | @JvmName("symbolName")
55 | actual external fun symbolName(symbol: UShort): String?
56 |
57 | /** Get the numerical ID for the given node type. */
58 | @FastNative
59 | @JvmName("symbolForName")
60 | actual external fun symbolForName(name: String, isNamed: Boolean): UShort
61 |
62 | /**
63 | * Check if the node for the given numerical ID is named
64 | *
65 | * @see [Node.isNamed]
66 | */
67 | @FastNative
68 | @JvmName("isNamed")
69 | actual external fun isNamed(symbol: UShort): Boolean
70 |
71 | /** Check if the node for the given numerical ID is visible. */
72 | @FastNative
73 | @JvmName("isVisible")
74 | actual external fun isVisible(symbol: UShort): Boolean
75 |
76 | /**
77 | * Check if the node for the given numerical ID is a supertype.
78 | *
79 | * @since 0.24.0
80 | */
81 | @FastNative
82 | @JvmName("isSupertype")
83 | actual external fun isSupertype(symbol: UShort): Boolean
84 |
85 | /** Get the field name for the given numerical id. */
86 | @FastNative
87 | @JvmName("fieldNameForId")
88 | actual external fun fieldNameForId(id: UShort): String?
89 |
90 | /** Get the numerical ID for the given field name. */
91 | @FastNative
92 | @JvmName("fieldIdForName")
93 | actual external fun fieldIdForName(name: String): UShort
94 |
95 | /**
96 | * Get the next parse state.
97 | *
98 | * Combine this with [lookaheadIterator] to generate
99 | * completion suggestions or valid symbols in error nodes.
100 | *
101 | * #### Example
102 | *
103 | * ```kotlin
104 | * language.nextState(node.parseState, node.grammarSymbol)
105 | * ```
106 | */
107 | @FastNative
108 | @JvmName("nextState")
109 | actual external fun nextState(state: UShort, symbol: UShort): UShort
110 |
111 | /**
112 | * Create a new [lookahead iterator][LookaheadIterator] for the given parse state.
113 | *
114 | * @throws [IllegalArgumentException] If the state is invalid for this language.
115 | */
116 | @JvmName("lookaheadIterator")
117 | @Throws(IllegalArgumentException::class)
118 | actual fun lookaheadIterator(state: UShort) = LookaheadIterator(this, state)
119 |
120 | /**
121 | * Create a new [Query] from a string containing one or more S-expression
122 | * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax).
123 | *
124 | * @throws [QueryError] If any error occurred while creating the query.
125 | */
126 | @Throws(QueryError::class)
127 | actual fun query(source: String) = Query(this, source)
128 |
129 | actual override fun equals(other: Any?) =
130 | this === other || (other is Language && self == other.self)
131 |
132 | actual override fun hashCode() = self.hashCode()
133 |
134 | override fun toString() = "Language(id=0x${self.toString(16)}, version=$version)"
135 |
136 | @FastNative
137 | @Throws(IllegalArgumentException::class)
138 | private external fun checkVersion()
139 |
140 | private companion object {
141 | @JvmStatic
142 | @CriticalNative
143 | private external fun copy(self: Long): Long
144 |
145 | init {
146 | System.loadLibrary("ktreesitter")
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import dalvik.annotation.optimization.CriticalNative
4 | import dalvik.annotation.optimization.FastNative
5 |
6 | /**
7 | * A class that is used to look up valid symbols in a specific parse state.
8 | *
9 | * Lookahead iterators can be useful to generate suggestions and improve syntax
10 | * error diagnostics. To get symbols valid in an `ERROR` node, use the lookahead
11 | * iterator on its first leaf node state. For `MISSING` nodes, a lookahead
12 | * iterator created on the previous non-extra leaf node may be appropriate.
13 | *
14 | * __NOTE:__ If you're targeting Android SDK level < 33,
15 | * you must `use` or [close] the instance to free up resources.
16 | */
17 | actual class LookaheadIterator @Throws(IllegalArgumentException::class) internal constructor(
18 | language: Language,
19 | private val state: UShort
20 | ) : AbstractIterator(), AutoCloseable {
21 | private val self: Long = init(language.self, state).takeIf { it > 0L }
22 | ?: throw IllegalArgumentException("State $state is not valid for $language")
23 |
24 | init {
25 | RefCleaner(this, CleanAction(self))
26 | }
27 |
28 | /** The current language of the lookahead iterator. */
29 | actual val language: Language
30 | @FastNative external get
31 |
32 | /**
33 | * The current symbol ID.
34 | *
35 | * The ID of the `ERROR` symbol is equal to `UShort.MAX_VALUE`.
36 | */
37 | @get:JvmName("getCurrentSymbol")
38 | actual val currentSymbol: UShort
39 | @FastNative external get
40 |
41 | /**
42 | * The current symbol name.
43 | *
44 | * Newly created lookahead iterators will contain the `ERROR` symbol.
45 | */
46 | actual val currentSymbolName: String
47 | @FastNative external get
48 |
49 | /**
50 | * Reset the lookahead iterator the given [state] and, optionally, another [language].
51 | *
52 | * @return `true` if the iterator was reset successfully or `false` if it failed.
53 | */
54 | @FastNative
55 | @JvmName("reset")
56 | actual external fun reset(state: UShort, language: Language?): Boolean
57 |
58 | /** Advance the lookahead iterator to the next symbol. */
59 | actual override fun next() = super.next()
60 |
61 | /** Iterate over the symbol IDs. */
62 | actual fun symbols(): Sequence {
63 | reset(state)
64 | return sequence {
65 | while (nativeNext()) {
66 | yield(currentSymbol)
67 | }
68 | }
69 | }
70 |
71 | /** Iterate over the symbol names. */
72 | actual fun symbolNames(): Sequence {
73 | reset(state)
74 | return sequence {
75 | while (nativeNext()) {
76 | yield(currentSymbolName)
77 | }
78 | }
79 | }
80 |
81 | override fun close() = delete(self)
82 |
83 | override fun computeNext() = if (nativeNext()) {
84 | setNext(Symbol(currentSymbol, currentSymbolName))
85 | } else {
86 | done()
87 | }
88 |
89 | operator fun iterator() = apply { reset(state) }
90 |
91 | @FastNative
92 | private external fun nativeNext(): Boolean
93 |
94 | /** A class that pairs a symbol ID with its name. */
95 | @JvmRecord
96 | actual data class Symbol actual constructor(actual val id: UShort, actual val name: String)
97 |
98 | private class CleanAction(private val ptr: Long) : Runnable {
99 | override fun run() = delete(ptr)
100 | }
101 |
102 | private companion object {
103 | @JvmStatic
104 | @JvmName("init")
105 | @CriticalNative
106 | private external fun init(language: Long, state: UShort): Long
107 |
108 | @JvmStatic
109 | @CriticalNative
110 | private external fun delete(self: Long)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import dalvik.annotation.optimization.CriticalNative
4 | import dalvik.annotation.optimization.FastNative
5 |
6 | /**
7 | * A class that is used to produce a [syntax tree][Tree] from source code.
8 | *
9 | * __NOTE:__ If you're targeting Android SDK level < 33,
10 | * you must `use` or [close] the instance to free up resources.
11 | *
12 | * @constructor Create a new instance with a certain [language], or `null` if empty.
13 | */
14 | actual class Parser actual constructor() : AutoCloseable {
15 | actual constructor(language: Language) : this() {
16 | this.language = language
17 | }
18 |
19 | private val self = init()
20 |
21 | init {
22 | RefCleaner(this, CleanAction(self))
23 | }
24 |
25 | /**
26 | * The language that the parser will use for parsing.
27 | *
28 | * Parsing cannot be performed while the language is `null`.
29 | */
30 | actual var language: Language? = null
31 | @FastNative external set
32 |
33 | /**
34 | * The ranges of text that the parser will include when parsing.
35 | *
36 | * By default, the parser will always include entire documents.
37 | * Setting this property allows you to parse only a _portion_ of a
38 | * document but still return a syntax tree whose ranges match up with
39 | * the document as a whole. You can also pass multiple disjoint ranges.
40 | *
41 | * @throws [IllegalArgumentException] If the ranges overlap or are not in ascending order.
42 | */
43 | @set:Throws(IllegalArgumentException::class)
44 | actual var includedRanges: List = emptyList()
45 | external set
46 |
47 | /**
48 | * The maximum duration in microseconds that parsing
49 | * should be allowed to take before halting.
50 | */
51 | @get:JvmName("getTimeoutMicros")
52 | @set:JvmName("setTimeoutMicros")
53 | actual var timeoutMicros: ULong
54 | @FastNative external get
55 |
56 | @FastNative external set
57 |
58 | /**
59 | * The logger that the parser will use during parsing.
60 | *
61 | * #### Example
62 | *
63 | * ```
64 | * import android.util.Log;
65 | *
66 | * parser.logger = { type, msg ->
67 | * Log.d("TS ${type.name}", msg)
68 | * }
69 | * ```
70 | */
71 | @get:Deprecated("The logger can't be called directly.", level = DeprecationLevel.HIDDEN)
72 | actual var logger: LogFunction? = null
73 | @FastNative external set
74 |
75 | /**
76 | * Parse a source code string and create a syntax tree.
77 | *
78 | * If you have already parsed an earlier version of this document and the document
79 | * has since been edited, pass the previous syntax tree to [oldTree] so that the
80 | * unchanged parts of it can be reused. This will save time and memory. For this
81 | * to work correctly, you must have already edited the old syntax tree using the
82 | * [Tree.edit] method in a way that exactly matches the source code changes.
83 | *
84 | * @throws [IllegalStateException]
85 | * If the parser does not have a [language] assigned or
86 | * if parsing was cancelled due to a [timeout][timeoutMicros].
87 | */
88 | @Throws(IllegalStateException::class)
89 | actual external fun parse(source: String, oldTree: Tree?): Tree
90 |
91 | /**
92 | * Parse source code from a callback and create a syntax tree.
93 | *
94 | * If you have already parsed an earlier version of this document and the document
95 | * has since been edited, pass the previous syntax tree to [oldTree] so that the
96 | * unchanged parts of it can be reused. This will save time and memory. For this
97 | * to work correctly, you must have already edited the old syntax tree using the
98 | * [Tree.edit] method in a way that exactly matches the source code changes.
99 | *
100 | * @throws [IllegalStateException]
101 | * If the parser does not have a [language] assigned or
102 | * if parsing was cancelled due to a [timeout][timeoutMicros].
103 | */
104 | @Throws(IllegalStateException::class)
105 | actual external fun parse(oldTree: Tree?, callback: ParseCallback): Tree
106 |
107 | /**
108 | * Instruct the parser to start the next [parse] from the beginning.
109 | *
110 | * If the parser previously failed because of a [timeout][timeoutMicros],
111 | * then by default, it will resume where it left off. If you don't
112 | * want to resume, and instead intend to use this parser to parse
113 | * some other document, you must call this method first.
114 | */
115 | @FastNative
116 | actual external fun reset()
117 |
118 | override fun toString() = "Parser(language=$language)"
119 |
120 | override fun close() = delete(self)
121 |
122 | /** The type of a log message. */
123 | @Suppress("unused")
124 | actual enum class LogType { LEX, PARSE }
125 |
126 | private class CleanAction(private val ptr: Long) : Runnable {
127 | override fun run() = delete(ptr)
128 | }
129 |
130 | private companion object {
131 | @JvmStatic
132 | @CriticalNative
133 | private external fun init(): Long
134 |
135 | @JvmStatic
136 | @FastNative
137 | private external fun delete(self: Long)
138 |
139 | init {
140 | System.loadLibrary("ktreesitter")
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/RefCleaner.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("RefCleaner")
2 |
3 | package io.github.treesitter.ktreesitter
4 |
5 | import android.os.Build.VERSION.SDK_INT
6 | import android.os.Build.VERSION_CODES.TIRAMISU
7 | import java.lang.ref.Cleaner
8 |
9 | internal object RefCleaner {
10 | private val INSTANCE = if (SDK_INT < TIRAMISU) null else Cleaner.create()
11 |
12 | @JvmName("register")
13 | operator fun invoke(obj: Any, action: Runnable) {
14 | if (SDK_INT >= TIRAMISU) INSTANCE!!.register(obj, action)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/Tree.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import dalvik.annotation.optimization.CriticalNative
4 | import dalvik.annotation.optimization.FastNative
5 |
6 | /**
7 | * A class that represents a syntax tree.
8 | *
9 | * __NOTE:__ If you're targeting Android SDK level < 33,
10 | * you must `use` or [close] the instance to free up resources.
11 | */
12 | actual class Tree internal constructor(
13 | private val self: Long,
14 | private var source: String?,
15 | /** The language that was used to parse the syntax tree. */
16 | actual val language: Language
17 | ) : AutoCloseable {
18 | init {
19 | RefCleaner(this, CleanAction(self))
20 | }
21 |
22 | /** The root node of the syntax tree. */
23 | actual val rootNode: Node
24 | @FastNative external get
25 |
26 | /** The included ranges that were used to parse the syntax tree. */
27 | actual val includedRanges by lazy { nativeIncludedRanges() }
28 |
29 | /**
30 | * Get the root node of the syntax tree, but with
31 | * its position shifted forward by the given offset.
32 | */
33 | @FastNative
34 | @JvmName("rootNodeWithOffset")
35 | actual external fun rootNodeWithOffset(bytes: UInt, extent: Point): Node?
36 |
37 | /**
38 | * Edit the syntax tree to keep it in sync
39 | * with source code that has been modified.
40 | */
41 | @FastNative
42 | actual external fun edit(edit: InputEdit)
43 |
44 | /**
45 | * Create a shallow copy of the syntax tree.
46 | *
47 | * You need to copy a syntax tree in order to use it on multiple
48 | * threads or coroutines, as syntax trees are not thread safe.
49 | */
50 | actual fun copy() = Tree(copy(self), source, language)
51 |
52 | /** Create a new tree cursor starting from the node of the tree. */
53 | actual fun walk() = TreeCursor(rootNode)
54 |
55 | /** Get the source code of the syntax tree, if available. */
56 | actual fun text(): CharSequence? = source
57 |
58 | /**
59 | * Compare an old edited syntax tree to a new
60 | * syntax tree representing the same document.
61 | *
62 | * For this to work correctly, this tree must have been
63 | * edited such that its ranges match up to the new tree.
64 | *
65 | * @return A list of ranges whose syntactic structure has changed.
66 | */
67 | actual external fun changedRanges(newTree: Tree): List
68 |
69 | override fun toString() = "Tree(language=$language, source=$source)"
70 |
71 | override fun close() = delete(self)
72 |
73 | private external fun nativeIncludedRanges(): List
74 |
75 | private class CleanAction(private val ptr: Long) : Runnable {
76 | override fun run() = delete(ptr)
77 | }
78 |
79 | private companion object {
80 | @JvmStatic
81 | @CriticalNative
82 | private external fun copy(self: Long): Long
83 |
84 | @JvmStatic
85 | @CriticalNative
86 | private external fun delete(self: Long)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/ktreesitter/src/androidMain/kotlin/io/github/treesitter/ktreesitter/TreeCursor.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import dalvik.annotation.optimization.CriticalNative
4 | import dalvik.annotation.optimization.FastNative
5 |
6 | /**
7 | * A class that can be used to efficiently walk a [syntax tree][Tree].
8 | *
9 | * __NOTE:__ If you're targeting Android SDK level < 33,
10 | * you must `use` or [close] the instance to free up resources.
11 | */
12 | actual class TreeCursor private constructor(
13 | private val self: Long,
14 | @JvmField internal actual val tree: Tree
15 | ) : AutoCloseable {
16 | internal constructor(node: Node) : this(init(node), node.tree) {
17 | internalNode = node
18 | }
19 |
20 | init {
21 | RefCleaner(this, CleanAction(self))
22 | }
23 |
24 | @Suppress("unused")
25 | private var internalNode: Node? = null
26 |
27 | /** The current node of the cursor. */
28 | actual val currentNode: Node
29 | @FastNative external get
30 |
31 | /**
32 | * The depth of the cursor's current node relative to the
33 | * original node that the cursor was constructed with.
34 | */
35 | @get:JvmName("getCurrentDepth")
36 | actual val currentDepth: UInt
37 | @FastNative external get
38 |
39 | /**
40 | * The field ID of the tree cursor's current node, or `0`.
41 | *
42 | * @see [Node.childByFieldId]
43 | * @see [Language.fieldIdForName]
44 | */
45 | @get:JvmName("getCurrentFieldId")
46 | actual val currentFieldId: UShort
47 | @FastNative external get
48 |
49 | /**
50 | * The field name of the tree cursor's current node, if available.
51 | *
52 | * @see [Node.childByFieldName]
53 | */
54 | actual val currentFieldName: String?
55 | @FastNative external get
56 |
57 | /**
58 | * The index of the cursor's current node out of all the descendants
59 | * of the original node that the cursor was constructed with.
60 | */
61 | @get:JvmName("getCurrentDescendantIndex")
62 | actual val currentDescendantIndex: UInt
63 | @FastNative external get
64 |
65 | /** Create a shallow copy of the tree cursor. */
66 | actual fun copy() = TreeCursor(copy(self), tree)
67 |
68 | /** Reset the cursor to start at a different node. */
69 | @FastNative
70 | actual external fun reset(node: Node)
71 |
72 | /** Reset the cursor to start at the same position as another cursor. */
73 | @FastNative
74 | actual external fun reset(cursor: TreeCursor)
75 |
76 | /**
77 | * Move the cursor to the first child of its current node.
78 | *
79 | * @return
80 | * `true` if the cursor successfully moved,
81 | * or `false` if there were no children.
82 | */
83 | @FastNative
84 | actual external fun gotoFirstChild(): Boolean
85 |
86 | /**
87 | * Move the cursor to the last child of its current node.
88 | *
89 | * @return
90 | * `true` if the cursor successfully moved,
91 | * or `false` if there were no children.
92 | */
93 | @FastNative
94 | actual external fun gotoLastChild(): Boolean
95 |
96 | /**
97 | * Move the cursor to the parent of its current node.
98 | *
99 | * @return
100 | * `true` if the cursor successfully moved,
101 | * or `false` if there was no parent node.
102 | */
103 | @FastNative
104 | actual external fun gotoParent(): Boolean
105 |
106 | /**
107 | * Move the cursor to the next sibling of its current node.
108 | *
109 | * @return
110 | * `true` if the cursor successfully moved,
111 | * or `false` if there was no next sibling node.
112 | */
113 | @FastNative
114 | actual external fun gotoNextSibling(): Boolean
115 |
116 | /**
117 | * Move the cursor to the previous sibling of its current node.
118 | *
119 | * This function may be slower than [gotoNextSibling] due to how node positions
120 | * are stored. In the worst case, this will need to iterate through all the
121 | * children up to the previous sibling node to recalculate its position.
122 | *
123 | * @return
124 | * `true` if the cursor successfully moved,
125 | * or `false` if there was no previous sibling node.
126 | */
127 | actual external fun gotoPreviousSibling(): Boolean
128 |
129 | /**
130 | * Move the cursor to the node that is the nth descendant of
131 | * the original node that the cursor was constructed with,
132 | * where `0` represents the original node itself.
133 | */
134 | @FastNative
135 | @JvmName("gotoDescendant")
136 | actual external fun gotoDescendant(index: UInt)
137 |
138 | /**
139 | * Move the cursor to the first child of its current
140 | * node that extends beyond the given byte offset.
141 | *
142 | * @return The index of the child node, or `null` if no such child was found.
143 | */
144 | @JvmName("gotoFirstChildForByte")
145 | actual fun gotoFirstChildForByte(byte: UInt): UInt? {
146 | val result = nativeGotoFirstChildForByte(byte)
147 | if (result == -1L) return null
148 | internalNode = null
149 | return result.toUInt()
150 | }
151 |
152 | /**
153 | * Move the cursor to the first child of its current
154 | * node that extends beyond the given point offset.
155 | *
156 | * @return The index of the child node, or `null` if no such child was found.
157 | */
158 | @JvmName("gotoFirstChildForPoint")
159 | actual fun gotoFirstChildForPoint(point: Point): UInt? {
160 | val result = nativeGotoFirstChildForPoint(point)
161 | if (result == -1L) return null
162 | internalNode = null
163 | return result.toUInt()
164 | }
165 |
166 | override fun toString() = "TreeCursor(tree=$tree)"
167 |
168 | override fun close() = delete(self)
169 |
170 | @FastNative
171 | @JvmName("nativeGotoFirstChildForByte")
172 | private external fun nativeGotoFirstChildForByte(byte: UInt): Long
173 |
174 | @FastNative
175 | @JvmName("nativeGotoFirstChildForPoint")
176 | private external fun nativeGotoFirstChildForPoint(point: Point): Long
177 |
178 | private class CleanAction(private val ptr: Long) : Runnable {
179 | override fun run() = delete(ptr)
180 | }
181 |
182 | private companion object {
183 | @JvmStatic
184 | @FastNative
185 | private external fun init(node: Node): Long
186 |
187 | @JvmStatic
188 | @CriticalNative
189 | private external fun copy(self: Long): Long
190 |
191 | @JvmStatic
192 | @CriticalNative
193 | private external fun delete(self: Long)
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/InputEdit.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import kotlin.jvm.JvmName
4 |
5 | /** An edit to a text document. */
6 | data class InputEdit(
7 | @get:JvmName("startByte") val startByte: UInt,
8 | @get:JvmName("oldEndByte") val oldEndByte: UInt,
9 | @get:JvmName("newEndByte") val newEndByte: UInt,
10 | @get:JvmName("startPoint") val startPoint: Point,
11 | @get:JvmName("oldEndPoint") val oldEndPoint: Point,
12 | @get:JvmName("newEndPoint") val newEndPoint: Point
13 | )
14 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Language.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A class that defines how to parse a particular language.
5 | *
6 | * When a [Language] is generated by the Tree-sitter CLI, it is assigned
7 | * an ABI [version] number that corresponds to the current CLI version.
8 | *
9 | * @constructor Create a new instance from the given language pointer.
10 | * @throws [IllegalArgumentException] If the pointer is invalid or the [version] is incompatible.
11 | */
12 | expect class Language @Throws(IllegalArgumentException::class) constructor(language: Any) {
13 | /** The ABI version number for this language. */
14 | val version: UInt
15 |
16 | /** The number of distinct node types in this language. */
17 | val symbolCount: UInt
18 |
19 | /** The number of valid states in this language. */
20 | val stateCount: UInt
21 |
22 | /** The number of distinct field names in this language. */
23 | val fieldCount: UInt
24 |
25 | /**
26 | * Get another reference to the language.
27 | *
28 | * @since 0.24.0
29 | */
30 | fun copy(): Language
31 |
32 | /** Get the node type for the given numerical ID. */
33 | fun symbolName(symbol: UShort): String?
34 |
35 | /** Get the numerical ID for the given node type. */
36 | fun symbolForName(name: String, isNamed: Boolean): UShort
37 |
38 | /**
39 | * Check if the node for the given numerical ID is named
40 | *
41 | * @see [Node.isNamed]
42 | */
43 | fun isNamed(symbol: UShort): Boolean
44 |
45 | /** Check if the node for the given numerical ID is visible. */
46 | fun isVisible(symbol: UShort): Boolean
47 |
48 | /**
49 | * Check if the node for the given numerical ID is a supertype.
50 | *
51 | * @since 0.24.0
52 | */
53 | fun isSupertype(symbol: UShort): Boolean
54 |
55 | /** Get the field name for the given numerical id. */
56 | fun fieldNameForId(id: UShort): String?
57 |
58 | /** Get the numerical ID for the given field name. */
59 | fun fieldIdForName(name: String): UShort
60 |
61 | /**
62 | * Get the next parse state.
63 | *
64 | * Combine this with [lookaheadIterator] to generate
65 | * completion suggestions or valid symbols in error nodes.
66 | *
67 | * #### Example
68 | *
69 | * ```kotlin
70 | * language.nextState(node.parseState, node.grammarSymbol)
71 | * ```
72 | */
73 | fun nextState(state: UShort, symbol: UShort): UShort
74 |
75 | /**
76 | * Create a new [lookahead iterator][LookaheadIterator] for the given parse state.
77 | *
78 | * @throws [IllegalArgumentException] If the state is invalid for this language.
79 | */
80 | @Throws(IllegalArgumentException::class)
81 | fun lookaheadIterator(state: UShort): LookaheadIterator
82 |
83 | /**
84 | * Create a new [Query] from a string containing one or more S-expression
85 | * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax).
86 | *
87 | * @throws [QueryError] If any error occurred while creating the query.
88 | */
89 | @Throws(QueryError::class)
90 | fun query(source: String): Query
91 |
92 | override fun equals(other: Any?): Boolean
93 |
94 | override fun hashCode(): Int
95 | }
96 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A class that is used to look up valid symbols in a specific parse state.
5 | *
6 | * Lookahead iterators can be useful to generate suggestions and improve syntax
7 | * error diagnostics. To get symbols valid in an `ERROR` node, use the lookahead
8 | * iterator on its first leaf node state. For `MISSING` nodes, a lookahead
9 | * iterator created on the previous non-extra leaf node may be appropriate.
10 | */
11 | expect class LookaheadIterator : AbstractIterator {
12 | /** The current language of the lookahead iterator. */
13 | val language: Language
14 |
15 | /**
16 | * The current symbol ID.
17 | *
18 | * The ID of the `ERROR` symbol is equal to `UShort.MAX_VALUE`.
19 | */
20 | val currentSymbol: UShort
21 |
22 | /**
23 | * The current symbol name.
24 | *
25 | * Newly created lookahead iterators will contain the `ERROR` symbol.
26 | */
27 | val currentSymbolName: String
28 |
29 | /**
30 | * Reset the lookahead iterator the given [state] and, optionally, another [language].
31 | *
32 | * @return `true` if the iterator was reset successfully or `false` if it failed.
33 | */
34 | fun reset(state: UShort, language: Language? = null): Boolean
35 |
36 | /** Advance the lookahead iterator to the next symbol. */
37 | override fun next(): Symbol
38 |
39 | /** Iterate over the symbol IDs. */
40 | fun symbols(): Sequence
41 |
42 | /** Iterate over the symbol names. */
43 | fun symbolNames(): Sequence
44 |
45 | /** A class that pairs a symbol ID with its name. */
46 | class Symbol(id: UShort, name: String) {
47 | val id: UShort
48 | val name: String
49 |
50 | operator fun component1(): UShort
51 | operator fun component2(): String
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A function to retrieve a chunk of text at a given byte offset and point.
5 | *
6 | * The function should return `null` to indicate the end of the document.
7 | */
8 | typealias ParseCallback = (byte: UInt, point: Point) -> CharSequence?
9 |
10 | /**
11 | * A function that logs parsing results.
12 | *
13 | * The first argument is the log type and the second argument is the message.
14 | */
15 | typealias LogFunction = (type: Parser.LogType, message: String) -> Unit
16 |
17 | /**
18 | * A class that is used to produce a [syntax tree][Tree] from source code.
19 | *
20 | * @constructor Create a new instance with a certain [language], or `null` if empty.
21 | */
22 | expect class Parser() {
23 | constructor(language: Language)
24 |
25 | /**
26 | * The language that the parser will use for parsing.
27 | *
28 | * Parsing cannot be performed while the language is `null`.
29 | */
30 | var language: Language?
31 |
32 | /**
33 | * The ranges of text that the parser will include when parsing.
34 | *
35 | * By default, the parser will always include entire documents.
36 | * Setting this property allows you to parse only a _portion_ of a
37 | * document but still return a syntax tree whose ranges match up with
38 | * the document as a whole. You can also pass multiple disjoint ranges.
39 | *
40 | * @throws [IllegalArgumentException] If the ranges overlap or are not in ascending order.
41 | */
42 | var includedRanges: List
43 |
44 | /**
45 | * The maximum duration in microseconds that parsing
46 | * should be allowed to take before halting.
47 | */
48 | var timeoutMicros: ULong
49 |
50 | /** The logger that the parser will use during parsing. */
51 | @get:Deprecated("The logger can't be called directly.", level = DeprecationLevel.HIDDEN)
52 | var logger: LogFunction?
53 |
54 | // TODO: add cancellationFlag
55 |
56 | /**
57 | * Parse a source code string and create a syntax tree.
58 | *
59 | * If you have already parsed an earlier version of this document and the document
60 | * has since been edited, pass the previous syntax tree to [oldTree] so that the
61 | * unchanged parts of it can be reused. This will save time and memory. For this
62 | * to work correctly, you must have already edited the old syntax tree using the
63 | * [Tree.edit] method in a way that exactly matches the source code changes.
64 | *
65 | * @throws [IllegalStateException]
66 | * If the parser does not have a [language] assigned or
67 | * if parsing was cancelled due to a [timeout][timeoutMicros].
68 | */
69 | @Throws(IllegalStateException::class)
70 | fun parse(source: String, oldTree: Tree? = null): Tree
71 |
72 | /**
73 | * Parse source code from a callback and create a syntax tree.
74 | *
75 | * If you have already parsed an earlier version of this document and the document
76 | * has since been edited, pass the previous syntax tree to [oldTree] so that the
77 | * unchanged parts of it can be reused. This will save time and memory. For this
78 | * to work correctly, you must have already edited the old syntax tree using the
79 | * [Tree.edit] method in a way that exactly matches the source code changes.
80 | *
81 | * @throws [IllegalStateException]
82 | * If the parser does not have a [language] assigned or
83 | * if parsing was cancelled due to a [timeout][timeoutMicros].
84 | */
85 | @Throws(IllegalStateException::class)
86 | fun parse(oldTree: Tree? = null, callback: ParseCallback): Tree
87 |
88 | /**
89 | * Instruct the parser to start the next [parse] from the beginning.
90 | *
91 | * If the parser previously failed because of a [timeout][timeoutMicros],
92 | * then by default, it will resume where it left off. If you don't
93 | * want to resume, and instead intend to use this parser to parse
94 | * some other document, you must call this method first.
95 | */
96 | fun reset()
97 |
98 | /** The type of a log message. */
99 | enum class LogType { LEX, PARSE }
100 | }
101 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Point.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import kotlin.jvm.JvmField
4 | import kotlin.jvm.JvmName
5 |
6 | /**
7 | * A position in a text document in terms of rows and columns.
8 | *
9 | * @property row The zero-based row of the document.
10 | * @property column The zero-based column of the document.
11 | */
12 | data class Point(
13 | @get:JvmName("row") val row: UInt,
14 | @get:JvmName("column") val column: UInt
15 | ) : Comparable {
16 | override operator fun compareTo(other: Point): Int {
17 | val rowDiff = row.compareTo(other.row)
18 | if (rowDiff != 0) return rowDiff
19 | return column.compareTo(other.column)
20 | }
21 |
22 | companion object {
23 | /** The minimum value a [Point] can have. */
24 | @JvmField
25 | val MIN = Point(UInt.MIN_VALUE, UInt.MIN_VALUE)
26 |
27 | /** The maximum value a [Point] can have. */
28 | @JvmField
29 | val MAX = Point(UInt.MAX_VALUE, UInt.MAX_VALUE)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Query.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A class that represents a set of patterns which match nodes in a syntax tree.
5 | *
6 | * @constructor
7 | * Create a new query from a particular language and a
8 | * string containing one or more S-expression patterns.
9 | * @throws [QueryError] If any error occurred while creating the query.
10 | */
11 | expect class Query @Throws(QueryError::class) constructor(language: Language, source: String) {
12 | /** The number of patterns in the query. */
13 | val patternCount: UInt
14 |
15 | /** The number of captures in the query. */
16 | val captureCount: UInt
17 |
18 | /**
19 | * The maximum duration in microseconds that query
20 | * execution should be allowed to take before halting.
21 | *
22 | * Default: `0`
23 | *
24 | * @since 0.23.0
25 | */
26 | var timeoutMicros: ULong
27 |
28 | /**
29 | * The maximum number of in-progress matches.
30 | *
31 | * Default: `UInt.MAX_VALUE`
32 | *
33 | * @throws [IllegalArgumentException] If the match limit is set to `0`.
34 | */
35 | var matchLimit: UInt
36 |
37 | /**
38 | * The maximum start depth for the query.
39 | *
40 | * This prevents cursors from exploring children nodes at a certain depth.
41 | * Note that if a pattern includes many children, then they will still be checked.
42 | *
43 | * Default: `UInt.MAX_VALUE`
44 | */
45 | var maxStartDepth: UInt
46 |
47 | /**
48 | * The range of bytes in which the query will be executed.
49 | *
50 | * Default: `UInt.MIN_VALUE..UInt.MAX_VALUE`
51 | */
52 | var byteRange: UIntRange
53 |
54 | /**
55 | * The range of points in which the query will be executed.
56 | *
57 | * Default: `Point.MIN..Point.MAX`
58 | */
59 | var pointRange: ClosedRange
60 |
61 | /**
62 | * Check if the query exceeded its maximum number of
63 | * in-progress matches during its last execution.
64 | */
65 | val didExceedMatchLimit: Boolean
66 |
67 | /**
68 | * Iterate over all the matches in the order that they were found.
69 | *
70 | * #### Example
71 | *
72 | * ```kotlin
73 | * query.matches(tree.rootNode) {
74 | * if (name != "ieq?") return@matches true
75 | * val node = it[(args[0] as QueryPredicateArg.Capture).value].first()
76 | * val value = (args[1] as QueryPredicateArg.Literal).value
77 | * value.equals(node.text()?.toString(), ignoreCase = true)
78 | * }
79 | * ```
80 | *
81 | * @param node The node that the query will run on.
82 | * @param predicate A function that handles custom predicates.
83 | */
84 | fun matches(
85 | node: Node,
86 | predicate: QueryPredicate.(QueryMatch) -> Boolean = { true }
87 | ): Sequence
88 |
89 | /**
90 | * Iterate over all the individual captures in the order that they appear.
91 | *
92 | * This is useful if you don't care about _which_ pattern matched.
93 | *
94 | * @param node The node that the query will run on.
95 | * @param predicate A function that handles custom predicates.
96 | */
97 | fun captures(
98 | node: Node,
99 | predicate: QueryPredicate.(QueryMatch) -> Boolean = { true }
100 | ): Sequence>
101 |
102 | /**
103 | * Get the property settings for the given pattern index.
104 | *
105 | * Properties are set using the `#set!` predicate.
106 | *
107 | * @return A map of properties with optional values.
108 | * @throws [IndexOutOfBoundsException]
109 | * If the index exceeds the [pattern count][patternCount].
110 | */
111 | fun settings(index: UInt): Map
112 |
113 | /**
114 | * Get the property assertions for the given pattern index.
115 | *
116 | * Assertions are performed using the `#is?` and `#is-not?` predicates.
117 | *
118 | * @return
119 | * A map of assertions, where the first item is the optional property value
120 | * and the second item indicates whether the assertion was positive or negative.
121 | * @throws [IndexOutOfBoundsException]
122 | * If the index exceeds the [pattern count][patternCount].
123 | */
124 | @Throws(IndexOutOfBoundsException::class)
125 | fun assertions(index: UInt): Map>
126 |
127 | /**
128 | * Disable a certain pattern within a query.
129 | *
130 | * This prevents the pattern from matching and removes most of the overhead
131 | * associated with the pattern. Currently, there is no way to undo this.
132 | *
133 | * @throws [IndexOutOfBoundsException]
134 | * If the index exceeds the [pattern count][patternCount].
135 | */
136 | @Throws(IndexOutOfBoundsException::class)
137 | fun disablePattern(index: UInt)
138 |
139 | /**
140 | * Disable a certain capture within a query.
141 | *
142 | * This prevents the capture from being returned in matches,
143 | * and also avoids most resource usage associated with recording
144 | * the capture. Currently, there is no way to undo this.
145 | *
146 | * @throws [NoSuchElementException] If the capture does not exist.
147 | */
148 | @Throws(NoSuchElementException::class)
149 | fun disableCapture(name: String)
150 |
151 | /**
152 | * Get the byte offset where the given pattern starts in the query's source.
153 | *
154 | * @throws [IndexOutOfBoundsException]
155 | * If the index exceeds the [pattern count][patternCount].
156 | */
157 | @Throws(IndexOutOfBoundsException::class)
158 | fun startByteForPattern(index: UInt): UInt
159 |
160 | /**
161 | * Get the byte offset where the given pattern ends in the query's source.
162 | *
163 | * @throws [IndexOutOfBoundsException]
164 | * If the index exceeds the [pattern count][patternCount].
165 | * @since 0.23.0
166 | */
167 | @Throws(IndexOutOfBoundsException::class)
168 | fun endByteForPattern(index: UInt): UInt
169 |
170 | /**
171 | * Check if the pattern with the given index has a single root node.
172 | *
173 | * @throws [IndexOutOfBoundsException]
174 | * If the index exceeds the [pattern count][patternCount].
175 | */
176 | @Throws(IndexOutOfBoundsException::class)
177 | fun isPatternRooted(index: UInt): Boolean
178 |
179 | /**
180 | * Check if the pattern with the given index is "non-local".
181 | *
182 | * A non-local pattern has multiple root nodes and can match within a
183 | * repeating sequence of nodes, as specified by the grammar. Non-local
184 | * patterns disable certain optimizations that would otherwise be possible
185 | * when executing a query on a specific range of a syntax tree.
186 | *
187 | * @throws [IndexOutOfBoundsException]
188 | * If the index exceeds the [pattern count][patternCount].
189 | */
190 | @Throws(IndexOutOfBoundsException::class)
191 | fun isPatternNonLocal(index: UInt): Boolean
192 |
193 | /**
194 | * Check if a pattern is guaranteed to match once a given byte offset is reached.
195 | *
196 | * @throws [IndexOutOfBoundsException] If the offset exceeds the source length.
197 | */
198 | @Throws(IndexOutOfBoundsException::class)
199 | fun isPatternGuaranteedAtStep(offset: UInt): Boolean
200 | }
201 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryCapture.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import kotlin.jvm.JvmName
4 |
5 | /**
6 | * A [Node] that was captured with a certain capture [name].
7 | *
8 | * @property node The captured node.
9 | * @property name The name of the capture.
10 | */
11 | data class QueryCapture internal constructor(
12 | @get:JvmName("node") val node: Node,
13 | @get:JvmName("name") val name: String
14 | ) {
15 | override fun toString() = "QueryCapture(name=$name, node=$node)"
16 | }
17 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryError.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /** Any error that occurred while instantiating a [Query]. */
4 | sealed class QueryError : IllegalArgumentException() {
5 | abstract override val message: String
6 |
7 | /** A query syntax error. */
8 | class Syntax(row: Long, column: Long) : QueryError() {
9 | override val message: String =
10 | if (row < 0 || column < 0) "Unexpected EOF"
11 | else "Invalid syntax at row $row, column $column"
12 | }
13 |
14 | /** A capture name error. */
15 | class Capture(row: UInt, column: UInt, capture: String) : QueryError() {
16 | override val message: String = "Invalid capture name at row $row, column $column: $capture"
17 | }
18 |
19 | /** A field name error. */
20 | class Field(row: UInt, column: UInt, field: String) : QueryError() {
21 | override val message: String = "Invalid field name at row $row, column $column: $field"
22 | }
23 |
24 | /** A node type error. */
25 | class NodeType(row: UInt, column: UInt, type: String) : QueryError() {
26 | override val message: String = "Invalid node type at row $row, column $column: $type"
27 | }
28 |
29 | /** A pattern structure error. */
30 | class Structure(row: UInt, column: UInt) : QueryError() {
31 | override val message: String = "Impossible pattern at row $row, column $column"
32 | }
33 |
34 | /** A query predicate error. */
35 | class Predicate(
36 | row: UInt,
37 | details: String,
38 | override val cause: Throwable? = null
39 | ) : QueryError() {
40 | override val message = "Invalid predicate in pattern at row $row: $details"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryMatch.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import kotlin.jvm.JvmName
4 |
5 | /**
6 | * A match that corresponds to a certain pattern in the query.
7 | *
8 | * @property patternIndex The index of the pattern.
9 | * @property captures The captures contained in the pattern.
10 | */
11 | class QueryMatch internal constructor(
12 | @get:JvmName("getPatternIndex") val patternIndex: UInt,
13 | val captures: List
14 | ) {
15 | /** Get the nodes that are captured by the given [capture] name. */
16 | operator fun get(capture: String): List =
17 | captures.mapNotNull { if (it.name == capture) it.node else null }
18 |
19 | override fun toString() = "QueryMatch(patternIndex=$patternIndex, captures=$captures)"
20 | }
21 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryPredicate.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A query [predicate](https://tree-sitter.github.io/tree-sitter/using-parsers#predicates)
5 | * that associates conditions or arbitrary metadata with a pattern.
6 | *
7 | * The following predicates are supported by default:
8 | *
9 | * - `#eq?`, `#not-eq?`, `#any-eq?`, `#any-not-eq?`
10 | * - `#match?`, `#not-match?`, `#any-match?`, `#any-not-match?`
11 | * - `#any-of?`, `#not-any-of?`
12 | *
13 | * @property name The name of the predicate.
14 | * @property args The arguments given to the predicate.
15 | */
16 | sealed class QueryPredicate(val name: String) {
17 | abstract val args: List
18 |
19 | internal abstract operator fun invoke(match: QueryMatch): Boolean
20 |
21 | final override fun toString() = "QueryPredicate(name=$name, args=$args)"
22 |
23 | internal class EqCapture(
24 | name: String,
25 | private val capture: String,
26 | private val value: String,
27 | private val isPositive: Boolean,
28 | private val isAny: Boolean
29 | ) : QueryPredicate(name) {
30 | override val args = listOf(
31 | QueryPredicateArg.Capture(capture),
32 | QueryPredicateArg.Capture(value)
33 | )
34 |
35 | override fun invoke(match: QueryMatch): Boolean {
36 | val nodes1 = match[capture]
37 | val nodes2 = match[value]
38 | val test = if (!isAny) nodes1::all else nodes1::any
39 | return test { n1 ->
40 | nodes2.any { n2 ->
41 | val result = n1.text() == n2.text()
42 | if (isPositive) result else !result
43 | }
44 | }
45 | }
46 | }
47 |
48 | internal class EqString(
49 | name: String,
50 | private val capture: String,
51 | private val value: String,
52 | private val isPositive: Boolean,
53 | private val isAny: Boolean
54 | ) : QueryPredicate(name) {
55 | override val args = listOf(
56 | QueryPredicateArg.Capture(capture),
57 | QueryPredicateArg.Literal(value)
58 | )
59 |
60 | override fun invoke(match: QueryMatch): Boolean {
61 | val nodes = match[capture]
62 | if (nodes.isEmpty()) return !isPositive
63 | val test = if (!isAny) nodes::all else nodes::any
64 | return test {
65 | val result = value == it.text()!!
66 | if (isPositive) result else !result
67 | }
68 | }
69 | }
70 |
71 | internal class Match(
72 | name: String,
73 | private val capture: String,
74 | private val pattern: Regex,
75 | private val isPositive: Boolean,
76 | private val isAny: Boolean
77 | ) : QueryPredicate(name) {
78 | override val args = listOf(
79 | QueryPredicateArg.Capture(capture),
80 | QueryPredicateArg.Literal(pattern.pattern)
81 | )
82 |
83 | override fun invoke(match: QueryMatch): Boolean {
84 | val nodes = match[capture]
85 | if (nodes.isEmpty()) return !isPositive
86 | val test = if (!isAny) nodes::all else nodes::any
87 | return test {
88 | val result = pattern.containsMatchIn(it.text()!!)
89 | if (isPositive) result else !result
90 | }
91 | }
92 | }
93 |
94 | internal class AnyOf(
95 | name: String,
96 | private val capture: String,
97 | private val value: List,
98 | private val isPositive: Boolean
99 | ) : QueryPredicate(name) {
100 | override val args = List(value.size + 1) {
101 | if (it == 0) QueryPredicateArg.Capture(capture)
102 | else QueryPredicateArg.Literal(value[it - 1])
103 | }
104 |
105 | override fun invoke(match: QueryMatch) =
106 | match[capture].none { (it.text()!! in value) != isPositive }
107 | }
108 |
109 | internal class Generic(
110 | name: String,
111 | override val args: List
112 | ) : QueryPredicate(name) {
113 | override fun invoke(match: QueryMatch) = true
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/QueryPredicateArg.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import kotlin.jvm.JvmInline
4 |
5 | /**
6 | * An argument to a [QueryPredicate].
7 | *
8 | * @property value The value of the argument.
9 | */
10 | sealed interface QueryPredicateArg {
11 | val value: String
12 |
13 | /** A capture argument (`@value`). */
14 | @JvmInline
15 | value class Capture(override val value: String) : QueryPredicateArg {
16 | override fun toString() = "@$value"
17 | }
18 |
19 | /** A literal string argument (`"value"`). */
20 | @JvmInline
21 | value class Literal(override val value: String) : QueryPredicateArg {
22 | override fun toString() = "\"$value\""
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Range.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import kotlin.jvm.JvmName
4 |
5 | /**
6 | * A range of positions in a text document,
7 | * both in terms of bytes and of row-column points.
8 | *
9 | * @constructor
10 | * @throws [IllegalArgumentException]
11 | * If the end point is smaller than the start point,
12 | * or the end byte is smaller than the start byte.
13 | */
14 | data class Range @Throws(IllegalArgumentException::class) constructor(
15 | @get:JvmName("startPoint") val startPoint: Point,
16 | @get:JvmName("endPoint") val endPoint: Point,
17 | @get:JvmName("startByte") val startByte: UInt,
18 | @get:JvmName("endByte") val endByte: UInt
19 | ) {
20 | init {
21 | require(startPoint <= endPoint) { "Invalid point range: $startPoint to $endPoint" }
22 | require(startByte <= endByte) { "Invalid byte range: $startByte to $endByte" }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/Tree.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /** A class that represents a syntax tree. */
4 | expect class Tree {
5 | /** The root node of the syntax tree. */
6 | val rootNode: Node
7 |
8 | /** The language that was used to parse the syntax tree. */
9 | val language: Language
10 |
11 | /** The included ranges that were used to parse the syntax tree. */
12 | val includedRanges: List
13 |
14 | /**
15 | * Get the root node of the syntax tree, but with
16 | * its position shifted forward by the given offset.
17 | */
18 | fun rootNodeWithOffset(bytes: UInt, extent: Point): Node?
19 |
20 | /**
21 | * Edit the syntax tree to keep it in sync
22 | * with source code that has been modified.
23 | */
24 | fun edit(edit: InputEdit)
25 |
26 | /**
27 | * Create a shallow copy of the syntax tree.
28 | *
29 | * You need to copy a syntax tree in order to use it on multiple
30 | * threads or coroutines, as syntax trees are not thread safe.
31 | */
32 | fun copy(): Tree
33 |
34 | /** Create a new tree cursor starting from the node of the tree. */
35 | fun walk(): TreeCursor
36 |
37 | /** Get the source code of the syntax tree, if available. */
38 | fun text(): CharSequence?
39 |
40 | /**
41 | * Compare an old edited syntax tree to a new
42 | * syntax tree representing the same document.
43 | *
44 | * For this to work correctly, this tree must have been
45 | * edited such that its ranges match up to the new tree.
46 | *
47 | * @return A list of ranges whose syntactic structure has changed.
48 | */
49 | fun changedRanges(newTree: Tree): List
50 | }
51 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/TreeCursor.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /** A class that can be used to efficiently walk a [syntax tree][Tree]. */
4 | expect class TreeCursor {
5 | internal val tree: Tree
6 |
7 | /** The current node of the cursor. */
8 | val currentNode: Node
9 |
10 | /**
11 | * The depth of the cursor's current node relative to the
12 | * original node that the cursor was constructed with.
13 | */
14 | val currentDepth: UInt
15 |
16 | /**
17 | * The field ID of the tree cursor's current node, or `0`.
18 | *
19 | * @see [Node.childByFieldId]
20 | * @see [Language.fieldIdForName]
21 | */
22 | val currentFieldId: UShort
23 |
24 | /**
25 | * The field name of the tree cursor's current node, if available.
26 | *
27 | * @see [Node.childByFieldName]
28 | */
29 | val currentFieldName: String?
30 |
31 | /**
32 | * The index of the cursor's current node out of all the descendants
33 | * of the original node that the cursor was constructed with.
34 | */
35 | val currentDescendantIndex: UInt
36 |
37 | /** Create a shallow copy of the tree cursor. */
38 | fun copy(): TreeCursor
39 |
40 | /** Reset the cursor to start at a different node. */
41 | fun reset(node: Node)
42 |
43 | /** Reset the cursor to start at the same position as another cursor. */
44 | fun reset(cursor: TreeCursor)
45 |
46 | /**
47 | * Move the cursor to the first child of its current node.
48 | *
49 | * @return
50 | * `true` if the cursor successfully moved,
51 | * or `false` if there were no children.
52 | */
53 | fun gotoFirstChild(): Boolean
54 |
55 | /**
56 | * Move the cursor to the last child of its current node.
57 | *
58 | * @return
59 | * `true` if the cursor successfully moved,
60 | * or `false` if there were no children.
61 | */
62 | fun gotoLastChild(): Boolean
63 |
64 | /**
65 | * Move the cursor to the parent of its current node.
66 | *
67 | * @return
68 | * `true` if the cursor successfully moved,
69 | * or `false` if there was no parent node.
70 | */
71 | fun gotoParent(): Boolean
72 |
73 | /**
74 | * Move the cursor to the next sibling of its current node.
75 | *
76 | * @return
77 | * `true` if the cursor successfully moved,
78 | * or `false` if there was no next sibling node.
79 | */
80 | fun gotoNextSibling(): Boolean
81 |
82 | /**
83 | * Move the cursor to the previous sibling of its current node.
84 | *
85 | * This function may be slower than [gotoNextSibling] due to how node positions
86 | * are stored. In the worst case, this will need to iterate through all the
87 | * children up to the previous sibling node to recalculate its position.
88 | *
89 | * @return
90 | * `true` if the cursor successfully moved,
91 | * or `false` if there was no previous sibling node.
92 | */
93 | fun gotoPreviousSibling(): Boolean
94 |
95 | /**
96 | * Move the cursor to the node that is the nth descendant of
97 | * the original node that the cursor was constructed with,
98 | * where `0` represents the original node itself.
99 | */
100 | fun gotoDescendant(index: UInt)
101 |
102 | /**
103 | * Move the cursor to the first child of its current
104 | * node that extends beyond the given byte offset.
105 | *
106 | * @return The index of the child node, or `null` if no such child was found.
107 | */
108 | fun gotoFirstChildForByte(byte: UInt): UInt?
109 |
110 | /**
111 | * Move the cursor to the first child of its current
112 | * node that extends beyond the given point offset.
113 | *
114 | * @return The index of the child node, or `null` if no such child was found.
115 | */
116 | fun gotoFirstChildForPoint(point: Point): UInt?
117 | }
118 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonMain/kotlin/io/github/treesitter/ktreesitter/TreeSitter.kt:
--------------------------------------------------------------------------------
1 | @file:kotlin.jvm.JvmName("KTreeSitter")
2 |
3 | package io.github.treesitter.ktreesitter
4 |
5 | // NOTE: don't forget to bump these when necessary
6 |
7 | /**
8 | * The latest ABI version that is supported by the current version of the library.
9 | *
10 | * The Tree-sitter library is generally backwards-compatible with languages
11 | * generated using older CLI versions, but is not forwards-compatible.
12 | */
13 | const val LANGUAGE_VERSION: UInt = 14U
14 |
15 | /** The earliest ABI version that is supported by the current version of the library. */
16 | const val MIN_COMPATIBLE_LANGUAGE_VERSION: UInt = 13U
17 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/LanguageTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
4 | import io.kotest.assertions.throwables.shouldNotThrowAny
5 | import io.kotest.core.spec.style.FunSpec
6 | import io.kotest.matchers.*
7 | import io.kotest.matchers.comparables.*
8 | import io.kotest.matchers.nulls.*
9 | import io.kotest.matchers.string.*
10 | import io.kotest.matchers.types.*
11 |
12 | class LanguageTest : FunSpec({
13 | val language = Language(TreeSitterJava.language())
14 |
15 | test("version") {
16 | language.version shouldBe 14U
17 | }
18 |
19 | test("symbolCount") {
20 | language.symbolCount shouldBeGreaterThan 1U
21 | }
22 |
23 | test("stateCount") {
24 | language.stateCount shouldBeGreaterThan 1U
25 | }
26 |
27 | test("fieldCount") {
28 | language.fieldCount shouldBeGreaterThan 1U
29 | }
30 |
31 | test("symbolName()") {
32 | language.symbolName(1U) shouldBe "identifier"
33 | }
34 |
35 | test("symbolForName()") {
36 | language.symbolForName(";", false) shouldBeGreaterThan 0U
37 | language.symbolForName("program", true) shouldBeGreaterThan 0U
38 | }
39 |
40 | test("isNamed()") {
41 | language.isNamed(1U) shouldBe true
42 | }
43 |
44 | test("isVisible()") {
45 | language.isVisible(1U) shouldBe true
46 | }
47 |
48 | test("isSupertype()") {
49 | language.isSupertype(1U) shouldBe false
50 | }
51 |
52 | test("fieldNameForId()") {
53 | language.fieldNameForId(1U).shouldNotBeNull()
54 | }
55 |
56 | test("fieldIdForName()") {
57 | language.fieldIdForName("body") shouldBeGreaterThan 0U
58 | }
59 |
60 | test("nextState()") {
61 | val program = language.symbolForName("program", true)
62 | language.nextState(1U, program) shouldBeGreaterThan 0U
63 | }
64 |
65 | test("lookaheadIterator()") {
66 | val program = language.symbolForName("program", true)
67 | val state = language.nextState(1U, program)
68 | val lookahead = language.lookaheadIterator(state)
69 | lookahead.language shouldBe language
70 | }
71 |
72 | test("query()") {
73 | shouldNotThrowAny { language.query("(program) @root") }
74 | }
75 |
76 | test("equals()") {
77 | Language(TreeSitterJava.language()) shouldBe language.copy()
78 | }
79 |
80 | test("hashCode()") {
81 | language shouldHaveSameHashCodeAs TreeSitterJava.language()
82 | }
83 |
84 | test("toString()") {
85 | language.toString() shouldMatch Regex("""Language\(id=0x[0-9a-f]+, version=14\)""")
86 | }
87 | })
88 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/LookaheadIteratorTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.*
6 | import io.kotest.matchers.collections.*
7 |
8 | class LookaheadIteratorTest : FunSpec({
9 | val language = Language(TreeSitterJava.language())
10 | val state = language.nextState(1U, 138U)
11 | val lookahead = language.lookaheadIterator(state)
12 |
13 | test("currentSymbol") {
14 | lookahead.currentSymbol shouldBe UShort.MAX_VALUE
15 | lookahead.currentSymbolName shouldBe "ERROR"
16 | }
17 |
18 | test("next()") {
19 | lookahead.next() shouldBe LookaheadIterator.Symbol(0U, "end")
20 | }
21 |
22 | test("reset()") {
23 | lookahead.reset(state) shouldBe true
24 | lookahead.reset(state, language) shouldBe true
25 | }
26 |
27 | test("symbols()") {
28 | lookahead.symbols().shouldBeStrictlyIncreasing()
29 | }
30 |
31 | test("names()") {
32 | val names = lookahead.symbolNames().toList()
33 | names shouldContainExactly listOf("end", "line_comment", "block_comment")
34 | }
35 |
36 | test("iterator") {
37 | for ((symbol, name) in lookahead) {
38 | symbol shouldBe lookahead.currentSymbol
39 | name shouldBe lookahead.currentSymbolName
40 | }
41 | }
42 | })
43 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/NodeTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
4 | import io.kotest.assertions.throwables.shouldThrow
5 | import io.kotest.core.spec.style.FunSpec
6 | import io.kotest.inspectors.forSingle
7 | import io.kotest.matchers.*
8 | import io.kotest.matchers.collections.*
9 | import io.kotest.matchers.nulls.*
10 | import io.kotest.matchers.types.*
11 |
12 | class NodeTest : FunSpec({
13 | val language = Language(TreeSitterJava.language())
14 | val parser = Parser(language)
15 | val source = "class Foo {}"
16 | val tree = parser.parse(source)
17 | val rootNode = tree.rootNode
18 |
19 | test("id") {
20 | rootNode.id shouldNotBe 0UL
21 | }
22 |
23 | test("symbol") {
24 | rootNode.symbol shouldNotBe UShort.MIN_VALUE
25 | }
26 |
27 | test("grammarSymbol") {
28 | rootNode.grammarSymbol shouldBe rootNode.symbol
29 | }
30 |
31 | test("type") {
32 | rootNode.type shouldBe "program"
33 | }
34 |
35 | test("grammarType") {
36 | rootNode.grammarType shouldBe rootNode.type
37 | }
38 |
39 | test("isNamed") {
40 | rootNode.isNamed shouldBe true
41 | }
42 |
43 | test("isExtra") {
44 | rootNode.isExtra shouldBe false
45 | }
46 |
47 | test("isError") {
48 | rootNode.isError shouldBe false
49 | }
50 |
51 | test("isMissing") {
52 | rootNode.isMissing shouldBe false
53 | }
54 |
55 | test("hasChanges") {
56 | rootNode.hasChanges shouldBe false
57 | }
58 |
59 | test("hasError") {
60 | rootNode.hasError shouldBe false
61 | }
62 |
63 | test("parseState") {
64 | rootNode.parseState shouldBe 0U
65 | }
66 |
67 | test("nextParseState") {
68 | rootNode.nextParseState shouldBe 0U
69 | }
70 |
71 | test("startByte") {
72 | rootNode.startByte shouldBe 0U
73 | }
74 |
75 | test("endByte") {
76 | rootNode.endByte shouldBe 12U
77 | }
78 |
79 | test("byteRange") {
80 | rootNode.byteRange shouldBe 0U..12U
81 | }
82 |
83 | test("range") {
84 | rootNode.range shouldBe Range(Point(0U, 0U), Point(0U, 12U), 0U, 12U)
85 | }
86 |
87 | test("startPoint") {
88 | rootNode.startPoint shouldBe Point(0U, 0U)
89 | }
90 |
91 | test("endPoint") {
92 | rootNode.endPoint shouldBe Point(0U, 12U)
93 | }
94 |
95 | test("childCount") {
96 | rootNode.childCount shouldBe 1U
97 | }
98 |
99 | test("namedChildCount") {
100 | rootNode.namedChildCount shouldBe 1U
101 | }
102 |
103 | test("descendantCount") {
104 | rootNode.descendantCount shouldBe 7U
105 | }
106 |
107 | test("parent") {
108 | rootNode.parent.shouldBeNull()
109 | }
110 |
111 | test("nextSibling") {
112 | rootNode.nextSibling.shouldBeNull()
113 | }
114 |
115 | test("prevSibling") {
116 | rootNode.prevSibling.shouldBeNull()
117 | }
118 |
119 | test("nextNamedSibling") {
120 | rootNode.nextNamedSibling.shouldBeNull()
121 | }
122 |
123 | test("prevNamedSibling") {
124 | rootNode.prevNamedSibling.shouldBeNull()
125 | }
126 |
127 | test("children") {
128 | val children = rootNode.children
129 | children.forSingle { it.type shouldBe "class_declaration" }
130 | rootNode.children shouldBeSameInstanceAs children
131 | }
132 |
133 | test("namedChildren") {
134 | val children = rootNode.namedChildren
135 | children shouldContainExactly rootNode.children
136 | }
137 |
138 | test("child()") {
139 | val node = rootNode.child(0U)
140 | node?.type shouldBe "class_declaration"
141 | shouldThrow { rootNode.child(1U) }
142 | }
143 |
144 | test("namedChild()") {
145 | rootNode.namedChild(0U) shouldBe rootNode.child(0U)
146 | shouldThrow { rootNode.namedChild(1U) }
147 | }
148 |
149 | test("childByFieldId()") {
150 | rootNode.childByFieldId(0U).shouldBeNull()
151 | }
152 |
153 | test("childByFieldName()") {
154 | val child = rootNode.child(0U)!!.childByFieldName("body")
155 | child?.type shouldBe "class_body"
156 | }
157 |
158 | test("childrenByFieldId()") {
159 | val id = language.fieldIdForName("name")
160 | val children = rootNode.child(0U)!!.childrenByFieldId(id)
161 | children.forSingle { it.type shouldBe "identifier" }
162 | }
163 |
164 | test("childrenByFieldName()") {
165 | val children = rootNode.child(0U)!!.childrenByFieldName("name")
166 | children.forSingle { it.type shouldBe "identifier" }
167 | }
168 |
169 | test("fieldNameForChild()") {
170 | rootNode.child(0U)!!.fieldNameForChild(2U) shouldBe "body"
171 | }
172 |
173 | test("fieldNameForNamedChild()") {
174 | rootNode.child(0U)!!.fieldNameForNamedChild(2U).shouldBeNull()
175 | }
176 |
177 | @Suppress("DEPRECATION")
178 | test("childContainingDescendant()") {
179 | val descendant = rootNode.child(0U)!!.child(0U)!!
180 | val child = rootNode.childContainingDescendant(descendant)
181 | child?.type shouldBe "class_declaration"
182 | }
183 |
184 | test("childWithDescendant()") {
185 | val descendant = rootNode.child(0U)!!
186 | val child = rootNode.childWithDescendant(descendant)
187 | child?.type shouldBe "class_declaration"
188 | }
189 |
190 | test("descendant()") {
191 | rootNode.descendant(0U, 5U)?.type shouldBe "class"
192 | rootNode.descendant(Point(0U, 10U), Point(0U, 12U))?.type shouldBe "class_body"
193 | }
194 |
195 | test("namedDescendant()") {
196 | rootNode.namedDescendant(0U, 5U)?.type shouldBe "class_declaration"
197 | rootNode.namedDescendant(Point(0U, 6U), Point(0U, 9U))?.type shouldBe "identifier"
198 | }
199 |
200 | test("walk()") {
201 | val cursor = rootNode.walk()
202 | cursor.currentNode shouldBeSameInstanceAs rootNode
203 | }
204 |
205 | test("copy()") {
206 | val copy = tree.copy()
207 | copy shouldNotBeSameInstanceAs tree
208 | copy.text() shouldBe tree.text()
209 | }
210 |
211 | @Suppress("UnnecessaryOptInAnnotation")
212 | @OptIn(ExperimentalMultiplatform::class)
213 | test("edit()") {
214 | val edit = InputEdit(0U, 12U, 10U, Point(0U, 0U), Point(0U, 12U), Point(0U, 10U))
215 | val copy = tree.copy()
216 | val node = copy.rootNode
217 | copy.edit(edit)
218 | node.edit(edit)
219 | node.hasChanges shouldBe true
220 | }
221 |
222 | test("text()") {
223 | rootNode.descendant(6U, 9U)?.text() shouldBe "Foo"
224 | }
225 |
226 | test("sexp()") {
227 | rootNode.child(0U)!!.sexp() shouldBe
228 | "(class_declaration name: (identifier) body: (class_body))"
229 | }
230 |
231 | test("equals()") {
232 | rootNode shouldNotBe rootNode.child(0U)
233 | }
234 |
235 | test("hashCode()") {
236 | rootNode.hashCode() shouldBe rootNode.id.toInt()
237 | }
238 |
239 | test("toString()") {
240 | rootNode.toString() shouldBe "Node(type=program, startByte=0, endByte=12)"
241 | }
242 | })
243 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/ParserTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
4 | import io.kotest.assertions.throwables.shouldNotThrowAnyUnit
5 | import io.kotest.core.spec.style.FunSpec
6 | import io.kotest.inspectors.forSome
7 | import io.kotest.matchers.*
8 | import io.kotest.matchers.collections.*
9 | import io.kotest.matchers.nulls.*
10 | import io.kotest.matchers.string.*
11 | import io.kotest.matchers.types.*
12 |
13 | class ParserTest : FunSpec({
14 | val parser = Parser()
15 |
16 | test("language") {
17 | val language = Language(TreeSitterJava.language())
18 | parser.language.shouldBeNull()
19 | parser.language = language
20 | parser.language shouldBeSameInstanceAs language
21 | }
22 |
23 | test("includedRanges") {
24 | val range = Range(Point(0U, 0U), Point(0U, 1U), 0U, 1U)
25 | parser.includedRanges.shouldBeEmpty()
26 | parser.includedRanges = listOf(range)
27 | parser.includedRanges shouldHaveSingleElement range
28 | }
29 |
30 | test("timeoutMicros") {
31 | parser.timeoutMicros shouldBe 0UL
32 | parser.timeoutMicros = 10000UL
33 | parser.timeoutMicros shouldBe 10000UL
34 | }
35 |
36 | test("logger") {
37 | shouldNotThrowAnyUnit {
38 | parser.logger = { _, _ ->
39 | throw UnsupportedOperationException()
40 | }
41 | }
42 | }
43 |
44 | test("parse(source)") {
45 | // UTF-8
46 | var source = "class Foo {}"
47 | var tree = parser.parse(source)
48 | tree.text()?.get(5) shouldBe ' '
49 |
50 | // logging
51 | val logs = mutableListOf()
52 | parser.logger = { type, msg -> logs += "$type - $msg" }
53 | parser.reset()
54 | parser.parse(source)
55 | parser.logger = null
56 | logs.shouldNotBeEmpty().forSome {
57 | it shouldStartWith "LEX"
58 | }
59 |
60 | // UTF-16
61 | source = "var java = \"💩\""
62 | tree = parser.parse(source)
63 | tree.text()?.subSequence(12, 14) shouldBe "\uD83D\uDCA9"
64 | }
65 |
66 | test("parse(callback)") {
67 | val source = "class Foo {}"
68 | val tree = parser.parse { byte, _ ->
69 | val end = minOf(byte.toInt() * 2, source.length)
70 | source.subSequence(byte.toInt(), end).ifEmpty { null }
71 | }
72 | tree.text().shouldBeNull()
73 | tree.rootNode.type shouldBe "program"
74 | }
75 |
76 | afterTest { (test, _) ->
77 | when (test.name.testName) {
78 | "includedRanges" -> parser.includedRanges = emptyList()
79 | "timeoutMicros" -> parser.timeoutMicros = 0UL
80 | "logger" -> parser.logger = null
81 | }
82 | }
83 | })
84 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/TreeCursorTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.*
6 | import io.kotest.matchers.nulls.*
7 | import io.kotest.matchers.types.*
8 |
9 | class TreeCursorTest : FunSpec({
10 | val language = Language(TreeSitterJava.language())
11 | val parser = Parser(language)
12 | val source = "class Foo {}"
13 | val tree = parser.parse(source)
14 | val cursor = tree.walk()
15 | val rootNode = tree.rootNode
16 |
17 | test("currentNode") {
18 | val node = cursor.currentNode
19 | node shouldBe rootNode
20 | node shouldBeSameInstanceAs cursor.currentNode
21 | }
22 |
23 | test("currentDepth") {
24 | cursor.currentDepth shouldBe 0U
25 | }
26 |
27 | test("currentFieldId") {
28 | cursor.currentFieldId shouldBe 0U
29 | }
30 |
31 | test("currentFieldName") {
32 | cursor.currentFieldName.shouldBeNull()
33 | }
34 |
35 | test("currentDescendantIndex") {
36 | cursor.currentDescendantIndex shouldBe 0U
37 | }
38 |
39 | test("copy()") {
40 | val copy = cursor.copy()
41 | copy shouldNotBeSameInstanceAs cursor
42 | cursor.reset(copy)
43 | copy.currentNode shouldBe cursor.currentNode
44 | }
45 |
46 | test("gotoFirstChild()") {
47 | cursor.gotoFirstChild() shouldBe true
48 | cursor.currentNode.type shouldBe "class_declaration"
49 | }
50 |
51 | test("gotoLastChild()") {
52 | cursor.gotoLastChild() shouldBe true
53 | cursor.currentFieldName shouldBe "body"
54 | }
55 |
56 | test("gotoParent()") {
57 | cursor.gotoParent() shouldBe true
58 | cursor.currentNode.type shouldBe "class_declaration"
59 | }
60 |
61 | test("gotoNextSibling()") {
62 | cursor.gotoNextSibling() shouldBe false
63 | }
64 |
65 | test("gotoPreviousSibling()") {
66 | cursor.gotoPreviousSibling() shouldBe false
67 | }
68 |
69 | test("gotoDescendant()") {
70 | cursor.gotoDescendant(2U)
71 | cursor.currentDescendantIndex shouldBe 2U
72 | cursor.reset(rootNode)
73 | }
74 |
75 | test("gotoFirstChildForByte()") {
76 | cursor.gotoFirstChildForByte(1U) shouldBe 0U
77 | cursor.currentNode.type shouldBe "class_declaration"
78 | }
79 |
80 | test("gotoFirstChildForPoint()") {
81 | cursor.gotoFirstChildForPoint(Point(0U, 7U)) shouldBe 1U
82 | cursor.currentFieldName shouldBe "name"
83 | }
84 | })
85 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonTest/kotlin/io/github/treesitter/ktreesitter/TreeTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.java.TreeSitterJava
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.*
6 | import io.kotest.matchers.collections.*
7 | import io.kotest.matchers.nulls.*
8 | import io.kotest.matchers.types.*
9 |
10 | class TreeTest : FunSpec({
11 | val language = Language(TreeSitterJava.language())
12 | val parser = Parser(language)
13 | var source = "class Foo {}"
14 | var tree = parser.parse(source)
15 |
16 | test("language") {
17 | tree.language shouldBeSameInstanceAs language
18 | }
19 |
20 | test("rootNode") {
21 | tree.rootNode.endByte shouldBe source.length.toUInt()
22 | }
23 |
24 | test("includedRanges") {
25 | tree.includedRanges.shouldHaveSingleElement {
26 | it.startByte == 0U && it.endByte == UInt.MAX_VALUE
27 | }
28 | }
29 |
30 | test("rootNodeWithOffset()") {
31 | val node = tree.rootNodeWithOffset(6U, Point(0U, 6U))
32 | node?.text() shouldBe "Foo {}"
33 | }
34 |
35 | test("text()") {
36 | tree.text() shouldBe source
37 | }
38 |
39 | test("edit()") {
40 | val edit = InputEdit(9U, 9U, 10U, Point(0U, 9U), Point(0U, 9U), Point(0U, 10U))
41 | source = "class Foo2 {}"
42 | tree.edit(edit)
43 | tree.text().shouldBeNull()
44 | tree = parser.parse(source, tree)
45 | tree.text() shouldBe source
46 | }
47 |
48 | test("walk()") {
49 | val cursor = tree.walk()
50 | cursor.tree shouldBeSameInstanceAs tree
51 | }
52 |
53 | test("changedRanges()") {
54 | val edit = InputEdit(0U, 0U, 7U, Point(0U, 0U), Point(0U, 0U), Point(0U, 7U))
55 | tree.edit(edit)
56 | val newTree = parser.parse("public $source", tree)
57 | tree.changedRanges(newTree).shouldHaveSingleElement {
58 | it.endByte == 7U && it.endPoint.column == 7U
59 | }
60 | }
61 | })
62 |
--------------------------------------------------------------------------------
/ktreesitter/src/commonTest/resources/kotest.properties:
--------------------------------------------------------------------------------
1 | # suppress inspection "UnusedProperty" for whole file
2 | kotest.framework.classpath.scanning.config.disable=true
3 | kotest.framework.classpath.scanning.autoscan.disable=true
4 | kotest.framework.assertion.globalassertsoftly=true
5 |
--------------------------------------------------------------------------------
/ktreesitter/src/jni/language.c:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | jlong JNICALL language_copy CRITICAL_ARGS(jlong self) {
4 | return (jlong)ts_language_copy((TSLanguage *)self);
5 | }
6 |
7 | jint JNICALL language_get_version(JNIEnv *env, jobject this) {
8 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
9 | return (jint)ts_language_version(self);
10 | }
11 |
12 | jint JNICALL language_get_symbol_count(JNIEnv *env, jobject this) {
13 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
14 | return (jint)ts_language_symbol_count(self);
15 | }
16 |
17 | jint JNICALL language_get_state_count(JNIEnv *env, jobject this) {
18 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
19 | return (jint)ts_language_state_count(self);
20 | }
21 |
22 | jint JNICALL language_get_field_count(JNIEnv *env, jobject this) {
23 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
24 | return (jint)ts_language_field_count(self);
25 | }
26 |
27 | jstring JNICALL language_symbol_name(JNIEnv *env, jobject this, jshort symbol) {
28 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
29 | const char *name = ts_language_symbol_name(self, (uint16_t)symbol);
30 | return (*env)->NewStringUTF(env, name);
31 | }
32 |
33 | jshort JNICALL language_symbol_for_name(JNIEnv *env, jobject this, jstring name,
34 | jboolean is_named) {
35 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
36 | const char *symbol_name = (*env)->GetStringUTFChars(env, name, NULL);
37 | uint32_t length = (uint32_t)(*env)->GetStringUTFLength(env, name);
38 | uint16_t symbol = ts_language_symbol_for_name(self, symbol_name, length, (bool)is_named);
39 | (*env)->ReleaseStringUTFChars(env, name, symbol_name);
40 | return (jshort)symbol;
41 | }
42 |
43 | jboolean JNICALL language_is_named(JNIEnv *env, jobject this, jshort symbol) {
44 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
45 | TSSymbolType symbol_type = ts_language_symbol_type(self, symbol);
46 | return (jboolean)(symbol_type == TSSymbolTypeRegular);
47 | }
48 |
49 | jboolean JNICALL language_is_visible(JNIEnv *env, jobject this, jshort symbol) {
50 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
51 | TSSymbolType symbol_type = ts_language_symbol_type(self, symbol);
52 | return (jboolean)(symbol_type <= TSSymbolTypeAnonymous);
53 | }
54 |
55 | jboolean JNICALL language_is_supertype(JNIEnv *env, jobject this, jshort symbol) {
56 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
57 | TSSymbolType symbol_type = ts_language_symbol_type(self, symbol);
58 | return (jboolean)(symbol_type == TSSymbolTypeSupertype);
59 | }
60 |
61 | jstring JNICALL language_field_name_for_id(JNIEnv *env, jobject this, jshort id) {
62 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
63 | const char *name = ts_language_field_name_for_id(self, (uint16_t)id);
64 | return name ? (*env)->NewStringUTF(env, name) : NULL;
65 | }
66 |
67 | jint JNICALL language_field_id_for_name(JNIEnv *env, jobject this, jstring name) {
68 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
69 | const char *field_name = (*env)->GetStringUTFChars(env, name, NULL);
70 | uint32_t length = (uint32_t)(*env)->GetStringUTFLength(env, name);
71 | uint16_t field_id = ts_language_field_id_for_name(self, field_name, length);
72 | (*env)->ReleaseStringUTFChars(env, name, field_name);
73 | return (jint)field_id;
74 | }
75 |
76 | jshort JNICALL language_next_state(JNIEnv *env, jobject this, jshort state, jshort symbol) {
77 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
78 | return (jshort)ts_language_next_state(self, (uint16_t)state, (uint16_t)symbol);
79 | }
80 |
81 | void JNICALL language_check_version(JNIEnv *env, jobject this) {
82 | TSLanguage *self = GET_POINTER(TSLanguage, this, Language_self);
83 | uint32_t version = ts_language_version(self);
84 | if (version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION ||
85 | version > TREE_SITTER_LANGUAGE_VERSION) {
86 | const char *fmt = "Incompatible language version %u. Must be between %u and %u.";
87 | char buffer[70] = {0}; // length(fmt) + digits(UINT32_MAX)
88 | sprintf_s(buffer, 70, fmt, version, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION,
89 | TREE_SITTER_LANGUAGE_VERSION);
90 | THROW(IllegalArgumentException, (const char *)buffer);
91 | }
92 | }
93 |
94 | const JNINativeMethod Language_methods[] = {
95 | {"copy", "(J)J", (void *)&language_copy},
96 | {"getVersion", "()I", (void *)&language_get_version},
97 | {"getSymbolCount", "()I", (void *)&language_get_symbol_count},
98 | {"getStateCount", "()I", (void *)&language_get_state_count},
99 | {"getFieldCount", "()I", (void *)&language_get_field_count},
100 | {"symbolName", "(S)Ljava/lang/String;", (void *)&language_symbol_name},
101 | {"symbolForName", "(Ljava/lang/String;Z)S", (void *)&language_symbol_for_name},
102 | {"isNamed", "(S)Z", (void *)&language_is_named},
103 | {"isVisible", "(S)Z", (void *)&language_is_visible},
104 | {"isSupertype", "(S)Z", (void *)&language_is_supertype},
105 | {"fieldNameForId", "(S)Ljava/lang/String;", (void *)&language_field_name_for_id},
106 | {"fieldIdForName", "(Ljava/lang/String;)S", (void *)&language_field_id_for_name},
107 | {"nextState", "(SS)S", (void *)&language_next_state},
108 | {"checkVersion", "()V", (void *)&language_check_version},
109 | };
110 |
111 | const size_t Language_methods_size = sizeof Language_methods / sizeof(JNINativeMethod);
112 |
--------------------------------------------------------------------------------
/ktreesitter/src/jni/lookahead_iterator.c:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | jlong JNICALL lookahead_iterator_init CRITICAL_ARGS(jlong language, jshort state) {
4 | TSLookaheadIterator *lookahead =
5 | ts_lookahead_iterator_new((TSLanguage *)language, (uint16_t)state);
6 | return (jlong)lookahead;
7 | }
8 |
9 | void JNICALL lookahead_iterator_delete CRITICAL_ARGS(jlong self) {
10 | ts_lookahead_iterator_delete((TSLookaheadIterator *)self);
11 | }
12 |
13 | jobject JNICALL lookahead_iterator_get_language(JNIEnv *env, jobject this) {
14 | TSLookaheadIterator *self = GET_POINTER(TSLookaheadIterator, this, LookaheadIterator_self);
15 | const TSLanguage *language = ts_lookahead_iterator_language(self);
16 | jobject language_obj = (*env)->AllocObject(env, global_class_cache.Language);
17 | (*env)->SetLongField(env, language_obj, global_field_cache.Language_self, (jlong)language);
18 | return language_obj;
19 | };
20 |
21 | jshort JNICALL lookahead_iterator_get_current_symbol(JNIEnv *env, jobject this) {
22 | TSLookaheadIterator *self = GET_POINTER(TSLookaheadIterator, this, LookaheadIterator_self);
23 | return (jshort)ts_lookahead_iterator_current_symbol(self);
24 | }
25 |
26 | jstring JNICALL lookahead_iterator_get_current_symbol_name(JNIEnv *env, jobject this) {
27 | TSLookaheadIterator *self = GET_POINTER(TSLookaheadIterator, this, LookaheadIterator_self);
28 | const char *name = ts_lookahead_iterator_current_symbol_name(self);
29 | return (*env)->NewStringUTF(env, name);
30 | }
31 |
32 | jboolean JNICALL lookahead_iterator_reset(JNIEnv *env, jobject this, jshort state,
33 | jobject language) {
34 | TSLookaheadIterator *self = GET_POINTER(TSLookaheadIterator, this, LookaheadIterator_self);
35 | if (language == NULL) {
36 | return (jboolean)ts_lookahead_iterator_reset_state(self, (uint16_t)state);
37 | }
38 | TSLanguage *language_ptr = GET_POINTER(TSLanguage, language, Language_self);
39 | return (jboolean)ts_lookahead_iterator_reset(self, language_ptr, (uint16_t)state);
40 | }
41 |
42 | jboolean JNICALL lookahead_iterator_native_next(JNIEnv *env, jobject this) {
43 | TSLookaheadIterator *self = GET_POINTER(TSLookaheadIterator, this, LookaheadIterator_self);
44 | return (jboolean)ts_lookahead_iterator_next(self);
45 | }
46 |
47 | const JNINativeMethod LookaheadIterator_methods[] = {
48 | {"init", "(JS)J", (void *)&lookahead_iterator_init},
49 | {"delete", "(J)V", (void *)&lookahead_iterator_delete},
50 | {"getLanguage", "()L" PACKAGE "Language;", (void *)&lookahead_iterator_get_language},
51 | {"getCurrentSymbol", "()S", (void *)&lookahead_iterator_get_current_symbol},
52 | {"getCurrentSymbolName", "()Ljava/lang/String;",
53 | (void *)&lookahead_iterator_get_current_symbol_name},
54 | {"reset", "(SL" PACKAGE "Language;)Z", (void *)&lookahead_iterator_reset},
55 | {"nativeNext", "()Z", (void *)&lookahead_iterator_native_next},
56 | };
57 |
58 | const size_t LookaheadIterator_methods_size =
59 | sizeof LookaheadIterator_methods / sizeof(JNINativeMethod);
60 |
--------------------------------------------------------------------------------
/ktreesitter/src/jni/tree.c:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | jlong JNICALL tree_copy CRITICAL_ARGS(jlong self) { return (jlong)ts_tree_copy((TSTree *)self); }
4 |
5 | void JNICALL tree_delete CRITICAL_ARGS(jlong self) { ts_tree_delete((TSTree *)self); }
6 |
7 | jobject JNICALL tree_get_root_node(JNIEnv *env, jobject this) {
8 | TSTree *self = GET_POINTER(TSTree, this, Tree_self);
9 | return marshal_node(env, ts_tree_root_node(self), this);
10 | }
11 |
12 | jobject JNICALL tree_root_node_with_offset(JNIEnv *env, jobject this, jint bytes, jobject extent) {
13 | TSTree *self = GET_POINTER(TSTree, this, Tree_self);
14 | TSPoint ts_point = unmarshal_point(env, extent);
15 | TSNode ts_node = ts_tree_root_node_with_offset(self, (uint32_t)bytes, ts_point);
16 | return marshal_node(env, ts_node, this);
17 | }
18 |
19 | void JNICALL tree_edit(JNIEnv *env, jobject this, jobject edit) {
20 | TSTree *self = GET_POINTER(TSTree, this, Tree_self);
21 | TSInputEdit input_edit = unmarshal_input_edit(env, edit);
22 | ts_tree_edit(self, &input_edit);
23 | (*env)->SetObjectField(env, this, global_field_cache.Tree_source, NULL);
24 | }
25 |
26 | jobject JNICALL tree_changed_ranges(JNIEnv *env, jobject this, jobject new_tree) {
27 | TSTree *self = GET_POINTER(TSTree, this, Tree_self);
28 | TSTree *other = GET_POINTER(TSTree, new_tree, Tree_self);
29 | uint32_t length;
30 | TSRange *ts_ranges = ts_tree_get_changed_ranges(self, other, &length);
31 | if (length == 0 || ts_ranges == NULL)
32 | return NEW_OBJECT(ArrayList, 0);
33 |
34 | TSRange *ts_range = ts_ranges;
35 | jobject ranges = NEW_OBJECT(ArrayList, (jint)length);
36 | for (uint32_t i = 0; i < length; ++i) {
37 | jobject range_obj = marshal_range(env, ts_range++);
38 | CALL_METHOD(Boolean, ranges, ArrayList_add, range_obj);
39 | (*env)->DeleteLocalRef(env, range_obj);
40 | }
41 | free(ts_ranges);
42 | return ranges;
43 | }
44 |
45 | jobject JNICALL tree_native_included_ranges(JNIEnv *env, jobject this) {
46 | TSTree *self = GET_POINTER(TSTree, this, Tree_self);
47 | uint32_t length;
48 | TSRange *ts_ranges = ts_tree_included_ranges(self, &length);
49 | if (length == 0 || ts_ranges == NULL)
50 | return NEW_OBJECT(ArrayList, 0);
51 |
52 | jobject ranges = NEW_OBJECT(ArrayList, (jint)length);
53 | for (uint32_t i = 0; i < length; ++i) {
54 | jobject range_obj = marshal_range(env, ts_ranges + i);
55 | CALL_METHOD(Boolean, ranges, ArrayList_add, range_obj);
56 | (*env)->DeleteLocalRef(env, range_obj);
57 | }
58 | free(ts_ranges);
59 | return ranges;
60 | }
61 |
62 | const JNINativeMethod Tree_methods[] = {
63 | {"copy", "(J)J", (void *)&tree_copy},
64 | {"delete", "(J)V", (void *)&tree_delete},
65 | {"getRootNode", "()L" PACKAGE "Node;", (void *)&tree_get_root_node},
66 | {"rootNodeWithOffset", "(IL" PACKAGE "Point;)L" PACKAGE "Node;",
67 | (void *)&tree_root_node_with_offset},
68 | {"edit", "(L" PACKAGE "InputEdit;)V", (void *)&tree_edit},
69 | {"changedRanges", "(L" PACKAGE "Tree;)Ljava/util/List;", (void *)&tree_changed_ranges},
70 | {"nativeIncludedRanges", "()Ljava/util/List;", (void *)&tree_native_included_ranges},
71 | };
72 |
73 | const size_t Tree_methods_size = sizeof Tree_methods / sizeof(JNINativeMethod);
74 |
--------------------------------------------------------------------------------
/ktreesitter/src/jni/tree_cursor.c:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #define SET_INTERNAL_NODE(value) \
4 | (*env)->SetObjectField(env, this, global_field_cache.TreeCursor_internalNode, value);
5 |
6 | static inline TSTreeCursor *tree_cursor_alloc(TSTreeCursor cursor) {
7 | TSTreeCursor *cursor_ptr = (TSTreeCursor *)malloc(sizeof(TSTreeCursor));
8 | cursor_ptr->id = cursor.id;
9 | cursor_ptr->tree = cursor.tree;
10 | cursor_ptr->context[0] = cursor.context[0];
11 | cursor_ptr->context[1] = cursor.context[1];
12 | cursor_ptr->context[2] = cursor.context[2];
13 | return cursor_ptr;
14 | }
15 |
16 | jlong JNICALL tree_cursor_init(JNIEnv *env, jclass _class, jobject node) {
17 | TSNode ts_node = unmarshal_node(env, node);
18 | TSTreeCursor cursor = ts_tree_cursor_new(ts_node);
19 | return (jlong)tree_cursor_alloc(cursor);
20 | }
21 |
22 | jlong JNICALL tree_cursor_copy CRITICAL_ARGS(jlong self) {
23 | TSTreeCursor copy = ts_tree_cursor_copy((TSTreeCursor *)self);
24 | return (jlong)tree_cursor_alloc(copy);
25 | }
26 |
27 | void JNICALL tree_cursor_delete CRITICAL_ARGS(jlong self) {
28 | ts_tree_cursor_delete((TSTreeCursor *)self);
29 | free((TSTreeCursor *)self);
30 | }
31 |
32 | jobject JNICALL tree_cursor_get_current_node(JNIEnv *env, jobject this) {
33 | jobject node = GET_FIELD(Object, this, TreeCursor_internalNode);
34 | if (node != NULL)
35 | return node;
36 |
37 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
38 | jobject tree = GET_FIELD(Object, this, TreeCursor_tree);
39 | TSNode ts_node = ts_tree_cursor_current_node(self);
40 | node = marshal_node(env, ts_node, tree);
41 | SET_INTERNAL_NODE(node);
42 | return node;
43 | }
44 |
45 | jint JNICALL tree_cursor_get_current_depth(JNIEnv *env, jobject this) {
46 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
47 | return (jint)ts_tree_cursor_current_depth(self);
48 | }
49 |
50 | jshort JNICALL tree_cursor_get_current_field_id(JNIEnv *env, jobject this) {
51 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
52 | return (short)ts_tree_cursor_current_field_id(self);
53 | }
54 |
55 | jstring JNICALL tree_cursor_get_current_field_name(JNIEnv *env, jobject this) {
56 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
57 | const char *name = ts_tree_cursor_current_field_name(self);
58 | return name ? (*env)->NewStringUTF(env, name) : NULL;
59 | }
60 |
61 | jint JNICALL tree_cursor_get_current_descendant_index(JNIEnv *env, jobject this) {
62 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
63 | return (jint)ts_tree_cursor_current_descendant_index(self);
64 | }
65 |
66 | void JNICALL tree_cursor_reset__node(JNIEnv *env, jobject this, jobject node) {
67 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
68 | TSNode ts_node = unmarshal_node(env, node);
69 | ts_tree_cursor_reset(self, ts_node);
70 | SET_INTERNAL_NODE(NULL);
71 | }
72 |
73 | void JNICALL tree_cursor_reset__cursor(JNIEnv *env, jobject this, jobject cursor) {
74 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
75 | TSTreeCursor *other = GET_POINTER(TSTreeCursor, cursor, TreeCursor_self);
76 | ts_tree_cursor_reset_to(self, other);
77 | SET_INTERNAL_NODE(NULL);
78 | }
79 |
80 | jboolean JNICALL tree_cursor_goto_first_child(JNIEnv *env, jobject this) {
81 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
82 | bool result = ts_tree_cursor_goto_first_child(self);
83 | if (result)
84 | SET_INTERNAL_NODE(NULL);
85 | return (jboolean)result;
86 | }
87 |
88 | jboolean JNICALL tree_cursor_goto_last_child(JNIEnv *env, jobject this) {
89 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
90 | bool result = ts_tree_cursor_goto_last_child(self);
91 | if (result)
92 | SET_INTERNAL_NODE(NULL);
93 | return (jboolean)result;
94 | }
95 |
96 | jboolean JNICALL tree_cursor_goto_parent(JNIEnv *env, jobject this) {
97 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
98 | bool result = ts_tree_cursor_goto_parent(self);
99 | if (result)
100 | SET_INTERNAL_NODE(NULL);
101 | return (jboolean)result;
102 | }
103 |
104 | jboolean JNICALL tree_cursor_goto_next_sibling(JNIEnv *env, jobject this) {
105 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
106 | bool result = ts_tree_cursor_goto_next_sibling(self);
107 | if (result)
108 | SET_INTERNAL_NODE(NULL);
109 | return (jboolean)result;
110 | }
111 |
112 | jboolean JNICALL tree_cursor_goto_previous_sibling(JNIEnv *env, jobject this) {
113 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
114 | bool result = ts_tree_cursor_goto_previous_sibling(self);
115 | if (result)
116 | SET_INTERNAL_NODE(NULL);
117 | return (jboolean)result;
118 | }
119 |
120 | void JNICALL tree_cursor_goto_descendant(JNIEnv *env, jobject this, jint index) {
121 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
122 | ts_tree_cursor_goto_descendant(self, (uint32_t)index);
123 | SET_INTERNAL_NODE(NULL);
124 | }
125 |
126 | jlong JNICALL tree_cursor_native_goto_first_child_for_byte(JNIEnv *env, jobject this, jint byte) {
127 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
128 | return (jlong)ts_tree_cursor_goto_first_child_for_byte(self, (uint32_t)byte);
129 | }
130 |
131 | jlong JNICALL tree_cursor_native_goto_first_child_for_point(JNIEnv *env, jobject this,
132 | jobject point) {
133 | TSTreeCursor *self = GET_POINTER(TSTreeCursor, this, TreeCursor_self);
134 | TSPoint ts_point = unmarshal_point(env, point);
135 | return (jlong)ts_tree_cursor_goto_first_child_for_point(self, ts_point);
136 | }
137 |
138 | const JNINativeMethod TreeCursor_methods[] = {
139 | {"init", "(L" PACKAGE "Node;)J", (void *)&tree_cursor_init},
140 | {"copy", "(J)J", (void *)&tree_cursor_copy},
141 | {"delete", "(J)V", (void *)&tree_cursor_delete},
142 | {"getCurrentNode", "()L" PACKAGE "Node;", (void *)&tree_cursor_get_current_node},
143 | {"getCurrentDepth", "()I", (void *)&tree_cursor_get_current_depth},
144 | {"getCurrentFieldId", "()S", (void *)&tree_cursor_get_current_field_id},
145 | {"getCurrentFieldName", "()Ljava/lang/String;", (void *)&tree_cursor_get_current_field_name},
146 | {"getCurrentDescendantIndex", "()I", (void *)&tree_cursor_get_current_descendant_index},
147 | {"reset", "(L" PACKAGE "Node;)V", (void *)&tree_cursor_reset__node},
148 | {"reset", "(L" PACKAGE "TreeCursor;)V", (void *)&tree_cursor_reset__cursor},
149 | {"gotoFirstChild", "()Z", (void *)&tree_cursor_goto_first_child},
150 | {"gotoLastChild", "()Z", (void *)&tree_cursor_goto_last_child},
151 | {"gotoParent", "()Z", (void *)&tree_cursor_goto_parent},
152 | {"gotoNextSibling", "()Z", (void *)&tree_cursor_goto_next_sibling},
153 | {"gotoPreviousSibling", "()Z", (void *)&tree_cursor_goto_previous_sibling},
154 | {"gotoDescendant", "(I)V", (void *)&tree_cursor_goto_descendant},
155 | {"nativeGotoFirstChildForByte", "(I)J", (void *)&tree_cursor_native_goto_first_child_for_byte},
156 | {"nativeGotoFirstChildForPoint", "(L" PACKAGE "Point;)J",
157 | (void *)&tree_cursor_native_goto_first_child_for_point},
158 | };
159 |
160 | const size_t TreeCursor_methods_size = sizeof TreeCursor_methods / sizeof(JNINativeMethod);
161 |
--------------------------------------------------------------------------------
/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Language.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A class that defines how to parse a particular language.
5 | *
6 | * When a [Language] is generated by the Tree-sitter CLI, it is assigned
7 | * an ABI [version] number that corresponds to the current CLI version.
8 | *
9 | * @constructor Create a new instance from the given language pointer.
10 | * @param language A pointer to a `TSLanguage` cast to [Long].
11 | * @throws [IllegalArgumentException] If the pointer is invalid or the [version] is incompatible.
12 | */
13 | actual class Language @Throws(IllegalArgumentException::class) actual constructor(language: Any) {
14 | @JvmField
15 | internal val self: Long = (language as? Long)?.takeIf { it > 0L }
16 | ?: throw IllegalArgumentException("Invalid language: $language")
17 |
18 | init {
19 | checkVersion()
20 | }
21 |
22 | /** The ABI version number for this language. */
23 | @get:JvmName("getVersion")
24 | actual val version: UInt
25 | external get
26 |
27 | /** The number of distinct node types in this language. */
28 | @get:JvmName("getSymbolCount")
29 | actual val symbolCount: UInt
30 | external get
31 |
32 | /** The number of valid states in this language. */
33 | @get:JvmName("getStateCount")
34 | actual val stateCount: UInt
35 | external get
36 |
37 | /** The number of distinct field names in this language. */
38 | @get:JvmName("getFieldCount")
39 | actual val fieldCount: UInt
40 | external get
41 |
42 | /**
43 | * Get another reference to the language.
44 | *
45 | * @since 0.24.0
46 | */
47 | actual fun copy() = Language(copy(self))
48 |
49 | /** Get the node type for the given numerical ID. */
50 | @JvmName("symbolName")
51 | actual external fun symbolName(symbol: UShort): String?
52 |
53 | /** Get the numerical ID for the given node type. */
54 | @JvmName("symbolForName")
55 | actual external fun symbolForName(name: String, isNamed: Boolean): UShort
56 |
57 | /**
58 | * Check if the node for the given numerical ID is named
59 | *
60 | * @see [Node.isNamed]
61 | */
62 | @JvmName("isNamed")
63 | actual external fun isNamed(symbol: UShort): Boolean
64 |
65 | /** Check if the node for the given numerical ID is visible. */
66 | @JvmName("isVisible")
67 | actual external fun isVisible(symbol: UShort): Boolean
68 |
69 | /**
70 | * Check if the node for the given numerical ID is a supertype.
71 | *
72 | * @since 0.24.0
73 | */
74 | @JvmName("isSupertype")
75 | actual external fun isSupertype(symbol: UShort): Boolean
76 |
77 | /** Get the field name for the given numerical id. */
78 | @JvmName("fieldNameForId")
79 | actual external fun fieldNameForId(id: UShort): String?
80 |
81 | /** Get the numerical ID for the given field name. */
82 | @JvmName("fieldIdForName")
83 | actual external fun fieldIdForName(name: String): UShort
84 |
85 | /**
86 | * Get the next parse state.
87 | *
88 | * Combine this with [lookaheadIterator] to generate
89 | * completion suggestions or valid symbols in error nodes.
90 | *
91 | * #### Example
92 | *
93 | * ```kotlin
94 | * language.nextState(node.parseState, node.grammarSymbol)
95 | * ```
96 | */
97 | @JvmName("nextState")
98 | actual external fun nextState(state: UShort, symbol: UShort): UShort
99 |
100 | /**
101 | * Create a new [lookahead iterator][LookaheadIterator] for the given parse state.
102 | *
103 | * @throws [IllegalArgumentException] If the state is invalid for this language.
104 | */
105 | @JvmName("lookaheadIterator")
106 | @Throws(IllegalArgumentException::class)
107 | actual fun lookaheadIterator(state: UShort) = LookaheadIterator(this, state)
108 |
109 | /**
110 | * Create a new [Query] from a string containing one or more S-expression
111 | * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax).
112 | *
113 | * @throws [QueryError] If any error occurred while creating the query.
114 | */
115 | @Throws(QueryError::class)
116 | actual fun query(source: String) = Query(this, source)
117 |
118 | actual override fun equals(other: Any?) =
119 | this === other || (other is Language && self == other.self)
120 |
121 | actual override fun hashCode() = self.hashCode()
122 |
123 | override fun toString() = "Language(id=0x${self.toString(16)}, version=$version)"
124 |
125 | @Throws(IllegalArgumentException::class)
126 | private external fun checkVersion()
127 |
128 | private companion object {
129 | @JvmStatic
130 | private external fun copy(self: Long): Long
131 |
132 | init {
133 | NativeUtils.loadLibrary()
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A class that is used to look up valid symbols in a specific parse state.
5 | *
6 | * Lookahead iterators can be useful to generate suggestions and improve syntax
7 | * error diagnostics. To get symbols valid in an `ERROR` node, use the lookahead
8 | * iterator on its first leaf node state. For `MISSING` nodes, a lookahead
9 | * iterator created on the previous non-extra leaf node may be appropriate.
10 | */
11 | actual class LookaheadIterator @Throws(IllegalArgumentException::class) internal constructor(
12 | language: Language,
13 | private val state: UShort
14 | ) : AbstractIterator() {
15 | private val self: Long = init(language.self, state).takeIf { it > 0L }
16 | ?: throw IllegalArgumentException("State $state is not valid for $language")
17 |
18 | init {
19 | RefCleaner(this, CleanAction(self))
20 | }
21 |
22 | /** The current language of the lookahead iterator. */
23 | actual val language: Language
24 | external get
25 |
26 | /**
27 | * The current symbol ID.
28 | *
29 | * The ID of the `ERROR` symbol is equal to `UShort.MAX_VALUE`.
30 | */
31 | @get:JvmName("getCurrentSymbol")
32 | actual val currentSymbol: UShort
33 | external get
34 |
35 | /**
36 | * The current symbol name.
37 | *
38 | * Newly created lookahead iterators will contain the `ERROR` symbol.
39 | */
40 | actual val currentSymbolName: String
41 | external get
42 |
43 | /**
44 | * Reset the lookahead iterator the given [state] and, optionally, another [language].
45 | *
46 | * @return `true` if the iterator was reset successfully or `false` if it failed.
47 | */
48 | @JvmName("reset")
49 | actual external fun reset(state: UShort, language: Language?): Boolean
50 |
51 | /** Advance the lookahead iterator to the next symbol. */
52 | actual override fun next() = super.next()
53 |
54 | /** Iterate over the symbol IDs. */
55 | actual fun symbols(): Sequence {
56 | reset(state)
57 | return sequence {
58 | while (nativeNext()) {
59 | yield(currentSymbol)
60 | }
61 | }
62 | }
63 |
64 | /** Iterate over the symbol names. */
65 | actual fun symbolNames(): Sequence {
66 | reset(state)
67 | return sequence {
68 | while (nativeNext()) {
69 | yield(currentSymbolName)
70 | }
71 | }
72 | }
73 |
74 | override fun computeNext() = if (nativeNext()) {
75 | setNext(Symbol(currentSymbol, currentSymbolName))
76 | } else {
77 | done()
78 | }
79 |
80 | operator fun iterator() = apply { reset(state) }
81 |
82 | private external fun nativeNext(): Boolean
83 |
84 | /** A class that pairs a symbol ID with its name. */
85 | @JvmRecord
86 | actual data class Symbol actual constructor(actual val id: UShort, actual val name: String)
87 |
88 | private class CleanAction(private val ptr: Long) : Runnable {
89 | override fun run() = delete(ptr)
90 | }
91 |
92 | private companion object {
93 | @JvmStatic
94 | @JvmName("init")
95 | private external fun init(language: Long, state: UShort): Long
96 |
97 | @JvmStatic
98 | private external fun delete(self: Long)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/NativeUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import java.io.File.createTempFile
4 |
5 | internal object NativeUtils {
6 | private const val LIB_NAME = "ktreesitter"
7 |
8 | @JvmStatic
9 | @Throws(UnsupportedOperationException::class)
10 | private fun libPath(): String? {
11 | val osName = System.getProperty("os.name")!!.lowercase()
12 | val archName = System.getProperty("os.arch")!!.lowercase()
13 | val ext: String
14 | val os: String
15 | val prefix: String
16 | when {
17 | "windows" in osName -> {
18 | ext = "dll"
19 | os = "windows"
20 | prefix = ""
21 | }
22 | "linux" in osName -> {
23 | ext = "so"
24 | os = "linux"
25 | prefix = "lib"
26 | }
27 | "mac" in osName -> {
28 | ext = "dylib"
29 | os = "macos"
30 | prefix = "lib"
31 | }
32 | else -> {
33 | throw UnsupportedOperationException("Unsupported operating system: $osName")
34 | }
35 | }
36 | val arch = when {
37 | "amd64" in archName || "x86_64" in archName -> "x64"
38 | "aarch64" in archName || "arm64" in archName -> "aarch64"
39 | else -> throw UnsupportedOperationException("Unsupported architecture: $archName")
40 | }
41 | val libUrl = javaClass.getResource("/lib/$os/$arch/$prefix$LIB_NAME.$ext") ?: return null
42 | return createTempFile("$prefix$LIB_NAME", ".$ext").apply {
43 | writeBytes(libUrl.openStream().use { it.readAllBytes() })
44 | deleteOnExit()
45 | }.path
46 | }
47 |
48 | @JvmStatic
49 | @Throws(UnsatisfiedLinkError::class)
50 | internal fun loadLibrary() {
51 | try {
52 | System.loadLibrary(LIB_NAME)
53 | } catch (ex: UnsatisfiedLinkError) {
54 | @Suppress("UnsafeDynamicallyLoadedCode")
55 | System.load(libPath() ?: throw ex)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Parser.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /**
4 | * A class that is used to produce a [syntax tree][Tree] from source code.
5 | *
6 | * @constructor Create a new instance with a certain [language], or `null` if empty.
7 | */
8 | actual class Parser actual constructor() {
9 | actual constructor(language: Language) : this() {
10 | this.language = language
11 | }
12 |
13 | private val self = init()
14 |
15 | init {
16 | RefCleaner(this, CleanAction(self))
17 | }
18 |
19 | /**
20 | * The language that the parser will use for parsing.
21 | *
22 | * Parsing cannot be performed while the language is `null`.
23 | */
24 | actual var language: Language? = null
25 | external set
26 |
27 | /**
28 | * The ranges of text that the parser will include when parsing.
29 | *
30 | * By default, the parser will always include entire documents.
31 | * Setting this property allows you to parse only a _portion_ of a
32 | * document but still return a syntax tree whose ranges match up with
33 | * the document as a whole. You can also pass multiple disjoint ranges.
34 | *
35 | * @throws [IllegalArgumentException] If the ranges overlap or are not in ascending order.
36 | */
37 | @set:Throws(IllegalArgumentException::class)
38 | actual var includedRanges: List = emptyList()
39 | external set
40 |
41 | /**
42 | * The maximum duration in microseconds that parsing
43 | * should be allowed to take before halting.
44 | */
45 | @get:JvmName("getTimeoutMicros")
46 | @set:JvmName("setTimeoutMicros")
47 | actual var timeoutMicros: ULong
48 | external get
49 | external set
50 |
51 | /**
52 | * The logger that the parser will use during parsing.
53 | *
54 | * #### Example
55 | *
56 | * ```
57 | * import org.slf4j.LoggerFactory;
58 | * import org.slf4j.MarkerFactory;
59 | *
60 | * val logger = LoggerFactory.getLogger(parser.javaClass)
61 | * val lexMarker = MarkerFactory.getMarker("TS LEX")
62 | * val parseMarker = MarkerFactory.getMarker("TS PARSE")
63 | *
64 | * parser.logger = { type, msg ->
65 | * val marker = when (type) {
66 | * LogType.LEX -> lexMarker
67 | * LogType.PARSE -> parseMarker
68 | * }
69 | * logger.debug(marker, msg)
70 | * }
71 | * ```
72 | */
73 | @get:Deprecated("Don't call the logger directly.", level = DeprecationLevel.HIDDEN)
74 | actual var logger: LogFunction? = null
75 | external set
76 |
77 | /**
78 | * Parse a source code string and create a syntax tree.
79 | *
80 | * If you have already parsed an earlier version of this document and the document
81 | * has since been edited, pass the previous syntax tree to [oldTree] so that the
82 | * unchanged parts of it can be reused. This will save time and memory. For this
83 | * to work correctly, you must have already edited the old syntax tree using the
84 | * [Tree.edit] method in a way that exactly matches the source code changes.
85 | *
86 | * @throws [IllegalStateException]
87 | * If the parser does not have a [language] assigned or
88 | * if parsing was cancelled due to a [timeout][timeoutMicros].
89 | */
90 | @Throws(IllegalStateException::class)
91 | actual external fun parse(source: String, oldTree: Tree?): Tree
92 |
93 | /**
94 | * Parse source code from a callback and create a syntax tree.
95 | *
96 | * If you have already parsed an earlier version of this document and the document
97 | * has since been edited, pass the previous syntax tree to [oldTree] so that the
98 | * unchanged parts of it can be reused. This will save time and memory. For this
99 | * to work correctly, you must have already edited the old syntax tree using the
100 | * [Tree.edit] method in a way that exactly matches the source code changes.
101 | *
102 | * @throws [IllegalStateException]
103 | * If the parser does not have a [language] assigned or
104 | * if parsing was cancelled due to a [timeout][timeoutMicros].
105 | */
106 | @Throws(IllegalStateException::class)
107 | actual external fun parse(oldTree: Tree?, callback: ParseCallback): Tree
108 |
109 | /**
110 | * Instruct the parser to start the next [parse] from the beginning.
111 | *
112 | * If the parser previously failed because of a [timeout][timeoutMicros],
113 | * then by default, it will resume where it left off. If you don't
114 | * want to resume, and instead intend to use this parser to parse
115 | * some other document, you must call this method first.
116 | */
117 | actual external fun reset()
118 |
119 | override fun toString() = "Parser(language=$language)"
120 |
121 | /** The type of a log message. */
122 | @Suppress("unused")
123 | actual enum class LogType { LEX, PARSE }
124 |
125 | private class CleanAction(private val ptr: Long) : Runnable {
126 | override fun run() = delete(ptr)
127 | }
128 |
129 | private companion object {
130 | @JvmStatic
131 | private external fun init(): Long
132 |
133 | @JvmStatic
134 | private external fun delete(self: Long)
135 |
136 | init {
137 | NativeUtils.loadLibrary()
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/RefCleaner.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("RefCleaner")
2 |
3 | package io.github.treesitter.ktreesitter
4 |
5 | import java.lang.ref.Cleaner
6 |
7 | internal object RefCleaner {
8 | private val INSTANCE: Cleaner = Cleaner.create()
9 |
10 | @JvmName("register")
11 | operator fun invoke(obj: Any, action: Runnable) {
12 | INSTANCE.register(obj, action)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/Tree.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /** A class that represents a syntax tree. */
4 | @Suppress("CanBeParameter")
5 | actual class Tree internal constructor(
6 | private val self: Long,
7 | private var source: String?,
8 | /** The language that was used to parse the syntax tree. */
9 | actual val language: Language
10 | ) {
11 | init {
12 | RefCleaner(this, CleanAction(self))
13 | }
14 |
15 | /** The root node of the syntax tree. */
16 | actual val rootNode: Node
17 | external get
18 |
19 | /** The included ranges that were used to parse the syntax tree. */
20 | actual val includedRanges by lazy { nativeIncludedRanges() }
21 |
22 | /**
23 | * Get the root node of the syntax tree, but with
24 | * its position shifted forward by the given offset.
25 | */
26 | @JvmName("rootNodeWithOffset")
27 | actual external fun rootNodeWithOffset(bytes: UInt, extent: Point): Node?
28 |
29 | /**
30 | * Edit the syntax tree to keep it in sync
31 | * with source code that has been modified.
32 | */
33 | actual external fun edit(edit: InputEdit)
34 |
35 | /**
36 | * Create a shallow copy of the syntax tree.
37 | *
38 | * You need to copy a syntax tree in order to use it on multiple
39 | * threads or coroutines, as syntax trees are not thread safe.
40 | */
41 | actual fun copy() = Tree(copy(self), source, language)
42 |
43 | /** Create a new tree cursor starting from the node of the tree. */
44 | actual fun walk() = TreeCursor(rootNode)
45 |
46 | /** Get the source code of the syntax tree, if available. */
47 | actual fun text(): CharSequence? = source
48 |
49 | /**
50 | * Compare an old edited syntax tree to a new
51 | * syntax tree representing the same document.
52 | *
53 | * For this to work correctly, this tree must have been
54 | * edited such that its ranges match up to the new tree.
55 | *
56 | * @return A list of ranges whose syntactic structure has changed.
57 | */
58 | actual external fun changedRanges(newTree: Tree): List
59 |
60 | override fun toString() = "Tree(language=$language, source=$source)"
61 |
62 | private external fun nativeIncludedRanges(): List
63 |
64 | private class CleanAction(private val ptr: Long) : Runnable {
65 | override fun run() = delete(ptr)
66 | }
67 |
68 | private companion object {
69 | @JvmStatic
70 | private external fun copy(self: Long): Long
71 |
72 | @JvmStatic
73 | private external fun delete(self: Long)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/ktreesitter/src/jvmMain/kotlin/io/github/treesitter/ktreesitter/TreeCursor.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | /** A class that can be used to efficiently walk a [syntax tree][Tree]. */
4 | actual class TreeCursor private constructor(
5 | private val self: Long,
6 | @JvmField internal actual val tree: Tree
7 | ) {
8 | internal constructor(node: Node) : this(init(node), node.tree) {
9 | internalNode = node
10 | }
11 |
12 | init {
13 | RefCleaner(this, CleanAction(self))
14 | }
15 |
16 | @Suppress("unused")
17 | private var internalNode: Node? = null
18 |
19 | /** The current node of the cursor. */
20 | actual val currentNode: Node
21 | external get
22 |
23 | /**
24 | * The depth of the cursor's current node relative to the
25 | * original node that the cursor was constructed with.
26 | */
27 | @get:JvmName("getCurrentDepth")
28 | actual val currentDepth: UInt
29 | external get
30 |
31 | /**
32 | * The field ID of the tree cursor's current node, or `0`.
33 | *
34 | * @see [Node.childByFieldId]
35 | * @see [Language.fieldIdForName]
36 | */
37 | @get:JvmName("getCurrentFieldId")
38 | actual val currentFieldId: UShort
39 | external get
40 |
41 | /**
42 | * The field name of the tree cursor's current node, if available.
43 | *
44 | * @see [Node.childByFieldName]
45 | */
46 | actual val currentFieldName: String?
47 | external get
48 |
49 | /**
50 | * The index of the cursor's current node out of all the descendants
51 | * of the original node that the cursor was constructed with.
52 | */
53 | @get:JvmName("getCurrentDescendantIndex")
54 | actual val currentDescendantIndex: UInt
55 | external get
56 |
57 | /** Create a shallow copy of the tree cursor. */
58 | actual fun copy() = TreeCursor(copy(self), tree)
59 |
60 | /** Reset the cursor to start at a different node. */
61 | actual external fun reset(node: Node)
62 |
63 | /** Reset the cursor to start at the same position as another cursor. */
64 | actual external fun reset(cursor: TreeCursor)
65 |
66 | /**
67 | * Move the cursor to the first child of its current node.
68 | *
69 | * @return
70 | * `true` if the cursor successfully moved,
71 | * or `false` if there were no children.
72 | */
73 | actual external fun gotoFirstChild(): Boolean
74 |
75 | /**
76 | * Move the cursor to the last child of its current node.
77 | *
78 | * @return
79 | * `true` if the cursor successfully moved,
80 | * or `false` if there were no children.
81 | */
82 | actual external fun gotoLastChild(): Boolean
83 |
84 | /**
85 | * Move the cursor to the parent of its current node.
86 | *
87 | * @return
88 | * `true` if the cursor successfully moved,
89 | * or `false` if there was no parent node.
90 | */
91 | actual external fun gotoParent(): Boolean
92 |
93 | /**
94 | * Move the cursor to the next sibling of its current node.
95 | *
96 | * @return
97 | * `true` if the cursor successfully moved,
98 | * or `false` if there was no next sibling node.
99 | */
100 | actual external fun gotoNextSibling(): Boolean
101 |
102 | /**
103 | * Move the cursor to the previous sibling of its current node.
104 | *
105 | * This function may be slower than [gotoNextSibling] due to how node positions
106 | * are stored. In the worst case, this will need to iterate through all the
107 | * children up to the previous sibling node to recalculate its position.
108 | *
109 | * @return
110 | * `true` if the cursor successfully moved,
111 | * or `false` if there was no previous sibling node.
112 | */
113 | actual external fun gotoPreviousSibling(): Boolean
114 |
115 | /**
116 | * Move the cursor to the node that is the nth descendant of
117 | * the original node that the cursor was constructed with,
118 | * where `0` represents the original node itself.
119 | */
120 | @JvmName("gotoDescendant")
121 | actual external fun gotoDescendant(index: UInt)
122 |
123 | /**
124 | * Move the cursor to the first child of its current
125 | * node that extends beyond the given byte offset.
126 | *
127 | * @return The index of the child node, or `null` if no such child was found.
128 | */
129 | @JvmName("gotoFirstChildForByte")
130 | actual fun gotoFirstChildForByte(byte: UInt): UInt? {
131 | val result = nativeGotoFirstChildForByte(byte)
132 | if (result == -1L) return null
133 | internalNode = null
134 | return result.toUInt()
135 | }
136 |
137 | /**
138 | * Move the cursor to the first child of its current
139 | * node that extends beyond the given point offset.
140 | *
141 | * @return The index of the child node, or `null` if no such child was found.
142 | */
143 | @JvmName("gotoFirstChildForPoint")
144 | actual fun gotoFirstChildForPoint(point: Point): UInt? {
145 | val result = nativeGotoFirstChildForPoint(point)
146 | if (result == -1L) return null
147 | internalNode = null
148 | return result.toUInt()
149 | }
150 |
151 | override fun toString() = "TreeCursor(tree=$tree)"
152 |
153 | @JvmName("nativeGotoFirstChildForByte")
154 | private external fun nativeGotoFirstChildForByte(byte: UInt): Long
155 |
156 | @JvmName("nativeGotoFirstChildForPoint")
157 | private external fun nativeGotoFirstChildForPoint(point: Point): Long
158 |
159 | private class CleanAction(private val ptr: Long) : Runnable {
160 | override fun run() = delete(ptr)
161 | }
162 |
163 | private companion object {
164 | @JvmStatic
165 | private external fun init(node: Node): Long
166 |
167 | @JvmStatic
168 | private external fun copy(self: Long): Long
169 |
170 | @JvmStatic
171 | private external fun delete(self: Long)
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/ktreesitter/src/nativeInterop/cinterop/treesitter.def:
--------------------------------------------------------------------------------
1 | package = io.github.treesitter.ktreesitter.internal
2 | headers = tree_sitter/api.h alloc.h
3 | headerFilter = tree_sitter/api.h
4 | compilerOpts = -DTREE_SITTER_HIDE_SYMBOLS
5 | staticLibraries = libtree-sitter.a
6 | strictEnums = \
7 | TSLogType \
8 | TSQuantifier \
9 | TSQueryError
10 | nonStrictEnums = \
11 | TSInputEncoding \
12 | TSQueryPredicateStepType \
13 | TSSymbolType
14 | excludedFunctions = \
15 | ts_language_is_wasm \
16 | ts_parser_set_wasm_store \
17 | ts_parser_take_wasm_store \
18 | ts_set_allocator \
19 | ts_wasm_store_delete \
20 | ts_wasm_store_language_count \
21 | ts_wasm_store_load_language \
22 | ts_wasm_store_new
23 |
24 | ---
25 |
26 | #define _isalnum(ch) \
27 | ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))
28 |
29 | static inline bool kts_is_valid_identifier_char(int ch) {
30 | return _isalnum(ch) || ch == '_';
31 | }
32 |
33 | static inline bool kts_is_valid_predicate_char(int ch) {
34 | return _isalnum(ch) || ch == '_' || ch == '-' || ch == '.' || ch == '?' || ch == '!';
35 | }
36 |
37 | static inline uintptr_t kts_node_id(TSNode node) {
38 | return (uintptr_t)node.id;
39 | }
40 |
41 | static inline int32_t kts_node_hash(TSNode node) {
42 | uintptr_t id = (uintptr_t)node.id;
43 | uintptr_t tree = (uintptr_t)node.tree;
44 | return (int32_t)(id == tree ? id : id ^ tree);
45 | }
46 |
47 | static inline void *kts_malloc(size_t size) {
48 | return ts_malloc(size);
49 | }
50 |
51 | static inline void kts_free(void *ptr) {
52 | return ts_free(ptr);
53 | }
54 |
--------------------------------------------------------------------------------
/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Language.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import cnames.structs.TSLanguage
4 | import io.github.treesitter.ktreesitter.internal.*
5 | import kotlinx.cinterop.*
6 |
7 | /**
8 | * A class that defines how to parse a particular language.
9 | *
10 | * When a [Language] is generated by the Tree-sitter CLI, it is assigned
11 | * an ABI [version] number that corresponds to the current CLI version.
12 | */
13 | @OptIn(ExperimentalForeignApi::class) actual class Language internal constructor(
14 | internal val self: CPointer
15 | ) {
16 | /**
17 | * Create a new instance from the given language pointer.
18 | *
19 | * @param language A [CPointer] to a `TSLanguage`.
20 | * @throws [IllegalArgumentException]
21 | * If the pointer is invalid or the [version] is incompatible.
22 | */
23 | @Throws(IllegalArgumentException::class)
24 | actual constructor(language: Any) : this(
25 | (language as? CPointer<*>)?.rawValue?.let(::interpretCPointer)
26 | ?: throw IllegalArgumentException("Invalid language: $language")
27 | )
28 |
29 | /** The ABI version number for this language. */
30 | actual val version: UInt = ts_language_version(self)
31 |
32 | init {
33 | require(version in MIN_COMPATIBLE_LANGUAGE_VERSION..LANGUAGE_VERSION) {
34 | "Incompatible language version $version. " +
35 | "Must be between $MIN_COMPATIBLE_LANGUAGE_VERSION and $LANGUAGE_VERSION."
36 | }
37 | }
38 |
39 | /** The number of distinct node types in this language. */
40 | actual val symbolCount: UInt = ts_language_symbol_count(self)
41 |
42 | /** The number of valid states in this language. */
43 | actual val stateCount: UInt = ts_language_state_count(self)
44 |
45 | /** The number of distinct field names in this language. */
46 | actual val fieldCount: UInt = ts_language_field_count(self)
47 |
48 | /**
49 | * Get another reference to the language.
50 | *
51 | * @since 0.24.0
52 | */
53 | actual fun copy() = Language(ts_language_copy(self)!!)
54 |
55 | /** Get the node type for the given numerical ID. */
56 | actual fun symbolName(symbol: UShort) = ts_language_symbol_name(self, symbol)?.toKString()
57 |
58 | /** Get the numerical ID for the given node type. */
59 | actual fun symbolForName(name: String, isNamed: Boolean): UShort =
60 | ts_language_symbol_for_name(self, name, name.length.convert(), isNamed)
61 |
62 | /**
63 | * Check if the node for the given numerical ID is named
64 | *
65 | * @see [Node.isNamed]
66 | */
67 | actual fun isNamed(symbol: UShort) =
68 | ts_language_symbol_type(self, symbol) == TSSymbolTypeRegular
69 |
70 | /** Check if the node for the given numerical ID is visible. */
71 | actual fun isVisible(symbol: UShort) =
72 | ts_language_symbol_type(self, symbol) <= TSSymbolTypeAnonymous
73 |
74 | /**
75 | * Check if the node for the given numerical ID is a supertype.
76 | *
77 | * @since 0.24.0
78 | */
79 | actual fun isSupertype(symbol: UShort) =
80 | ts_language_symbol_type(self, symbol) == TSSymbolTypeSupertype
81 |
82 | /** Get the field name for the given numerical id. */
83 | actual fun fieldNameForId(id: UShort) = ts_language_field_name_for_id(self, id)?.toKString()
84 |
85 | /** Get the numerical ID for the given field name. */
86 | actual fun fieldIdForName(name: String): UShort =
87 | ts_language_field_id_for_name(self, name, name.length.convert())
88 |
89 | /**
90 | * Get the next parse state.
91 | *
92 | * Combine this with [lookaheadIterator] to generate
93 | * completion suggestions or valid symbols in error nodes.
94 | *
95 | * #### Example
96 | *
97 | * ```kotlin
98 | * language.nextState(node.parseState, node.grammarSymbol)
99 | * ```
100 | */
101 | actual fun nextState(state: UShort, symbol: UShort): UShort =
102 | ts_language_next_state(self, state, symbol)
103 |
104 | /**
105 | * Create a new [lookahead iterator][LookaheadIterator] for the given parse state.
106 | *
107 | * @throws [IllegalArgumentException] If the state is invalid for this language.
108 | */
109 | @Throws(
110 | IllegalArgumentException::class
111 | ) actual fun lookaheadIterator(state: UShort) = LookaheadIterator(this, state)
112 |
113 | /**
114 | * Create a new [Query] from a string containing one or more S-expression
115 | * [patterns](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax).
116 | *
117 | * @throws [QueryError] If any error occurred while creating the query.
118 | */
119 | @Throws(QueryError::class) actual fun query(source: String) = Query(this, source)
120 |
121 | actual override fun equals(other: Any?) =
122 | this === other || (other is Language && self == other.self)
123 |
124 | actual override fun hashCode() = self.hashCode()
125 |
126 | override fun toString() = "Language(id=${self.rawValue}, version=$version)"
127 | }
128 |
--------------------------------------------------------------------------------
/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/LookaheadIterator.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.internal.*
4 | import kotlin.experimental.ExperimentalNativeApi
5 | import kotlin.native.ref.createCleaner
6 | import kotlinx.cinterop.*
7 |
8 | /**
9 | * A class that is used to look up valid symbols in a specific parse state.
10 | *
11 | * Lookahead iterators can be useful to generate suggestions and improve syntax
12 | * error diagnostics. To get symbols valid in an `ERROR` node, use the lookahead
13 | * iterator on its first leaf node state. For `MISSING` nodes, a lookahead
14 | * iterator created on the previous non-extra leaf node may be appropriate.
15 | */
16 | @OptIn(ExperimentalForeignApi::class)
17 | actual class LookaheadIterator @Throws(IllegalArgumentException::class) internal constructor(
18 | language: Language,
19 | private val state: UShort
20 | ) : AbstractIterator() {
21 | private val self = ts_lookahead_iterator_new(language.self, state)
22 | ?: throw IllegalArgumentException("State $state is not valid for $language")
23 |
24 | /** The current language of the lookahead iterator. */
25 | actual val language: Language
26 | get() = Language(ts_lookahead_iterator_language(self)!!)
27 |
28 | @Suppress("unused")
29 | @OptIn(ExperimentalNativeApi::class)
30 | private val cleaner = createCleaner(self, ::ts_lookahead_iterator_delete)
31 |
32 | /**
33 | * The current symbol ID.
34 | *
35 | * The ID of the `ERROR` symbol is equal to `UShort.MAX_VALUE`.
36 | */
37 | actual val currentSymbol: UShort
38 | get() = ts_lookahead_iterator_current_symbol(self)
39 |
40 | /**
41 | * The current symbol name.
42 | *
43 | * Newly created lookahead iterators will contain the `ERROR` symbol.
44 | */
45 | actual val currentSymbolName: String
46 | get() = ts_lookahead_iterator_current_symbol_name(self)!!.toKString()
47 |
48 | /**
49 | * Reset the lookahead iterator the given [state] and, optionally, another [language].
50 | *
51 | * @return `true` if the iterator was reset successfully or `false` if it failed.
52 | */
53 | actual fun reset(state: UShort, language: Language?): Boolean = if (language == null) {
54 | ts_lookahead_iterator_reset_state(self, state)
55 | } else {
56 | ts_lookahead_iterator_reset(self, language.self, state)
57 | }
58 |
59 | /** Advance the lookahead iterator to the next symbol. */
60 | actual override fun next() = super.next()
61 |
62 | /** Iterate over the symbol IDs. */
63 | actual fun symbols(): Sequence {
64 | ts_lookahead_iterator_reset_state(self, state)
65 | return sequence {
66 | while (ts_lookahead_iterator_next(self)) {
67 | yield(ts_lookahead_iterator_current_symbol(self))
68 | }
69 | }
70 | }
71 |
72 | /** Iterate over the symbol names. */
73 | actual fun symbolNames(): Sequence {
74 | ts_lookahead_iterator_reset_state(self, state)
75 | return sequence {
76 | while (ts_lookahead_iterator_next(self)) {
77 | yield(ts_lookahead_iterator_current_symbol_name(self)!!.toKString())
78 | }
79 | }
80 | }
81 |
82 | override fun computeNext() = if (ts_lookahead_iterator_next(self)) {
83 | val id = ts_lookahead_iterator_current_symbol(self)
84 | val name = ts_lookahead_iterator_current_symbol_name(self)
85 | setNext(Symbol(id, name!!.toKString()))
86 | } else {
87 | done()
88 | }
89 |
90 | operator fun iterator() = apply { reset(state) }
91 |
92 | /** A class that pairs a symbol ID with its name. */
93 | actual data class Symbol actual constructor(actual val id: UShort, actual val name: String)
94 | }
95 |
--------------------------------------------------------------------------------
/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Tree.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import cnames.structs.TSTree
4 | import io.github.treesitter.ktreesitter.internal.*
5 | import kotlin.experimental.ExperimentalNativeApi
6 | import kotlin.native.ref.createCleaner
7 | import kotlinx.cinterop.*
8 |
9 | /** A class that represents a syntax tree. */
10 | @OptIn(ExperimentalForeignApi::class)
11 | actual class Tree internal constructor(
12 | internal val self: CPointer,
13 | private var source: String?,
14 | /** The language that was used to parse the syntax tree. */
15 | actual val language: Language
16 | ) {
17 | @Suppress("unused")
18 | @OptIn(ExperimentalNativeApi::class)
19 | private val cleaner = createCleaner(self, ::ts_tree_delete)
20 |
21 | /** The root node of the syntax tree. */
22 | actual val rootNode = Node(ts_tree_root_node(self), this)
23 |
24 | /** The included ranges of the syntax tree. */
25 | actual val includedRanges by lazy {
26 | memScoped {
27 | val length = alloc()
28 | val ranges = ts_tree_included_ranges(self, length.ptr) ?: return@lazy emptyList()
29 | val result = List(length.value.convert()) { ranges[it].convert() }
30 | kts_free(ranges)
31 | result
32 | }
33 | }
34 |
35 | /**
36 | * Get the root node of the syntax tree, but with
37 | * its position shifted forward by the given offset.
38 | */
39 | actual fun rootNodeWithOffset(bytes: UInt, extent: Point): Node? {
40 | val offsetExtent = cValue { from(extent) }
41 | return ts_tree_root_node_with_offset(self, bytes, offsetExtent).convert(this)
42 | }
43 |
44 | /**
45 | * Edit the syntax tree to keep it in sync
46 | * with source code that has been modified.
47 | */
48 | actual fun edit(edit: InputEdit) {
49 | val inputEdit = cValue { from(edit) }
50 | ts_tree_edit(self, inputEdit)
51 | source = null
52 | }
53 |
54 | /**
55 | * Create a shallow copy of the syntax tree.
56 | *
57 | * You need to copy a syntax tree in order to use it on multiple
58 | * threads or coroutines, as syntax trees are not thread safe.
59 | */
60 | actual fun copy() = Tree(ts_tree_copy(self)!!, source, language)
61 |
62 | /** Create a new tree cursor starting from the node of the tree. */
63 | actual fun walk() = TreeCursor(rootNode)
64 |
65 | /** Get the source code of the syntax tree, if available. */
66 | actual fun text(): CharSequence? = source
67 |
68 | /**
69 | * Compare an old edited syntax tree to a new
70 | * syntax tree representing the same document.
71 | *
72 | * For this to work correctly, this tree must have been
73 | * edited such that its ranges match up to the new tree.
74 | *
75 | * @return A list of ranges whose syntactic structure has changed.
76 | */
77 | actual fun changedRanges(newTree: Tree): List = memScoped {
78 | val length = alloc()
79 | val ranges = ts_tree_get_changed_ranges(self, newTree.self, length.ptr)
80 | if (length.value == 0U || ranges == null) return emptyList()
81 | val result = List(length.value.convert()) { ranges[it].convert() }
82 | kts_free(ranges)
83 | return result
84 | }
85 |
86 | override fun toString() = "Tree(language=$language, source=$source)"
87 | }
88 |
--------------------------------------------------------------------------------
/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/TreeCursor.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.internal.*
4 | import kotlin.experimental.ExperimentalNativeApi
5 | import kotlin.native.ref.createCleaner
6 | import kotlinx.cinterop.*
7 |
8 | /** A class that can be used to efficiently walk a [syntax tree][Tree]. */
9 | @OptIn(ExperimentalForeignApi::class)
10 | actual class TreeCursor private constructor(
11 | private val self: CPointer,
12 | internal actual val tree: Tree
13 | ) {
14 | internal constructor(node: Node) : this(ts_tree_cursor_new(node.self).ptr, node.tree) {
15 | internalNode = node
16 | }
17 |
18 | @Suppress("unused")
19 | @OptIn(ExperimentalNativeApi::class)
20 | private val cleaner = createCleaner(self) {
21 | ts_tree_cursor_delete(it)
22 | kts_free(it)
23 | }
24 |
25 | private var internalNode: Node? = null
26 |
27 | /** The current node of the cursor. */
28 | actual val currentNode: Node
29 | get() {
30 | if (internalNode == null)
31 | internalNode = ts_tree_cursor_current_node(self).convert(tree)
32 | return internalNode!!
33 | }
34 |
35 | /**
36 | * The depth of the cursor's current node relative to the
37 | * original node that the cursor was constructed with.
38 | */
39 | actual val currentDepth: UInt
40 | get() = ts_tree_cursor_current_depth(self)
41 |
42 | /**
43 | * The field ID of the tree cursor's current node, or `0`.
44 | *
45 | * @see [Node.childByFieldId]
46 | * @see [Language.fieldIdForName]
47 | */
48 | actual val currentFieldId: UShort
49 | get() = ts_tree_cursor_current_field_id(self)
50 |
51 | /**
52 | * The field name of the tree cursor's current node, if available.
53 | *
54 | * @see [Node.childByFieldName]
55 | */
56 | actual val currentFieldName: String?
57 | get() = ts_tree_cursor_current_field_name(self)?.toKString()
58 |
59 | /**
60 | * The index of the cursor's current node out of all the descendants
61 | * of the original node that the cursor was constructed with.
62 | */
63 | actual val currentDescendantIndex: UInt
64 | get() = ts_tree_cursor_current_descendant_index(self)
65 |
66 | /** Create a shallow copy of the tree cursor. */
67 | actual fun copy() = TreeCursor(ts_tree_cursor_copy(self).ptr, tree)
68 |
69 | /** Reset the cursor to start at a different node. */
70 | actual fun reset(node: Node) {
71 | ts_tree_cursor_reset(self, node.self)
72 | internalNode = null
73 | }
74 |
75 | /** Reset the cursor to start at the same position as another cursor. */
76 | actual fun reset(cursor: TreeCursor) {
77 | ts_tree_cursor_reset_to(self, cursor.self)
78 | internalNode = null
79 | }
80 |
81 | /**
82 | * Move the cursor to the first child of its current node.
83 | *
84 | * @return
85 | * `true` if the cursor successfully moved,
86 | * or `false` if there were no children.
87 | */
88 | actual fun gotoFirstChild(): Boolean {
89 | val result = ts_tree_cursor_goto_first_child(self)
90 | if (result) internalNode = null
91 | return result
92 | }
93 |
94 | /**
95 | * Move the cursor to the last child of its current node.
96 | *
97 | * @return
98 | * `true` if the cursor successfully moved,
99 | * or `false` if there were no children.
100 | */
101 | actual fun gotoLastChild(): Boolean {
102 | val result = ts_tree_cursor_goto_last_child(self)
103 | if (result) internalNode = null
104 | return result
105 | }
106 |
107 | /**
108 | * Move the cursor to the parent of its current node.
109 | *
110 | * @return
111 | * `true` if the cursor successfully moved,
112 | * or `false` if there was no parent node.
113 | */
114 | actual fun gotoParent(): Boolean {
115 | val result = ts_tree_cursor_goto_parent(self)
116 | if (result) internalNode = null
117 | return result
118 | }
119 |
120 | /**
121 | * Move the cursor to the next sibling of its current node.
122 | *
123 | * @return
124 | * `true` if the cursor successfully moved,
125 | * or `false` if there was no next sibling node.
126 | */
127 | actual fun gotoNextSibling(): Boolean {
128 | val result = ts_tree_cursor_goto_next_sibling(self)
129 | if (result) internalNode = null
130 | return result
131 | }
132 |
133 | /**
134 | * Move the cursor to the previous sibling of its current node.
135 | *
136 | * This function may be slower than [gotoNextSibling] due to how node positions
137 | * are stored. In the worst case, this will need to iterate through all the
138 | * children up to the previous sibling node to recalculate its position.
139 | *
140 | * @return
141 | * `true` if the cursor successfully moved,
142 | * or `false` if there was no previous sibling node.
143 | */
144 | actual fun gotoPreviousSibling(): Boolean {
145 | val result = ts_tree_cursor_goto_previous_sibling(self)
146 | if (result) internalNode = null
147 | return result
148 | }
149 |
150 | /**
151 | * Move the cursor to the node that is the nth descendant of
152 | * the original node that the cursor was constructed with,
153 | * where `0` represents the original node itself.
154 | */
155 | actual fun gotoDescendant(index: UInt) {
156 | ts_tree_cursor_goto_descendant(self, index)
157 | internalNode = null
158 | }
159 |
160 | /**
161 | * Move the cursor to the first child of its current
162 | * node that extends beyond the given byte offset.
163 | *
164 | * @return The index of the child node, or `null` if no such child was found.
165 | */
166 | actual fun gotoFirstChildForByte(byte: UInt): UInt? {
167 | val index = ts_tree_cursor_goto_first_child_for_byte(self, byte)
168 | if (index == -1L) return null
169 | internalNode = null
170 | return index.convert()
171 | }
172 |
173 | /**
174 | * Move the cursor to the first child of its current
175 | * node that extends beyond the given point offset.
176 | *
177 | * @return The index of the child node, or `null` if no such child was found.
178 | */
179 | actual fun gotoFirstChildForPoint(point: Point): UInt? {
180 | val pointValue = cValue { from(point) }
181 | val index = ts_tree_cursor_goto_first_child_for_point(self, pointValue)
182 | if (index == -1L) return null
183 | internalNode = null
184 | return index.convert()
185 | }
186 |
187 | override fun toString() = "TreeCursor(tree=$tree)"
188 | }
189 |
--------------------------------------------------------------------------------
/ktreesitter/src/nativeMain/kotlin/io/github/treesitter/ktreesitter/Utils.kt:
--------------------------------------------------------------------------------
1 | package io.github.treesitter.ktreesitter
2 |
3 | import io.github.treesitter.ktreesitter.internal.*
4 | import kotlinx.cinterop.*
5 |
6 | @ExperimentalForeignApi
7 | internal inline fun TSInputEdit.from(edit: InputEdit) = apply {
8 | start_byte = edit.startByte
9 | old_end_byte = edit.oldEndByte
10 | new_end_byte = edit.newEndByte
11 | start_point.from(edit.startPoint)
12 | old_end_point.from(edit.oldEndPoint)
13 | new_end_point.from(edit.newEndPoint)
14 | }
15 |
16 | @ExperimentalForeignApi
17 | internal inline fun TSPoint.from(point: Point) = apply {
18 | row = point.row
19 | column = point.column
20 | }
21 |
22 | @ExperimentalForeignApi
23 | internal inline fun TSRange.from(range: Range) = apply {
24 | start_point.from(range.startPoint)
25 | end_point.from(range.endPoint)
26 | start_byte = range.startByte
27 | end_byte = range.endByte
28 | }
29 |
30 | @ExperimentalForeignApi
31 | internal inline fun TSPoint.convert() = Point(row, column)
32 |
33 | @ExperimentalForeignApi
34 | internal inline fun TSRange.convert() =
35 | Range(start_point.convert(), end_point.convert(), start_byte, end_byte)
36 |
37 | @ExperimentalForeignApi
38 | internal inline fun CValue.convert(tree: Tree) =
39 | if (ts_node_is_null(this)) null else Node(this, tree)
40 |
41 | @ExperimentalForeignApi
42 | internal inline val CValue.ptr: CPointer
43 | get() = place(kts_malloc(sizeOf().convert())!!.reinterpret())
44 |
--------------------------------------------------------------------------------
/languages/java/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.io.OutputStream.nullOutputStream
2 | import org.gradle.internal.os.OperatingSystem
3 | import org.gradle.kotlin.dsl.support.useToRun
4 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
5 | import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
6 | import org.jetbrains.kotlin.konan.target.PlatformManager
7 |
8 | inline val File.unixPath: String
9 | get() = if (!os.isWindows) path else path.replace("\\", "/")
10 |
11 | val os: OperatingSystem = OperatingSystem.current()
12 | val libsDir = layout.buildDirectory.get().dir("libs")
13 | val grammarDir = projectDir.resolve("tree-sitter-java")
14 |
15 | version = grammarDir.resolve("Makefile").readLines()
16 | .first { it.startsWith("VERSION := ") }.removePrefix("VERSION := ")
17 |
18 | plugins {
19 | `maven-publish`
20 | signing
21 | alias(libs.plugins.kotlin.mpp)
22 | alias(libs.plugins.android.library)
23 | id("io.github.tree-sitter.ktreesitter-plugin")
24 | }
25 |
26 | grammar {
27 | baseDir = grammarDir
28 | grammarName = project.name
29 | className = "TreeSitterJava"
30 | packageName = "io.github.treesitter.ktreesitter.java"
31 | files = arrayOf(
32 | // grammarDir.resolve("src/scanner.c"),
33 | grammarDir.resolve("src/parser.c")
34 | )
35 | }
36 |
37 | val generateTask = tasks.generateGrammarFiles.get()
38 |
39 | kotlin {
40 | jvm {}
41 |
42 | androidTarget {
43 | withSourcesJar(true)
44 | publishLibraryVariants("release")
45 | }
46 |
47 | when {
48 | os.isLinux -> listOf(linuxX64(), linuxArm64())
49 | os.isWindows -> listOf(mingwX64())
50 | os.isMacOsX -> listOf(
51 | macosArm64(),
52 | macosX64(),
53 | iosArm64(),
54 | iosSimulatorArm64()
55 | )
56 | else -> {
57 | val arch = System.getProperty("os.arch")
58 | throw GradleException("Unsupported platform: $os ($arch)")
59 | }
60 | }.forEach { target ->
61 | target.compilations.configureEach {
62 | cinterops.create(grammar.interopName.get()) {
63 | defFileProperty.set(generateTask.interopFile.asFile)
64 | includeDirs.allHeaders(grammarDir.resolve("bindings/c"))
65 | extraOpts("-libraryPath", libsDir.dir(konanTarget.name))
66 | tasks.getByName(interopProcessingTaskName).mustRunAfter(generateTask)
67 | }
68 | }
69 | }
70 |
71 | jvmToolchain(17)
72 |
73 | sourceSets {
74 | val generatedSrc = generateTask.generatedSrc.get()
75 | configureEach {
76 | kotlin.srcDir(generatedSrc.dir(name).dir("kotlin"))
77 | }
78 |
79 | commonMain {
80 | @OptIn(ExperimentalKotlinGradlePluginApi::class)
81 | languageSettings {
82 | compilerOptions {
83 | freeCompilerArgs.add("-Xexpect-actual-classes")
84 | }
85 | }
86 |
87 | dependencies {
88 | implementation(libs.kotlin.stdlib)
89 | }
90 | }
91 |
92 | jvmMain {
93 | resources.srcDir(generatedSrc.dir(name).dir("resources"))
94 | }
95 | }
96 | }
97 |
98 | android {
99 | namespace = "io.github.treesitter.ktreesitter.${grammar.grammarName.get()}"
100 | compileSdk = (property("sdk.version.compile") as String).toInt()
101 | ndkVersion = property("ndk.version") as String
102 | defaultConfig {
103 | minSdk = (property("sdk.version.min") as String).toInt()
104 | ndk {
105 | moduleName = grammar.libraryName.get()
106 | //noinspection ChromeOsAbiSupport
107 | abiFilters += setOf("x86_64", "arm64-v8a", "armeabi-v7a")
108 | }
109 | resValue("string", "version", version as String)
110 | }
111 | externalNativeBuild {
112 | cmake {
113 | path = generateTask.cmakeListsFile.get().asFile
114 | buildStagingDirectory = file(".cmake")
115 | version = property("cmake.version") as String
116 | }
117 | }
118 | compileOptions {
119 | sourceCompatibility = JavaVersion.VERSION_17
120 | targetCompatibility = JavaVersion.VERSION_17
121 | }
122 | }
123 |
124 | tasks.withType().configureEach {
125 | if (name.startsWith("cinteropTest")) return@configureEach
126 |
127 | val grammarFiles = grammar.files.get()
128 | val grammarName = grammar.grammarName.get()
129 | val runKonan = File(konanHome.get()).resolve("bin")
130 | .resolve(if (os.isWindows) "run_konan.bat" else "run_konan").path
131 | val libFile = libsDir.dir(konanTarget.name).file("libtree-sitter-$grammarName.a").asFile
132 | val objectFiles = grammarFiles.map {
133 | grammarDir.resolve(it.nameWithoutExtension + ".o").path
134 | }.toTypedArray()
135 | val loader = PlatformManager(konanHome.get(), false, konanDataDir.orNull).loader(konanTarget)
136 |
137 | doFirst {
138 | if (!File(loader.absoluteTargetToolchain).isDirectory) loader.downloadDependencies()
139 |
140 | val argsFile = File.createTempFile("args", null)
141 | argsFile.deleteOnExit()
142 | argsFile.writer().useToRun {
143 | write("-I" + grammarDir.resolve("src").unixPath + "\n")
144 | write("-DTREE_SITTER_HIDE_SYMBOLS\n")
145 | write("-fvisibility=hidden\n")
146 | write("-std=c11\n")
147 | write("-O2\n")
148 | write("-g\n")
149 | write("-c\n")
150 | grammarFiles.forEach { write(it.unixPath + "\n") }
151 | }
152 |
153 | exec {
154 | executable = runKonan
155 | workingDir = grammarDir
156 | standardOutput = nullOutputStream()
157 | args("clang", "clang", konanTarget.name, "@" + argsFile.path)
158 | }
159 |
160 | exec {
161 | executable = runKonan
162 | workingDir = grammarDir
163 | standardOutput = nullOutputStream()
164 | args("llvm", "llvm-ar", "rcs", libFile.path, *objectFiles)
165 | }
166 | }
167 |
168 | inputs.files(*grammarFiles)
169 | outputs.file(libFile)
170 | }
171 |
172 | tasks.create("javadocJar") {
173 | group = "documentation"
174 | archiveClassifier.set("javadoc")
175 | }
176 |
177 | publishing {
178 | publications.withType(MavenPublication::class) {
179 | val grammarName = grammar.grammarName.get()
180 | artifactId = grammar.libraryName.get()
181 | artifact(tasks["javadocJar"])
182 | pom {
183 | name.set("KTreeSitter $grammarName")
184 | description.set("$grammarName grammar for KTreeSitter")
185 | licenses {
186 | license {
187 | name.set("MIT License")
188 | url.set("https://spdx.org/licenses/MIT.html")
189 | }
190 | }
191 | developers {
192 | developer {
193 | id.set("ObserverOfTime")
194 | email.set("chronobserver@disroot.org")
195 | url.set("https://github.com/ObserverOfTime")
196 | }
197 | }
198 | scm {
199 | url.set("https://github.com/tree-sitter/kotlin-tree-sitter")
200 | connection.set("scm:git:git://github.com/tree-sitter/kotlin-tree-sitter.git")
201 | developerConnection.set(
202 | "scm:git:ssh://github.com/tree-sitter/kotlin-tree-sitter.git"
203 | )
204 | }
205 | }
206 | }
207 |
208 | repositories {
209 | maven {
210 | name = "local"
211 | url = uri(layout.buildDirectory.dir("repo"))
212 | }
213 | }
214 | }
215 |
216 | signing {
217 | isRequired = System.getenv("CI") != null
218 | if (isRequired) {
219 | val key = System.getenv("SIGNING_KEY")
220 | val password = System.getenv("SIGNING_PASSWORD")
221 | useInMemoryPgpKeys(key, password)
222 | }
223 | sign(publishing.publications)
224 | }
225 |
226 | tasks.withType().configureEach {
227 | mustRunAfter(tasks.withType())
228 | }
229 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "ktreesitter"
2 |
3 | pluginManagement {
4 | includeBuild("ktreesitter-plugin")
5 | repositories {
6 | google()
7 | mavenCentral()
8 | gradlePluginPortal()
9 | }
10 | }
11 |
12 | include(":ktreesitter")
13 |
14 | file("languages").listFiles { file -> file.isDirectory }?.forEach {
15 | include(":languages:${it.name}")
16 | }
17 |
--------------------------------------------------------------------------------