aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/link.zig87
-rw-r--r--src/paragraph.zig43
-rw-r--r--src/parser.zig5
-rw-r--r--src/root.zig2
-rw-r--r--src/testing.zig4
-rw-r--r--src/title.zig20
6 files changed, 125 insertions, 36 deletions
diff --git a/src/link.zig b/src/link.zig
new file mode 100644
index 0000000..c9fc93a
--- /dev/null
+++ b/src/link.zig
@@ -0,0 +1,87 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const eql = std.mem.eql;
+const Lexed = @import("lexer/Lexed.zig");
+const Lexer = @import("lexer/Lexer.zig");
+const Element = @import("dom/Element.zig");
+const paragraph = @import("paragraph.zig");
+const testing = @import("testing.zig");
+const doTest = testing.do;
+const doTestError = testing.doError;
+
+pub const Error = error{InvalidLink} || Lexer.Error || paragraph.Error;
+
+pub fn parse(alloc: Allocator, l: *Lexer) Error!Element {
+ var el = try Element.init(alloc, .content, "a");
+ errdefer el.deinit();
+ const data = try parseData(alloc, l);
+ const second = data.second orelse {
+ el.deinit();
+ return data.first.?;
+ };
+ defer alloc.free(second);
+ var content = if (data.first) |first| first else try Element.initLitEscaped(alloc, second);
+ errdefer content.deinit();
+ try el.appendContent(content);
+ try el.setAttribute("href", second);
+ return el;
+}
+
+pub const Data = struct {
+ first: ?Element,
+ second: ?[]const u8,
+};
+
+pub fn parseData(alloc: Allocator, l: *Lexer) Error!Data {
+ var el = Element.initEmpty(alloc);
+ errdefer el.deinit();
+ var v = (try l.next(alloc)).?;
+ defer v.deinit();
+ if (v.kind != .link) return Error.InvalidLink;
+ if (!eql(u8, v.content.items, "[")) {
+ const first = try Element.initLitEscaped(alloc, v.content.items);
+ el.deinit();
+ return .{ .first = first, .second = null };
+ }
+ while (l.nextKind()) |kind| {
+ switch (kind) {
+ .weak_delimiter, .strong_delimiter => return Error.InvalidLink,
+ .link => {
+ var next = (try l.next(alloc)).?;
+ defer next.deinit();
+ if (!eql(u8, next.content.items, "](")) return Error.InvalidLink;
+ break;
+ },
+ else => {
+ const content = try paragraph.parseContent(alloc, l);
+ try el.appendContent(content);
+ },
+ }
+ }
+ var href = try l.next(alloc) orelse return Error.InvalidLink;
+ defer href.deinit();
+ if (href.kind != .literal) return Error.InvalidLink;
+ var finisher = try l.next(alloc) orelse return Error.InvalidLink;
+ defer finisher.deinit();
+ if (finisher.kind != .link or !eql(u8, finisher.content.items, ")")) return Error.InvalidLink;
+ return .{
+ .first = if (el.content.items.len > 0) el else null,
+ .second = try href.content.toOwnedSlice(alloc),
+ };
+}
+
+test "parse links" {
+ var arena = std.heap.DebugAllocator(.{}).init;
+ defer if (arena.deinit() == .leak) std.debug.print("leaking!\n", .{});
+ const alloc = arena.allocator();
+
+ try doTest(parse, alloc, "[](bar)", "<a href=\"bar\">bar</a>");
+ try doTest(parse, alloc, "[foo](bar)", "<a href=\"bar\">foo</a>");
+ try doTest(parse, alloc, "[f*o*o](bar)", "<a href=\"bar\">f<b>o</b>o</a>");
+ try doTest(parse, alloc, ")", ")");
+
+ try doTestError(parse, alloc, "[foo :::](bar)", Error.IllegalPlacement);
+ try doTestError(parse, alloc, "[foo", Error.InvalidLink);
+ try doTestError(parse, alloc, "[foo](", Error.InvalidLink);
+ try doTestError(parse, alloc, "[foo]()", Error.InvalidLink);
+}
diff --git a/src/paragraph.zig b/src/paragraph.zig
index a164b1d..2b96e19 100644
--- a/src/paragraph.zig
+++ b/src/paragraph.zig
@@ -4,9 +4,12 @@ const Lexed = @import("lexer/Lexed.zig");
const Lexer = @import("lexer/Lexer.zig");
const Element = @import("dom/Element.zig");
const parser = @import("parser.zig");
+const link = @import("link.zig");
const testing = @import("testing.zig");
+const doTest = testing.do;
+const doTestError = testing.doError;
-pub const Error = error{ ModifierNotClosed, IllegalPlacement } || Lexer.Error;
+pub const Error = error{ ModifierNotClosed, IllegalPlacement, InvalidLink } || Lexer.Error;
pub fn parse(alloc: Allocator, l: *Lexer) Error!Element {
var el = try Element.init(alloc, .content, "p");
@@ -20,7 +23,7 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element {
if (v.kind == .strong_delimiter) return el;
const next = l.nextKind() orelse return el;
switch (next) {
- .literal, .italic, .code, .bold => try el.appendContent(try Element.initLit(alloc, " ")),
+ .literal, .italic, .code, .bold, .link => try el.appendContent(try Element.initLit(alloc, " ")),
else => return el,
}
},
@@ -36,8 +39,14 @@ pub fn parseLine(alloc: Allocator, l: *Lexer) Error!Element {
while (l.nextKind()) |kind| {
switch (kind) {
.weak_delimiter, .strong_delimiter => return content,
+ .link => {
+ var el = try link.parse(alloc, l);
+ errdefer el.deinit();
+ try content.appendContent(el);
+ },
else => {
- const el = try parseContent(alloc, l);
+ var el = try parseContent(alloc, l);
+ errdefer el.deinit();
try content.appendContent(el);
},
}
@@ -45,7 +54,7 @@ pub fn parseLine(alloc: Allocator, l: *Lexer) Error!Element {
return content;
}
-fn parseContent(alloc: Allocator, l: *Lexer) Error!Element {
+pub fn parseContent(alloc: Allocator, l: *Lexer) Error!Element {
var content = Element.initEmpty(alloc);
errdefer content.deinit();
var v = (try l.next(alloc)).?;
@@ -79,25 +88,21 @@ fn parseModifier(alloc: Allocator, l: *Lexer, knd: Lexed.Kind, tag: []const u8)
return Error.ModifierNotClosed;
}
-fn doTest(alloc: Allocator, t: []const u8, v: []const u8) !void {
- return testing.do(parse, alloc, t, v);
-}
-
-fn doTestError(alloc: Allocator, t: []const u8, err: Error) !void {
- return testing.doError(parse, alloc, t, err);
-}
-
test "parse paragraphs" {
var arena = std.heap.DebugAllocator(.{}).init;
defer if (arena.deinit() == .leak) std.debug.print("leaking!\n", .{});
const alloc = arena.allocator();
- try doTest(alloc, "hello world", "<p>hello world</p>");
- try doTest(alloc, "*hello* world", "<p><b>hello</b> world</p>");
- try doTest(alloc, "*he_ll_o* world", "<p><b>he<em>ll</em>o</b> world</p>");
+ try doTest(parse, alloc, "hello world", "<p>hello world</p>");
+ try doTest(parse, alloc, "*hello* world", "<p><b>hello</b> world</p>");
+ try doTest(parse, alloc, "*he_ll_o* world", "<p><b>he<em>ll</em>o</b> world</p>");
+ try doTest(parse, alloc, "(foo)", "<p>(foo)</p>");
+ try doTest(parse, alloc, "[](bar)", "<p><a href=\"bar\">bar</a></p>");
+ try doTest(parse, alloc, "[foo](bar)", "<p><a href=\"bar\">foo</a></p>");
+ try doTest(parse, alloc, "hello [foo](bar) world", "<p>hello <a href=\"bar\">foo</a> world</p>");
- try doTestError(alloc, "hello *world", Error.ModifierNotClosed);
- try doTestError(alloc, "hello *wo_rld*", Error.ModifierNotClosed);
- try doTestError(alloc, "*hell*o *wo_rld*", Error.ModifierNotClosed);
- try doTestError(alloc, "hello ::: world", Error.IllegalPlacement);
+ try doTestError(parse, alloc, "hello *world", Error.ModifierNotClosed);
+ try doTestError(parse, alloc, "hello *wo_rld*", Error.ModifierNotClosed);
+ try doTestError(parse, alloc, "*hell*o *wo_rld*", Error.ModifierNotClosed);
+ try doTestError(parse, alloc, "hello ::: world", Error.IllegalPlacement);
}
diff --git a/src/parser.zig b/src/parser.zig
index f109a88..7419c2e 100644
--- a/src/parser.zig
+++ b/src/parser.zig
@@ -5,10 +5,11 @@ const Lexer = @import("lexer/Lexer.zig");
const Element = @import("dom/Element.zig");
const paragraph = @import("paragraph.zig");
const title = @import("title.zig");
+const link = @import("link.zig");
pub const Error = error{
FeatureNotSupported,
-} || Lexer.Error || paragraph.Error || title.Error;
+} || Lexer.Error || paragraph.Error || title.Error || link.Error;
pub fn parse(parent: Allocator, content: []const u8) Error![]const u8 {
var arena = std.heap.ArenaAllocator.init(parent);
@@ -20,7 +21,7 @@ pub fn parse(parent: Allocator, content: []const u8) Error![]const u8 {
var l = try Lexer.init(content);
base: while (l.nextKind()) |it| {
try elements.append(alloc, switch (it) {
- .literal, .bold, .italic, .code => try paragraph.parse(alloc, &l),
+ .literal, .bold, .italic, .code, .link => try paragraph.parse(alloc, &l),
.title => try title.parse(alloc, &l),
.weak_delimiter, .strong_delimiter => {
var v = (try l.next(alloc)).?;
diff --git a/src/root.zig b/src/root.zig
index 830ba1c..152170e 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -10,6 +10,7 @@ fn getErrorCode(err: Error) u8 {
Error.ModifierNotClosed => 4,
Error.InvalidTitleContent => 5,
Error.IllegalPlacement => 6,
+ Error.InvalidLink => 7,
};
}
@@ -22,6 +23,7 @@ export fn getErrorString(code: u8) [*:0]const u8 {
4 => "modifier not closed",
5 => "invalid title content",
6 => "illegal placement",
+ 7 => "invalid link",
else => unreachable,
};
}
diff --git a/src/testing.zig b/src/testing.zig
index ecf6eb3..8d96808 100644
--- a/src/testing.zig
+++ b/src/testing.zig
@@ -4,7 +4,7 @@ const Lexer = @import("lexer/Lexer.zig");
const Element = @import("dom/Element.zig");
const parser = @import("parser.zig");
-pub fn do(comptime parse: fn(Allocator, *Lexer) parser.Error!Element, alloc: Allocator, t: []const u8, v: []const u8) !void {
+pub fn do(comptime parse: fn (Allocator, *Lexer) parser.Error!Element, alloc: Allocator, t: []const u8, v: []const u8) !void {
var l = try Lexer.init(t);
var p = try parse(alloc, &l);
defer p.deinit();
@@ -16,7 +16,7 @@ pub fn do(comptime parse: fn(Allocator, *Lexer) parser.Error!Element, alloc: All
};
}
-pub fn doError(comptime parse: fn(Allocator, *Lexer) parser.Error!Element, alloc: Allocator, t: []const u8, err: parser.Error) !void {
+pub fn doError(comptime parse: fn (Allocator, *Lexer) parser.Error!Element, alloc: Allocator, t: []const u8, err: parser.Error) !void {
var l = try Lexer.init(t);
_ = parse(alloc, &l) catch |e| return std.testing.expect(err == e);
return std.testing.expect(false);
diff --git a/src/title.zig b/src/title.zig
index a29cece..352460f 100644
--- a/src/title.zig
+++ b/src/title.zig
@@ -5,6 +5,8 @@ const Lexer = @import("lexer/Lexer.zig");
const Element = @import("dom/Element.zig");
const paragraph = @import("paragraph.zig");
const testing = @import("testing.zig");
+const doTest = testing.do;
+const doTestError = testing.doError;
pub const Error = error{InvalidTitleContent} || paragraph.Error || Lexer.Error;
@@ -31,24 +33,16 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element {
return el;
}
-fn doTest(alloc: Allocator, t: []const u8, v: []const u8) !void {
- return testing.do(parse, alloc, t, v);
-}
-
-fn doTestError(alloc: Allocator, t: []const u8, err: Error) !void {
- return testing.doError(parse, alloc, t, err);
-}
-
test "parse title" {
var arena = std.heap.DebugAllocator(.{}).init;
defer if (arena.deinit() == .leak) std.debug.print("leaking!\n", .{});
const alloc = arena.allocator();
- try doTest(alloc, "# hey", "<h1>hey</h1>");
- try doTest(alloc, "## hey", "<h2>hey</h2>");
- try doTest(alloc, "### hey", "<h3>hey</h3>");
+ try doTest(parse, alloc, "# hey", "<h1>hey</h1>");
+ try doTest(parse, alloc, "## hey", "<h2>hey</h2>");
+ try doTest(parse, alloc, "### hey", "<h3>hey</h3>");
- try doTest(alloc, "# hello *world*", "<h1>hello <b>world</b></h1>");
+ try doTest(parse, alloc, "# hello *world*", "<h1>hello <b>world</b></h1>");
- try doTestError(alloc, "# aa :::", Error.InvalidTitleContent);
+ try doTestError(parse, alloc, "# aa :::", Error.InvalidTitleContent);
}