diff --git a/src/pug.zig b/src/pug.zig index 8d9704d..a72f597 100644 --- a/src/pug.zig +++ b/src/pug.zig @@ -99,12 +99,17 @@ pub fn compile( source: []const u8, options: CompileOptions, ) CompileError!CompileResult { + // Create arena for entire compilation pipeline - all temporary allocations freed at once + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const temp_allocator = arena.allocator(); + var result = CompileResult{ .html = &[_]u8{}, }; // Stage 1: Lex the source - var lex_inst = Lexer.init(allocator, source, .{ + var lex_inst = Lexer.init(temp_allocator, source, .{ .filename = options.filename, }) catch { return error.LexerError; @@ -139,7 +144,7 @@ pub fn compile( // Stage 2: Strip comments var stripped = strip_comments.stripComments( - allocator, + temp_allocator, tokens, .{ .strip_unbuffered = options.strip_unbuffered_comments, @@ -149,10 +154,10 @@ pub fn compile( ) catch { return error.LexerError; }; - defer stripped.deinit(allocator); + defer stripped.deinit(temp_allocator); // Stage 3: Parse tokens to AST - var parse = Parser.init(allocator, stripped.tokens.items, options.filename, source); + var parse = Parser.init(temp_allocator, stripped.tokens.items, options.filename, source); defer parse.deinit(); const ast = parse.parse() catch { @@ -181,18 +186,18 @@ pub fn compile( return error.ParserError; }; defer { - ast.deinit(allocator); - allocator.destroy(ast); + ast.deinit(temp_allocator); + temp_allocator.destroy(ast); } // Stage 4: Link (resolve extends/blocks) - var link_result = linker.link(allocator, ast) catch { + var link_result = linker.link(temp_allocator, ast) catch { return error.LinkerError; }; - defer link_result.deinit(allocator); + defer link_result.deinit(temp_allocator); // Stage 5: Generate HTML - var compiler = Compiler.init(allocator, .{ + var compiler = Compiler.init(temp_allocator, .{ .pretty = options.pretty, .doctype = options.doctype, .debug = options.debug, @@ -203,7 +208,8 @@ pub fn compile( return error.CodegenError; }; - result.html = html; + // Dupe final HTML to base allocator before arena cleanup + result.html = try allocator.dupe(u8, html); return result; } diff --git a/src/template.zig b/src/template.zig index c278e63..b1fb370 100644 --- a/src/template.zig +++ b/src/template.zig @@ -59,29 +59,38 @@ pub const RenderContext = struct { /// Render a template with data pub fn renderWithData(allocator: Allocator, source: []const u8, data: anytype) ![]const u8 { + // Create arena for entire compilation pipeline - all temporary allocations freed at once + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const temp_allocator = arena.allocator(); + // Lex - var lex = pug.lexer.Lexer.init(allocator, source, .{}) catch return error.OutOfMemory; + var lex = pug.lexer.Lexer.init(temp_allocator, source, .{}) catch return error.OutOfMemory; defer lex.deinit(); const tokens = lex.getTokens() catch return error.LexerError; // Strip comments - var stripped = pug.strip_comments.stripComments(allocator, tokens, .{}) catch return error.OutOfMemory; - defer stripped.deinit(allocator); + var stripped = pug.strip_comments.stripComments(temp_allocator, tokens, .{}) catch return error.OutOfMemory; + defer stripped.deinit(temp_allocator); // Parse - var pug_parser = pug.parser.Parser.init(allocator, stripped.tokens.items, null, source); + var pug_parser = pug.parser.Parser.init(temp_allocator, stripped.tokens.items, null, source); defer pug_parser.deinit(); const ast = pug_parser.parse() catch { return error.ParserError; }; defer { - ast.deinit(allocator); - allocator.destroy(ast); + ast.deinit(temp_allocator); + temp_allocator.destroy(ast); } - return renderAst(allocator, ast, data); + // Render to temporary buffer + const html = try renderAst(temp_allocator, ast, data); + + // Dupe final HTML to base allocator before arena cleanup + return allocator.dupe(u8, html); } /// Render a pre-parsed AST with data. Use this for better performance when @@ -144,6 +153,7 @@ pub fn parse(allocator: Allocator, source: []const u8) !*Node { /// AST string values are slices into normalized_source, so it must stay alive. /// Caller must call result.deinit(allocator) when done. pub fn parseWithSource(allocator: Allocator, source: []const u8) !ParseResult { + // Note: Cannot use ArenaAllocator here since returned AST must outlive function scope // Lex var lex = pug.lexer.Lexer.init(allocator, source, .{}) catch return error.OutOfMemory; errdefer lex.deinit();