mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
The compiler actually doesn't need any functional changes for this: Sema does reification based on the tag indices of `std.builtin.Type` already! So, no zig1.wasm update is necessary. This change is necessary to disallow name clashes between fields and decls on a type, which is a prerequisite of #9938.
195 lines
5.2 KiB
Zig
195 lines
5.2 KiB
Zig
//! An abstract tree representation of a Markdown document.
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const Renderer = @import("renderer.zig").Renderer;
|
|
|
|
nodes: Node.List.Slice,
|
|
extra: []u32,
|
|
string_bytes: []u8,
|
|
|
|
const Document = @This();
|
|
|
|
pub const Node = struct {
|
|
tag: Tag,
|
|
data: Data,
|
|
|
|
pub const Index = enum(u32) {
|
|
root = 0,
|
|
_,
|
|
};
|
|
pub const List = std.MultiArrayList(Node);
|
|
|
|
pub const Tag = enum {
|
|
/// Data is `container`.
|
|
root,
|
|
|
|
// Blocks
|
|
/// Data is `list`.
|
|
list,
|
|
/// Data is `list_item`.
|
|
list_item,
|
|
/// Data is `container`.
|
|
table,
|
|
/// Data is `container`.
|
|
table_row,
|
|
/// Data is `table_cell`.
|
|
table_cell,
|
|
/// Data is `heading`.
|
|
heading,
|
|
/// Data is `code_block`.
|
|
code_block,
|
|
/// Data is `container`.
|
|
blockquote,
|
|
/// Data is `container`.
|
|
paragraph,
|
|
/// Data is `none`.
|
|
thematic_break,
|
|
|
|
// Inlines
|
|
/// Data is `link`.
|
|
link,
|
|
/// Data is `text`.
|
|
autolink,
|
|
/// Data is `link`.
|
|
image,
|
|
/// Data is `container`.
|
|
strong,
|
|
/// Data is `container`.
|
|
emphasis,
|
|
/// Data is `text`.
|
|
code_span,
|
|
/// Data is `text`.
|
|
text,
|
|
/// Data is `none`.
|
|
line_break,
|
|
};
|
|
|
|
pub const Data = union {
|
|
none: void,
|
|
container: struct {
|
|
children: ExtraIndex,
|
|
},
|
|
text: struct {
|
|
content: StringIndex,
|
|
},
|
|
list: struct {
|
|
start: ListStart,
|
|
children: ExtraIndex,
|
|
},
|
|
list_item: struct {
|
|
tight: bool,
|
|
children: ExtraIndex,
|
|
},
|
|
table_cell: struct {
|
|
info: packed struct {
|
|
alignment: TableCellAlignment,
|
|
header: bool,
|
|
},
|
|
children: ExtraIndex,
|
|
},
|
|
heading: struct {
|
|
/// Between 1 and 6, inclusive.
|
|
level: u3,
|
|
children: ExtraIndex,
|
|
},
|
|
code_block: struct {
|
|
tag: StringIndex,
|
|
content: StringIndex,
|
|
},
|
|
link: struct {
|
|
target: StringIndex,
|
|
children: ExtraIndex,
|
|
},
|
|
|
|
comptime {
|
|
// In Debug and ReleaseSafe builds, there may be hidden extra fields
|
|
// included for safety checks. Without such safety checks enabled,
|
|
// we always want this union to be 8 bytes.
|
|
if (builtin.mode != .Debug and builtin.mode != .ReleaseSafe) {
|
|
assert(@sizeOf(Data) == 8);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// The starting number of a list. This is either a number between 0 and
|
|
/// 999,999,999, inclusive, or `unordered` to indicate an unordered list.
|
|
pub const ListStart = enum(u30) {
|
|
// When https://github.com/ziglang/zig/issues/104 is implemented, this
|
|
// type can be more naturally expressed as ?u30. As it is, we want
|
|
// values to fit within 4 bytes, so ?u30 does not yet suffice for
|
|
// storage.
|
|
unordered = std.math.maxInt(u30),
|
|
_,
|
|
|
|
pub fn asNumber(start: ListStart) ?u30 {
|
|
if (start == .unordered) return null;
|
|
assert(@intFromEnum(start) <= 999_999_999);
|
|
return @intFromEnum(start);
|
|
}
|
|
};
|
|
|
|
pub const TableCellAlignment = enum(u2) {
|
|
unset,
|
|
left,
|
|
center,
|
|
right,
|
|
};
|
|
|
|
/// Trailing: `len` times `Node.Index`
|
|
pub const Children = struct {
|
|
len: u32,
|
|
};
|
|
};
|
|
|
|
pub const ExtraIndex = enum(u32) { _ };
|
|
|
|
/// The index of a null-terminated string in `string_bytes`.
|
|
pub const StringIndex = enum(u32) {
|
|
empty = 0,
|
|
_,
|
|
};
|
|
|
|
pub fn deinit(doc: *Document, allocator: Allocator) void {
|
|
doc.nodes.deinit(allocator);
|
|
allocator.free(doc.extra);
|
|
allocator.free(doc.string_bytes);
|
|
doc.* = undefined;
|
|
}
|
|
|
|
/// Renders a document directly to a writer using the default renderer.
|
|
pub fn render(doc: Document, writer: anytype) @TypeOf(writer).Error!void {
|
|
const renderer: Renderer(@TypeOf(writer), void) = .{ .context = {} };
|
|
try renderer.render(doc, writer);
|
|
}
|
|
|
|
pub fn ExtraData(comptime T: type) type {
|
|
return struct { data: T, end: usize };
|
|
}
|
|
|
|
pub fn extraData(doc: Document, comptime T: type, index: ExtraIndex) ExtraData(T) {
|
|
const fields = @typeInfo(T).@"struct".fields;
|
|
var i: usize = @intFromEnum(index);
|
|
var result: T = undefined;
|
|
inline for (fields) |field| {
|
|
@field(result, field.name) = switch (field.type) {
|
|
u32 => doc.extra[i],
|
|
else => @compileError("bad field type"),
|
|
};
|
|
i += 1;
|
|
}
|
|
return .{ .data = result, .end = i };
|
|
}
|
|
|
|
pub fn extraChildren(doc: Document, index: ExtraIndex) []const Node.Index {
|
|
const children = doc.extraData(Node.Children, index);
|
|
return @ptrCast(doc.extra[children.end..][0..children.data.len]);
|
|
}
|
|
|
|
pub fn string(doc: Document, index: StringIndex) [:0]const u8 {
|
|
const start = @intFromEnum(index);
|
|
return std.mem.span(@as([*:0]u8, @ptrCast(doc.string_bytes[start..].ptr)));
|
|
}
|