diff --git a/CMakeLists.txt b/CMakeLists.txt index a5b1747..dc0eafc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,17 @@ endif() find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI") +add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" + "${CMAKE_CURRENT_SOURCE_DIR}/src/node-types.json" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js" + COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating grammar.json") + add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" + BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/parser.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/alloc.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/array.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json --abi=${TREE_SITTER_ABI_VERSION} @@ -49,8 +59,9 @@ configure_file(bindings/c/tree-sitter-rescript.pc.in include(GNUInstallDirs) -install(FILES bindings/c/tree-sitter-rescript.h - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/tree_sitter") +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bindings/c/tree_sitter" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING PATTERN "*.h") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-rescript.pc" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig") install(TARGETS tree-sitter-rescript diff --git a/Makefile b/Makefile index ccfa9bd..ae01f0d 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,6 @@ -ifeq ($(OS),Windows_NT) -$(error Windows is not supported) -endif - LANGUAGE_NAME := tree-sitter-rescript HOMEPAGE_URL := https://github.com/tree-sitter/tree-sitter-rescript -VERSION := 0.1.0 +VERSION := 5.0.0 # repository SRC_DIR := src @@ -16,6 +12,7 @@ PREFIX ?= /usr/local DATADIR ?= $(PREFIX)/share INCLUDEDIR ?= $(PREFIX)/include LIBDIR ?= $(PREFIX)/lib +BINDIR ?= $(PREFIX)/bin PCLIBDIR ?= $(LIBDIR)/pkgconfig # source/object files @@ -32,20 +29,25 @@ SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER)) SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION))) # OS-specific bits -ifeq ($(shell uname),Darwin) +MACHINE := $(shell $(CC) -dumpmachine) + +ifneq ($(findstring darwin,$(MACHINE)),) SOEXT = dylib SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT) SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT) LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks +else ifneq ($(findstring mingw32,$(MACHINE)),) + SOEXT = dll + LINKSHARED += -s -shared -Wl,--out-implib,lib$(LANGUAGE_NAME).dll.a else SOEXT = so SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR) SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR) LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER) -endif ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),) PCLIBDIR := $(PREFIX)/libdata/pkgconfig endif +endif all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc @@ -58,6 +60,10 @@ ifneq ($(STRIP),) $(STRIP) $@ endif +ifneq ($(findstring mingw32,$(MACHINE)),) +lib$(LANGUAGE_NAME).dll.a: lib$(LANGUAGE_NAME).$(SOEXT) +endif + $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \ -e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \ @@ -66,6 +72,9 @@ $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in -e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \ -e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@ +$(SRC_DIR)/grammar.json: grammar.js + $(TS) generate --no-parser $^ + $(PARSER): $(SRC_DIR)/grammar.json $(TS) generate $^ @@ -75,9 +84,18 @@ install: all install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) - ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) - ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) +ifneq ($(findstring mingw32,$(MACHINE)),) + install -d '$(DESTDIR)$(BINDIR)' + install -m755 lib$(LANGUAGE_NAME).dll '$(DESTDIR)$(BINDIR)'/lib$(LANGUAGE_NAME).dll + install -m755 lib$(LANGUAGE_NAME).dll.a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).dll.a +else + install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) + cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) + cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) lib$(LANGUAGE_NAME).$(SOEXT) +endif +ifneq ($(wildcard queries/*.scm),) install -m644 queries/*.scm '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/rescript +endif uninstall: $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \ @@ -89,7 +107,7 @@ uninstall: $(RM) -r '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/rescript clean: - $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) + $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) lib$(LANGUAGE_NAME).dll.a test: $(TS) test diff --git a/Package.swift b/Package.swift index 3bc35ce..5e4d11f 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( .library(name: "TreeSitterReScript", targets: ["TreeSitterReScript"]), ], dependencies: [ - .package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"), + .package(name: "SwiftTreeSitter", url: "https://github.com/tree-sitter/swift-tree-sitter", from: "0.8.0"), ], targets: [ .target( diff --git a/bindings/c/tree-sitter-rescript.h b/bindings/c/tree-sitter-rescript.h deleted file mode 100644 index 3d40a36..0000000 --- a/bindings/c/tree-sitter-rescript.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef TREE_SITTER_RESCRIPT_H_ -#define TREE_SITTER_RESCRIPT_H_ - -typedef struct TSLanguage TSLanguage; - -#ifdef __cplusplus -extern "C" { -#endif - -const TSLanguage *tree_sitter_rescript(void); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_RESCRIPT_H_ diff --git a/bindings/node/binding_test.js b/bindings/node/binding_test.js index 55becac..7a91a84 100644 --- a/bindings/node/binding_test.js +++ b/bindings/node/binding_test.js @@ -1,9 +1,11 @@ -const assert = require("node:assert"); -const { test } = require("node:test"); - -const Parser = require("tree-sitter"); +import assert from "node:assert"; +import { test } from "node:test"; +import Parser from "tree-sitter"; test("can load grammar", () => { const parser = new Parser(); - assert.doesNotThrow(() => parser.setLanguage(require("."))); + assert.doesNotReject(async () => { + const { default: language } = await import("./index.js"); + parser.setLanguage(language); + }); }); diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts index 528e060..f987adc 100644 --- a/bindings/node/index.d.ts +++ b/bindings/node/index.d.ts @@ -18,10 +18,43 @@ type NodeInfo = children: ChildNode[]; }); -type Language = { +/** + * The tree-sitter language object for this grammar. + * + * @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html Parser.Language} + * + * @example + * import Parser from "tree-sitter"; + * import ReScript from "tree-sitter-rescript"; + * + * const parser = new Parser(); + * parser.setLanguage(ReScript); + */ +declare const binding: { + /** + * The inner language object. + * @private + */ language: unknown; + + /** + * The content of the `node-types.json` file for this grammar. + * + * @see {@linkplain https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types Static Node Types} + */ nodeTypeInfo: NodeInfo[]; + + /** The syntax highlighting query for this grammar. */ + HIGHLIGHTS_QUERY?: string; + + /** The language injection query for this grammar. */ + INJECTIONS_QUERY?: string; + + /** The local variable query for this grammar. */ + LOCALS_QUERY?: string; + + /** The symbol tagging query for this grammar. */ + TAGS_QUERY?: string; }; -declare const language: Language; -export = language; +export default binding; diff --git a/bindings/node/index.js b/bindings/node/index.js index 3e64d60..485e5d5 100644 --- a/bindings/node/index.js +++ b/bindings/node/index.js @@ -1,11 +1,37 @@ -const root = require("path").join(__dirname, "..", ".."); +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; -module.exports = - typeof process.versions.bun === "string" - // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time - ? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-rescript.node`) - : require("node-gyp-build")(root); +const root = fileURLToPath(new URL("../..", import.meta.url)); + +const binding = typeof process.versions.bun === "string" + // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time + ? await import(`${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-rescript.node`) + : (await import("node-gyp-build")).default(root); try { - module.exports.nodeTypeInfo = require("../../src/node-types.json"); -} catch (_) {} + const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } }); + binding.nodeTypeInfo = nodeTypes.default; +} catch { } + +const queries = [ + ["HIGHLIGHTS_QUERY", `${root}/queries/highlights.scm`], + ["INJECTIONS_QUERY", `${root}/queries/injections.scm`], + ["LOCALS_QUERY", `${root}/queries/locals.scm`], + ["TAGS_QUERY", `${root}/queries/tags.scm`], +]; + +for (const [prop, path] of queries) { + Object.defineProperty(binding, prop, { + configurable: true, + enumerable: true, + get() { + delete binding[prop]; + try { + binding[prop] = readFileSync(path, "utf8"); + } catch { } + return binding[prop]; + } + }); +} + +export default binding; diff --git a/bindings/python/tests/test_binding.py b/bindings/python/tests/test_binding.py index af3d0d5..c79eda6 100644 --- a/bindings/python/tests/test_binding.py +++ b/bindings/python/tests/test_binding.py @@ -1,12 +1,12 @@ from unittest import TestCase -import tree_sitter +from tree_sitter import Language, Parser import tree_sitter_rescript class TestLanguage(TestCase): def test_can_load_grammar(self): try: - tree_sitter.Language(tree_sitter_rescript.language()) + Parser(Language(tree_sitter_rescript.language())) except Exception: self.fail("Error loading ReScript grammar") diff --git a/bindings/python/tree_sitter_rescript/__init__.py b/bindings/python/tree_sitter_rescript/__init__.py index f832ba2..2b72d0f 100644 --- a/bindings/python/tree_sitter_rescript/__init__.py +++ b/bindings/python/tree_sitter_rescript/__init__.py @@ -6,32 +6,33 @@ def _get_query(name, file): - query = _files(f"{__package__}.queries") / file - globals()[name] = query.read_text() + try: + query = _files(f"{__package__}") / file + globals()[name] = query.read_text() + except FileNotFoundError: + globals()[name] = None return globals()[name] def __getattr__(name): - # NOTE: uncomment these to include any queries that this grammar contains: - - # if name == "HIGHLIGHTS_QUERY": - # return _get_query("HIGHLIGHTS_QUERY", "highlights.scm") - # if name == "INJECTIONS_QUERY": - # return _get_query("INJECTIONS_QUERY", "injections.scm") - # if name == "LOCALS_QUERY": - # return _get_query("LOCALS_QUERY", "locals.scm") - # if name == "TAGS_QUERY": - # return _get_query("TAGS_QUERY", "tags.scm") + if name == "HIGHLIGHTS_QUERY": + return _get_query("HIGHLIGHTS_QUERY", "queries/highlights.scm") + if name == "INJECTIONS_QUERY": + return _get_query("INJECTIONS_QUERY", "queries/injections.scm") + if name == "LOCALS_QUERY": + return _get_query("LOCALS_QUERY", "queries/locals.scm") + if name == "TAGS_QUERY": + return _get_query("TAGS_QUERY", "queries/tags.scm") raise AttributeError(f"module {__name__!r} has no attribute {name!r}") __all__ = [ "language", - # "HIGHLIGHTS_QUERY", - # "INJECTIONS_QUERY", - # "LOCALS_QUERY", - # "TAGS_QUERY", + "HIGHLIGHTS_QUERY", + "INJECTIONS_QUERY", + "LOCALS_QUERY", + "TAGS_QUERY", ] diff --git a/bindings/python/tree_sitter_rescript/__init__.pyi b/bindings/python/tree_sitter_rescript/__init__.pyi index abf6633..5c88ff6 100644 --- a/bindings/python/tree_sitter_rescript/__init__.pyi +++ b/bindings/python/tree_sitter_rescript/__init__.pyi @@ -1,10 +1,17 @@ from typing import Final +from typing_extensions import CapsuleType -# NOTE: uncomment these to include any queries that this grammar contains: +HIGHLIGHTS_QUERY: Final[str] | None +"""The syntax highlighting query for this grammar.""" -# HIGHLIGHTS_QUERY: Final[str] -# INJECTIONS_QUERY: Final[str] -# LOCALS_QUERY: Final[str] -# TAGS_QUERY: Final[str] +INJECTIONS_QUERY: Final[str] | None +"""The language injection query for this grammar.""" -def language() -> object: ... +LOCALS_QUERY: Final[str] | None +"""The local variable query for this grammar.""" + +TAGS_QUERY: Final[str] | None +"""The symbol tagging query for this grammar.""" + +def language() -> CapsuleType: + """The tree-sitter language function for this grammar.""" diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs index 94df3db..b297c8d 100644 --- a/bindings/rust/build.rs +++ b/bindings/rust/build.rs @@ -7,6 +7,24 @@ fn main() { #[cfg(target_env = "msvc")] c_config.flag("-utf-8"); + if std::env::var("TARGET").unwrap() == "wasm32-unknown-unknown" { + let Ok(wasm_headers) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else { + panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate"); + }; + let Ok(wasm_src) = + std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_SRC").map(std::path::PathBuf::from) + else { + panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate"); + }; + + c_config.include(&wasm_headers); + c_config.files([ + wasm_src.join("stdio.c"), + wasm_src.join("stdlib.c"), + wasm_src.join("string.c"), + ]); + } + let parser_path = src_dir.join("parser.c"); c_config.file(&parser_path); println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); @@ -18,4 +36,21 @@ fn main() { } c_config.compile("tree-sitter-rescript"); + + println!("cargo:rustc-check-cfg=cfg(with_highlights_query)"); + if !"queries/highlights.scm".is_empty() && std::path::Path::new("queries/highlights.scm").exists() { + println!("cargo:rustc-cfg=with_highlights_query"); + } + println!("cargo:rustc-check-cfg=cfg(with_injections_query)"); + if !"queries/injections.scm".is_empty() && std::path::Path::new("queries/injections.scm").exists() { + println!("cargo:rustc-cfg=with_injections_query"); + } + println!("cargo:rustc-check-cfg=cfg(with_locals_query)"); + if !"queries/locals.scm".is_empty() && std::path::Path::new("queries/locals.scm").exists() { + println!("cargo:rustc-cfg=with_locals_query"); + } + println!("cargo:rustc-check-cfg=cfg(with_tags_query)"); + if !"queries/tags.scm".is_empty() && std::path::Path::new("queries/tags.scm").exists() { + println!("cargo:rustc-cfg=with_tags_query"); + } } diff --git a/bindings/rust/lib.rs b/bindings/rust/lib.rs index 3e00159..65eb791 100644 --- a/bindings/rust/lib.rs +++ b/bindings/rust/lib.rs @@ -34,12 +34,21 @@ pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_rescr /// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types pub const NODE_TYPES: &str = include_str!("../../src/node-types.json"); -// NOTE: uncomment these to include any queries that this grammar contains: +#[cfg(with_highlights_query)] +/// The syntax highlighting query for this grammar. +pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm"); -// pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm"); -// pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm"); -// pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm"); -// pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm"); +#[cfg(with_injections_query)] +/// The language injection query for this grammar. +pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm"); + +#[cfg(with_locals_query)] +/// The local variable query for this grammar. +pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm"); + +#[cfg(with_tags_query)] +/// The symbol tagging query for this grammar. +pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm"); #[cfg(test)] mod tests { diff --git a/grammar.js b/grammar.js index 2fb03e1..5a6439f 100644 --- a/grammar.js +++ b/grammar.js @@ -1,6 +1,6 @@ /// -module.exports = grammar({ +export default grammar({ name: "rescript", externals: ($) => [ diff --git a/setup.py b/setup.py index ed3f69a..3fe0fb8 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ def run(self): class BdistWheel(bdist_wheel): def get_tag(self): python, abi, platform = super().get_tag() - if python.startswith("cp"): + if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"): python, abi = "cp310", "abi3" return python, abi, platform diff --git a/tree-sitter.json b/tree-sitter.json index 4c90078..aa364b5 100644 --- a/tree-sitter.json +++ b/tree-sitter.json @@ -5,12 +5,15 @@ "name": "rescript", "camelcase": "ReScript", "scope": "source.rescript", - "file-types": ["res", "resi"], - "injection-regex": "^(res|rescript)$", - "class-name": "TreeSitterReScript", + "file-types": [ + "res", + "resi" + ], "highlights": "queries/highlights.scm", "injections": "queries/injections.scm", - "locals": "queries/locals.scm" + "locals": "queries/locals.scm", + "injection-regex": "^(res|rescript)$", + "class-name": "TreeSitterReScript" } ], "metadata": { @@ -29,10 +32,11 @@ "bindings": { "c": true, "go": true, + "java": false, "node": true, "python": true, "rust": true, "swift": true, "zig": false } -} +} \ No newline at end of file