aboutsummaryrefslogtreecommitdiff
path: root/src/parser.zig
diff options
context:
space:
mode:
authorAnhgelus Morhtuuzh <william@herges.fr>2026-05-04 16:31:51 +0200
committerAnhgelus Morhtuuzh <william@herges.fr>2026-05-04 16:31:51 +0200
commit308c8893d9706318e8e756069f40c1435a70df91 (patch)
tree5dd53a179f3b76d5638d300a3a9713fa37a42275 /src/parser.zig
parent4d151a25403e734ab3724d3bda21e29c396cd147 (diff)
feat(lib): return multiple errors
Diffstat (limited to 'src/parser.zig')
-rw-r--r--src/parser.zig85
1 files changed, 64 insertions, 21 deletions
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", "<p>hello world</p>");
+ try doTest(alloc, "# foo", "<h1>foo</h1>");
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));
}