From 053bbb8aade85be87318f052a2cfc5375f8235d8 Mon Sep 17 00:00:00 2001 From: Ronald Casili Date: Thu, 3 Jul 2025 18:43:43 +0800 Subject: [PATCH 1/4] restore and fix zig bindings Fixed and updated to be compatible zig v0.14.1 Changes: 1. use the latest commit of master branch 2. return anyopaque pointer for language() 3. move test on separate file 4. replace deprecated fields and tags 5. check if files exist before adding to the build In (1), the latest release of zig-tree-sitter does not yet use the enum literal for the package name, but the recent commits fixed it, so use the latest commit (b4b72c9) of the master branch for now until they make a new release. (2) and (3) fixes the error: `bindings/zig/root.zig:1:1: error: file exists in modules 'tree-sitter' and 'tree-sitter0'` Initially, I thought this is because the test module and tree-sitter-c module both uses bindings/zig/root.zig as the root_source_file. But even removing the test module, the error still persists when this project is used as a dependency on another project. The solution is to avoid exporting any references to zig-tree-sitter symbols, which could be conflicting to the user's own zig-tree-sitter dependency. (4) replaces the deprecated .C with .c, and addSharedLibrary/addStaticLibrary with addLibrary. Note that since this package depends on the zig-tree-sitter that still uses the removed deprecated tags and methods, this won't build with current dev versions of zig (i.e. writergate). (5) The zig files on tree-sitter-c is used as template for the project generator, and the some files or directories are optional and may not exist. So add a check first to avoid a failing build. --- bindings/zig/root.zig | 5 +++ bindings/zig/test.zig | 17 +++++++++ build.zig | 84 +++++++++++++++++++++++++++++++++++++++++++ build.zig.zon | 20 +++++++++++ tree-sitter.json | 3 +- 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 bindings/zig/root.zig create mode 100644 bindings/zig/test.zig create mode 100644 build.zig create mode 100644 build.zig.zon diff --git a/bindings/zig/root.zig b/bindings/zig/root.zig new file mode 100644 index 0000000..10f7299 --- /dev/null +++ b/bindings/zig/root.zig @@ -0,0 +1,5 @@ +extern fn tree_sitter_c() callconv(.c) *const anyopaque; + +pub fn language() *const anyopaque { + return tree_sitter_c(); +} diff --git a/bindings/zig/test.zig b/bindings/zig/test.zig new file mode 100644 index 0000000..4ef6162 --- /dev/null +++ b/bindings/zig/test.zig @@ -0,0 +1,17 @@ +const testing = @import("std").testing; + +const ts = @import("tree-sitter"); +const root = @import("tree-sitter-c"); +const Language = ts.Language; +const Parser = ts.Parser; + +test "can load grammar" { + const parser = Parser.create(); + defer parser.destroy(); + + const lang: *const ts.Language = @ptrCast(root.language()); + defer lang.destroy(); + + try testing.expectEqual(parser.setLanguage(lang), void{}); + try testing.expectEqual(parser.getLanguage(), lang); +} diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..c8c903b --- /dev/null +++ b/build.zig @@ -0,0 +1,84 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const shared = b.option(bool, "build-shared", "Build a shared library") orelse true; + const reuse_alloc = b.option(bool, "reuse-allocator", "Reuse the library allocator") orelse false; + + const library_name = "tree-sitter-c"; + + const lib: *std.Build.Step.Compile = b.addLibrary(.{ + .name = library_name, + .linkage = if (shared) .dynamic else .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = if (shared) true else null, + }), + }); + + lib.addCSourceFile(.{ + .file = b.path("src/parser.c"), + .flags = &.{"-std=c11"}, + }); + if (fileExists(b, "src/scanner.c")) { + lib.addCSourceFile(.{ + .file = b.path("src/scanner.c"), + .flags = &.{"-std=c11"}, + }); + } + + if (reuse_alloc) { + lib.root_module.addCMacro("TREE_SITTER_REUSE_ALLOCATOR", ""); + } + if (optimize == .Debug) { + lib.root_module.addCMacro("TREE_SITTER_DEBUG", ""); + } + + lib.addIncludePath(b.path("src")); + + b.installArtifact(lib); + b.installFile("src/node-types.json", "node-types.json"); + + if (fileExists(b, "queries")) { + b.installDirectory(.{ + .source_dir = b.path("queries"), + .install_dir = .prefix, + .install_subdir = "queries", + .include_extensions = &.{"scm"}, + }); + } + + const ts_dep = b.dependency("tree_sitter", .{}); + const ts_mod = ts_dep.module("tree-sitter"); + + const module = b.addModule(library_name, .{ + .root_source_file = b.path("bindings/zig/root.zig"), + .target = target, + .optimize = optimize, + }); + module.linkLibrary(lib); + + const tests = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("bindings/zig/test.zig"), + .target = target, + .optimize = optimize, + }), + }); + tests.root_module.addImport("tree-sitter", ts_mod); + tests.root_module.addImport(library_name, module); + + const run_tests = b.addRunArtifact(tests); + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_tests.step); +} + +inline fn fileExists(b: *std.Build, filename: []const u8) bool { + const dir = b.build_root.handle; + dir.access(filename, .{}) catch return false; + return true; +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..6679e85 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,20 @@ +.{ + .name = .tree_sitter_c, + .fingerprint = 0x14eb68f98c869b54, + .version = "0.24.0", + .dependencies = .{ + .tree_sitter = .{ + .url = "git+https://github.com/tree-sitter/zig-tree-sitter#b4b72c903e69998fc88e27e154a5e3cc9166551b", + .hash = "tree_sitter-0.25.0-8heIf51vAQConvVIgvm-9mVIbqh7yabZYqPXfOpS3YoG", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "bindings/zig", + "src", + "queries", + "LICENSE", + "README.md", + }, +} diff --git a/tree-sitter.json b/tree-sitter.json index 84bb3a7..69f6e07 100644 --- a/tree-sitter.json +++ b/tree-sitter.json @@ -38,6 +38,7 @@ "node": true, "python": true, "rust": true, - "swift": true + "swift": true, + "zig": true } } From 030fcacf7047ca191b2b10a8e91a4ea3bfaa8c42 Mon Sep 17 00:00:00 2001 From: Ronald Casili Date: Fri, 25 Jul 2025 15:54:12 +0800 Subject: [PATCH 2/4] Fix expectEqual arg order --- bindings/zig/test.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/zig/test.zig b/bindings/zig/test.zig index 4ef6162..b02c16f 100644 --- a/bindings/zig/test.zig +++ b/bindings/zig/test.zig @@ -12,6 +12,6 @@ test "can load grammar" { const lang: *const ts.Language = @ptrCast(root.language()); defer lang.destroy(); - try testing.expectEqual(parser.setLanguage(lang), void{}); - try testing.expectEqual(parser.getLanguage(), lang); + try testing.expectEqual(void{}, parser.setLanguage(lang)); + try testing.expectEqual(lang, parser.getLanguage()); } From 976d7858cde138f967035f94ba2038ffc533c11e Mon Sep 17 00:00:00 2001 From: Ronald Casili Date: Fri, 25 Jul 2025 15:55:21 +0800 Subject: [PATCH 3/4] Make tree-sitter a lazy dependency --- build.zig | 14 ++++++++++---- build.zig.zon | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index c8c903b..a6bca0b 100644 --- a/build.zig +++ b/build.zig @@ -52,9 +52,6 @@ pub fn build(b: *std.Build) !void { }); } - const ts_dep = b.dependency("tree_sitter", .{}); - const ts_mod = ts_dep.module("tree-sitter"); - const module = b.addModule(library_name, .{ .root_source_file = b.path("bindings/zig/root.zig"), .target = target, @@ -69,9 +66,18 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }), }); - tests.root_module.addImport("tree-sitter", ts_mod); tests.root_module.addImport(library_name, module); + var args = try std.process.argsWithAllocator(b.allocator); + defer args.deinit(); + while (args.next()) |a| { + if (std.mem.eql(u8, a, "test")) { + const ts_dep = b.lazyDependency("tree_sitter", .{}) orelse continue; + tests.root_module.addImport("tree-sitter", ts_dep.module("tree-sitter")); + break; + } + } + const run_tests = b.addRunArtifact(tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_tests.step); diff --git a/build.zig.zon b/build.zig.zon index 6679e85..bd690b9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,6 +6,7 @@ .tree_sitter = .{ .url = "git+https://github.com/tree-sitter/zig-tree-sitter#b4b72c903e69998fc88e27e154a5e3cc9166551b", .hash = "tree_sitter-0.25.0-8heIf51vAQConvVIgvm-9mVIbqh7yabZYqPXfOpS3YoG", + .lazy = true, }, }, .paths = .{ From 34501415cceb673459f2494d1d0603c00b748ec8 Mon Sep 17 00:00:00 2001 From: Ronald Casili Date: Thu, 28 Aug 2025 18:58:19 +0800 Subject: [PATCH 4/4] Improve zig dependency fetching logic Based on the commit https://github.com/tree-sitter/tree-sitter/commit/107bd800b02c8daf1f0f830bbeaf52749a79792c --- build.zig | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/build.zig b/build.zig index a6bca0b..911e3af 100644 --- a/build.zig +++ b/build.zig @@ -68,13 +68,16 @@ pub fn build(b: *std.Build) !void { }); tests.root_module.addImport(library_name, module); - var args = try std.process.argsWithAllocator(b.allocator); - defer args.deinit(); - while (args.next()) |a| { - if (std.mem.eql(u8, a, "test")) { - const ts_dep = b.lazyDependency("tree_sitter", .{}) orelse continue; - tests.root_module.addImport("tree-sitter", ts_dep.module("tree-sitter")); - break; + // HACK: fetch tree-sitter dependency only when testing this module + if (b.pkg_hash.len == 0) { + var args = try std.process.argsWithAllocator(b.allocator); + defer args.deinit(); + while (args.next()) |a| { + if (std.mem.eql(u8, a, "test")) { + const ts_dep = b.lazyDependency("tree_sitter", .{}) orelse continue; + tests.root_module.addImport("tree-sitter", ts_dep.module("tree-sitter")); + break; + } } }