aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/parser.zig85
-rw-r--r--src/root.zig86
2 files changed, 121 insertions, 50 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));
}
diff --git a/src/root.zig b/src/root.zig
index 3a7c134..30386e9 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -2,12 +2,14 @@ const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const parser = @import("parser.zig");
+const Element = @import("eval/Element.zig");
pub const Document = parser.Document;
pub const Error = parser.Error;
/// Parse the content.
///
/// Use typdown_parse if you are not in Zig.
pub const parse = parser.parse;
+pub const parseReader = parser.parseReader;
inline fn getErrorCode(err: Error) u8 {
return switch (err) {
@@ -43,6 +45,40 @@ export fn typdown_getErrorString(code: u8) [*:0]const u8 {
};
}
+/// typdown_Document is a Document for the C ABI.
+const typdown_Document = extern struct {
+ root: ?*anyopaque = null,
+ errors: ?[*]typdown_Error = null,
+ errors_len: usize = 0,
+
+ const typdown_Error = extern struct {
+ code: u8,
+ location: extern struct { beg: usize, end: usize },
+ };
+};
+
+fn from(alloc: Allocator, doc: Document) typdown_Document {
+ var res = typdown_Document{ .root = doc.root };
+ if (doc.errors) |errors| {
+ defer alloc.free(errors);
+ res.errors = (alloc.alloc(typdown_Document.typdown_Error, errors.len) catch |err| @panic(@errorName(err))).ptr;
+ for (errors) |err| {
+ res.errors.?[0] = .{
+ .code = getErrorCode(err.err),
+ .location = .{ .beg = err.location.beg, .end = err.location.end },
+ };
+ }
+ res.errors_len = errors.len;
+ }
+ return res;
+}
+
+fn fromError(alloc: Allocator, err: Error) typdown_Document {
+ var v = alloc.alloc(typdown_Document.typdown_Error, 1) catch |e| @panic(@errorName(e));
+ v[0] = .{ .code = getErrorCode(err), .location = .{ .beg = 0, .end = 0 } };
+ return .{ .errors = v.ptr, .errors_len = 1 };
+}
+
var default_alloc: std.mem.Allocator =
if (builtin.target.isWasiLibC())
std.heap.wasm_allocator
@@ -52,31 +88,29 @@ var default_alloc: std.mem.Allocator =
std.heap.c_allocator;
/// Parse the content.
-/// Code is a pointer to an u8 populated with an error code > 0.
-///
-/// Returns a non-null void pointer containing the document and set the code to 0 if everything is fine.
-/// Else, it returns null and set an error code above 0.
-/// Use typdown_getErrorString to retrieve the string linked with the error code.
-/// Use parse if you are in Zig.
///
/// You must free the document with typdown_free.
-export fn typdown_parse(content: [*:0]const u8, code: *u8) ?*anyopaque {
- return parse(default_alloc, std.mem.span(content)) catch |err| {
- code.* = getErrorCode(err);
- return null;
- };
+export fn typdown_parse(content: [*:0]const u8) typdown_Document {
+ const alloc = default_alloc;
+ return from(alloc, parse(alloc, std.mem.span(content)) catch |err| {
+ return fromError(alloc, err);
+ });
}
/// Free the document.
-export fn typdown_free(document: *anyopaque) void {
- const doc: *Document = @ptrCast(@alignCast(document));
- doc.deinit();
+export fn typdown_free(self: typdown_Document) void {
+ const alloc = default_alloc;
+ if (self.root) |r| {
+ const root: *Element.Root = @ptrCast(@alignCast(r));
+ root.deinit();
+ }
+ if (self.errors) |errors| alloc.free(errors[0..self.errors_len]);
}
/// Render an HTML from the document.
-export fn typdown_renderHTML(document: *anyopaque, code: *u8) ?[*:0]const u8 {
- const doc: *Document = @ptrCast(@alignCast(document));
- const res = doc.renderHTML(default_alloc) catch |err| {
+export fn typdown_renderHTML(document: typdown_Document, code: *u8) ?[*:0]const u8 {
+ const root: *Element.Root = @ptrCast(@alignCast(document.root));
+ const res = root.renderHTML(default_alloc) catch |err| {
code.* = getErrorCode(err);
return null;
};
@@ -88,10 +122,6 @@ export fn typdown_renderHTML(document: *anyopaque, code: *u8) ?[*:0]const u8 {
};
}
-pub fn parseReader(alloc: std.mem.Allocator, r: *std.io.Reader) (Error || std.io.Reader.Error)!*Document {
- return parser.parseReader(alloc, r);
-}
-
test {
std.testing.refAllDeclsRecursive(@This());
}
@@ -99,15 +129,13 @@ test {
fn doTest(content: [*:0]const u8, exp: []const u8, comptime exp_code: u8) !void {
const expect = std.testing.expect;
- var code: u8 = undefined;
- const doc = typdown_parse(content, &code) orelse {
- expect(code == exp_code) catch |err| {
- std.debug.print("{}\n", .{code});
- return err;
- };
- return;
- };
+ const doc = typdown_parse(content);
defer typdown_free(doc);
+ if (doc.errors) |errors| {
+ for (0..doc.errors_len) |i| if (errors[i].code == exp_code) return;
+ return try expect(false);
+ }
+ var code: u8 = undefined;
const raw = typdown_renderHTML(doc, &code) orelse {
expect(code == exp_code) catch |err| {
std.debug.print("{}\n", .{code});