mymarkdown

My markdown
git clone https://git.grace.moe/mymarkdown
Log | Files | Refs

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:
Msrc/Ast.zig | 13+++++++++----
Msrc/AstGen3.zig | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/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" {