Document.zig (5303B) - Raw
1 //! An abstract tree representation of a Markdown document. 2 3 const std = @import("std"); 4 const builtin = @import("builtin"); 5 const assert = std.debug.assert; 6 const Allocator = std.mem.Allocator; 7 const Renderer = @import("renderer.zig").Renderer; 8 9 nodes: Node.List.Slice, 10 extra: []u32, 11 string_bytes: []u8, 12 13 const Document = @This(); 14 15 pub const Node = struct { 16 tag: Tag, 17 data: Data, 18 19 pub const Index = enum(u32) { 20 root = 0, 21 _, 22 }; 23 pub const List = std.MultiArrayList(Node); 24 25 pub const Tag = enum { 26 /// Data is `container`. 27 root, 28 29 // Blocks 30 /// Data is `list`. 31 list, 32 /// Data is `list_item`. 33 list_item, 34 /// Data is `container`. 35 table, 36 /// Data is `container`. 37 table_row, 38 /// Data is `table_cell`. 39 table_cell, 40 /// Data is `heading`. 41 heading, 42 /// Data is `code_block`. 43 code_block, 44 /// Data is `container`. 45 blockquote, 46 /// Data is `container`. 47 paragraph, 48 /// Data is `none`. 49 thematic_break, 50 51 // Inlines 52 /// Data is `link`. 53 link, 54 /// Data is `text`. 55 autolink, 56 /// Data is `link`. 57 image, 58 /// Data is `container`. 59 strong, 60 /// Data is `container`. 61 emphasis, 62 /// Data is `text`. 63 code_span, 64 /// Data is `text`. 65 text, 66 /// Data is `none`. 67 line_break, 68 }; 69 70 pub const Data = union { 71 none: void, 72 container: struct { 73 children: ExtraIndex, 74 }, 75 text: struct { 76 content: StringIndex, 77 }, 78 list: struct { 79 start: ListStart, 80 children: ExtraIndex, 81 }, 82 list_item: struct { 83 tight: bool, 84 children: ExtraIndex, 85 }, 86 table_cell: struct { 87 info: packed struct { 88 alignment: TableCellAlignment, 89 header: bool, 90 }, 91 children: ExtraIndex, 92 }, 93 heading: struct { 94 /// Between 1 and 6, inclusive. 95 level: u3, 96 children: ExtraIndex, 97 }, 98 code_block: struct { 99 tag: StringIndex, 100 content: StringIndex, 101 }, 102 link: struct { 103 target: StringIndex, 104 children: ExtraIndex, 105 }, 106 107 comptime { 108 // In Debug and ReleaseSafe builds, there may be hidden extra fields 109 // included for safety checks. Without such safety checks enabled, 110 // we always want this union to be 8 bytes. 111 if (builtin.mode != .Debug and builtin.mode != .ReleaseSafe) { 112 assert(@sizeOf(Data) == 8); 113 } 114 } 115 }; 116 117 /// The starting number of a list. This is either a number between 0 and 118 /// 999,999,999, inclusive, or `unordered` to indicate an unordered list. 119 pub const ListStart = enum(u30) { 120 // When https://github.com/ziglang/zig/issues/104 is implemented, this 121 // type can be more naturally expressed as ?u30. As it is, we want 122 // values to fit within 4 bytes, so ?u30 does not yet suffice for 123 // storage. 124 unordered = std.math.maxInt(u30), 125 _, 126 127 pub fn asNumber(start: ListStart) ?u30 { 128 if (start == .unordered) return null; 129 assert(@intFromEnum(start) <= 999_999_999); 130 return @intFromEnum(start); 131 } 132 }; 133 134 pub const TableCellAlignment = enum(u2) { 135 unset, 136 left, 137 center, 138 right, 139 }; 140 141 /// Trailing: `len` times `Node.Index` 142 pub const Children = struct { 143 len: u32, 144 }; 145 }; 146 147 pub const ExtraIndex = enum(u32) { _ }; 148 149 /// The index of a null-terminated string in `string_bytes`. 150 pub const StringIndex = enum(u32) { 151 empty = 0, 152 _, 153 }; 154 155 pub fn deinit(doc: *Document, allocator: Allocator) void { 156 doc.nodes.deinit(allocator); 157 allocator.free(doc.extra); 158 allocator.free(doc.string_bytes); 159 doc.* = undefined; 160 } 161 162 /// Renders a document directly to a writer using the default renderer. 163 pub fn render(doc: Document, writer: anytype) @TypeOf(writer).Error!void { 164 const renderer: Renderer(@TypeOf(writer), void) = .{ .context = {} }; 165 try renderer.render(doc, writer); 166 } 167 168 pub fn ExtraData(comptime T: type) type { 169 return struct { data: T, end: usize }; 170 } 171 172 pub fn extraData(doc: Document, comptime T: type, index: ExtraIndex) ExtraData(T) { 173 const fields = @typeInfo(T).@"struct".fields; 174 var i: usize = @intFromEnum(index); 175 var result: T = undefined; 176 inline for (fields) |field| { 177 @field(result, field.name) = switch (field.type) { 178 u32 => doc.extra[i], 179 else => @compileError("bad field type"), 180 }; 181 i += 1; 182 } 183 return .{ .data = result, .end = i }; 184 } 185 186 pub fn extraChildren(doc: Document, index: ExtraIndex) []const Node.Index { 187 const children = doc.extraData(Node.Children, index); 188 return @ptrCast(doc.extra[children.end..][0..children.data.len]); 189 } 190 191 pub fn string(doc: Document, index: StringIndex) [:0]const u8 { 192 const start = @intFromEnum(index); 193 return std.mem.span(@as([*:0]u8, @ptrCast(doc.string_bytes[start..].ptr))); 194 }