diff options
Diffstat (limited to 'src/syntax.zig')
| -rw-r--r-- | src/syntax.zig | 198 |
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; +} |
