diff options
| author | Anhgelus Morhtuuzh <william@herges.fr> | 2026-04-25 16:56:45 +0200 |
|---|---|---|
| committer | Anhgelus Morhtuuzh <william@herges.fr> | 2026-04-25 16:56:45 +0200 |
| commit | a3e7c462dadadc6986d93f6f0203ca7a02863ef8 (patch) | |
| tree | 8be2002817241138554fc3f00440cbdb9e0fa9e1 | |
| parent | 22ce4f7a80fb6692da3a675e3a652b8f305d157a (diff) | |
refactor(ast): separate ast and exec
| -rw-r--r-- | src/Element.zig | 243 | ||||
| -rw-r--r-- | src/content.zig | 28 | ||||
| -rw-r--r-- | src/dom/Element.zig | 39 | ||||
| -rw-r--r-- | src/dom/html.zig | 4 | ||||
| -rw-r--r-- | src/link.zig | 31 | ||||
| -rw-r--r-- | src/paragraph.zig | 37 | ||||
| -rw-r--r-- | src/parser.zig | 5 | ||||
| -rw-r--r-- | src/testing.zig | 6 | ||||
| -rw-r--r-- | src/title.zig | 19 |
9 files changed, 319 insertions, 93 deletions
diff --git a/src/Element.zig b/src/Element.zig new file mode 100644 index 0000000..7206125 --- /dev/null +++ b/src/Element.zig @@ -0,0 +1,243 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const DOMElement = @import("dom/Element.zig"); + +const Parent = @This(); + +vtable: struct { + deinit: *const fn (*anyopaque, Allocator) void, + dom: *const fn (*anyopaque, Allocator) DOMElement.Error!DOMElement, +}, +ptr: *anyopaque, + +pub fn renderHTML(self: Parent, alloc: Allocator) DOMElement.Error![]const u8 { + var el = try self.vtable.dom(self.ptr, alloc); + defer el.deinit(); + return el.render(alloc); +} + +pub fn deinit(self: Parent, alloc: Allocator) void { + self.vtable.deinit(self.ptr, alloc); +} + +fn dom(self: Parent, alloc: Allocator) DOMElement.Error!DOMElement { + return self.vtable.dom(self.ptr, alloc); +} + +pub const Paragraph = Modifier("p"); +pub const Bold = Modifier("b"); +pub const Italic = Modifier("em"); +pub const Code = Modifier("code"); + +pub const Empty = struct { + content: std.ArrayList(Parent), + + const Self = @This(); + + pub fn init(alloc: Allocator) !*Self { + const v = try alloc.create(Self); + v.* = .{ .content = try .initCapacity(alloc, 2) }; + return v; + } + + pub fn element(self: *Self) Parent { + return .{ .ptr = self, .vtable = .{ .deinit = destroy, .dom = Self.dom } }; + } + + pub fn deinit(self: *Self, alloc: Allocator) void { + destroy(self, alloc); + } + + fn destroy(context: *anyopaque, alloc: Allocator) void { + const self: *Self = @ptrCast(@alignCast(context)); + for (self.content.items) |it| it.deinit(alloc); + self.content.deinit(alloc); + alloc.destroy(self); + } + + fn dom(context: *anyopaque, alloc: Allocator) DOMElement.Error!DOMElement { + const self: *Self = @ptrCast(@alignCast(context)); + var el = DOMElement.initEmpty(alloc); + errdefer el.deinit(); + for (self.content.items) |it| try el.appendContent(try it.dom(alloc)); + return el; + } +}; + +pub const Literal = struct { + content: []const u8, + + const Self = @This(); + + pub fn init(alloc: Allocator, content: []const u8) !*Self { + const v = try alloc.create(Self); + v.* = .{ .content = content }; + return v; + } + + pub fn element(self: *Self) Parent { + return .{ .ptr = self, .vtable = .{ .deinit = destroy, .dom = Self.dom } }; + } + + pub fn deinit(self: *Self, alloc: Allocator) void { + destroy(self, alloc); + } + + fn destroy(context: *anyopaque, alloc: Allocator) void { + const self: *Self = @ptrCast(@alignCast(context)); + alloc.destroy(self); + } + + fn dom(context: *anyopaque, alloc: Allocator) DOMElement.Error!DOMElement { + const self: *Self = @ptrCast(@alignCast(context)); + return DOMElement.initLitEscaped(alloc, self.content); + } +}; + +pub fn Modifier(comptime tag: []const u8) type { + return struct { + content: std.ArrayList(Parent), + + const Self = @This(); + + pub fn init(alloc: Allocator) !*Self { + const v = try alloc.create(Self); + v.* = .{ .content = try .initCapacity(alloc, 2) }; + return v; + } + + pub fn element(self: *Self) Parent { + return .{ .ptr = self, .vtable = .{ .deinit = destroy, .dom = Self.dom } }; + } + + pub fn deinit(self: *Self, alloc: Allocator) void { + destroy(self, alloc); + } + + fn destroy(context: *anyopaque, alloc: Allocator) void { + var self: *Self = @ptrCast(@alignCast(context)); + for (self.content.items) |it| it.deinit(alloc); + self.content.deinit(alloc); + alloc.destroy(self); + } + + fn dom(context: *anyopaque, alloc: Allocator) DOMElement.Error!DOMElement { + const self: *Self = @ptrCast(@alignCast(context)); + var el = try DOMElement.init(alloc, .content, tag); + errdefer el.deinit(); + for (self.content.items) |it| try el.appendContent(try it.dom(alloc)); + return el; + } + }; +} + +pub const Link = struct { + link: []const u8, + content: Parent, + target: ?[]const u8 = null, + + const Self = @This(); + + pub fn init(alloc: Allocator, content: Parent, link: []const u8) !*Self { + const v = try alloc.create(Self); + v.* = .{ + .content = content, + .link = link, + }; + return v; + } + + pub fn element(self: *Self) Parent { + return .{ .ptr = self, .vtable = .{ .deinit = destroy, .dom = Self.dom } }; + } + + pub fn deinit(self: *Self, alloc: Allocator) void { + destroy(self, alloc); + } + + fn destroy(context: *anyopaque, alloc: Allocator) void { + var self: *Self = @ptrCast(@alignCast(context)); + self.content.deinit(alloc); + alloc.destroy(self); + } + + fn dom(context: *anyopaque, alloc: Allocator) DOMElement.Error!DOMElement { + const self: *Self = @ptrCast(@alignCast(context)); + var el = try DOMElement.init(alloc, .content, "a"); + errdefer el.deinit(); + try el.appendContent(try self.content.dom(alloc)); + try el.setAttribute("href", self.link); + if (self.target) |target| try el.setAttribute("target", target); + return el; + } +}; + +pub const Title = struct { + level: u3, + content: Parent, + + const Self = @This(); + + pub fn init(alloc: Allocator, level: u3, content: Parent) !*Self { + const v = try alloc.create(Self); + v.* = .{ .level = level, .content = content }; + return v; + } + + pub fn element(self: *Self) Parent { + return .{ .ptr = self, .vtable = .{ .deinit = destroy, .dom = Self.dom } }; + } + + pub fn deinit(self: *Self, alloc: Allocator) void { + self.element().deinit(alloc); + } + + fn destroy(context: *anyopaque, alloc: Allocator) void { + var self: *Self = @ptrCast(@alignCast(context)); + self.content.deinit(alloc); + alloc.destroy(self); + } + + fn dom(context: *anyopaque, alloc: Allocator) DOMElement.Error!DOMElement { + const self: *Self = @ptrCast(@alignCast(context)); + var el = try DOMElement.init(alloc, .content, switch (self.level) { + 1 => "h1", + 2 => "h2", + 3 => "h3", + 4 => "h4", + 5 => "h5", + 6 => "h6", + else => unreachable, + }); + errdefer el.deinit(); + try el.appendContent(try self.content.dom(alloc)); + return el; + } +}; + +fn doTest(alloc: Allocator, el: Parent, exp: []const u8) !void { + const got = try el.renderHTML(alloc); + defer alloc.free(got); + std.testing.expect(std.mem.eql(u8, got, exp)) catch |err| { + std.debug.print("{s}\n", .{got}); + return err; + }; +} + +test "paragraph" { + const alloc = std.testing.allocator; + + const lit = (try Literal.init(alloc, "hello world")).element(); + try doTest(alloc, lit, "hello world"); + + var p = try Paragraph.init(alloc); + try p.content.append(alloc, lit); + defer p.deinit(alloc); + try doTest(alloc, p.element(), "<p>hello world</p>"); + + const link = (try Link.init(alloc, (try Literal.init(alloc, "foo")).element(), "example.org")).element(); + try doTest(alloc, link, "<a href=\"example.org\">foo</a>"); + + try p.content.append(alloc, link); + try doTest(alloc, p.element(), "<p>hello world<a href=\"example.org\">foo</a></p>"); +} diff --git a/src/content.zig b/src/content.zig index 72acbc7..68d88ad 100644 --- a/src/content.zig +++ b/src/content.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Token = @import("lexer/Token.zig"); const Lexer = @import("lexer/Lexer.zig"); -const Element = @import("dom/Element.zig"); +const Element = @import("Element.zig"); const parser = @import("parser.zig"); const link = @import("link.zig"); const testing = @import("testing.zig"); @@ -12,33 +12,33 @@ const doTestError = testing.doError; pub const Error = error{ ModifierNotClosed, IllegalPlacement } || Lexer.Error || Allocator.Error; pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { - var content = Element.initEmpty(alloc); - errdefer content.deinit(); + var content = try Element.Empty.init(alloc); + errdefer content.deinit(alloc); const v = l.next().?; switch (v.kind) { .literal => { - const el = try Element.initLitEscaped(alloc, v.content); - try content.appendContent(el); + const el = try Element.Literal.init(alloc, v.content); + try content.content.append(alloc, el.element()); }, - .bold => try content.appendContent(try parseModifier(alloc, l, .bold, "b")), - .italic => try content.appendContent(try parseModifier(alloc, l, .italic, "em")), - .code => try content.appendContent(try parseModifier(alloc, l, .code, "code")), + .bold => try content.content.append(alloc, try parseModifier(alloc, l, .bold, "b")), + .italic => try content.content.append(alloc, try parseModifier(alloc, l, .italic, "em")), + .code => try content.content.append(alloc, try parseModifier(alloc, l, .code, "code")), else => return Error.IllegalPlacement, } - return content; + return content.element(); } -fn parseModifier(alloc: Allocator, l: *Lexer, knd: Token.Kind, tag: []const u8) Error!Element { - var el = try Element.init(alloc, .content, tag); - errdefer el.deinit(); +fn parseModifier(alloc: Allocator, l: *Lexer, knd: Token.Kind, comptime tag: []const u8) Error!Element { + var el = try Element.Modifier(tag).init(alloc); + errdefer el.deinit(alloc); while (l.peek()) |next| { if (next.kind == knd) { // consuming the finisher l.consume(); - return el; + return el.element(); } if (next.kind.isDelimiter()) return Error.ModifierNotClosed; - try el.appendContent(try parse(alloc, l)); + try el.content.append(alloc, try parse(alloc, l)); } return Error.ModifierNotClosed; } diff --git a/src/dom/Element.zig b/src/dom/Element.zig index ff1a0f2..87fd876 100644 --- a/src/dom/Element.zig +++ b/src/dom/Element.zig @@ -9,6 +9,8 @@ pub const Kind = enum { literal, }; +pub const Error = html.Error || Allocator.Error; + const Self = @This(); kind: Kind, @@ -22,7 +24,7 @@ literal: ?[]const u8 = null, /// Init a new Element with the given kind. /// The tag will never be escaped. /// It always duplicates strings. -pub fn init(alloc: Allocator, knd: Kind, tag: []const u8) !Self { +pub fn init(alloc: Allocator, knd: Kind, tag: []const u8) Error!Self { var v = Self{ .kind = knd, .arena = .init(alloc), @@ -46,7 +48,7 @@ pub fn initEmpty(alloc: Allocator) Self { /// Init a new literal element. /// The literal content will never be escaped, see initLitEscaped if you want to escape it. /// It always duplicates strings. -pub fn initLit(alloc: Allocator, literal: []const u8) !Self { +pub fn initLit(alloc: Allocator, literal: []const u8) Error!Self { var v = Self{ .kind = .literal, .arena = .init(alloc), @@ -61,7 +63,7 @@ pub fn initLit(alloc: Allocator, literal: []const u8) !Self { /// Init a new literal element that is escaped. /// The literal content will be escaped, see initLit if you don't want this behavior. /// It always duplicates strings. -pub fn initLitEscaped(alloc: Allocator, literal: []const u8) !Self { +pub fn initLitEscaped(alloc: Allocator, literal: []const u8) Error!Self { const escaped = try html.escape(alloc, literal); defer alloc.free(escaped); return .initLit(alloc, escaped); @@ -78,7 +80,7 @@ pub fn deinit(self: *Self) void { self.arena.deinit(); } -pub fn render(self: *Self, alloc: Allocator) ![]const u8 { +pub fn render(self: *Self, alloc: Allocator) Error![]const u8 { const attr = try self.renderAttribute(alloc); defer if (attr) |it| alloc.free(it); var acc = try std.ArrayList(u8).initCapacity(alloc, self.content.items.len + if (self.literal) |it| it.len else 0); @@ -109,7 +111,7 @@ pub fn render(self: *Self, alloc: Allocator) ![]const u8 { return acc.toOwnedSlice(alloc); } -fn renderAttribute(self: *Self, alloc: Allocator) !?[]const u8 { +fn renderAttribute(self: *Self, alloc: Allocator) Error!?[]const u8 { const class = try self.renderClass(alloc); defer if (class) |it| alloc.free(it); if (class) |it| try self.setAttribute("class", it); @@ -131,7 +133,7 @@ fn renderAttribute(self: *Self, alloc: Allocator) !?[]const u8 { return try acc.toOwnedSlice(alloc); } -fn renderClass(self: *const Self, alloc: Allocator) !?[]const u8 { +fn renderClass(self: *const Self, alloc: Allocator) Error!?[]const u8 { var iter = self.class_list.iterator(); if (iter.len == 0) return null; const n = self.class_list.count(); @@ -145,7 +147,7 @@ fn renderClass(self: *const Self, alloc: Allocator) !?[]const u8 { return try acc.toOwnedSlice(alloc); } -pub fn setAttribute(self: *Self, k: []const u8, v: []const u8) !void { +pub fn setAttribute(self: *Self, k: []const u8, v: []const u8) Error!void { var alloc = self.arena.allocator(); try self.attributes.put(try alloc.dupe(u8, k), try alloc.dupe(u8, v)); } @@ -158,7 +160,7 @@ pub fn hasAttribute(self: *Self, k: []const u8) bool { return self.attributes.contains(k); } -pub fn appendClass(self: *Self, v: []const u8) !void { +pub fn appendClass(self: *Self, v: []const u8) Error!void { var alloc = self.arena.allocator(); try self.class_list.insert(try alloc.dupe(u8, v)); } @@ -171,31 +173,24 @@ pub fn removeClass(self: *Self, v: []const u8) void { self.class_list.remove(v); } -pub fn appendContent(self: *Self, content: Self) !void { +pub fn appendContent(self: *Self, content: Self) Error!void { const alloc = self.arena.allocator(); return self.content.append(alloc, content); } -pub fn initImg(alloc: Allocator, src: []const u8, alt: []const u8) !Self { +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) !Self { +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; } -/// Init a paragraph tag with an automatically escaped content. -pub fn initParagraph(alloc: Allocator, content: []const u8) !Self { - var el = try init(alloc, .content, "p"); - try el.appendContent(try initLitEscaped(alloc, content)); - return el; -} - fn doTest(alloc: Allocator, el: *Self, exp: []const u8) !void { const got = try el.render(alloc); defer alloc.free(got); @@ -229,7 +224,6 @@ test "content element" { const alloc = std.testing.allocator; var p = try init(alloc, .content, "p"); - defer p.deinit(); var content = try initLit(alloc, "hello world"); try p.appendContent(content); @@ -237,15 +231,10 @@ test "content element" { try doTest(alloc, &content, "hello world"); try doTest(alloc, &p, "<p>hello world</p>"); - var p_managed = try initParagraph(alloc, "hello world"); - defer p_managed.deinit(); - - try doTest(alloc, &p_managed, "<p>hello world</p>"); - var div = try init(alloc, .content, "div"); defer div.deinit(); try div.appendClass("foo-bar"); - try div.appendContent(try initParagraph(alloc, "hello world")); + try div.appendContent(p); try div.appendContent(try initImg(alloc, "example.org", "example")); try doTest(alloc, &div, "<div class=\"foo-bar\"><p>hello world</p><img src=\"example.org\" alt=\"example\"></div>"); diff --git a/src/dom/html.zig b/src/dom/html.zig index 1734c60..064ebb1 100644 --- a/src/dom/html.zig +++ b/src/dom/html.zig @@ -1,7 +1,9 @@ const std = @import("std"); const eql = std.mem.eql; -pub fn escape(alloc: std.mem.Allocator, v: []const u8) ![]const u8 { +pub const Error = error{InvalidUtf8} || std.mem.Allocator.Error; + +pub fn escape(alloc: std.mem.Allocator, v: []const u8) Error![]const u8 { var acc = try std.ArrayList(u8).initCapacity(alloc, v.len); errdefer acc.deinit(alloc); const view = try std.unicode.Utf8View.init(v); diff --git a/src/link.zig b/src/link.zig index 6da585f..85156dc 100644 --- a/src/link.zig +++ b/src/link.zig @@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator; const eql = std.mem.eql; const Token = @import("lexer/Token.zig"); const Lexer = @import("lexer/Lexer.zig"); -const Element = @import("dom/Element.zig"); +const Element = @import("Element.zig"); const content = @import("content.zig"); const testing = @import("testing.zig"); const doTest = testing.do; @@ -14,13 +14,9 @@ pub const Error = error{InvalidLink} || Lexer.Error || content.Error || Allocato 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.initLitEscaped(alloc, second); - errdefer in.deinit(); - var el = try Element.init(alloc, .content, "a"); - errdefer el.deinit(); - try el.appendContent(in); - try el.setAttribute("href", second); - return el; + var in = if (data.first) |first| first else (try Element.Literal.init(alloc, second)).element(); + errdefer in.deinit(alloc); + return (try Element.Link.init(alloc, in, data.second.?)).element(); } pub const Data = struct { @@ -32,11 +28,11 @@ 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.initLitEscaped(alloc, v.content); - return .{ .first = el, .second = null }; + const el = try Element.Literal.init(alloc, v.content); + return .{ .first = el.element(), .second = null }; } - var el = Element.initEmpty(alloc); - errdefer el.deinit(); + var el = try Element.Empty.init(alloc); + errdefer el.deinit(alloc); while (l.peek()) |next| { switch (next.kind) { .weak_delimiter, .strong_delimiter => return Error.InvalidLink, @@ -47,7 +43,7 @@ pub fn parseData(alloc: Allocator, l: *Lexer) Error!Data { }, else => { const in = try content.parse(alloc, l); - try el.appendContent(in); + try el.content.append(alloc, in); }, } } @@ -55,10 +51,15 @@ 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; - return .{ - .first = if (el.content.items.len > 0) el else null, + var res = Data{ + .first = el.element(), .second = href.content, }; + if (el.content.items.len == 0) { + res.first = null; + el.deinit(alloc); + } + return res; } test "parse links" { diff --git a/src/paragraph.zig b/src/paragraph.zig index 92c3792..8edfa12 100644 --- a/src/paragraph.zig +++ b/src/paragraph.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Token = @import("lexer/Token.zig"); const Lexer = @import("lexer/Lexer.zig"); -const Element = @import("dom/Element.zig"); +const Element = @import("Element.zig"); const parser = @import("parser.zig"); const link = @import("link.zig"); const content = @import("content.zig"); @@ -13,45 +13,46 @@ const doTestError = testing.doError; pub const Error = content.Error || link.Error || Lexer.Error || Allocator.Error; pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { - var el = try Element.init(alloc, .content, "p"); - errdefer el.deinit(); + var el = try Element.Paragraph.init(alloc); + errdefer el.deinit(alloc); while (l.peek()) |next| { switch (next.kind) { - // because nextKind returns only an hint for the next rune - .strong_delimiter => return el, + .strong_delimiter => return el.element(), .weak_delimiter => { l.consume(); - const future = l.peek() orelse return el; + const future = l.peek() orelse return el.element(); switch (future.kind) { - .literal, .italic, .code, .bold, .link => try el.appendContent(try Element.initLit(alloc, " ")), - else => return el, + .literal, .italic, .code, .bold, .link => { + try el.content.append(alloc, (try Element.Literal.init(alloc, " ")).element()); + }, + else => return el.element(), } }, - else => try el.appendContent(try parseLine(alloc, l)), + else => try el.content.append(alloc, try parseLine(alloc, l)), } } - return el; + return el.element(); } pub fn parseLine(alloc: Allocator, l: *Lexer) Error!Element { - var line = Element.initEmpty(alloc); - errdefer line.deinit(); + var line = try Element.Empty.init(alloc); + errdefer line.deinit(alloc); while (l.peek()) |next| { switch (next.kind) { - .weak_delimiter, .strong_delimiter => return line, + .weak_delimiter, .strong_delimiter => return line.element(), .link => { var el = try link.parse(alloc, l); - errdefer el.deinit(); - try line.appendContent(el); + errdefer el.deinit(alloc); + try line.content.append(alloc, el); }, else => { var el = try content.parse(alloc, l); - errdefer el.deinit(); - try line.appendContent(el); + errdefer el.deinit(alloc); + try line.content.append(alloc, el); }, } } - return line; + return line.element(); } test "parse paragraphs" { diff --git a/src/parser.zig b/src/parser.zig index dcbf236..639160b 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Token = @import("lexer/Token.zig"); const Lexer = @import("lexer/Lexer.zig"); -const Element = @import("dom/Element.zig"); +const Element = @import("Element.zig"); const paragraph = @import("paragraph.zig"); const title = @import("title.zig"); const link = @import("link.zig"); @@ -46,8 +46,7 @@ fn gen(parent: Allocator, l: *Lexer) Error![]const u8 { var res = try std.ArrayList(u8).initCapacity(parent, elements.items.len); errdefer res.deinit(parent); for (elements.items) |it| { - var v = it; - try res.appendSlice(parent, try v.render(alloc)); + try res.appendSlice(parent, try it.renderHTML(alloc)); } return res.toOwnedSlice(parent); } diff --git a/src/testing.zig b/src/testing.zig index 8d96808..0911438 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -1,14 +1,14 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Lexer = @import("lexer/Lexer.zig"); -const Element = @import("dom/Element.zig"); +const Element = @import("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 { var l = try Lexer.init(t); var p = try parse(alloc, &l); - defer p.deinit(); - const g = try p.render(alloc); + defer p.deinit(alloc); + const g = try p.renderHTML(alloc); defer alloc.free(g); std.testing.expect(std.mem.eql(u8, g, v)) catch |err| { std.debug.print("{s}\n", .{g}); diff --git a/src/title.zig b/src/title.zig index dc1113a..506af21 100644 --- a/src/title.zig +++ b/src/title.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Token = @import("lexer/Token.zig"); const Lexer = @import("lexer/Lexer.zig"); -const Element = @import("dom/Element.zig"); +const Element = @import("Element.zig"); const paragraph = @import("paragraph.zig"); const testing = @import("testing.zig"); const doTest = testing.do; @@ -12,23 +12,14 @@ pub const Error = error{InvalidTitleContent} || paragraph.Error || Lexer.Error; pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { const v = l.next().?; - var el = try Element.init(alloc, .content, switch (v.content.len) { - 1 => "h1", - 2 => "h2", - 3 => "h3", - 4 => "h4", - 5 => "h5", - 6 => "h6", - else => unreachable, - }); - errdefer el.deinit(); - try el.appendContent(paragraph.parseLine(alloc, l) catch |err| switch (err) { + const el = try Element.Title.init(alloc, @intCast(v.content.len), paragraph.parseLine(alloc, l) catch |err| switch (err) { paragraph.Error.IllegalPlacement => return Error.InvalidTitleContent, else => return err, }); - var next = l.next() orelse return el; + errdefer el.deinit(alloc); + var next = l.next() orelse return el.element(); if (!next.kind.isDelimiter()) return Error.InvalidTitleContent; - return el; + return el.element(); } test "parse title" { |
