diff options
| -rw-r--r-- | src/content.zig | 2 | ||||
| -rw-r--r-- | src/eval/Element.zig | 1 | ||||
| -rw-r--r-- | src/eval/Image.zig | 68 | ||||
| -rw-r--r-- | src/eval/html/Element.zig | 21 | ||||
| -rw-r--r-- | src/lexer/Lexer.zig | 23 | ||||
| -rw-r--r-- | src/lexer/Token.zig | 7 | ||||
| -rw-r--r-- | src/link.zig | 85 | ||||
| -rw-r--r-- | src/list.zig | 10 | ||||
| -rw-r--r-- | src/paragraph.zig | 10 | ||||
| -rw-r--r-- | src/parser.zig | 12 | ||||
| -rw-r--r-- | src/root.zig | 2 | ||||
| -rw-r--r-- | src/title.zig | 2 |
12 files changed, 184 insertions, 59 deletions
diff --git a/src/content.zig b/src/content.zig index 53f9418..b1ac0c7 100644 --- a/src/content.zig +++ b/src/content.zig @@ -9,7 +9,7 @@ const testing = @import("testing.zig"); const doTest = testing.do; const doTestError = testing.doError; -pub const Error = error{ ModifierNotClosed, IllegalPlacement } || Lexer.Error || Allocator.Error; +pub const Error = error{ ModifierNotClosed, IllegalPlacement } || Allocator.Error; pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { var content = try Element.Empty.init(alloc); diff --git a/src/eval/Element.zig b/src/eval/Element.zig index 5d6fce5..a8b424d 100644 --- a/src/eval/Element.zig +++ b/src/eval/Element.zig @@ -4,6 +4,7 @@ pub const HTML = @import("html/Element.zig"); pub const paragraph = @import("paragraph.zig"); pub const Title = @import("Title.zig"); pub const list = @import("list.zig"); +pub const Image = @import("Image.zig"); const Element = @This(); diff --git a/src/eval/Image.zig b/src/eval/Image.zig new file mode 100644 index 0000000..771c8ee --- /dev/null +++ b/src/eval/Image.zig @@ -0,0 +1,68 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const HTML = @import("html/Element.zig"); +const Element = @import("Element.zig"); + +const Self = @This(); + +src: []const u8, +alt: ?[]const u8 = null, +source: ?Element = null, + +pub fn init(alloc: Allocator, src: []const u8) !*Self { + const v = try alloc.create(Self); + v.* = .{ + .src = src, + }; + return v; +} + +pub fn element(self: *Self) Element { + return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = html } }; +} + +pub fn deinit(self: *Self, alloc: Allocator) void { + destroy(self, alloc); +} + +fn destroy(context: *anyopaque, alloc: Allocator) void { + const self: *Self = @ptrCast(@alignCast(context)); + if (self.source) |it| it.deinit(alloc); + alloc.destroy(self); +} + +fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { + const self: *Self = @ptrCast(@alignCast(context)); + + var img = try HTML.init(alloc, .void, "img"); + errdefer img.deinit(); + try img.setAttribute("src", self.src); + if (self.alt) |it| try img.setAttribute("alt", it); + var el = try HTML.init(alloc, .content, "figure"); + errdefer el.deinit(); + try el.appendContent(img); + + const source = self.source orelse return el; + var caption = try HTML.init(alloc, .content, "figcaption"); + errdefer caption.deinit(); + try caption.content.append(alloc, try source.html(alloc)); + try el.appendContent(caption); + return el; +} + +test "html" { + const alloc = std.testing.allocator; + const expect = std.testing.expect; + const eql = std.mem.eql; + + var img = try init(alloc, "foo"); + defer img.deinit(alloc); + const h = try img.element().renderHTML(alloc); + defer alloc.free(h); + try expect(eql(u8, h, "<figure><img src=\"foo\"></figure>")); + + img.alt = "bar"; + const h2 = try img.element().renderHTML(alloc); + defer alloc.free(h2); + try expect(eql(u8, h2, "<figure><img src=\"foo\" alt=\"bar\"></figure>")); +} diff --git a/src/eval/html/Element.zig b/src/eval/html/Element.zig index 87fd876..666e6f0 100644 --- a/src/eval/html/Element.zig +++ b/src/eval/html/Element.zig @@ -178,19 +178,6 @@ pub fn appendContent(self: *Self, content: Self) Error!void { return self.content.append(alloc, content); } -pub fn initImg(alloc: Allocator, src: []const u8, alt: []const u8) Error!Self { - var el = try init(alloc, .void, "img"); - try el.setAttribute("src", src); - try el.setAttribute("alt", alt); - return el; -} - -pub fn initContent(alloc: Allocator, tag: []const u8, content: []Self) Error!Self { - var el = try init(alloc, .content, tag); - for (content) |it| try el.appendContent(it); - return el; -} - fn doTest(alloc: Allocator, el: *Self, exp: []const u8) !void { const got = try el.render(alloc); defer alloc.free(got); @@ -214,10 +201,6 @@ test "void element" { try img.setAttribute("alt", "bar"); try doTest(alloc, &img, "<img src=\"foo\" alt=\"bar\">"); - - var img2 = try initImg(alloc, "foo", "bar"); - defer img2.deinit(); - try doTest(alloc, &img2, "<img src=\"foo\" alt=\"bar\">"); } test "content element" { @@ -235,7 +218,7 @@ test "content element" { defer div.deinit(); try div.appendClass("foo-bar"); try div.appendContent(p); - try div.appendContent(try initImg(alloc, "example.org", "example")); + try div.appendContent(try init(alloc, .void, "br")); - try doTest(alloc, &div, "<div class=\"foo-bar\"><p>hello world</p><img src=\"example.org\" alt=\"example\"></div>"); + try doTest(alloc, &div, "<div class=\"foo-bar\"><p>hello world</p><br></div>"); } diff --git a/src/lexer/Lexer.zig b/src/lexer/Lexer.zig index cdf1bd8..983aa23 100644 --- a/src/lexer/Lexer.zig +++ b/src/lexer/Lexer.zig @@ -223,6 +223,29 @@ test "lexer common" { try std.testing.expect(l.next() == null); } +test "lexer image" { + var l = try init(""); + + try doTest(&l, .image, "!"); + try doTest(&l, .link, "["); + try doTest(&l, .literal, "alt"); + try doTest(&l, .link, "]("); + try doTest(&l, .literal, "src"); + try doTest(&l, .link, ")"); + + try std.testing.expect(l.next() == null); + + l = try init(""); + + try doTest(&l, .image, "!"); + try doTest(&l, .link, "["); + try doTest(&l, .link, "]("); + try doTest(&l, .literal, "src"); + try doTest(&l, .link, ")"); + + try std.testing.expect(l.next() == null); +} + test "lexer multiline" { var l = try init( \\# Title diff --git a/src/lexer/Token.zig b/src/lexer/Token.zig index 18b2d10..bd2a07b 100644 --- a/src/lexer/Token.zig +++ b/src/lexer/Token.zig @@ -27,6 +27,13 @@ pub const Kind = enum { else => false, }; } + + pub fn isPar(self: @This()) bool { + return switch (self) { + .literal, .link, .code, .math, .bold, .italic, .ref => true, + else => false, + }; + } }; kind: Kind, diff --git a/src/link.zig b/src/link.zig index 8eb5778..50e89f5 100644 --- a/src/link.zig +++ b/src/link.zig @@ -6,32 +6,17 @@ const Lexer = @import("lexer/Lexer.zig"); const Element = @import("eval/Element.zig"); const Link = Element.paragraph.Link; const content = @import("content.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 || content.Error || Allocator.Error; +pub const Error = error{InvalidLink} || content.Error || Allocator.Error; pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { - const data = try parseData(alloc, l); - const second = data.second orelse return data.first.?; - var in = if (data.first) |first| first else (try Element.Literal.init(alloc, second)).element(); - errdefer in.deinit(alloc); - return (try Link.init(alloc, in, data.second.?)).element(); -} - -pub const Data = struct { - first: ?Element, - second: ?[]const u8, -}; - -pub fn parseData(alloc: Allocator, l: *Lexer) Error!Data { const v = l.next().?; if (v.kind != .link) return Error.InvalidLink; - if (!eql(u8, v.content, "[")) { - const el = try Element.Literal.init(alloc, v.content); - return .{ .first = el.element(), .second = null }; - } + if (!eql(u8, v.content, "[")) return (try Element.Literal.init(alloc, v.content)).element(); var el = try Element.Empty.init(alloc); errdefer el.deinit(alloc); while (l.peek()) |next| { @@ -52,15 +37,58 @@ pub fn parseData(alloc: Allocator, l: *Lexer) Error!Data { if (href.kind != .literal) return Error.InvalidLink; const finisher = l.next() orelse return Error.InvalidLink; if (!finisher.equals(.link, ")")) return Error.InvalidLink; - var res = Data{ - .first = el.element(), - .second = href.content, - }; - if (el.content.items.len == 0) { - res.first = null; + var in: Element = undefined; + if (el.content.items.len > 0) { + in = el.element(); + } else { el.deinit(alloc); + in = (try Element.Literal.init(alloc, href.content)).element(); } - return res; + errdefer in.deinit(alloc); + return (try Link.init(alloc, in, href.content)).element(); +} + +pub const ImageError = error{InvalidImage} || paragraph.Error || Allocator.Error; + +pub fn parseImage(alloc: Allocator, l: *Lexer) ImageError!Element { + _ = l.next().?; + const beg = l.next() orelse return ImageError.InvalidImage; + if (!eql(u8, beg.content, "[")) return ImageError.InvalidImage; + var it = l.next() orelse return ImageError.InvalidImage; + var alt: ?[]const u8 = null; + switch (it.kind) { + .link => if (!eql(u8, it.content, "](")) return ImageError.InvalidImage, + .literal => { + alt = it.content; + const next = l.next() orelse return ImageError.InvalidImage; + if (!next.equals(.link, "](")) return ImageError.InvalidImage; + }, + else => return ImageError.InvalidImage, + } + it = l.next() orelse return ImageError.InvalidImage; + if (it.kind != .literal) return ImageError.InvalidImage; + const src = it.content; + it = l.next() orelse return ImageError.InvalidImage; + if (!it.equals(.link, ")")) return ImageError.InvalidImage; + const el = try Element.Image.init(alloc, src); + errdefer el.deinit(alloc); + el.alt = alt; + it = l.peek() orelse return el.element(); + switch (it.kind) { + .strong_delimiter => return el.element(), + .weak_delimiter => l.consume(), + else => return ImageError.InvalidImage, + } + const p = try paragraph.parse(alloc, l); + errdefer p.deinit(alloc); + el.source = p; + const p_el: *Element.paragraph.Block = @ptrCast(@alignCast(p.ptr)); + defer p_el.deinit(alloc); + const in = try Element.Empty.init(alloc); + errdefer in.deinit(alloc); + in.content = try p_el.content.clone(alloc); + el.source = in.element(); + return el.element(); } test "parse links" { @@ -76,3 +104,10 @@ test "parse links" { try doTestError(parse, alloc, "[foo](", Error.InvalidLink); try doTestError(parse, alloc, "[foo]()", Error.InvalidLink); } + +test "parse image" { + const alloc = std.testing.allocator; + + try doTest(parseImage, alloc, "", "<figure><img src=\"src\"></figure>"); + try doTest(parseImage, alloc, "", "<figure><img src=\"src\" alt=\"alt\"></figure>"); +} diff --git a/src/list.zig b/src/list.zig index 1375d86..b8c7458 100644 --- a/src/list.zig +++ b/src/list.zig @@ -8,14 +8,16 @@ const testing = @import("testing.zig"); const doTest = testing.do; const doTestError = testing.doError; -pub fn parseOrdored(alloc: Allocator, l: *Lexer) !Element { +pub const Error = paragraph.Error || Allocator.Error; + +pub fn parseOrdored(alloc: Allocator, l: *Lexer) Error!Element { const el = try Element.list.Ordored.init(alloc); errdefer el.deinit(alloc); try parse(alloc, &el.content, l, .list_ordored); return el.element(); } -pub fn parseUnordored(alloc: Allocator, l: *Lexer) !Element { +pub fn parseUnordored(alloc: Allocator, l: *Lexer) Error!Element { const el = try Element.list.Unordored.init(alloc); errdefer el.deinit(alloc); try parse(alloc, &el.content, l, .list_unordored); @@ -54,6 +56,8 @@ test "parse ordored list" { \\. two \\no more , "<ol><li>one</li><li>two</li></ol>"); + + try doTestError(parseOrdored, alloc, ".one :::", Error.IllegalPlacement); } test "parse unordored list" { @@ -68,4 +72,6 @@ test "parse unordored list" { \\- two \\no more , "<ul><li>one</li><li>two</li></ul>"); + + try doTestError(parseOrdored, alloc, "- one :::", Error.IllegalPlacement); } diff --git a/src/paragraph.zig b/src/paragraph.zig index f03bc8f..c2e0175 100644 --- a/src/paragraph.zig +++ b/src/paragraph.zig @@ -11,7 +11,7 @@ const testing = @import("testing.zig"); const doTest = testing.do; const doTestError = testing.doError; -pub const Error = content.Error || link.Error || Lexer.Error || Allocator.Error; +pub const Error = content.Error || link.Error || Allocator.Error; pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { var el = try Paragraph.Block.init(alloc); @@ -22,12 +22,8 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { .weak_delimiter => { l.consume(); const future = l.peek() orelse return el.element(); - switch (future.kind) { - .literal, .italic, .code, .bold, .link => { - try el.content.append(alloc, (try Element.Literal.init(alloc, " ")).element()); - }, - else => return el.element(), - } + if (!future.kind.isPar()) return el.element(); + try el.content.append(alloc, (try Element.Literal.init(alloc, " ")).element()); }, else => try el.content.append(alloc, try parseLine(alloc, l)), } diff --git a/src/parser.zig b/src/parser.zig index 8c170e7..a5a49cd 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -10,7 +10,7 @@ const list = @import("list.zig"); pub const Error = error{ FeatureNotSupported, -} || Lexer.Error || paragraph.Error || title.Error || link.Error || Allocator.Error; +} || Lexer.Error || paragraph.Error || title.Error || link.Error || list.Error || link.ImageError || Allocator.Error; pub const Document = struct { arena: std.heap.ArenaAllocator, @@ -51,17 +51,21 @@ fn gen(parent: Allocator, l: *Lexer) Error!Document { var elements = try std.ArrayList(Element).initCapacity(alloc, 2); base: while (l.peek()) |it| { try elements.append(alloc, switch (it.kind) { - // block paragraph - .literal, .bold, .italic, .code, .link => try paragraph.parse(alloc, l), // 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), .weak_delimiter, .strong_delimiter => { l.consume(); continue :base; }, - else => return Error.FeatureNotSupported, + else => + // block paragraph + if (it.kind.isPar()) + try paragraph.parse(alloc, l) + else + return Error.FeatureNotSupported, }); } return .{ .root = try elements.toOwnedSlice(alloc), .arena = arena }; diff --git a/src/root.zig b/src/root.zig index 185e6b6..5062832 100644 --- a/src/root.zig +++ b/src/root.zig @@ -14,6 +14,7 @@ inline fn getErrorCode(err: Error) u8 { Error.InvalidTitleContent => 5, Error.IllegalPlacement => 6, Error.InvalidLink => 7, + Error.InvalidImage => 8, }; } @@ -27,6 +28,7 @@ export fn typdown_getErrorString(code: u8) [*:0]const u8 { 5 => "invalid title content", 6 => "illegal placement", 7 => "invalid link", + 8 => "invalid image", else => unreachable, }; } diff --git a/src/title.zig b/src/title.zig index 9fdc116..87d7f5f 100644 --- a/src/title.zig +++ b/src/title.zig @@ -8,7 +8,7 @@ const testing = @import("testing.zig"); const doTest = testing.do; const doTestError = testing.doError; -pub const Error = error{InvalidTitleContent} || paragraph.Error || Lexer.Error; +pub const Error = error{InvalidTitleContent} || paragraph.Error; pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { const v = l.next().?; |
