mymarkdown

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

commit 6b247bda5cc373436fa4e01b97d353b39610c2b8
parent 66c853fe0f3ec8fcd923fffab06e3b2432bda264
Author: gracefu <81774659+gracefuu@users.noreply.github.com>
Date:   Wed, 21 May 2025 04:06:53 +0800

Nest elaborations under the node it's elaborating on, and implement errors for it

Diffstat:
Msrc/Ast.zig | 6+++++-
Msrc/AstGen3.zig | 63+++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/test/test.zig | 90++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
3 files changed, 124 insertions(+), 35 deletions(-)

diff --git a/src/Ast.zig b/src/Ast.zig @@ -92,6 +92,8 @@ pub const Node = utils.Packed(union(Tag) { pub const Error = utils.Packed(union(enum(u8)) { marker_too_long: NodeError, unexpected_block_in_inline_context: NodeError, + elaboration_after_unelaboratable_node: NodeError, + incorrect_elaboration_marker: NodeError, invalid_marker: PointError, inconsistent_indentation: PointError, @@ -231,8 +233,10 @@ fn AstRenderer(Writer: type) type { ); }, // NodeError - .unexpected_block_in_inline_context, .marker_too_long, + .unexpected_block_in_inline_context, + .elaboration_after_unelaboratable_node, + .incorrect_elaboration_marker, => {}, } try renderer.writer.writeByte('\n'); diff --git a/src/AstGen3.zig b/src/AstGen3.zig @@ -266,7 +266,10 @@ inline fn parseColumnImpl( self: *AstGen, parent_idx: Node.Idx, ) !void { + // The type of list and the idx of the list node var current_list: ?struct { Node.Tag, Node.Idx } = null; + // The elaboratable node idx and the required length of the elaboration marker + var last_elaboratable_idx: ?struct { Node.Idx, usize } = null; parsing_blocks: while (self.scanner.peek()) |peek| { const alignment, const raw_line = peek; @@ -288,17 +291,18 @@ inline fn parseColumnImpl( 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| { - const marker_len = line.indexOfNotChar(m); - tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m)))); - const block_idx = try self.appendContainerNode(parent_idx, @enumFromInt(m), line.ptr); + // Headings + inline '#' => { + const marker_len = line.indexOfNotChar('#'); + tracy_frame.setName(@tagName(.heading)); + const block_idx = try self.appendContainerNode(parent_idx, .heading, line.ptr); _ = try self.appendLeafNode(block_idx, .marker, line.ptr, try castStrLen(marker_len, error.MarkerTooLong)); + last_elaboratable_idx = .{ block_idx, marker_len }; try self.scanner.indent(.{ .skip = line_start + marker_len }); try self.parseInlineColumn(block_idx); self.scanner.dedent(); @@ -307,26 +311,26 @@ inline fn parseColumnImpl( // List markers inline '-', '.', ':' => |m| { + const base_marker_len = line.indexOfNotChar(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) == ' ') + var potential_task_item = line.sliceOpen(base_marker_len).indexOfNone("[ ]xX"); + while (potential_task_item >= 3 and line.index(base_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], ' ')) + line.index(base_marker_len + potential_task_item - 1) == ']' and + line.index(base_marker_len + potential_task_item - 3) == '[' and + (line.index(base_marker_len + potential_task_item - 2) == ' ' or + line.index(base_marker_len + potential_task_item - 2) == 'x' or + line.index(base_marker_len + potential_task_item - 2) == 'X') and + std.mem.allEqual(u8, line.toUnpaddedSlice()[base_marker_len .. base_marker_len + potential_task_item - 3], ' ')) { tracy_frame.setName(@tagName(.task_item)); - break :blk .{ marker_len + potential_task_item, .task_item }; + break :blk .{ base_marker_len + potential_task_item, .task_item }; } } tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m)))); - break :blk .{ marker_len, @enumFromInt(m) }; + break :blk .{ base_marker_len, @enumFromInt(m) }; }; switch (list_type) { inline .unordered_item, .ordered_item, .term_item, .task_item => |tag| { @@ -341,6 +345,7 @@ inline fn parseColumnImpl( }; 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)); + last_elaboratable_idx = .{ block_idx, base_marker_len }; try self.scanner.indent(.{ .skip = line_start + marker_len }); try self.parseInlineColumn(block_idx); self.scanner.dedent(); @@ -350,11 +355,21 @@ inline fn parseColumnImpl( } }, - // Div-like repeatable markers - inline '+' => |m| { - tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m)))); - const marker_len = try castStrLen(line.indexOfNotChar(m), error.MarkerTooLong); - const block_idx = try self.appendContainerNode(parent_idx, @enumFromInt(m), line.ptr); + // Elaborations + inline '+' => { + tracy_frame.setName(@tagName(.elaboration)); + const marker_len = try castStrLen(line.indexOfNotChar('+'), error.MarkerTooLong); + const block_idx = if (last_elaboratable_idx) |elab| blk: { + const elaboratable_idx, const required_marker_len = elab; + const block_idx = try self.appendContainerNode(elaboratable_idx, .elaboration, line.ptr); + if (marker_len != required_marker_len) + try self.appendNodeError(.incorrect_elaboration_marker, elaboratable_idx); + break :blk block_idx; + } else blk: { + const block_idx = try self.appendContainerNode(parent_idx, .elaboration, line.ptr); + try self.appendNodeError(.elaboration_after_unelaboratable_node, block_idx); + break :blk block_idx; + }; _ = try self.appendLeafNode(block_idx, .marker, line.ptr, marker_len); try self.scanner.indent(.{ .skip = line_start + marker_len }); try self.parseColumn(block_idx); @@ -366,6 +381,7 @@ inline fn parseColumnImpl( inline ';' => |m| { tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m)))); const block_idx = try self.appendContainerNode(parent_idx, @enumFromInt(m), line.ptr); + last_elaboratable_idx = null; try self.scanner.indent(.{ .skip = line_start + 1 }); try self.parseInlineColumn(block_idx); self.scanner.dedent(); @@ -376,6 +392,7 @@ inline fn parseColumnImpl( inline '>' => |m| { tracy_frame.setName(@tagName(@as(Node.Tag, @enumFromInt(m)))); const block_idx = try self.appendContainerNode(parent_idx, @enumFromInt(m), line.ptr); + last_elaboratable_idx = null; try self.scanner.indent(.{ .skip = line_start + 1 }); try self.parseColumn(block_idx); self.scanner.dedent(); @@ -388,6 +405,7 @@ inline fn parseColumnImpl( if (after_stars.len == after_stars.indexOfNotSpaceOrTab()) { self.scanner.advance(); tracy_frame.setName(@tagName(.thematic_break)); + last_elaboratable_idx = null; break :block_idx try self.appendLeafNode(parent_idx, .thematic_break, line.ptr, 3); } } @@ -400,6 +418,7 @@ inline fn parseColumnImpl( { tracy_frame.setName(@tagName(.paragraph)); const paragraph_idx = try self.appendContainerNode(parent_idx, .paragraph, line.ptr); + last_elaboratable_idx = null; try self.insertTextLine(.text, .text, paragraph_idx, line); self.scanner.advance(); diff --git a/src/test/test.zig b/src/test/test.zig @@ -342,6 +342,9 @@ test "Happy path list elaboration" { \\+ bb \\ \\ ccc + \\+ dddd + \\## heading + \\++ subtitle \\ , \\.document @@ -351,15 +354,82 @@ test "Happy path list elaboration" { \\ "-" \\ .text \\ "a" + \\ .elaboration + \\ .marker + \\ "+" + \\ .paragraph + \\ .text + \\ "bb" + \\ .paragraph + \\ .text + \\ "ccc" + \\ .elaboration + \\ .marker + \\ "+" + \\ .paragraph + \\ .text + \\ "dddd" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "heading" + \\ .elaboration + \\ .marker + \\ "++" + \\ .paragraph + \\ .text + \\ "subtitle" + \\ + , parse3); +} + +test "Elaboration errors" { + try testParseWithFn( + \\- a + \\++ bb + \\-- ccc + \\+ dddd + \\paragraph + \\+ eeeee + \\ + , + \\.document + \\ .list + \\ .unordered_item + \\ .error .incorrect_elaboration_marker + \\ .marker + \\ "-" + \\ .text + \\ "a" + \\ .elaboration + \\ .marker + \\ "++" + \\ .paragraph + \\ .text + \\ "bb" + \\ .unordered_item + \\ .error .incorrect_elaboration_marker + \\ .marker + \\ "--" + \\ .text + \\ "ccc" + \\ .elaboration + \\ .marker + \\ "+" + \\ .paragraph + \\ .text + \\ "dddd" + \\ .paragraph + \\ .text + \\ "paragraph" \\ .elaboration + \\ .error .elaboration_after_unelaboratable_node \\ .marker \\ "+" \\ .paragraph \\ .text - \\ "bb" - \\ .paragraph - \\ .text - \\ "ccc" + \\ "eeeee" \\ , parse3); } @@ -398,14 +468,12 @@ test "Thematic break" { test "Mixed indentation" { // AstGen1 used to try very hard to recover when it sees a tab try testParseWithFn("" ++ - "+ aaa\n" ++ + "> aaa\n" ++ "\n" ++ "\tbbbbb\n", \\.document - \\ .elaboration + \\ .quote \\ .error .inconsistent_indentation at 3:1 - \\ .marker - \\ "+" \\ .paragraph \\ .text \\ "aaa" @@ -416,13 +484,11 @@ test "Mixed indentation" { , parse); try testParseWithFn("" ++ - "+ aaa\n" ++ + "> aaa\n" ++ "\n" ++ "\tbbbbb\n", \\.document - \\ .elaboration - \\ .marker - \\ "+" + \\ .quote \\ .paragraph \\ .text \\ "aaa"