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:
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"