From 308c8893d9706318e8e756069f40c1435a70df91 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Mon, 4 May 2026 16:31:51 +0200 Subject: feat(lib): return multiple errors --- src/parser.zig | 85 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 21 deletions(-) (limited to 'src/parser.zig') diff --git a/src/parser.zig b/src/parser.zig index 3dba456..e3eb5d5 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -25,33 +25,60 @@ pub const Error = error{FeatureNotSupported} || math.Error || Allocator.Error; -pub const Document = Element.Root; +/// Document represents a parsed typdown document. +pub const Document = struct { + /// Root of the document: used to render the document is other languages. + root: *Element.Root, + /// Errors got while parsing the document. + errors: ?[]DocError = null, -pub fn parseReader(parent: Allocator, r: *std.io.Reader) !*Document { + pub fn deinit(self: @This(), alloc: Allocator) void { + self.root.deinit(); + if (self.errors) |errors| alloc.free(errors); + } +}; + +/// DocError contains information about the error. +pub const DocError = struct { + /// Error returned. + err: Error, + /// Location of the error in the source. + location: struct { beg: usize, end: usize }, + + /// Extract the invalid content from the source. + pub fn extract(self: @This(), source: []const u8) []const u8 { + return source[self.location.beg..self.location.end]; + } +}; + +pub fn parseReader(parent: Allocator, r: *std.io.Reader) !Document { var l = try Lexer.initReader(parent, r); defer parent.free(l.iter.bytes); return gen(parent, &l); } -pub fn parse(parent: Allocator, content: []const u8) Error!*Document { +pub fn parse(parent: Allocator, content: []const u8) !Document { var l = try Lexer.init(content); return gen(parent, &l); } -fn gen(parent: Allocator, l: *Lexer) Error!*Document { - var root = try Document.init(parent); +fn gen(parent: Allocator, l: *Lexer) Allocator.Error!Document { + var root = try Element.Root.init(parent); errdefer root.deinit(); const alloc = root.allocator(); + var doc_errors = std.ArrayList(DocError).empty; + errdefer doc_errors.deinit(parent); base: while (l.peek()) |it| { - root.append(switch (it.kind) { + const beg = l.iter.i; + const v = switch (it.kind) { // other blocks - .title => try title.parse(alloc, l), - .list_ordored => try list.parseOrdored(alloc, l), - .list_unordored => try list.parseUnordored(alloc, l), - .image => try link.parseImage(alloc, l), - .code_block => try code.parse(alloc, l), - .quote => try quote.parse(alloc, l), - .math_block => try math.parse(alloc, l), + .title => title.parse(alloc, l), + .list_ordored => list.parseOrdored(alloc, l), + .list_unordored => list.parseUnordored(alloc, l), + .image => link.parseImage(alloc, l), + .code_block => code.parse(alloc, l), + .quote => quote.parse(alloc, l), + .math_block => math.parse(alloc, l), .weak_delimiter, .strong_delimiter => { l.consume(); continue :base; @@ -59,18 +86,32 @@ fn gen(parent: Allocator, l: *Lexer) Error!*Document { else => // block paragraph if (it.kind.isInParagraph()) - try paragraph.parse(alloc, l) + paragraph.parse(alloc, l) else - return Error.FeatureNotSupported, - }); + Error.FeatureNotSupported, + } catch |err| { + if (err == Error.OutOfMemory) return Error.OutOfMemory; + var end = l.iter.i; + if (beg == end) end += 1; + try doc_errors.append(parent, .{ .err = err, .location = .{ .beg = beg, .end = end } }); + _ = l.next(); + // consume until next delimiter + while (l.next()) |next| if (next.kind.isDelimiter()) continue :base; + break :base; + }; + root.append(v); } - return root; + return .{ .root = root, .errors = if (doc_errors.items.len == 0) null else try doc_errors.toOwnedSlice(parent) }; } fn doTest(alloc: Allocator, t: []const u8, v: []const u8) !void { const g = try parse(alloc, t); - defer g.deinit(); - const res = try g.renderHTML(alloc); + defer g.deinit(alloc); + if (g.errors) |errors| { + for (errors) |it| std.debug.print("{}: {s}\n", .{ it.err, it.extract(t) }); + try std.testing.expect(false); + } + const res = try g.root.renderHTML(alloc); defer alloc.free(res); std.testing.expect(std.mem.eql(u8, res, v)) catch |err| { std.debug.print("{s}\n", .{res}); @@ -82,6 +123,7 @@ test "parse multilines" { const alloc = std.testing.allocator; try doTest(alloc, "hello world", "

hello world

"); + try doTest(alloc, "# foo", "

foo

"); try doTest(alloc, \\hello \\world @@ -104,7 +146,8 @@ test "multiple render doc" { const alloc = arena.allocator(); const g = try parse(alloc, "hello *world*!"); - const a = try g.renderHTML(alloc); - const b = try g.renderHTML(alloc); + defer g.deinit(alloc); + const a = try g.root.renderHTML(alloc); + const b = try g.root.renderHTML(alloc); try std.testing.expect(std.mem.eql(u8, a, b)); } -- cgit v1.2.3