From a0e03ddd73bc568b6d9062d0eeed302ca193fc0f Mon Sep 17 00:00:00 2001 From: E Dunbar Date: Tue, 15 Apr 2025 17:01:11 -0500 Subject: [PATCH] Initial commit --- .editorconfig | 46 +++++++++ .gitattributes | 41 ++++++++ .gitignore | 47 +++++++++ CMakeLists.txt | 66 +++++++++++++ Cargo.toml | 34 +++++++ Makefile | 97 +++++++++++++++++++ Package.swift | 41 ++++++++ binding.gyp | 35 +++++++ bindings/c/tree-sitter-hy.pc.in | 10 ++ bindings/c/tree_sitter/tree-sitter-hy.h | 16 +++ bindings/go/binding.go | 15 +++ bindings/go/binding_test.go | 15 +++ bindings/node/binding.cc | 19 ++++ bindings/node/binding_test.js | 9 ++ bindings/node/index.d.ts | 27 ++++++ bindings/node/index.js | 11 +++ bindings/python/tests/test_binding.py | 12 +++ bindings/python/tree_sitter_hy/__init__.py | 42 ++++++++ bindings/python/tree_sitter_hy/__init__.pyi | 10 ++ bindings/python/tree_sitter_hy/binding.c | 35 +++++++ bindings/python/tree_sitter_hy/py.typed | 0 bindings/rust/build.rs | 21 ++++ bindings/rust/lib.rs | 53 ++++++++++ bindings/swift/TreeSitterHy/hy.h | 16 +++ .../TreeSitterHyTests/TreeSitterHyTests.swift | 12 +++ go.mod | 5 + grammar.js | 17 ++++ package.json | 51 ++++++++++ pyproject.toml | 29 ++++++ setup.py | 77 +++++++++++++++ tree-sitter.json | 39 ++++++++ 31 files changed, 948 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 Package.swift create mode 100644 binding.gyp create mode 100644 bindings/c/tree-sitter-hy.pc.in create mode 100644 bindings/c/tree_sitter/tree-sitter-hy.h create mode 100644 bindings/go/binding.go create mode 100644 bindings/go/binding_test.go create mode 100644 bindings/node/binding.cc create mode 100644 bindings/node/binding_test.js create mode 100644 bindings/node/index.d.ts create mode 100644 bindings/node/index.js create mode 100644 bindings/python/tests/test_binding.py create mode 100644 bindings/python/tree_sitter_hy/__init__.py create mode 100644 bindings/python/tree_sitter_hy/__init__.pyi create mode 100644 bindings/python/tree_sitter_hy/binding.c create mode 100644 bindings/python/tree_sitter_hy/py.typed create mode 100644 bindings/rust/build.rs create mode 100644 bindings/rust/lib.rs create mode 100644 bindings/swift/TreeSitterHy/hy.h create mode 100644 bindings/swift/TreeSitterHyTests/TreeSitterHyTests.swift create mode 100644 go.mod create mode 100644 grammar.js create mode 100644 package.json create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 tree-sitter.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..65330c4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,46 @@ +root = true + +[*] +charset = utf-8 + +[*.{json,toml,yml,gyp}] +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.scm] +indent_style = space +indent_size = 2 + +[*.{c,cc,h}] +indent_style = space +indent_size = 4 + +[*.rs] +indent_style = space +indent_size = 4 + +[*.{py,pyi}] +indent_style = space +indent_size = 4 + +[*.swift] +indent_style = space +indent_size = 4 + +[*.go] +indent_style = tab +indent_size = 8 + +[Makefile] +indent_style = tab +indent_size = 8 + +[parser.c] +indent_size = 2 + +[{alloc,array,parser}.h] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..79475a5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,41 @@ +* text=auto eol=lf + +# Generated source files +src/*.json linguist-generated +src/parser.c linguist-generated +src/tree_sitter/* linguist-generated + +# C bindings +bindings/c/** linguist-generated +CMakeLists.txt linguist-generated +Makefile linguist-generated + +# Rust bindings +bindings/rust/* linguist-generated +Cargo.toml linguist-generated +Cargo.lock linguist-generated + +# Node.js bindings +bindings/node/* linguist-generated +binding.gyp linguist-generated +package.json linguist-generated +package-lock.json linguist-generated + +# Python bindings +bindings/python/** linguist-generated +setup.py linguist-generated +pyproject.toml linguist-generated + +# Go bindings +bindings/go/* linguist-generated +go.mod linguist-generated +go.sum linguist-generated + +# Swift bindings +bindings/swift/** linguist-generated +Package.swift linguist-generated +Package.resolved linguist-generated + +# Zig bindings +build.zig linguist-generated +build.zig.zon linguist-generated diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc9e191 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Rust artifacts +target/ + +# Node artifacts +build/ +prebuilds/ +node_modules/ + +# Swift artifacts +.build/ + +# Go artifacts +_obj/ + +# Python artifacts +.venv/ +dist/ +*.egg-info +*.whl + +# C artifacts +*.a +*.so +*.so.* +*.dylib +*.dll +*.pc +*.exp +*.lib + +# Zig artifacts +.zig-cache/ +zig-cache/ +zig-out/ + +# Example dirs +/examples/*/ + +# Grammar volatiles +*.wasm +*.obj +*.o + +# Archives +*.tar.gz +*.tgz +*.zip diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b608591 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.13) + +project(tree-sitter-hy + VERSION "0.1.0" + DESCRIPTION "Hy grammar for tree-sitter" + HOMEPAGE_URL "https://github.com/evdunbar/tree-sitter-hy" + LANGUAGES C) + +option(BUILD_SHARED_LIBS "Build using shared libraries" ON) +option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF) + +set(TREE_SITTER_ABI_VERSION 15 CACHE STRING "Tree-sitter ABI version") +if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$") + unset(TREE_SITTER_ABI_VERSION CACHE) + message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer") +endif() + +find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI") + +add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" + COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json + --abi=${TREE_SITTER_ABI_VERSION} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating parser.c") + +add_library(tree-sitter-hy src/parser.c) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c) + target_sources(tree-sitter-hy PRIVATE src/scanner.c) +endif() +target_include_directories(tree-sitter-hy + PRIVATE src + INTERFACE $ + $) + +target_compile_definitions(tree-sitter-hy PRIVATE + $<$:TREE_SITTER_REUSE_ALLOCATOR> + $<$:TREE_SITTER_DEBUG>) + +set_target_properties(tree-sitter-hy + PROPERTIES + C_STANDARD 11 + POSITION_INDEPENDENT_CODE ON + SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}" + DEFINE_SYMBOL "") + +configure_file(bindings/c/tree-sitter-hy.pc.in + "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-hy.pc" @ONLY) + +include(GNUInstallDirs) + +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-hy.pc" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig") +install(TARGETS tree-sitter-hy + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + +file(GLOB QUERIES queries/*.scm) +install(FILES ${QUERIES} + DESTINATION "${CMAKE_INSTALL_DATADIR}/tree-sitter/queries/hy") + +add_custom_target(ts-test "${TREE_SITTER_CLI}" test + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "tree-sitter test") diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7840437 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "tree-sitter-hy" +description = "Hy grammar for tree-sitter" +version = "0.1.0" +authors = ["E Dunbar "] +license = "MIT" +readme = "README.md" +keywords = ["incremental", "parsing", "tree-sitter", "hy"] +categories = ["parser-implementations", "parsing", "text-editors"] +repository = "https://github.com/evdunbar/tree-sitter-hy" +edition = "2021" +autoexamples = false + +build = "bindings/rust/build.rs" +include = [ + "bindings/rust/*", + "grammar.js", + "queries/*", + "src/*", + "tree-sitter.json", + "LICENSE", +] + +[lib] +path = "bindings/rust/lib.rs" + +[dependencies] +tree-sitter-language = "0.1" + +[build-dependencies] +cc = "1.2" + +[dev-dependencies] +tree-sitter = "0.25.3" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5a8e001 --- /dev/null +++ b/Makefile @@ -0,0 +1,97 @@ +ifeq ($(OS),Windows_NT) +$(error Windows is not supported) +endif + +LANGUAGE_NAME := tree-sitter-hy +HOMEPAGE_URL := https://github.com/evdunbar/tree-sitter-hy +VERSION := 0.1.0 + +# repository +SRC_DIR := src + +TS ?= tree-sitter + +# install directory layout +PREFIX ?= /usr/local +DATADIR ?= $(PREFIX)/share +INCLUDEDIR ?= $(PREFIX)/include +LIBDIR ?= $(PREFIX)/lib +PCLIBDIR ?= $(LIBDIR)/pkgconfig + +# source/object files +PARSER := $(SRC_DIR)/parser.c +EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c)) +OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS)) + +# flags +ARFLAGS ?= rcs +override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC + +# ABI versioning +SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER)) +SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION))) + +# OS-specific bits +ifeq ($(shell uname),Darwin) + 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 + 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 + +all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc + +lib$(LANGUAGE_NAME).a: $(OBJS) + $(AR) $(ARFLAGS) $@ $^ + +lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS) + $(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@ +ifneq ($(STRIP),) + $(STRIP) $@ +endif + +$(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in + sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \ + -e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \ + -e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \ + -e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \ + -e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \ + -e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@ + +$(PARSER): $(SRC_DIR)/grammar.json + $(TS) generate $^ + +install: all + install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/hy '$(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) + install -m644 queries/*.scm '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/hy + +uninstall: + $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \ + '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \ + '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \ + '$(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/hy + +clean: + $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) + +test: + $(TS) test + +.PHONY: all install uninstall clean test diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..1892c2e --- /dev/null +++ b/Package.swift @@ -0,0 +1,41 @@ +// swift-tools-version:5.3 + +import Foundation +import PackageDescription + +var sources = ["src/parser.c"] +if FileManager.default.fileExists(atPath: "src/scanner.c") { + sources.append("src/scanner.c") +} + +let package = Package( + name: "TreeSitterHy", + products: [ + .library(name: "TreeSitterHy", targets: ["TreeSitterHy"]), + ], + dependencies: [ + .package(url: "https://github.com/tree-sitter/swift-tree-sitter", from: "0.8.0"), + ], + targets: [ + .target( + name: "TreeSitterHy", + dependencies: [], + path: ".", + sources: sources, + resources: [ + .copy("queries") + ], + publicHeadersPath: "bindings/swift", + cSettings: [.headerSearchPath("src")] + ), + .testTarget( + name: "TreeSitterHyTests", + dependencies: [ + "SwiftTreeSitter", + "TreeSitterHy", + ], + path: "bindings/swift/TreeSitterHyTests" + ) + ], + cLanguageStandard: .c11 +) diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..0af28d7 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,35 @@ +{ + "targets": [ + { + "target_name": "tree_sitter_hy_binding", + "dependencies": [ + " + +typedef struct TSLanguage TSLanguage; + +extern "C" TSLanguage *tree_sitter_hy(); + +// "tree-sitter", "language" hashed with BLAKE2 +const napi_type_tag LANGUAGE_TYPE_TAG = { + 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 +}; + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + auto language = Napi::External::New(env, tree_sitter_hy()); + language.TypeTag(&LANGUAGE_TYPE_TAG); + exports["language"] = language; + return exports; +} + +NODE_API_MODULE(tree_sitter_hy_binding, Init) diff --git a/bindings/node/binding_test.js b/bindings/node/binding_test.js new file mode 100644 index 0000000..55becac --- /dev/null +++ b/bindings/node/binding_test.js @@ -0,0 +1,9 @@ +const assert = require("node:assert"); +const { test } = require("node:test"); + +const Parser = require("tree-sitter"); + +test("can load grammar", () => { + const parser = new Parser(); + assert.doesNotThrow(() => parser.setLanguage(require("."))); +}); diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts new file mode 100644 index 0000000..528e060 --- /dev/null +++ b/bindings/node/index.d.ts @@ -0,0 +1,27 @@ +type BaseNode = { + type: string; + named: boolean; +}; + +type ChildNode = { + multiple: boolean; + required: boolean; + types: BaseNode[]; +}; + +type NodeInfo = + | (BaseNode & { + subtypes: BaseNode[]; + }) + | (BaseNode & { + fields: { [name: string]: ChildNode }; + children: ChildNode[]; + }); + +type Language = { + language: unknown; + nodeTypeInfo: NodeInfo[]; +}; + +declare const language: Language; +export = language; diff --git a/bindings/node/index.js b/bindings/node/index.js new file mode 100644 index 0000000..aa06f1a --- /dev/null +++ b/bindings/node/index.js @@ -0,0 +1,11 @@ +const root = require("path").join(__dirname, "..", ".."); + +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-hy.node`) + : require("node-gyp-build")(root); + +try { + module.exports.nodeTypeInfo = require("../../src/node-types.json"); +} catch (_) {} diff --git a/bindings/python/tests/test_binding.py b/bindings/python/tests/test_binding.py new file mode 100644 index 0000000..20cd53b --- /dev/null +++ b/bindings/python/tests/test_binding.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +import tree_sitter +import tree_sitter_hy + + +class TestLanguage(TestCase): + def test_can_load_grammar(self): + try: + tree_sitter.Language(tree_sitter_hy.language()) + except Exception: + self.fail("Error loading Hy grammar") diff --git a/bindings/python/tree_sitter_hy/__init__.py b/bindings/python/tree_sitter_hy/__init__.py new file mode 100644 index 0000000..e622e6c --- /dev/null +++ b/bindings/python/tree_sitter_hy/__init__.py @@ -0,0 +1,42 @@ +"""Hy grammar for tree-sitter""" + +from importlib.resources import files as _files + +from ._binding import language + + +def _get_query(name, file): + query = _files(f"{__package__}.queries") / file + globals()[name] = query.read_text() + 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") + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +__all__ = [ + "language", + # "HIGHLIGHTS_QUERY", + # "INJECTIONS_QUERY", + # "LOCALS_QUERY", + # "TAGS_QUERY", +] + + +def __dir__(): + return sorted(__all__ + [ + "__all__", "__builtins__", "__cached__", "__doc__", "__file__", + "__loader__", "__name__", "__package__", "__path__", "__spec__", + ]) diff --git a/bindings/python/tree_sitter_hy/__init__.pyi b/bindings/python/tree_sitter_hy/__init__.pyi new file mode 100644 index 0000000..abf6633 --- /dev/null +++ b/bindings/python/tree_sitter_hy/__init__.pyi @@ -0,0 +1,10 @@ +from typing import Final + +# NOTE: uncomment these to include any queries that this grammar contains: + +# HIGHLIGHTS_QUERY: Final[str] +# INJECTIONS_QUERY: Final[str] +# LOCALS_QUERY: Final[str] +# TAGS_QUERY: Final[str] + +def language() -> object: ... diff --git a/bindings/python/tree_sitter_hy/binding.c b/bindings/python/tree_sitter_hy/binding.c new file mode 100644 index 0000000..1c5c52e --- /dev/null +++ b/bindings/python/tree_sitter_hy/binding.c @@ -0,0 +1,35 @@ +#include + +typedef struct TSLanguage TSLanguage; + +TSLanguage *tree_sitter_hy(void); + +static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { + return PyCapsule_New(tree_sitter_hy(), "tree_sitter.Language", NULL); +} + +static struct PyModuleDef_Slot slots[] = { +#ifdef Py_GIL_DISABLED + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL} +}; + +static PyMethodDef methods[] = { + {"language", _binding_language, METH_NOARGS, + "Get the tree-sitter language for this grammar."}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_binding", + .m_doc = NULL, + .m_size = 0, + .m_methods = methods, + .m_slots = slots, +}; + +PyMODINIT_FUNC PyInit__binding(void) { + return PyModuleDef_Init(&module); +} diff --git a/bindings/python/tree_sitter_hy/py.typed b/bindings/python/tree_sitter_hy/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs new file mode 100644 index 0000000..26b0e24 --- /dev/null +++ b/bindings/rust/build.rs @@ -0,0 +1,21 @@ +fn main() { + let src_dir = std::path::Path::new("src"); + + let mut c_config = cc::Build::new(); + c_config.std("c11").include(src_dir); + + #[cfg(target_env = "msvc")] + c_config.flag("-utf-8"); + + let parser_path = src_dir.join("parser.c"); + c_config.file(&parser_path); + println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); + + let scanner_path = src_dir.join("scanner.c"); + if scanner_path.exists() { + c_config.file(&scanner_path); + println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); + } + + c_config.compile("tree-sitter-hy"); +} diff --git a/bindings/rust/lib.rs b/bindings/rust/lib.rs new file mode 100644 index 0000000..ab59459 --- /dev/null +++ b/bindings/rust/lib.rs @@ -0,0 +1,53 @@ +//! This crate provides Hy language support for the [tree-sitter][] parsing library. +//! +//! Typically, you will use the [LANGUAGE][] constant to add this language to a +//! tree-sitter [Parser][], and then use the parser to parse some code: +//! +//! ``` +//! let code = r#" +//! "#; +//! let mut parser = tree_sitter::Parser::new(); +//! let language = tree_sitter_hy::LANGUAGE; +//! parser +//! .set_language(&language.into()) +//! .expect("Error loading Hy parser"); +//! let tree = parser.parse(code, None).unwrap(); +//! assert!(!tree.root_node().has_error()); +//! ``` +//! +//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html +//! [tree-sitter]: https://tree-sitter.github.io/ + +use tree_sitter_language::LanguageFn; + +extern "C" { + fn tree_sitter_hy() -> *const (); +} + +/// The tree-sitter [`LanguageFn`][LanguageFn] for this grammar. +/// +/// [LanguageFn]: https://docs.rs/tree-sitter-language/*/tree_sitter_language/struct.LanguageFn.html +pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_hy) }; + +/// The content of the [`node-types.json`][] file for this grammar. +/// +/// [`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: + +// 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(test)] +mod tests { + #[test] + fn test_can_load_grammar() { + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&super::LANGUAGE.into()) + .expect("Error loading Hy parser"); + } +} diff --git a/bindings/swift/TreeSitterHy/hy.h b/bindings/swift/TreeSitterHy/hy.h new file mode 100644 index 0000000..0fc1caf --- /dev/null +++ b/bindings/swift/TreeSitterHy/hy.h @@ -0,0 +1,16 @@ +#ifndef TREE_SITTER_HY_H_ +#define TREE_SITTER_HY_H_ + +typedef struct TSLanguage TSLanguage; + +#ifdef __cplusplus +extern "C" { +#endif + +const TSLanguage *tree_sitter_hy(void); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_HY_H_ diff --git a/bindings/swift/TreeSitterHyTests/TreeSitterHyTests.swift b/bindings/swift/TreeSitterHyTests/TreeSitterHyTests.swift new file mode 100644 index 0000000..6779052 --- /dev/null +++ b/bindings/swift/TreeSitterHyTests/TreeSitterHyTests.swift @@ -0,0 +1,12 @@ +import XCTest +import SwiftTreeSitter +import TreeSitterHy + +final class TreeSitterHyTests: XCTestCase { + func testCanLoadGrammar() throws { + let parser = Parser() + let language = Language(language: tree_sitter_hy()) + XCTAssertNoThrow(try parser.setLanguage(language), + "Error loading Hy grammar") + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b760c4b --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/evdunbar/tree-sitter-hy + +go 1.22 + +require github.com/tree-sitter/go-tree-sitter v0.24.0 diff --git a/grammar.js b/grammar.js new file mode 100644 index 0000000..88553f9 --- /dev/null +++ b/grammar.js @@ -0,0 +1,17 @@ +/** + * @file Hy grammar for tree-sitter + * @author E Dunbar + * @license MIT + */ + +/// +// @ts-check + +module.exports = grammar({ + name: "hy", + + rules: { + // TODO: add the actual grammar rules + source_file: $ => "hello" + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a77f81 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "tree-sitter-hy", + "version": "0.1.0", + "description": "Hy grammar for tree-sitter", + "repository": "https://github.com/evdunbar/tree-sitter-hy", + "license": "MIT", + "author": { + "name": "E Dunbar", + "email": "evdunbar@protonmail.com" + }, + "main": "bindings/node", + "types": "bindings/node", + "keywords": [ + "incremental", + "parsing", + "tree-sitter", + "hy" + ], + "files": [ + "grammar.js", + "tree-sitter.json", + "binding.gyp", + "prebuilds/**", + "bindings/node/*", + "queries/*", + "src/**", + "*.wasm" + ], + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + }, + "devDependencies": { + "prebuildify": "^6.0.1", + "tree-sitter-cli": "^0.25.3" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + }, + "scripts": { + "install": "node-gyp-build", + "prestart": "tree-sitter build --wasm", + "start": "tree-sitter playground", + "test": "node --test bindings/node/*_test.js" + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e409ed3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "tree-sitter-hy" +description = "Hy grammar for tree-sitter" +version = "0.1.0" +keywords = ["incremental", "parsing", "tree-sitter", "hy"] +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Compilers", + "Topic :: Text Processing :: Linguistic", + "Typing :: Typed", +] +authors = [{ name = "E Dunbar", email = "evdunbar@protonmail.com" }] +requires-python = ">=3.10" +license.text = "MIT" +readme = "README.md" + +[project.urls] +Homepage = "https://github.com/evdunbar/tree-sitter-hy" + +[project.optional-dependencies] +core = ["tree-sitter~=0.24"] + +[tool.cibuildwheel] +build = "cp310-*" +build-frontend = "build" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a865270 --- /dev/null +++ b/setup.py @@ -0,0 +1,77 @@ +from os import path +from platform import system +from sysconfig import get_config_var + +from setuptools import Extension, find_packages, setup +from setuptools.command.build import build +from setuptools.command.egg_info import egg_info +from wheel.bdist_wheel import bdist_wheel + +sources = [ + "bindings/python/tree_sitter_hy/binding.c", + "src/parser.c", +] +if path.exists("src/scanner.c"): + sources.append("src/scanner.c") + +macros: list[tuple[str, str | None]] = [ + ("PY_SSIZE_T_CLEAN", None), + ("TREE_SITTER_HIDE_SYMBOLS", None), +] +if limited_api := not get_config_var("Py_GIL_DISABLED"): + macros.append(("Py_LIMITED_API", "0x030A0000")) + +if system() != "Windows": + cflags = ["-std=c11", "-fvisibility=hidden"] +else: + cflags = ["/std:c11", "/utf-8"] + + +class Build(build): + def run(self): + if path.isdir("queries"): + dest = path.join(self.build_lib, "tree_sitter_hy", "queries") + self.copy_tree("queries", dest) + super().run() + + +class BdistWheel(bdist_wheel): + def get_tag(self): + python, abi, platform = super().get_tag() + if python.startswith("cp"): + python, abi = "cp310", "abi3" + return python, abi, platform + + +class EggInfo(egg_info): + def find_sources(self): + super().find_sources() + self.filelist.recursive_include("queries", "*.scm") + self.filelist.include("src/tree_sitter/*.h") + + +setup( + packages=find_packages("bindings/python"), + package_dir={"": "bindings/python"}, + package_data={ + "tree_sitter_hy": ["*.pyi", "py.typed"], + "tree_sitter_hy.queries": ["*.scm"], + }, + ext_package="tree_sitter_hy", + ext_modules=[ + Extension( + name="_binding", + sources=sources, + extra_compile_args=cflags, + define_macros=macros, + include_dirs=["src"], + py_limited_api=limited_api, + ) + ], + cmdclass={ + "build": Build, + "bdist_wheel": BdistWheel, + "egg_info": EggInfo, + }, + zip_safe=False +) diff --git a/tree-sitter.json b/tree-sitter.json new file mode 100644 index 0000000..f2d5c94 --- /dev/null +++ b/tree-sitter.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json", + "grammars": [ + { + "name": "hy", + "camelcase": "Hy", + "title": "Hy", + "scope": "source.hy", + "file-types": [ + "hy" + ], + "injection-regex": "^hy$", + "class-name": "TreeSitterHy" + } + ], + "metadata": { + "version": "0.1.0", + "license": "MIT", + "description": "Hy grammar for tree-sitter", + "authors": [ + { + "name": "E Dunbar", + "email": "evdunbar@protonmail.com" + } + ], + "links": { + "repository": "https://github.com/evdunbar/tree-sitter-hy" + } + }, + "bindings": { + "c": true, + "go": true, + "node": true, + "python": true, + "rust": true, + "swift": true, + "zig": false + } +} \ No newline at end of file