diff --git a/build.sbt b/build.sbt index 48dec6d..46cb04d 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" ), ) 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 2ff51cc..77763d4 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 @@ -20,6 +20,8 @@ import com.sun.jna.* import org.polyvariant.treesitter4s.internal.TreeSitterLibrary import java.util.concurrent.ConcurrentHashMap +import scala.jdk.CollectionConverters.* +import java.io.Closeable object TreeSitterPlatform { @@ -33,7 +35,7 @@ object TreeSitterPlatform { case e: UnsatisfiedLinkError => throw new Exception("Couldn't load tree-sitter", e) } - val instance: TreeSitter = + def instance(): TreeSitter = new TreeSitter { type Parser = TreeSitterLibrary.Parser type Tree = TreeSitterLibrary.Tree @@ -43,24 +45,42 @@ object TreeSitterPlatform { val NullTree: Tree = null } - type Language = org.polyvariant.treesitter4s.Language + trait CC extends Closeable { + def lang: org.polyvariant.treesitter4s.Language + } - private val languageMap = new ConcurrentHashMap[String, Language] + type Language = CC + + /* private */ + // val languages = new ConcurrentHashMap[String, (NativeLibrary, Language)] + + // def close(): Unit = { + // languages.values().asScala.foreach(_._1.close()) + // languages.clear() + // } + def close(): Unit = () val Language: LanguageMethods = new { def apply( languageName: String - ): Language = languageMap.computeIfAbsent( - languageName, - _ => { - val library = NativeLibrary.getInstance("tree-sitter-python") + ): 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] - val function = library.getFunction("tree_sitter_python"); - function.invoke(classOf[Language], Array()).asInstanceOf[Language] - }, - ) + // We need to keep all references to the libraries in a map + // otherwise they get GC'd and the app segfaults! Fun times. + new CC { + def lang: org.polyvariant.treesitter4s.Language = langg + def close() = library.close() + } + } } @@ -72,7 +92,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, @@ -83,9 +103,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..fd5315a 100644 --- a/core/src/main/scala/org/polyvariant/treesitter4s/TreeSitterAPI.scala +++ b/core/src/main/scala/org/polyvariant/treesitter4s/TreeSitterAPI.scala @@ -18,6 +18,8 @@ package org.polyvariant.treesitter4s import org.polyvariant.treesitter4s.lowlevel.TreeSitter +import scala.util.Using + // High-level Tree Sitter API. // For the lower-level one, see TreeSitter in the lowlevel package. trait TreeSitterAPI { @@ -28,11 +30,15 @@ trait TreeSitterAPI { object TreeSitterAPI { - def make(language: (ts: TreeSitter) => ts.Language): TreeSitterAPI = { - val ts = TreeSitter.instance - - internal.Facade.make(ts, language(ts)) - } + // hm what's interesting is that the committed version segfaults on + // a different function (ts_node_string) and here we crash at ts_parser_set_language. + // strange things + def make(language: (ts: TreeSitter) => ts.Language): TreeSitterAPI = + Using.resource(TreeSitter.instance()) { ts => + Using.resource(language(ts)) { lang => + 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 0a00f95..3c559ca 100644 --- a/core/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitter.scala +++ b/core/src/main/scala/org/polyvariant/treesitter4s/lowlevel/TreeSitter.scala @@ -16,7 +16,9 @@ package org.polyvariant.treesitter4s.lowlevel -trait TreeSitter { +import java.io.Closeable + +trait TreeSitter extends AutoCloseable { type Parser type Tree @@ -26,7 +28,7 @@ trait TreeSitter { val Tree: TreeMethods - type Language + type Language <: Closeable trait LanguageMethods { def apply(libraryName: String): Language @@ -57,5 +59,6 @@ trait TreeSitter { } object TreeSitter { - val instance: TreeSitter = TreeSitterPlatform.instance + // remember to close! + def instance(): TreeSitter = TreeSitterPlatform.instance() } 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 474207f..d143659 100644 --- a/tests/src/test/scala/org/polyvariant/treesitter4s/tests/BindingTests.scala +++ b/tests/src/test/scala/org/polyvariant/treesitter4s/tests/BindingTests.scala @@ -33,20 +33,20 @@ object BindingTests extends FunSuite { assert.eql(rootNode.map(_.children.length), Some(2)) } - // test("root node child type") { - // val tree = parseExample("class Hello {}") - // val rootNode = tree.rootNode + test("root node child type") { + val tree = parseExample("class Hello {}") + val rootNode = tree.rootNode - // assert.eql(rootNode.map(_.tpe), Some("compilation_unit")) - // } + 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 {}")