zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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 }