diff --git a/.gitignore b/.gitignore index 4c26700..f4ee13d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules/ result .vscode/ **/.DS_Store +hs_err_pid* diff --git a/bindingsPython/.jvm/src/main/scala/org/polyvariant/treesitter4s/bindings/python/PythonLanguage.scala b/bindingsPython/.jvm/src/main/scala/org/polyvariant/treesitter4s/bindings/python/PythonLanguage.scala deleted file mode 100644 index 626d384..0000000 --- a/bindingsPython/.jvm/src/main/scala/org/polyvariant/treesitter4s/bindings/python/PythonLanguage.scala +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2022 Polyvariant - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.polyvariant.treesitter4s.bindings.python - -import org.polyvariant.treesitter4s.lowlevel.TreeSitter - -val PythonLanguage: (ts: TreeSitter) => ts.Language = - _.Language(internal.PythonLanguageBindings.Python) diff --git a/bindingsPython/.jvm/src/main/scala/org/polyvariant/treesitter4s/bindings/python/internal/PythonLanguageBindings.java b/bindingsPython/.jvm/src/main/scala/org/polyvariant/treesitter4s/bindings/python/internal/PythonLanguageBindings.java deleted file mode 100644 index 0c81470..0000000 --- a/bindingsPython/.jvm/src/main/scala/org/polyvariant/treesitter4s/bindings/python/internal/PythonLanguageBindings.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 Polyvariant - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.polyvariant.treesitter4s.bindings.python.internal; - -import com.sun.jna.Library; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Native; -import org.polyvariant.treesitter4s.Language; - -public class PythonLanguageBindings { - - private static interface Bindings extends Library { - Language tree_sitter_python(); - } - - private static final Bindings LIBRARY = Language.loadLanguageLibrary("python", Bindings.class); - - public static final Language Python = LIBRARY.tree_sitter_python(); - -} diff --git a/build.sbt b/build.sbt index 2eb80c8..c0912e3 100644 --- a/build.sbt +++ b/build.sbt @@ -66,7 +66,7 @@ lazy val core = crossProject(JVMPlatform) .jvmSettings( commonJVMSettings, libraryDependencies ++= Seq( - "net.java.dev.jna" % "jna" % "5.14.0" + "net.java.dev.jna" % "jna" % "5.15.0" ), ) @@ -82,7 +82,13 @@ lazy val bindingsPython = crossProject(JVMPlatform) lazy val tests = crossProject(JVMPlatform) .crossType(CrossType.Pure) .settings( - commonSettings + commonSettings, + run / fork := true, + // options for debugging JNA issues + // Test / javaOptions ++= Seq( + // "-Djna.debug_load=true", + // "-Djna.debug_load.jna=true", + // ), ) .dependsOn(bindingsPython) .jvmSettings(commonJVMSettings) diff --git a/core/.jvm/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitterPlatform.scala b/core/.jvm/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitterPlatform.scala index 1422a40..ec0923b 100644 --- a/core/.jvm/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitterPlatform.scala +++ b/core/.jvm/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitterPlatform.scala @@ -16,8 +16,8 @@ package org.polyvariant.treesitter4s.lowlevel -import org.polyvariant.treesitter4s.internal.TreeSitterLibrary import com.sun.jna.* +import org.polyvariant.treesitter4s.internal.TreeSitterLibrary object TreeSitterPlatform { @@ -41,11 +41,38 @@ object TreeSitterPlatform { val NullTree: Tree = null } - type Language = org.polyvariant.treesitter4s.Language + trait LanguageWrapper { + def lang: org.polyvariant.treesitter4s.Language + } + + type Language = LanguageWrapper val Language: LanguageMethods = new { - def apply(language: org.polyvariant.treesitter4s.Language): Language = language + + def apply( + languageName: String + ): Language = { + val library = NativeLibrary.getInstance(s"tree-sitter-$languageName") + + val function = library.getFunction(s"tree_sitter_$languageName"); + + val langg = function + .invoke(classOf[org.polyvariant.treesitter4s.Language], Array()) + .asInstanceOf[org.polyvariant.treesitter4s.Language] + + new LanguageWrapper { + def lang: org.polyvariant.treesitter4s.Language = { + // but we need to keep a reference to the library for... reasons + // probably related to, but not quite the same, as: + // https://github.com/java-native-access/jna/pull/1378 + // basically, segfaults. + library.hashCode() + langg + } + } + } + } type Node = TreeSitterLibrary.Node @@ -56,7 +83,7 @@ object TreeSitterPlatform { def tsParserSetLanguage( parser: Parser, language: Language, - ): Boolean = LIBRARY.ts_parser_set_language(parser, language) + ): Boolean = LIBRARY.ts_parser_set_language(parser, language.lang) def tsParserParseString( parser: Parser, @@ -67,9 +94,9 @@ object TreeSitterPlatform { def tsLanguageSymbolCount( language: Language - ): Long = LIBRARY.ts_language_symbol_count(language) + ): Long = LIBRARY.ts_language_symbol_count(language.lang) - def tsLanguageVersion(language: Language): Long = LIBRARY.ts_language_version(language) + def tsLanguageVersion(language: Language): Long = LIBRARY.ts_language_version(language.lang) def tsNodeChild(node: Node, index: Long): Node = LIBRARY.ts_node_child(node, index) diff --git a/core/src/main/scala/org/polyvariant/treesitter4s/TreeSitterAPI.scala b/core/src/main/scala/org/polyvariant/treesitter4s/TreeSitterAPI.scala index c019cfd..3aac73f 100644 --- a/core/src/main/scala/org/polyvariant/treesitter4s/TreeSitterAPI.scala +++ b/core/src/main/scala/org/polyvariant/treesitter4s/TreeSitterAPI.scala @@ -28,10 +28,11 @@ trait TreeSitterAPI { object TreeSitterAPI { - def make(language: (ts: TreeSitter) => ts.Language): TreeSitterAPI = { + def make(language: String): TreeSitterAPI = { val ts = TreeSitter.instance + val lang = ts.Language(language) - internal.Facade.make(ts, language(ts)) + internal.Facade.make(ts, lang) } } diff --git a/core/src/main/scala/org/polyvariant/treesitter4s/internal/TreeSitterApiImpl.scala b/core/src/main/scala/org/polyvariant/treesitter4s/internal/TreeSitterApiImpl.scala index e490d69..26747ac 100644 --- a/core/src/main/scala/org/polyvariant/treesitter4s/internal/TreeSitterApiImpl.scala +++ b/core/src/main/scala/org/polyvariant/treesitter4s/internal/TreeSitterApiImpl.scala @@ -16,10 +16,10 @@ package org.polyvariant.treesitter4s.internal -import org.polyvariant.treesitter4s.lowlevel.TreeSitter -import org.polyvariant.treesitter4s.TreeSitterAPI -import org.polyvariant.treesitter4s.Tree import org.polyvariant.treesitter4s.Node +import org.polyvariant.treesitter4s.Tree +import org.polyvariant.treesitter4s.TreeSitterAPI +import org.polyvariant.treesitter4s.lowlevel.TreeSitter private[treesitter4s] object Facade { diff --git a/core/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitter.scala b/core/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitter.scala index 763d56b..0a00f95 100644 --- a/core/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitter.scala +++ b/core/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitter.scala @@ -29,7 +29,7 @@ trait TreeSitter { type Language trait LanguageMethods { - def apply(language: org.polyvariant.treesitter4s.Language): Language + def apply(libraryName: String): Language } val Language: LanguageMethods diff --git a/fun-times.c b/fun-times.c new file mode 100644 index 0000000..bb6408b --- /dev/null +++ b/fun-times.c @@ -0,0 +1,62 @@ +#include +#include +#include + +// POC of 'reflection' on tree-sitter grammar dylibs in C +// to see if we can apply the same paradigm on all scala platforms +// and avoid language implementors having to write any binding traits whatsoever. +// usage: cc fun-times.c -o get-lang && ./get-lang python +// (requires the dylib to be in the current dir of course) +int main(int argc, char *argv[]) +{ + // Ensure a language name is provided + if (argc != 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + const char *language_name = argv[1]; // Read the language name from argv + + // Construct the shared library name dynamically + char lib_name[256]; + snprintf(lib_name, sizeof(lib_name), "libtree-sitter-%s.dylib", language_name); + + // Load the shared library + void *handle = dlopen(lib_name, RTLD_LAZY); + if (!handle) + { + fprintf(stderr, "Error loading library: %s\n", dlerror()); + return 1; + } + + const char *func_name_prefix = "tree_sitter"; // Prefix for the function name + char func_name_full[256]; + + // Construct the full function name dynamically + snprintf(func_name_full, sizeof(func_name_full), "%s_%s", func_name_prefix, language_name); + + void *(*func)(); // Adjust the function pointer type to return a pointer + + // Use dlsym to get the function address + *(void **)(&func) = dlsym(handle, func_name_full); + + // Check for errors in retrieving the function + char *error = dlerror(); + if (error != NULL) + { + fprintf(stderr, "Error locating function: %s\n", error); + dlclose(handle); + return 1; + } + + // Call the function and get the returned pointer + void *result = func(); + + // Print the address of the returned pointer + printf("Address of the returned pointer: %p\n", result); + + // Clean up + dlclose(handle); + return 0; +} diff --git a/tests/src/main/scala/org/polyvariant/treesitter4s/tests/Demo.scala b/tests/src/main/scala/org/polyvariant/treesitter4s/tests/Demo.scala index 639493b..bdc1cc1 100644 --- a/tests/src/main/scala/org/polyvariant/treesitter4s/tests/Demo.scala +++ b/tests/src/main/scala/org/polyvariant/treesitter4s/tests/Demo.scala @@ -17,12 +17,11 @@ package org.polyvariant.treesitter4s.tests import org.polyvariant.treesitter4s.TreeSitterAPI -import org.polyvariant.treesitter4s.bindings.python.PythonLanguage object Demo { def main(args: Array[String]): Unit = { - val ts = TreeSitterAPI.make(PythonLanguage) + val ts = TreeSitterAPI.make("python") System.out.println(ts.parse("""def main = print("hello world")""").rootNode.map(_.tpe)) } diff --git a/tests/src/test/scala/org/polyvariant/treesitter4s/tests/BindingTests.scala b/tests/src/test/scala/org/polyvariant/treesitter4s/tests/BindingTests.scala index 42e51d0..10b0834 100644 --- a/tests/src/test/scala/org/polyvariant/treesitter4s/tests/BindingTests.scala +++ b/tests/src/test/scala/org/polyvariant/treesitter4s/tests/BindingTests.scala @@ -20,12 +20,10 @@ import cats.implicits._ import org.polyvariant.treesitter4s.Tree import weaver._ import org.polyvariant.treesitter4s.TreeSitterAPI -import org.polyvariant.treesitter4s.bindings.python.PythonLanguage object BindingTests extends FunSuite { - val tsPython = TreeSitterAPI.make(PythonLanguage) - - def parseExample(s: String): Tree = tsPython.parse(s) + val ts = TreeSitterAPI.make("python") + def parseExample(s: String): Tree = ts.parse(s) test("root node child count") { val tree = parseExample("def main = print('Hello')\n") @@ -41,13 +39,13 @@ object BindingTests extends FunSuite { // assert.eql(rootNode.map(_.tpe), Some("compilation_unit")) // } - // test("root node child by index (in range)") { - // val tree = parseExample("class Hello {}") + test("root node child by index (in range)") { + val tree = parseExample("class Hello {}") - // val rootNode = tree.rootNode.getOrElse(sys.error("missing root node")) + val rootNode = tree.rootNode.getOrElse(sys.error("missing root node")) - // assert.eql(rootNode.children.lift(0).isDefined, true) - // } + assert.eql(rootNode.children.lift(0).isDefined, true) + } // test("root node child by index (out of range)") { // val tree = parseExample("class Hello {}")