diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d270f6b..9d7c2833 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} diff --git a/Cargo.toml b/Cargo.toml index d9a5a3d4..0e5d0849 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ edition = "2021" autoexamples = false build = "bindings/rust/build.rs" -include = ["LICENSE", "bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json"] +include = ["/LICENSE", "bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json"] [lib] path = "bindings/rust/lib.rs" diff --git a/Makefile b/Makefile index b895dd70..abbedecd 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,3 @@ -ifeq ($(OS),Windows_NT) -$(error Windows is not supported) -endif - LANGUAGE_NAME := tree-sitter-bash HOMEPAGE_URL := https://github.com/tree-sitter/tree-sitter-bash VERSION := 0.25.1 @@ -13,8 +9,10 @@ TS ?= tree-sitter # install directory layout PREFIX ?= /usr/local +DATADIR ?= $(PREFIX)/share INCLUDEDIR ?= $(PREFIX)/include LIBDIR ?= $(PREFIX)/lib +BINDIR ?= $(PREFIX)/bin PCLIBDIR ?= $(LIBDIR)/pkgconfig # source/object files @@ -31,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 @@ -57,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)/%=%)|' \ @@ -65,17 +72,30 @@ $(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 $^ install: all - install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)' + install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/bash '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)' install -m644 bindings/c/tree_sitter/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h 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/bash +endif uninstall: $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \ @@ -84,9 +104,10 @@ uninstall: '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \ '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \ '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc + $(RM) -r '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/bash 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/bindings/node/binding_test.js b/bindings/node/binding_test.js index 55becacf..7a91a84d 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 efe259ee..379d5675 100644 --- a/bindings/node/index.d.ts +++ b/bindings/node/index.d.ts @@ -18,11 +18,43 @@ type NodeInfo = children: ChildNode[]; }); -type Language = { - name: string; +/** + * 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 Bash from "tree-sitter-bash"; + * + * const parser = new Parser(); + * parser.setLanguage(Bash); + */ +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 32c99303..dc489c35 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-bash.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-bash.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 287af27d..4462d9c7 100644 --- a/bindings/python/tests/test_binding.py +++ b/bindings/python/tests/test_binding.py @@ -6,6 +6,6 @@ class TestLanguage(TestCase): def test_can_load_grammar(self): try: - tree_sitter.Language(tree_sitter_bash.language()) + Parser(Language(tree_sitter_bash.language())) except Exception: self.fail("Error loading Bash grammar") diff --git a/bindings/python/tree_sitter_bash/__init__.pyi b/bindings/python/tree_sitter_bash/__init__.pyi index 9a5b00a9..4994534c 100644 --- a/bindings/python/tree_sitter_bash/__init__.pyi +++ b/bindings/python/tree_sitter_bash/__init__.pyi @@ -1,5 +1,6 @@ from typing import Final +from typing_extensions import CapsuleType HIGHLIGHTS_QUERY: Final[str] -def language() -> object: ... +def language() -> CapsuleType: ... diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs index ada5b274..9ef947f6 100644 --- a/bindings/rust/build.rs +++ b/bindings/rust/build.rs @@ -10,6 +10,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()); @@ -19,4 +37,21 @@ fn main() { println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); c_config.compile("tree-sitter-bash"); + + 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/grammar.js b/grammar.js index 513f4852..95a1a5ad 100644 --- a/grammar.js +++ b/grammar.js @@ -42,7 +42,7 @@ const PREC = { POSTFIX: 18, }; -module.exports = grammar({ +export default grammar({ name: 'bash', conflicts: $ => [ diff --git a/package.json b/package.json index 6f1fd4b2..d4ef6ce9 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "tree-sitter-bash", "version": "0.25.1", "description": "Bash grammar for tree-sitter", + "type": "module", "repository": "https://github.com/tree-sitter/tree-sitter-bash", "license": "MIT", "author": { diff --git a/setup.py b/setup.py index f038a5fe..70af7f5c 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 6dce7a50..09758564 100644 --- a/tree-sitter.json +++ b/tree-sitter.json @@ -1,4 +1,5 @@ { + "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json", "grammars": [ { "name": "bash", @@ -38,9 +39,11 @@ "bindings": { "c": true, "go": true, + "java": false, "node": true, "python": true, "rust": true, - "swift": true + "swift": true, + "zig": false } -} +} \ No newline at end of file