const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const SplitIterator = std.mem.SplitIterator; const DelimiterType = std.mem.DelimiterType; const ezts = @import("easy-zig-tree-sitter"); const ts = @import("tree-sitter"); const parser = @import("../root.zig"); const Parser = parser.Parser; pub const SyntaxTagError = error{InvalidLanguage}; pub const Syntax: parser.ParserTag = .{ .name = "syntax", .parse_fn = &parse, }; fn parse( arena: *ArenaAllocator, io: Io, state: *Parser, reader: *Io.Reader, writer: *Io.Writer, attrs: *SplitIterator(u8, DelimiterType.scalar), ) anyerror!void { _ = state; _ = io; const raw_lang_attr: []const u8 = while (attrs.next()) |attr| { var it = std.mem.splitScalar(u8, attr, '='); if (std.mem.eql(u8, it.first(), "lang")) { break it.next() orelse ""; } } else ""; const lang_key = try arena.allocator().dupe(u8, raw_lang_attr); _ = try reader.takeDelimiterInclusive('\n'); const first_line = try reader.peekDelimiterInclusive('\n'); const whitespace_chr = for (first_line, 0..) |chr, idx| { if (!(chr == ' ' or chr == '\t')) break idx; } else 0; var code = try std.ArrayList(u8).initCapacity(arena.allocator(), 1024); while (true) { const line = try reader.takeDelimiterInclusive('\n'); if (std.mem.indexOf(u8, line, "")) |idx| { try code.appendSlice(arena.allocator(), line[0..idx]); // TODO: tags at the end will break the parser break; } const whitespace_lem = @min(line.len, whitespace_chr); if (std.mem.countScalar(u8, line[0..whitespace_lem], ' ') + std.mem.countScalar(u8, line[0..whitespace_lem], '\t') < whitespace_lem) { try code.appendSlice(arena.allocator(), line); } else { try code.appendSlice(arena.allocator(), line[whitespace_lem..line.len]); } } // Alignment issues? // ts.setAllocator(arena.allocator()); // TODO: TS Thread Mutex // TODO: null handeling var hlp = HLParser.create(lang_key, code.items) orelse { std.log.warn("Failed to make Parser for {s}", .{lang_key}); return; }; defer hlp.destroy(); var next_hl = hlp.nextHL(); var end_stack = try std.ArrayList(u32).initCapacity(arena.allocator(), 1024); var code_idx: u32 = 0; try writer.writeAll("
");

    while (code_idx != code.items.len) {
        var next_idx: u32 = @intCast(code.items.len);
        if (end_stack.getLastOrNull()) |last| {
            if (last == code_idx) {
                try writer.writeAll("");
                _ = end_stack.pop();
                continue;
            } else if (last < next_idx) {
                next_idx = last;
            }
        }
        if (next_hl) |hl| {
            if (hl.start == code_idx) {
                try writer.writeAll("");
                try end_stack.append(arena.allocator(), hl.end);
                next_hl = hlp.nextHL();
                continue;
            } else if (hl.start < next_idx) {
                next_idx = hl.start;
            }
        }
        try writer.writeAll(code.items[code_idx..next_idx]);
        code_idx = next_idx;
    }

    try writer.writeAll("
"); } const HLGroup = struct { name: []const u8, start: u32, end: u32, }; const HLParser = struct { lang: *ts.Language, tree: *ts.Tree, query: *ts.Query, cursor: *ts.QueryCursor, fn create(lang_key: []const u8, code: []const u8) ?HLParser { const lang = ezts.getLang(lang_key) orelse return null; const query_string = ezts.getQuery(lang_key, "highlights") orelse return null; const ps = ts.Parser.create(); ps.setLanguage(lang) catch return null; const tree = ps.parseString(code, null) orelse return null; var error_offset: u32 = 0; const query = ts.Query.create(lang, query_string, &error_offset) catch return null; const cursor = ts.QueryCursor.create(); cursor.exec(query, tree.rootNode()); return .{ .lang = lang, .tree = tree, .query = query, .cursor = cursor, }; } fn destroy(self: *HLParser) void { self.cursor.destroy(); self.query.destroy(); self.tree.destroy(); self.lang.destroy(); } fn nextHL(self: *HLParser) ?HLGroup { if (self.cursor.nextCapture()) |data| { const idx, const match = data; const c = match.captures[idx]; return .{ .start = c.node.startByte(), .end = c.node.endByte(), .name = self.query.captureNameForId(c.index) orelse return null, }; } else return null; } };