master
  1//! An abstract tree representation of a Markdown document.
  2
  3const std = @import("std");
  4const builtin = @import("builtin");
  5const assert = std.debug.assert;
  6const Allocator = std.mem.Allocator;
  7const Renderer = @import("renderer.zig").Renderer;
  8
  9nodes: Node.List.Slice,
 10extra: []u32,
 11string_bytes: []u8,
 12
 13const Document = @This();
 14
 15pub 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
147pub const ExtraIndex = enum(u32) { _ };
148
149/// The index of a null-terminated string in `string_bytes`.
150pub const StringIndex = enum(u32) {
151    empty = 0,
152    _,
153};
154
155pub 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.
163pub 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
168pub fn ExtraData(comptime T: type) type {
169    return struct { data: T, end: usize };
170}
171
172pub 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
186pub 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
191pub 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}