From 3b0e9424a66058da82d11d432da886ec7b6ce7eb Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Mon, 27 Apr 2026 17:45:13 +0200 Subject: perf(eval): reduce memory syscall --- src/code.zig | 1 - src/content.zig | 2 -- src/eval/Element.zig | 43 ++++------------------------------------ src/eval/Image.zig | 16 ++++----------- src/eval/Root.zig | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/eval/Title.zig | 12 +----------- src/eval/blocks.zig | 26 ++----------------------- src/eval/list.zig | 15 ++------------ src/eval/paragraph.zig | 17 ++++------------ src/link.zig | 16 ++++----------- src/list.zig | 2 -- src/paragraph.zig | 14 ++----------- src/parser.zig | 32 ++++++------------------------ src/testing.zig | 14 +++++++++---- src/title.zig | 1 - 15 files changed, 92 insertions(+), 172 deletions(-) create mode 100644 src/eval/Root.zig diff --git a/src/code.zig b/src/code.zig index 7b7d23a..5c5c0f2 100644 --- a/src/code.zig +++ b/src/code.zig @@ -25,7 +25,6 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { const code = try Element.Code.init(alloc); code.attribute = data; const el = try Element.Figure.init(alloc, code.element()); - errdefer el.deinit(alloc); while (l.next()) |it| { if (it.kind == .code_block) return Error.InvalidCodeBlock; if (it.kind.isDelimiter()) { diff --git a/src/content.zig b/src/content.zig index b1ac0c7..96a361a 100644 --- a/src/content.zig +++ b/src/content.zig @@ -13,7 +13,6 @@ pub const Error = error{ ModifierNotClosed, IllegalPlacement } || Allocator.Erro pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { var content = try Element.Empty.init(alloc); - errdefer content.deinit(alloc); const v = l.next().?; switch (v.kind) { .literal => { @@ -30,7 +29,6 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { fn parseModifier(alloc: Allocator, l: *Lexer, knd: Token.Kind, comptime tag: []const u8) Error!Element { var el = try Element.Simple(tag).init(alloc); - errdefer el.deinit(alloc); while (l.peek()) |next| { if (next.kind == knd) { // consuming the finisher diff --git a/src/eval/Element.zig b/src/eval/Element.zig index 265ccf1..72b6d7d 100644 --- a/src/eval/Element.zig +++ b/src/eval/Element.zig @@ -5,6 +5,7 @@ pub const paragraph = @import("paragraph.zig"); pub const Title = @import("Title.zig"); pub const list = @import("list.zig"); pub const Image = @import("Image.zig"); +pub const Root = @import("Root.zig"); const blocks = @import("blocks.zig"); pub const Code = blocks.Code; pub const Figure = blocks.Figure; @@ -12,7 +13,6 @@ pub const Figure = blocks.Figure; const Element = @This(); vtable: struct { - deinit: *const fn (*anyopaque, Allocator) void, html: *const fn (*anyopaque, Allocator) HTML.Error!HTML, }, ptr: *anyopaque, @@ -24,10 +24,6 @@ pub fn renderHTML(self: Element, alloc: Allocator) HTML.Error![]const u8 { return el.render(alloc); } -pub fn deinit(self: Element, alloc: Allocator) void { - self.vtable.deinit(self.ptr, alloc); -} - pub fn html(self: Element, alloc: Allocator) HTML.Error!HTML { return self.vtable.html(self.ptr, alloc); } @@ -44,18 +40,7 @@ pub const Empty = struct { } pub fn element(self: *Self) Element { - return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = Self.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)); - for (self.content.items) |it| it.deinit(alloc); - self.content.deinit(alloc); - alloc.destroy(self); + return .{ .ptr = self, .vtable = .{ .html = Self.html } }; } fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { @@ -79,16 +64,7 @@ pub const Literal = struct { } pub fn element(self: *Self) Element { - return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = Self.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)); - alloc.destroy(self); + return .{ .ptr = self, .vtable = .{ .html = Self.html } }; } fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { @@ -110,11 +86,7 @@ pub fn Simple(comptime tag: []const u8) type { } pub fn element(self: *Self) Element { - return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = Self.html } }; - } - - pub fn deinit(self: *Self, alloc: Allocator) void { - destroy(self, alloc); + return .{ .ptr = self, .vtable = .{ .html = Self.html } }; } pub fn toTag(self: *Self, alloc: Allocator, comptime target: []const u8) !*Simple(target) { @@ -135,13 +107,6 @@ pub fn Simple(comptime tag: []const u8) type { alloc.destroy(self); } - 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 html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { const self: *Self = @ptrCast(@alignCast(context)); var el = try HTML.Content.init(alloc, tag); diff --git a/src/eval/Image.zig b/src/eval/Image.zig index aa30585..53478b6 100644 --- a/src/eval/Image.zig +++ b/src/eval/Image.zig @@ -17,16 +17,7 @@ pub fn init(alloc: Allocator, src: []const u8) !*Self { } 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)); - alloc.destroy(self); + return .{ .ptr = self, .vtable = .{ .html = html } }; } fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { @@ -38,12 +29,13 @@ fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { } test "html" { - const alloc = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var alloc = arena.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, "")); diff --git a/src/eval/Root.zig b/src/eval/Root.zig new file mode 100644 index 0000000..1834dd0 --- /dev/null +++ b/src/eval/Root.zig @@ -0,0 +1,53 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Arena = std.heap.ArenaAllocator; +const HTML = @import("html/Element.zig"); +const Element = @import("Element.zig"); + +const Self = @This(); + +content: std.ArrayList(Element), +arena: Arena, + +pub fn init(parent: Allocator) !*Self { + var s = Self{ + .content = undefined, + .arena = .init(parent), + }; + var alloc = s.arena.allocator(); + s.content = try .initCapacity(alloc, 2); + const v = try alloc.create(Self); + v.* = s; + return v; +} + +pub fn deinit(self: *Self) void { + self.arena.deinit(); +} + +pub fn allocator(self: *Self) Allocator { + return self.arena.allocator(); +} + +pub fn append(self: *Self, el: Element) !void { + try self.content.append(self.allocator(), el); +} + +pub fn element(self: *Self) Element { + return .{ .vtable = .{ .html = html }, .ptr = self }; +} + +pub fn renderHTML(self: *Self, alloc: Allocator) HTML.Error![]const u8 { + return try self.element().renderHTML(alloc); +} + +fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { + const self: *Self = @ptrCast(@alignCast(context)); + const el = try HTML.Root.init(alloc); + errdefer el.deinit(); + if (self.content.items.len == 0) return el.element(); + for (self.content.items) |it| { + try el.append(try it.html(el.allocator())); + } + return el.element(); +} diff --git a/src/eval/Title.zig b/src/eval/Title.zig index 2e89953..ebb7fa2 100644 --- a/src/eval/Title.zig +++ b/src/eval/Title.zig @@ -15,17 +15,7 @@ pub fn init(alloc: Allocator, level: u3, content: Parent) !*Self { } pub fn element(self: *Self) Parent { - return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = html } }; -} - -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); + return .{ .ptr = self, .vtable = .{ .html = html } }; } fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { diff --git a/src/eval/blocks.zig b/src/eval/blocks.zig index 63a8f12..63c0291 100644 --- a/src/eval/blocks.zig +++ b/src/eval/blocks.zig @@ -16,18 +16,7 @@ pub const Code = struct { } pub fn element(self: *Self) Element { - return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = Self.html } }; - } - - 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); + return .{ .ptr = self, .vtable = .{ .html = Self.html } }; } fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { @@ -54,18 +43,7 @@ pub const Figure = struct { } pub fn element(self: *Self) Element { - return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = Self.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)); - self.content.deinit(alloc); - if (self.caption) |cap| cap.deinit(alloc); - alloc.destroy(self); + return .{ .ptr = self, .vtable = .{ .html = Self.html } }; } fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { diff --git a/src/eval/list.zig b/src/eval/list.zig index 08f180a..06b7af7 100644 --- a/src/eval/list.zig +++ b/src/eval/list.zig @@ -18,21 +18,10 @@ fn List(comptime tag: []const u8) type { } pub fn element(self: *Self) Element { - return .{ .ptr = self, .vtable = .{ .deinit = destroy, .html = html } }; + return .{ .ptr = self, .vtable = .{ .html = html } }; } - 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 html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { + fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { const self: *Self = @ptrCast(@alignCast(context)); var el = try HTML.Content.init(alloc, tag); for (self.content.items) |it| { diff --git a/src/eval/paragraph.zig b/src/eval/paragraph.zig index b076081..e57f995 100644 --- a/src/eval/paragraph.zig +++ b/src/eval/paragraph.zig @@ -26,17 +26,7 @@ pub const Link = struct { } 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 { - var self: *Self = @ptrCast(@alignCast(context)); - self.content.deinit(alloc); - alloc.destroy(self); + return .{ .ptr = self, .vtable = .{ .html = html } }; } fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML { @@ -59,14 +49,15 @@ fn doTest(alloc: Allocator, el: Element, exp: []const u8) !void { } test "paragraph" { - const alloc = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); const lit = (try Element.Literal.init(alloc, "hello world")).element(); try doTest(alloc, lit, "hello world"); var p = try Block.init(alloc); try p.content.append(alloc, lit); - defer p.deinit(alloc); try doTest(alloc, p.element(), "

hello world

"); const link = (try Link.init(alloc, (try Element.Literal.init(alloc, "foo")).element(), "example.org")).element(); diff --git a/src/link.zig b/src/link.zig index ef92b1c..07cbf95 100644 --- a/src/link.zig +++ b/src/link.zig @@ -18,7 +18,6 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { if (v.kind != .link) return Error.InvalidLink; 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| switch (next.kind) { .weak_delimiter, .strong_delimiter => return Error.InvalidLink, .link => { @@ -35,14 +34,10 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { if (href.kind != .literal) return Error.InvalidLink; const finisher = l.next() orelse return Error.InvalidLink; if (!finisher.equals(.link, ")")) return Error.InvalidLink; - 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(); - } - errdefer in.deinit(alloc); + const in: Element = if (el.content.items.len > 0) + el.element() + else + (try Element.Literal.init(alloc, href.content)).element(); return (try Link.init(alloc, in, href.content)).element(); } @@ -69,10 +64,8 @@ pub fn parseImage(alloc: Allocator, l: *Lexer) ImageError!Element { it = l.next() orelse return ImageError.InvalidImage; if (!it.equals(.link, ")")) return ImageError.InvalidImage; const img = try Element.Image.init(alloc, src); - errdefer img.deinit(alloc); img.alt = alt; const el = try Element.Figure.init(alloc, img.element()); - errdefer el.deinit(alloc); it = l.peek() orelse return el.element(); switch (it.kind) { .strong_delimiter => return el.element(), @@ -80,7 +73,6 @@ pub fn parseImage(alloc: Allocator, l: *Lexer) ImageError!Element { else => return ImageError.InvalidImage, } const p = try paragraph.parse(alloc, l); - errdefer p.deinit(alloc); const p_el: *Element.paragraph.Block = @ptrCast(@alignCast(p.ptr)); el.caption = (try p_el.toEmpty(alloc)).element(); return el.element(); diff --git a/src/list.zig b/src/list.zig index 0facef9..fe3a246 100644 --- a/src/list.zig +++ b/src/list.zig @@ -12,14 +12,12 @@ 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) Error!Element { const el = try Element.list.Unordored.init(alloc); - errdefer el.deinit(alloc); try parse(alloc, &el.content, l, .list_unordored); return el.element(); } diff --git a/src/paragraph.zig b/src/paragraph.zig index b50b2ec..e1a01cf 100644 --- a/src/paragraph.zig +++ b/src/paragraph.zig @@ -15,7 +15,6 @@ 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); - errdefer el.deinit(alloc); while (l.peek()) |next| switch (next.kind) { .strong_delimiter => return el.element(), .weak_delimiter => { @@ -31,19 +30,10 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { pub fn parseLine(alloc: Allocator, l: *Lexer) Error!Element { var line = try Element.Empty.init(alloc); - errdefer line.deinit(alloc); while (l.peek()) |next| switch (next.kind) { .weak_delimiter, .strong_delimiter => return line.element(), - .link => { - var el = try link.parse(alloc, l); - errdefer el.deinit(alloc); - try line.content.append(alloc, el); - }, - else => { - var el = try content.parse(alloc, l); - errdefer el.deinit(alloc); - try line.content.append(alloc, el); - }, + .link => try line.content.append(alloc, try link.parse(alloc, l)), + else => try line.content.append(alloc, try content.parse(alloc, l)), }; return line.element(); } diff --git a/src/parser.zig b/src/parser.zig index c35d47f..9d89d7d 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -19,25 +19,7 @@ pub const Error = error{FeatureNotSupported} || code.Error || Allocator.Error; -pub const Document = struct { - arena: std.heap.ArenaAllocator, - root: []Element, - - pub fn renderHTML(self: @This(), alloc: Allocator) Element.HTML.Error![]const u8 { - var content = try std.ArrayList(u8).initCapacity(alloc, self.root.len * 6); - errdefer content.deinit(alloc); - for (self.root) |it| { - const v = try it.renderHTML(alloc); - defer alloc.free(v); - try content.appendSlice(alloc, v); - } - return content.toOwnedSlice(alloc); - } - - pub fn deinit(self: @This()) void { - self.arena.deinit(); - } -}; +pub const Document = *Element.Root; pub fn parseReader(parent: Allocator, r: *std.io.Reader) !Document { var l = try Lexer.initReader(parent, r); @@ -51,13 +33,11 @@ pub fn parse(parent: Allocator, content: []const u8) Error!Document { } fn gen(parent: Allocator, l: *Lexer) Error!Document { - var arena = std.heap.ArenaAllocator.init(parent); - const alloc = arena.allocator(); - errdefer arena.deinit(); - - var elements = try std.ArrayList(Element).initCapacity(alloc, 2); + var root = try Element.Root.init(parent); + errdefer root.deinit(); + const alloc = root.allocator(); base: while (l.peek()) |it| { - try elements.append(alloc, switch (it.kind) { + try root.append(switch (it.kind) { // other blocks .title => try title.parse(alloc, l), .list_ordored => try list.parseOrdored(alloc, l), @@ -76,7 +56,7 @@ fn gen(parent: Allocator, l: *Lexer) Error!Document { return Error.FeatureNotSupported, }); } - return .{ .root = try elements.toOwnedSlice(alloc), .arena = arena }; + return root; } fn doTest(alloc: Allocator, t: []const u8, v: []const u8) !void { diff --git a/src/testing.zig b/src/testing.zig index 6d19e0e..bf0690e 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -4,10 +4,13 @@ const Lexer = @import("lexer/Lexer.zig"); const Element = @import("eval/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, parent: Allocator, t: []const u8, v: []const u8) !void { + var arena = std.heap.ArenaAllocator.init(parent); + defer arena.deinit(); + var alloc = arena.allocator(); + var l = try Lexer.init(t); var p = try parse(alloc, &l); - 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| { @@ -16,8 +19,11 @@ pub fn do(comptime parse: fn (Allocator, *Lexer) parser.Error!Element, alloc: Al }; } -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, parent: Allocator, t: []const u8, err: parser.Error) !void { + var arena = std.heap.ArenaAllocator.init(parent); + defer arena.deinit(); + var l = try Lexer.init(t); - _ = parse(alloc, &l) catch |e| return std.testing.expect(err == e); + _ = parse(arena.allocator(), &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 87d7f5f..cf432b4 100644 --- a/src/title.zig +++ b/src/title.zig @@ -16,7 +16,6 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { paragraph.Error.IllegalPlacement => return Error.InvalidTitleContent, else => return err, }); - errdefer el.deinit(alloc); var next = l.next() orelse return el.element(); if (!next.kind.isDelimiter()) return Error.InvalidTitleContent; return el.element(); -- cgit v1.2.3