Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit 6a50a04

Browse files
committed
docs: update README to include info about external grammars
1 parent f9f0925 commit 6a50a04

File tree

1 file changed

+118
-70
lines changed

1 file changed

+118
-70
lines changed

README.md

Lines changed: 118 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -59,95 +59,143 @@ git clone --recurse-submodules https://github.com/AndroidIDEOfficial/android-tre
5959

6060
A normal Gradle build (`./gradlew build`) can be executed in order to build everything for Android _and_ the host OS. To build `android-tree-sitter` and the grammars _only_ for the host OS, you can execute `buildForHost` task on appropriate subprojects.
6161

62-
## Examples
62+
## Adding grammars
6363

64-
First, load the shared libraries somewhere in your application:
64+
The Gradle modules for the grammars are almost identical, with only minor differences in the CMakeLists file and the Java binding class.
6565

66-
```java
67-
public class App {
68-
static {
69-
// load the tree sitter library
70-
TreeSitter.loadLibrary();
71-
72-
// native libraries for languages are automatically loaded
73-
// no need to load them manually
74-
}
75-
}
76-
```
66+
These Gradle modules are automatically generated by the [`DynamicModulePlugin`](https://github.com/AndroidIDEOfficial/android-tree-sitter/tree/dev/build-logic/ats/src/main/java/com/itsaky/androidide/treesitter/DynamicModulePlugin.kt). The generation process relies on the [`grammars.json`](https://github.com/AndroidIDEOfficial/android-tree-sitter/tree/dev/grammars/grammars.json) file. More information about the structure of this JSON file can be found in the [README](https://github.com/AndroidIDEOfficial/android-tree-sitter/blob/dev/grammars/README.md) under the `grammars` directory.
67+
68+
Apart from the `DynamicModulePlugin`, there are [other Gradle plugins](https://github.com/AndroidIDEOfficial/android-tree-sitter/tree/dev/build-logic/ats/src/main/java/com/itsaky/androidide/treesitter) which are used to configure and build the grammars effectively.
7769

78-
Then, you can create a `TSParser`, set the language, and start parsing:
79-
80-
```java
81-
try (TSParser parser = new TSParser()) {
82-
parser.setLanguage(TSLanguagePython.newInstance());
83-
try (TSTree tree = parser.parseString("def foo(bar, baz):\n print(bar)\n print(baz)", TSInputEncoding.TSInputEncodingUTF8 /*specify encoding, default is UTF-8*/)) {
84-
TSNode root = tree.getRootNode();
85-
assertEquals(1, root.getChildCount());
86-
assertEquals("module", root.getType());
87-
assertEquals(0, root.getStartByte());
88-
assertEquals(44, root.getEndByte());
89-
90-
TSNode function = root.getChild(0);
91-
assertEquals("function_definition", function.getType());
92-
assertEquals(5, function.getChildCount());
93-
}
94-
}
70+
The common configuration for the grammars can be found in the [`build.gradle.kts`](https://github.com/AndroidIDEOfficial/android-tree-sitter/blob/52cc0400feee5079cac25b27d1e7b673ee53f436/build.gradle.kts#L136) file. This is where you can make changes or adjustments to the module configuration that applies to all grammars.
71+
72+
The generated modules are located in the `rootDir/build/grammar-modules` directory. This is where you can find the output of the module generation process.
73+
74+
To add a new grammar to the project, follow these steps:
75+
76+
1. Begin by adding the grammar source code to the `grammars` directory. To accomplish this, you can add a submodule using the following command:
77+
```bash
78+
git submodule add <remote_url> grammars/<language_name>
9579
```
80+
2. The `language_name` should be the simple name of the language, without the `tree-sitter-` prefix. This name is used to generate both the shared library and the Gradle module. For example, if the `language_name` is `abc`:
81+
- The module `tree-sitter-abc` will be automatically generated.
82+
- The name of the resulting shared library will be `libtree-sitter-abc.so`.
83+
3. After adding the grammar source, update the `grammars.json` file to include the newly added grammar in the project.
84+
4. Finally, sync the project to trigger the generation of the module for the newly added grammar.
85+
86+
## Loading external grammars
87+
88+
You have two ways to load grammars that are not published along with this project :
9689

97-
For debugging, it can be helpful to see string representation of the tree:
90+
- Package the grammar with your application.
91+
- Load the grammar at runtime using `TSLanguage.loadLanguage`.
9892

93+
`TSLanguage.loadLanguage` uses `dlopen` to load the library and must be CAREFULLY used. Also, grammars that are loaded using this method must be closed when they are not used.
94+
95+
> **_Prefer using the first method whenever possible._**
96+
97+
### Package the grammar with your application
98+
99+
You can package the grammar in your Android application as you would package any other shared library :
100+
101+
- Include the `libtree-sitter-myLang.so` file in the `jniLibs` directory of your project.
102+
- Create a native method in a Java class which will return the pointer to the language :
99103
```java
100-
try (TSParser parser = new TSParser()) {
101-
parser.setLanguage(TSLanguagePython.newInstance());
102-
try (TSTree tree = parser.parseString("print(\"hi\")")) {
103-
assertEquals(
104-
"(module (expression_statement (call function: (identifier) arguments: (argument_list (string)))))",
105-
tree.getRootNode().getNodeString()
106-
);
107-
}
108-
}
104+
package com.my.app;
105+
106+
public class MyClass {
107+
108+
static {
109+
System.loadLibrary("tree-sitter-myLang");
110+
}
111+
112+
public static native long myLang();
113+
}
109114
```
110115

111-
If you're going to be traversing a tree, then you can use the `walk` method, which is much more efficient than the above getters:
116+
- Write the C/C++ implementation for the method :
117+
```c++
118+
extern "C" TSLanguage *tree_sitter_myLang();
119+
120+
extern "C"
121+
JNIEXPORT jlong JNICALL
122+
Java_com_my_app_MyClass_myLang(JNIEnv *env, jclass clazz) {
123+
// simply cast the language pointer to jlong
124+
return (jlong) tree_sitter_myLang();
125+
}
126+
```
127+
128+
- Create and use the `TSLanguage` instance :
112129
113130
```java
114-
try (TSParser parser = new TSParser()) {
115-
parser.setLanguage(TSLanguagePython.newInstance());
116-
try (TSTree tree = parser.parseString("def foo(bar, baz):\n print(bar)\n print(baz)")) {
117-
try (TSTreeCursor cursor = tree.getRootNode().walk()) {
118-
assertEquals("module", cursor.getCurrentTreeCursorNode().getType());
119-
cursor.gotoFirstChild();
120-
assertEquals("function_definition", cursor.getCurrentTreeCursorNode().getType());
121-
cursor.gotoFirstChild();
122-
assertEquals("def", cursor.getCurrentTreeCursorNode().getType());
123-
cursor.gotoNextSibling();
124-
cursor.gotoParent();
125-
}
126-
}
131+
final TSLanguage myLang = new TSLanguage("myLang", MyClass.myLang());
132+
133+
// use it with TSParser
134+
try (final var parser = new TSParser()) {
135+
parser.setLanguage(myLang);
136+
...
137+
}
127138
```
128139

129-
For more examples, see the tests in `android-tree-sitter/src/test/java/com/itsaky/androidide/treesitter`.
140+
### Load grammars at runtime
130141

131-
## Adding more grammars
142+
`TSLanguage` provides `loadLanguage(String, String)` method which can be used to load the grammars at runtime. This method uses `dlopen` to load the shared library, get the language instance and return its pointer. Use this method CAREFULLY.
132143

133-
The Gradle modules for the grammars are almost identical, with only minor differences in the CMakeLists file and the Java binding class.
144+
The language instances created using this method **MUST** be closed using `TSLanguage.close()`. Calling the `close` method ensures that the underlying `dlopen`'ed library handle is closed using `dlclose`.
134145

135-
These Gradle modules are automatically generated by the [`DynamicModulePlugin`](https://github.com/AndroidIDEOfficial/android-tree-sitter/tree/dev/build-logic/ats/src/main/java/com/itsaky/androidide/treesitter/DynamicModulePlugin.kt). The generation process relies on the [`grammars.json`](https://github.com/AndroidIDEOfficial/android-tree-sitter/tree/dev/grammars/grammars.json) file. More information about the structure of this JSON file can be found in the [README](https://github.com/AndroidIDEOfficial/android-tree-sitter/blob/dev/grammars/README.md) under the `grammars` directory.
146+
Usage :
147+
```java
148+
// provide the path to the shared library and the name of the language
149+
// the name is used to cache the language instance
150+
// further invocations of this method with the same lang name returns the
151+
// cached language instance
152+
final TSLanguage myLang = TSLanguage.loadLanguage("/path/to/libtree-sitter-myLang.so", "myLang");
153+
154+
if (myLang != null) {
155+
// loaded successfully
156+
} else {
157+
// failed to load the language
158+
// see logcat for details
159+
}
160+
```
136161

137-
Apart from the `DynamicModulePlugin`, there are [other Gradle plugins](https://github.com/AndroidIDEOfficial/android-tree-sitter/tree/dev/build-logic/ats/src/main/java/com/itsaky/androidide/treesitter) which are used to configure and build the grammars effectively.
162+
Use this language :
163+
```java
164+
try (final var parser = new TSParser()) {
165+
parser.setLanguage(myLang);
166+
...
167+
}
168+
```
138169

139-
The common configuration for the grammars can be found in the [`build.gradle.kts`](https://github.com/AndroidIDEOfficial/android-tree-sitter/blob/52cc0400feee5079cac25b27d1e7b673ee53f436/build.gradle.kts#L136) file. This is where you can make changes or adjustments to the module configuration that applies to all grammars.
170+
You don't have to keep a reference to `myLang`. Once loaded, the language can be accessed using `TSLanguageCache` :
171+
```java
172+
// returns the 'myLang' instance i.e. both are same
173+
final TSLanguage cachedMyLang = TSLanguageCache.get("myLang");
174+
```
140175

141-
The generated modules are located in the `rootDir/build/grammar-modules` directory. This is where you can find the output of the module generation process.
176+
**DO NOT FORGET** to close the language :
177+
```java
178+
// this closes the underlying library handle
179+
myLang.close();
180+
```
142181

143-
To add a new grammar to the project, follow these steps:
182+
## Examples
183+
184+
For examples, see the [tests](https://github.com/AndroidIDEOfficial/android-tree-sitter/tree/dev/android-tree-sitter/src/test/java/com/itsaky/androidide/treesitter).
185+
186+
## License
144187

145-
1. Begin by adding the grammar source code to the `grammars` directory. To accomplish this, you can add a submodule using the following command:
146-
```bash
147-
git submodule add <remote_url> grammars/<language_name>
148188
```
149-
2. The `language_name` should be the simple name of the language, without the `tree-sitter-` prefix. This name is used to generate both the shared library and the Gradle module. For example, if the `language_name` is `abc`:
150-
- The module `tree-sitter-abc` will be automatically generated.
151-
- The name of the resulting shared library will be `libtree-sitter-abc.so`.
152-
3. After adding the grammar source, update the `grammars.json` file to include the newly added grammar in the project.
153-
4. Finally, sync the project to trigger the generation of the module for the newly added grammar.
189+
android-tree-sitter library is free software; you can redistribute it and/or
190+
modify it under the terms of the GNU Lesser General Public
191+
License as published by the Free Software Foundation; either
192+
version 2.1 of the License, or (at your option) any later version.
193+
194+
android-tree-sitter library is distributed in the hope that it will be useful,
195+
but WITHOUT ANY WARRANTY; without even the implied warranty of
196+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
197+
Lesser General Public License for more details.
198+
199+
You should have received a copy of the GNU General Public License
200+
along with android-tree-sitter. If not, see <https://www.gnu.org/licenses/>.
201+
```

0 commit comments

Comments
 (0)