commit 66c853fe0f3ec8fcd923fffab06e3b2432bda264
parent dd9e881dbd3f92d3f2274e3cdc5616146463a393
Author: gracefu <81774659+gracefuu@users.noreply.github.com>
Date: Wed, 21 May 2025 03:42:02 +0800
Group list items into lists
Diffstat:
| M | src/Ast.zig | | | 13 | +++++++++---- |
| M | src/AstGen3.zig | | | 76 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
| M | src/test/test.zig | | | 164 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
3 files changed, 179 insertions(+), 74 deletions(-)
diff --git a/src/Ast.zig b/src/Ast.zig
@@ -22,24 +22,28 @@ pub const Tag = enum(u8) {
document = 255,
marker = 254, // First child of nodes like heading, list items, ...
- thematic_break = 253,
+ list = 253,
+
+ thematic_break = 252,
heading = '#',
quote = '>',
paragraph = ';',
unordered_item = '-',
ordered_item = '.',
term_item = ':',
- task_item = 252,
+ task_item = 251,
elaboration = '+',
- text = 251,
- space_text = 250,
+ text = 250,
+ space_text = 249,
};
pub const Node = utils.Packed(union(Tag) {
document: Root,
marker: Leaf, // First child of nodes like heading, list items, ...
+ list: Container,
+
thematic_break: Leaf,
heading: Container,
quote: Container,
@@ -249,6 +253,7 @@ fn AstRenderer(Writer: type) type {
return .{ cur_node, cur_error };
},
+ .list,
.heading,
.quote,
.paragraph,
diff --git a/src/AstGen3.zig b/src/AstGen3.zig
@@ -266,6 +266,8 @@ inline fn parseColumnImpl(
self: *AstGen,
parent_idx: Node.Idx,
) !void {
+ var current_list: ?struct { Node.Tag, Node.Idx } = null;
+
parsing_blocks: while (self.scanner.peek()) |peek| {
const alignment, const raw_line = peek;
@@ -284,31 +286,16 @@ inline fn parseColumnImpl(
defer tracy_frame.end();
// Parse one block and get its id so we can attach an error to it if the block was misaligned.
const block_idx: Node.Idx = block_idx: {
+ // Reset list if any other block type appears
+ switch (line.index(0)) {
+ '-', '.', ':' => {},
+ else => current_list = null,
+ }
+
switch (line.index(0)) {
// Par-like repeatable markers
- inline '-', '.', ':', '#' => |m| {
+ inline '#' => |m| {
const marker_len = line.indexOfNotChar(m);
- if (comptime m == '-') {
- var potential_task_item = line.sliceOpen(marker_len).indexOfNone("[ ]xX");
- while (potential_task_item >= 3 and line.index(marker_len + potential_task_item - 1) == ' ')
- potential_task_item -= 1;
- if (potential_task_item >= 3 and
- line.index(marker_len + potential_task_item - 1) == ']' and
- line.index(marker_len + potential_task_item - 3) == '[' and
- (line.index(marker_len + potential_task_item - 2) == ' ' or
- line.index(marker_len + potential_task_item - 2) == 'x' or
- line.index(marker_len + potential_task_item - 2) == 'X') and
- std.mem.allEqual(u8, line.toUnpaddedSlice()[marker_len .. marker_len + potential_task_item - 3], ' '))
- {
- tracy_frame.setName(@tagName(.task_item));
- const block_idx = try self.appendContainerNode(parent_idx, .task_item, line.ptr);
- _ = try self.appendLeafNode(block_idx, .marker, line.ptr, try castStrLen(marker_len + potential_task_item, error.MarkerTooLong));
- try self.scanner.indent(.{ .skip = line_start + marker_len + potential_task_item });
- try self.parseInlineColumn(block_idx);
- self.scanner.dedent();
- break :block_idx block_idx;
- }
- }
tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m))));
const block_idx = try self.appendContainerNode(parent_idx, @enumFromInt(m), line.ptr);
_ = try self.appendLeafNode(block_idx, .marker, line.ptr, try castStrLen(marker_len, error.MarkerTooLong));
@@ -318,6 +305,51 @@ inline fn parseColumnImpl(
break :block_idx block_idx;
},
+ // List markers
+ inline '-', '.', ':' => |m| {
+ const marker_len, const list_type: Node.Tag = blk: {
+ const marker_len = line.indexOfNotChar(m);
+ if (comptime m == '-') {
+ var potential_task_item = line.sliceOpen(marker_len).indexOfNone("[ ]xX");
+ while (potential_task_item >= 3 and line.index(marker_len + potential_task_item - 1) == ' ')
+ potential_task_item -= 1;
+ if (potential_task_item >= 3 and
+ line.index(marker_len + potential_task_item - 1) == ']' and
+ line.index(marker_len + potential_task_item - 3) == '[' and
+ (line.index(marker_len + potential_task_item - 2) == ' ' or
+ line.index(marker_len + potential_task_item - 2) == 'x' or
+ line.index(marker_len + potential_task_item - 2) == 'X') and
+ std.mem.allEqual(u8, line.toUnpaddedSlice()[marker_len .. marker_len + potential_task_item - 3], ' '))
+ {
+ tracy_frame.setName(@tagName(.task_item));
+ break :blk .{ marker_len + potential_task_item, .task_item };
+ }
+ }
+ tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m))));
+ break :blk .{ marker_len, @enumFromInt(m) };
+ };
+ switch (list_type) {
+ inline .unordered_item, .ordered_item, .term_item, .task_item => |tag| {
+ const list_node_idx = blk: {
+ if (current_list) |cur| {
+ const cur_tag, const cur_node_idx = cur;
+ if (tag == cur_tag) break :blk cur_node_idx;
+ }
+ const list_node_idx = try self.appendContainerNode(parent_idx, .list, line.ptr);
+ current_list = .{ tag, list_node_idx };
+ break :blk list_node_idx;
+ };
+ const block_idx = try self.appendContainerNode(list_node_idx, tag, line.ptr);
+ _ = try self.appendLeafNode(block_idx, .marker, line.ptr, try castStrLen(marker_len, error.MarkerTooLong));
+ try self.scanner.indent(.{ .skip = line_start + marker_len });
+ try self.parseInlineColumn(block_idx);
+ self.scanner.dedent();
+ break :block_idx block_idx;
+ },
+ else => unreachable,
+ }
+ },
+
// Div-like repeatable markers
inline '+' => |m| {
tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m))));
diff --git a/src/test/test.zig b/src/test/test.zig
@@ -213,7 +213,7 @@ test "Happy path quote" {
}
test "Happy path list" {
- try testParse(
+ try testParseWithFn(
\\- text
\\- [ ] text
\\. text
@@ -225,52 +225,119 @@ test "Happy path list" {
\\
,
\\.document
- \\ .unordered_item
- \\ .marker
- \\ "-"
- \\ .text
- \\ "text"
- \\ .task_item
- \\ .marker
- \\ "- [ ]"
- \\ .text
- \\ "text"
- \\ .ordered_item
- \\ .marker
- \\ "."
- \\ .text
- \\ "text"
- \\ .term_item
- \\ .marker
- \\ ":"
- \\ .text
- \\ "text"
- \\ .unordered_item
- \\ .marker
- \\ "--"
- \\ .text
- \\ "text"
- \\ .task_item
- \\ .marker
- \\ "-- [ ]"
- \\ .text
- \\ "text"
- \\ .ordered_item
- \\ .marker
- \\ ".."
- \\ .text
- \\ "text"
- \\ .term_item
- \\ .marker
- \\ "::"
+ \\ .list
+ \\ .unordered_item
+ \\ .marker
+ \\ "-"
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .task_item
+ \\ .marker
+ \\ "- [ ]"
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .ordered_item
+ \\ .marker
+ \\ "."
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .term_item
+ \\ .marker
+ \\ ":"
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .unordered_item
+ \\ .marker
+ \\ "--"
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .task_item
+ \\ .marker
+ \\ "-- [ ]"
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .ordered_item
+ \\ .marker
+ \\ ".."
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .term_item
+ \\ .marker
+ \\ "::"
+ \\ .text
+ \\ "text"
+ \\
+ , parse3);
+}
+
+test "List grouping" {
+ try testParseWithFn(
+ \\- text
+ \\- text
+ \\-- text
+ \\- text
+ \\
+ \\- text
+ \\paragraph
+ \\- text
+ \\. text
+ \\
+ ,
+ \\.document
+ \\ .list
+ \\ .unordered_item
+ \\ .marker
+ \\ "-"
+ \\ .text
+ \\ "text"
+ \\ .unordered_item
+ \\ .marker
+ \\ "-"
+ \\ .text
+ \\ "text"
+ \\ .unordered_item
+ \\ .marker
+ \\ "--"
+ \\ .text
+ \\ "text"
+ \\ .unordered_item
+ \\ .marker
+ \\ "-"
+ \\ .text
+ \\ "text"
+ \\ .unordered_item
+ \\ .marker
+ \\ "-"
+ \\ .text
+ \\ "text"
+ \\ .paragraph
\\ .text
- \\ "text"
+ \\ "paragraph"
+ \\ .list
+ \\ .unordered_item
+ \\ .marker
+ \\ "-"
+ \\ .text
+ \\ "text"
+ \\ .list
+ \\ .ordered_item
+ \\ .marker
+ \\ "."
+ \\ .text
+ \\ "text"
\\
- );
+ , parse3);
}
test "Happy path list elaboration" {
- try testParse(
+ try testParseWithFn(
\\- a
\\+ bb
\\
@@ -278,11 +345,12 @@ test "Happy path list elaboration" {
\\
,
\\.document
- \\ .unordered_item
- \\ .marker
- \\ "-"
- \\ .text
- \\ "a"
+ \\ .list
+ \\ .unordered_item
+ \\ .marker
+ \\ "-"
+ \\ .text
+ \\ "a"
\\ .elaboration
\\ .marker
\\ "+"
@@ -293,7 +361,7 @@ test "Happy path list elaboration" {
\\ .text
\\ "ccc"
\\
- );
+ , parse3);
}
test "Thematic break" {