summaryrefslogtreecommitdiff
path: root/src/syntax.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax.zig')
-rw-r--r--src/syntax.zig198
1 files changed, 129 insertions, 69 deletions
diff --git a/src/syntax.zig b/src/syntax.zig
index 43e370d5..a745b678 100644
--- a/src/syntax.zig
+++ b/src/syntax.zig
@@ -5,6 +5,7 @@
const std = @import("std");
const config = @import("pardes_config");
const tracy = @import("tracy.zig");
+const grammar_manifest = @import("grammar_manifest.zig");
pub const enabled = config.syntax_highlighting;
const zig_grammar = config.syntax_zig_grammar;
@@ -17,32 +18,7 @@ const ts = if (enabled) @import("tree-sitter") else struct {
};
const ts_queries = if (enabled) @import("ts_queries") else struct {};
-extern fn tree_sitter_ada() callconv(.c) *const ts.Language;
-extern fn tree_sitter_bash() callconv(.c) *const ts.Language;
-extern fn tree_sitter_c() callconv(.c) *const ts.Language;
-extern fn tree_sitter_c_sharp() callconv(.c) *const ts.Language;
-extern fn tree_sitter_clojure() callconv(.c) *const ts.Language;
-extern fn tree_sitter_cpp() callconv(.c) *const ts.Language;
-extern fn tree_sitter_css() callconv(.c) *const ts.Language;
-extern fn tree_sitter_elixir() callconv(.c) *const ts.Language;
-extern fn tree_sitter_erlang() callconv(.c) *const ts.Language;
-extern fn tree_sitter_fortran() callconv(.c) *const ts.Language;
-extern fn tree_sitter_go() callconv(.c) *const ts.Language;
-extern fn tree_sitter_haskell() callconv(.c) *const ts.Language;
-extern fn tree_sitter_html() callconv(.c) *const ts.Language;
-extern fn tree_sitter_java() callconv(.c) *const ts.Language;
-extern fn tree_sitter_javascript() callconv(.c) *const ts.Language;
-extern fn tree_sitter_json() callconv(.c) *const ts.Language;
-extern fn tree_sitter_kotlin() callconv(.c) *const ts.Language;
-extern fn tree_sitter_ocaml() callconv(.c) *const ts.Language;
-extern fn tree_sitter_pascal() callconv(.c) *const ts.Language;
-extern fn tree_sitter_php() callconv(.c) *const ts.Language;
-extern fn tree_sitter_powershell() callconv(.c) *const ts.Language;
-extern fn tree_sitter_python() callconv(.c) *const ts.Language;
-extern fn tree_sitter_ruby() callconv(.c) *const ts.Language;
-extern fn tree_sitter_rust() callconv(.c) *const ts.Language;
-extern fn tree_sitter_scala() callconv(.c) *const ts.Language;
-extern fn tree_sitter_zig() callconv(.c) *const ts.Language;
+const LanguageFn = *const fn () callconv(.c) *const ts.Language;
pub const Syn = enum(u8) { none, keyword, string, number, comment };
@@ -53,40 +29,115 @@ const Spec = struct {
compiled_query: ?*ts.Query = null,
};
-var zig_specs = if (zig_grammar) [_]Spec{
- .{ .exts = &.{ ".zig", ".zon" }, .language = tree_sitter_zig, .query_src = ts_queries.zig_highlights },
-} else [_]Spec{};
+fn grammarSelected(comptime g: grammar_manifest.Grammar) bool {
+ return switch (g.tier) {
+ .zig => zig_grammar,
+ .minimal => minimal_grammars,
+ .full => full_grammars,
+ };
+}
+
+fn specCount() comptime_int {
+ var count = 0;
+ for (grammar_manifest.all) |g| {
+ if (grammarSelected(g)) count += 1;
+ }
+ return count;
+}
+
+fn initSpecs() [specCount()]Spec {
+ var out: [specCount()]Spec = undefined;
+ var i = 0;
+ inline for (grammar_manifest.all) |g| {
+ if (grammarSelected(g)) {
+ out[i] = .{
+ .exts = g.exts,
+ .language = @extern(LanguageFn, .{ .name = "tree_sitter_" ++ g.name }),
+ .query_src = @field(ts_queries, g.name ++ "_highlights"),
+ };
+ i += 1;
+ }
+ }
+ return out;
+}
+
+var specs = initSpecs();
+
+const allocation_header_size = 16;
+var syntax_allocator: std.mem.Allocator = undefined;
+var syntax_started = false;
+
+extern fn ts_set_allocator(
+ new_malloc: ?*const fn (size: usize) callconv(.c) ?*anyopaque,
+ new_calloc: ?*const fn (nmemb: usize, size: usize) callconv(.c) ?*anyopaque,
+ new_realloc: ?*const fn (ptr: ?*anyopaque, size: usize) callconv(.c) ?*anyopaque,
+ new_free: ?*const fn (ptr: ?*anyopaque) callconv(.c) void,
+) void;
+
+fn syntaxAlloc(size_arg: usize) callconv(.c) ?*anyopaque {
+ const size = @max(size_arg, 1);
+ const total = std.math.add(usize, allocation_header_size, size) catch return null;
+ const bytes = syntax_allocator.alignedAlloc(u8, .@"16", total) catch return null;
+ const header: *align(16) usize = @ptrCast(bytes.ptr);
+ header.* = total;
+ return @ptrCast(bytes.ptr + allocation_header_size);
+}
+
+fn syntaxCalloc(count: usize, size: usize) callconv(.c) ?*anyopaque {
+ const len = std.math.mul(usize, count, size) catch return null;
+ const pointer = syntaxAlloc(len) orelse return null;
+ const bytes: [*]u8 = @ptrCast(pointer);
+ @memset(bytes[0..len], 0);
+ return pointer;
+}
+
+fn syntaxRealloc(ptr: ?*anyopaque, new_size: usize) callconv(.c) ?*anyopaque {
+ const pointer = ptr orelse return syntaxAlloc(new_size);
+ if (new_size == 0) {
+ syntaxFree(pointer);
+ return null;
+ }
+ const user: [*]u8 = @ptrCast(pointer);
+ const header: *align(16) usize = @ptrCast(@alignCast(user - allocation_header_size));
+ const old_total = header.*;
+ const old_bytes: []align(16) u8 = @as([*]align(16) u8, @ptrCast(header))[0..old_total];
+ const new_total = std.math.add(usize, allocation_header_size, new_size) catch return null;
+ const new_bytes = syntax_allocator.realloc(old_bytes, new_total) catch return null;
+ const new_header: *align(16) usize = @ptrCast(new_bytes.ptr);
+ new_header.* = new_total;
+ return @ptrCast(new_bytes.ptr + allocation_header_size);
+}
+
+fn syntaxFree(ptr: ?*anyopaque) callconv(.c) void {
+ const pointer = ptr orelse return;
+ const user: [*]u8 = @ptrCast(pointer);
+ const header: *align(16) usize = @ptrCast(@alignCast(user - allocation_header_size));
+ const total = header.*;
+ const bytes: []align(16) u8 = @as([*]align(16) u8, @ptrCast(header))[0..total];
+ syntax_allocator.free(bytes);
+}
-var minimal_specs = if (minimal_grammars) [_]Spec{
- .{ .exts = &.{ ".c", ".h" }, .language = tree_sitter_c, .query_src = ts_queries.c_highlights },
- .{ .exts = &.{ ".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx" }, .language = tree_sitter_cpp, .query_src = ts_queries.cpp_highlights },
-} else [_]Spec{};
+pub fn start(gpa: std.mem.Allocator) void {
+ if (comptime enabled) {
+ std.debug.assert(!syntax_started);
+ syntax_allocator = gpa;
+ syntax_started = true;
+ ts_set_allocator(syntaxAlloc, syntaxCalloc, syntaxRealloc, syntaxFree);
+ }
+}
-var full_specs = if (full_grammars) [_]Spec{
- .{ .exts = &.{ ".adb", ".ads", ".ada" }, .language = tree_sitter_ada, .query_src = ts_queries.ada_highlights },
- .{ .exts = &.{ ".sh", ".bash", ".zsh" }, .language = tree_sitter_bash, .query_src = ts_queries.bash_highlights },
- .{ .exts = &.{ ".cs", ".csx" }, .language = tree_sitter_c_sharp, .query_src = ts_queries.c_sharp_highlights },
- .{ .exts = &.{ ".clj", ".cljs", ".cljc", ".edn" }, .language = tree_sitter_clojure, .query_src = ts_queries.clojure_highlights },
- .{ .exts = &.{".css"}, .language = tree_sitter_css, .query_src = ts_queries.css_highlights },
- .{ .exts = &.{ ".ex", ".exs" }, .language = tree_sitter_elixir, .query_src = ts_queries.elixir_highlights },
- .{ .exts = &.{ ".erl", ".hrl" }, .language = tree_sitter_erlang, .query_src = ts_queries.erlang_highlights },
- .{ .exts = &.{ ".f", ".for", ".ftn", ".f90", ".f95", ".f03", ".f08" }, .language = tree_sitter_fortran, .query_src = ts_queries.fortran_highlights },
- .{ .exts = &.{".go"}, .language = tree_sitter_go, .query_src = ts_queries.go_highlights },
- .{ .exts = &.{ ".hs", ".lhs" }, .language = tree_sitter_haskell, .query_src = ts_queries.haskell_highlights },
- .{ .exts = &.{ ".html", ".htm" }, .language = tree_sitter_html, .query_src = ts_queries.html_highlights },
- .{ .exts = &.{".java"}, .language = tree_sitter_java, .query_src = ts_queries.java_highlights },
- .{ .exts = &.{ ".js", ".jsx", ".mjs", ".cjs" }, .language = tree_sitter_javascript, .query_src = ts_queries.javascript_highlights },
- .{ .exts = &.{".json"}, .language = tree_sitter_json, .query_src = ts_queries.json_highlights },
- .{ .exts = &.{ ".kt", ".kts" }, .language = tree_sitter_kotlin, .query_src = ts_queries.kotlin_highlights },
- .{ .exts = &.{ ".ml", ".mli" }, .language = tree_sitter_ocaml, .query_src = ts_queries.ocaml_highlights },
- .{ .exts = &.{ ".pas", ".pp", ".p" }, .language = tree_sitter_pascal, .query_src = ts_queries.pascal_highlights },
- .{ .exts = &.{ ".php", ".phtml", ".php3", ".php4", ".php5" }, .language = tree_sitter_php, .query_src = ts_queries.php_highlights },
- .{ .exts = &.{ ".ps1", ".psm1", ".psd1" }, .language = tree_sitter_powershell, .query_src = ts_queries.powershell_highlights },
- .{ .exts = &.{ ".py", ".pyw" }, .language = tree_sitter_python, .query_src = ts_queries.python_highlights },
- .{ .exts = &.{ ".rb", ".rake" }, .language = tree_sitter_ruby, .query_src = ts_queries.ruby_highlights },
- .{ .exts = &.{".rs"}, .language = tree_sitter_rust, .query_src = ts_queries.rust_highlights },
- .{ .exts = &.{ ".scala", ".sc" }, .language = tree_sitter_scala, .query_src = ts_queries.scala_highlights },
-} else [_]Spec{};
+pub fn stop() void {
+ if (comptime enabled) {
+ std.debug.assert(syntax_started);
+ for (&specs) |*spec| {
+ if (spec.compiled_query) |query| query.destroy();
+ spec.compiled_query = null;
+ }
+ ts_set_allocator(null, null, null, null);
+ syntax_started = false;
+ syntax_allocator = undefined;
+ }
+}
const Selected = struct { lang: *const ts.Language, query: *ts.Query };
@@ -102,17 +153,7 @@ fn ensure(spec: *Spec) !Selected {
}
fn forExt(ext: []const u8) !?Selected {
- for (&zig_specs) |*spec| {
- for (spec.exts) |choice| {
- if (std.ascii.eqlIgnoreCase(ext, choice)) return try ensure(spec);
- }
- }
- for (&minimal_specs) |*spec| {
- for (spec.exts) |choice| {
- if (std.ascii.eqlIgnoreCase(ext, choice)) return try ensure(spec);
- }
- }
- for (&full_specs) |*spec| {
+ for (&specs) |*spec| {
for (spec.exts) |choice| {
if (std.ascii.eqlIgnoreCase(ext, choice)) return try ensure(spec);
}
@@ -183,3 +224,22 @@ pub fn highlightFileRange(gpa: std.mem.Allocator, path: []const u8, content: []c
}
return styles;
}
+
+test "tree-sitter allocator callbacks preserve and free exact allocations" {
+ syntax_allocator = std.testing.allocator;
+ defer syntax_allocator = undefined;
+
+ var live: ?*anyopaque = syntaxCalloc(4, 1) orelse return error.OutOfMemory;
+ defer if (live) |pointer| syntaxFree(pointer);
+ const original: [*]u8 = @ptrCast(live.?);
+ try std.testing.expectEqualSlices(u8, &.{ 0, 0, 0, 0 }, original[0..4]);
+ @memcpy(original[0..4], "data");
+
+ live = syntaxRealloc(live, 32) orelse return error.OutOfMemory;
+ const grown: [*]u8 = @ptrCast(live.?);
+ try std.testing.expectEqualSlices(u8, "data", grown[0..4]);
+
+ try std.testing.expect(syntaxCalloc(std.math.maxInt(usize), 2) == null);
+ try std.testing.expect(syntaxRealloc(live, 0) == null);
+ live = null;
+}