const std = @import("std"); const cbor = @import("cbor"); const treez = @import("treez"); pub const tss = @import("ts_serializer.zig"); const verbose = false; pub fn main() anyerror!void { const allocator = std.heap.c_allocator; const args = try std.process.argsAlloc(allocator); var opt_output_file_path: ?[]const u8 = null; var i: usize = 1; while (i < args.len) : (i += 1) { const arg = args[i]; if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg}); opt_output_file_path = args[i]; } const output_file_path = opt_output_file_path orelse fatal("missing output file", .{}); var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| { fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) }); }; defer output_file.close(); var output = std.Io.Writer.Allocating.init(allocator); const writer = &output.writer; try cbor.writeMapHeader(writer, file_types.len); for (file_types) |file_type| { const lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}); const lang_abi = lang.getLanguageAbiVersion(); if (lang_abi < treez.TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) std.debug.panic("tree-sitter language '{s}' API is too old: {d} (our MIN API:{d})", .{ lang.getLanguageName(), lang_abi, treez.TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, }); if (lang_abi > treez.TREE_SITTER_LANGUAGE_VERSION) std.debug.panic("tree-sitter language '{s}' API is too new: {d} (our API:{d})", .{ lang.getLanguageName(), lang_abi, treez.TREE_SITTER_LANGUAGE_VERSION, }); try cbor.writeValue(writer, file_type.name); try cbor.writeMapHeader(writer, if (file_type.injections) |_| 3 else 2); var error_offset: u32 = 0; const highlights_in = treez.Query.create(lang, file_type.highlights, &error_offset) catch |e| switch (e) { error.InvalidLanguage => std.debug.panic("tree-sitter invalid language error: {s}", .{file_type.name}), else => |e_| std.debug.panic("tree-sitter failed to read highlights: {s} -> {t} at pos {d}", .{ file_type.name, e_, error_offset }), }; const ts_highlights_in: *tss.TSQuery = @ptrCast(@alignCast(highlights_in)); const highlights_cb = try tss.toCbor(ts_highlights_in, allocator); defer allocator.free(highlights_cb); try cbor.writeValue(writer, "highlights"); try cbor.writeValue(writer, highlights_cb); if (verbose) std.log.info("file_type {s} highlights {d} bytes", .{ file_type.name, highlights_cb.len }); const errors_in = try treez.Query.create(lang, "(ERROR) @error", &error_offset); const ts_errors_in: *tss.TSQuery = @ptrCast(@alignCast(errors_in)); const errors_cb = try tss.toCbor(ts_errors_in, allocator); defer allocator.free(errors_cb); try cbor.writeValue(writer, "errors"); try cbor.writeValue(writer, errors_cb); if (verbose) std.log.info("file_type {s} errors {d} bytes", .{ file_type.name, errors_cb.len }); if (file_type.injections) |injections| { const injections_in = treez.Query.create(lang, injections, &error_offset) catch |e| switch (e) { error.InvalidLanguage => std.debug.panic("tree-sitter invalid language error: {s}", .{file_type.name}), else => |e_| std.debug.panic("tree-sitter failed to read injections: {s} -> {t} at pos {d}", .{ file_type.name, e_, error_offset }), }; const ts_injections_in: *tss.TSQuery = @ptrCast(@alignCast(injections_in)); const injections_cb = try tss.toCbor(ts_injections_in, allocator); defer allocator.free(injections_cb); try cbor.writeValue(writer, "injections"); try cbor.writeValue(writer, injections_cb); if (verbose) std.log.info("file_type {s} injections {d} bytes", .{ file_type.name, injections_cb.len }); } } try output_file.writeAll(output.written()); if (verbose) std.log.info("file_types total {d} bytes", .{output.written().len}); } fn fatal(comptime format: []const u8, args: anytype) noreturn { std.debug.print(format, args); std.process.exit(1); } pub const file_types = load_file_types(@import("file_types.zig")); const FileType = struct { name: []const u8, lang_fn: LangFn, highlights: [:0]const u8, injections: ?[:0]const u8, }; const LangFn = *const fn () callconv(.c) ?*const treez.Language; fn load_file_types(comptime Namespace: type) []const FileType { comptime switch (@typeInfo(Namespace)) { .@"struct" => |info| { var count = 0; for (info.decls) |_| count += 1; var construct_types: [count]FileType = undefined; var i = 0; for (info.decls) |decl| { const lang = decl.name; const args = @field(Namespace, lang); construct_types[i] = .{ .name = lang, .lang_fn = if (@hasField(@TypeOf(args), "parser")) args.parser else get_parser(lang), .highlights = if (@hasField(@TypeOf(args), "highlights")) @embedFile(args.highlights) else if (@hasField(@TypeOf(args), "highlights_list")) @embedFile(args.highlights_list[0]) ++ "\n" ++ @embedFile(args.highlights_list[1]) else @embedFile("tree-sitter-" ++ lang ++ "/queries/highlights.scm"), .injections = if (@hasField(@TypeOf(args), "injections")) @embedFile(args.injections) else null, }; i += 1; } const types = construct_types; return &types; }, else => @compileError("expected tuple or struct type"), }; } fn get_parser(comptime lang: []const u8) LangFn { const language_name = ft_func_name(lang); return @extern(?LangFn, .{ .name = "tree_sitter_" ++ language_name }) orelse @compileError(std.fmt.comptimePrint("Cannot find extern tree_sitter_{s}", .{language_name})); } fn ft_func_name(comptime lang: []const u8) []const u8 { var transform: [lang.len]u8 = undefined; for (lang, 0..) |c, i| transform[i] = if (c == '-') '_' else c; const func_name = transform; return &func_name; }