const std = @import("std"); pub const NodeType = enum { text, variable, tag, if_block, for_block, include, with_block, now, extends, block, super, filter_block, autoescape, spaceless, url, }; pub const AutoescapeNode = struct { body: []Node, raw_open: []const u8, raw_close: []const u8, enabled: bool, }; pub const UrlNode = struct { name: []const u8, args: []const []const u8, }; pub const SpacelessNode = struct { body: []Node, raw_open: []const u8, raw_close: []const u8, }; pub const FilterBlockNode = struct { filters: []const u8, // "upper|escape|truncatewords:30" body: []Node, raw_open: []const u8, raw_close: []const u8, }; pub const Filter = struct { name: []const u8, arg: ?[]const u8 = null, // null ou string com argumento }; pub const VariableNode = struct { expr: []const u8, filters: []const Filter, }; pub const NowNode = struct { format: []const u8, }; pub const ExtendsNode = struct { parent_name: []const u8, }; pub const BlockNode = struct { name: []const u8, body: []Node, raw_open: []const u8, raw_close: []const u8, }; pub const Assignment = struct { key: []const u8, value_expr: []const u8, is_literal: bool, }; pub const WithNode = struct { assignments: []const Assignment, body: []Node, raw_open: []const u8, raw_close: []const u8, }; pub const IncludeNode = struct { template_name: []const u8, }; pub const TextNode = struct { content: []const u8, }; pub const TagNode = struct { name: []const u8, args: []const u8, raw: []const u8, }; pub const IfNode = struct { condition: []const u8, // dupe esse sim true_body: []Node, false_body: []Node, raw_open: []const u8, // slice original, NÃO free raw_close: []const u8, // slice original, NÃO free }; pub const ForNode = struct { loop_var: []const u8, iterable: []const u8, body: []Node, empty_body: []Node, raw_open: []const u8, raw_close: []const u8, }; pub const Node = struct { type: NodeType, text: ?TextNode = null, variable: ?VariableNode = null, tag: ?TagNode = null, @"if": ?IfNode = null, @"for": ?ForNode = null, include: ?IncludeNode = null, with: ?WithNode = null, now: ?NowNode = null, extends: ?ExtendsNode = null, block: ?BlockNode = null, super: bool = false, // para {{ block.super }} filter_block: ?FilterBlockNode = null, autoescape: ?AutoescapeNode = null, spaceless: ?SpacelessNode = null, url: ?UrlNode = null, pub fn deinit(self: Node, allocator: std.mem.Allocator) void { switch (self.type) { .text => if (self.text) |t| allocator.free(t.content), .variable => if (self.variable) |v| { allocator.free(v.expr); for (v.filters) |f| { allocator.free(f.name); if (f.arg) |a| allocator.free(a); } allocator.free(v.filters); }, .tag => if (self.tag) |t| { allocator.free(t.name); allocator.free(t.args); }, .if_block => if (self.@"if") |ib| { allocator.free(ib.condition); const true_copy = ib.true_body; for (true_copy) |n| n.deinit(allocator); allocator.free(ib.true_body); const false_copy = ib.false_body; for (false_copy) |n| n.deinit(allocator); allocator.free(ib.false_body); }, .for_block => if (self.@"for") |fb| { allocator.free(fb.loop_var); allocator.free(fb.iterable); const body_copy = fb.body; for (body_copy) |n| n.deinit(allocator); allocator.free(fb.body); const empty_copy = fb.empty_body; for (empty_copy) |n| n.deinit(allocator); allocator.free(fb.empty_body); }, .with_block => if (self.with) |w| { for (w.assignments) |a| { allocator.free(a.key); allocator.free(a.value_expr); } allocator.free(w.assignments); const body_copy = w.body; for (body_copy) |n| n.deinit(allocator); allocator.free(w.body); }, .block => if (self.block) |b| { allocator.free(b.name); const body_copy = b.body; for (body_copy) |n| n.deinit(allocator); allocator.free(b.body); }, .filter_block => if (self.filter_block) |fb| { allocator.free(fb.filters); const body_copy = fb.body; for (body_copy) |n| n.deinit(allocator); allocator.free(fb.body); }, .autoescape => if (self.autoescape) |ae| { const body_copy = ae.body; for (body_copy) |n| n.deinit(allocator); allocator.free(ae.body); }, .spaceless => if (self.spaceless) |sl| { const body_copy = sl.body; for (body_copy) |n| n.deinit(allocator); allocator.free(sl.body); }, .include => if (self.include) |inc| allocator.free(inc.template_name), .now => if (self.now) |n| allocator.free(n.format), .extends => if (self.extends) |e| allocator.free(e.parent_name), .super => {}, .url => if (self.url) |u| { allocator.free(u.name); for (u.args) |a| allocator.free(a); allocator.free(u.args); }, } } }; pub const Parser = struct { template: []const u8, pos: usize = 0, pub fn init(template: []const u8) Parser { return .{ .template = template }; } fn advance(self: *Parser, n: usize) void { self.pos += n; if (self.pos > self.template.len) self.pos = self.template.len; } fn peek(self: Parser, comptime n: usize) ?[]const u8 { if (self.pos + n > self.template.len) return null; return self.template[self.pos .. self.pos + n]; } fn skipWhitespace(self: *Parser) void { while (self.pos < self.template.len and std.ascii.isWhitespace(self.template[self.pos])) : (self.advance(1)) {} } fn parseAssignments( self: *Parser, allocator: std.mem.Allocator, args: []const u8, ) ![]const Assignment { var list = std.ArrayList(Assignment){}; defer list.deinit(allocator); _ = self; var i: usize = 0; while (i < args.len) { // Pula whitespaces iniciais while (i < args.len and std.mem.indexOfScalar(u8, " \t\r\n", args[i]) != null) : (i += 1) {} if (i >= args.len) break; // Parse key (até '=') const key_start = i; while (i < args.len and args[i] != '=') : (i += 1) {} if (i >= args.len or args[i] != '=') return error.InvalidAssignmentSyntax; const key = std.mem.trim(u8, args[key_start..i], " \t\r\n"); if (key.len == 0) return error.InvalidAssignmentSyntax; i += 1; // Pula '=' // Pula whitespaces após '=' while (i < args.len and std.mem.indexOfScalar(u8, " \t\r\n", args[i]) != null) : (i += 1) {} // Parse value: se começa com ", parse até próximo " não escapado; senão, até próximo espaço const value_start = i; var in_quote = false; if (i < args.len and args[i] == '"') { in_quote = true; i += 1; // Pula aspa inicial } while (i < args.len) { if (in_quote) { if (args[i] == '"' and (i == 0 or args[i - 1] != '\\')) break; // Fecha aspa não escapada } else { if (std.mem.indexOfScalar(u8, " \t\r\n", args[i]) != null) break; // Fim sem quote } i += 1; } const value_end = i; var value = args[value_start..value_end]; if (in_quote) { if (i >= args.len or args[i] != '"') return error.UnclosedQuoteInAssignment; i += 1; // Pula aspa final value = args[value_start + 1 .. value_end]; // Remove aspas // TODO: Se precisar, handle escapes como \" aqui (remova \\ antes de ") } else { value = std.mem.trim(u8, value, " \t\r\n"); } try list.append(allocator, .{ .key = try allocator.dupe(u8, key), .value_expr = try allocator.dupe(u8, value), .is_literal = in_quote, }); } return try list.toOwnedSlice(allocator); } fn parseAutoescapeBlock(self: *Parser, allocator: std.mem.Allocator, enabled: bool, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { try body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { try body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { const tag_name = tag_node.tag.?.name; if (std.mem.eql(u8, tag_name, "autoescape")) { depth += 1; try body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "endautoescape")) { depth -= 1; const raw_close = tag_node.tag.?.raw; allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); if (depth == 0) { return Node{ .type = .autoescape, .autoescape = .{ .enabled = enabled, .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } try body.append(allocator, tag_node); continue; } try body.append(allocator, tag_node); } else { self.advance(1); } } return error.UnclosedBlock; } fn parseVerbatim(self: *Parser, allocator: std.mem.Allocator) !Node { const start = self.pos; var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "{%")) { self.advance(2); self.skipWhitespace(); const content_start = self.pos; while (self.pos < self.template.len and !std.mem.eql(u8, self.peek(2) orelse "", "%}")) : (self.advance(1)) {} if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "%}")) { return error.UnclosedTag; } const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n"); if (std.mem.eql(u8, inner, "verbatim")) { depth += 1; } else if (std.mem.eql(u8, inner, "endverbatim")) { depth -= 1; if (depth == 0) { // Copia até o início da tag endverbatim const content_end = content_start - 3; // retrocede "{% " const content = try allocator.dupe(u8, self.template[start..content_end]); self.advance(2); // consome %} return Node{ .type = .text, .text = .{ .content = content }, }; } } self.advance(2); continue; } } self.advance(1); } return error.UnclosedVerbatim; } fn parseSpacelessBlock(self: *Parser, allocator: std.mem.Allocator, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { // Adiciona texto (mesmo vazio — o renderer vai limpar depois) try body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { try body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { const tag_name = tag_node.tag.?.name; if (std.mem.eql(u8, tag_name, "spaceless")) { depth += 1; // Ignora a tag open aninhada allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); continue; } if (std.mem.eql(u8, tag_name, "endspaceless")) { depth -= 1; const raw_close = tag_node.tag.?.raw; allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); if (depth == 0) { return Node{ .type = .spaceless, .spaceless = .{ .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } // depth > 0: endspaceless aninhado — IGNORA a tag // NÃO adicione ao body continue; } try body.append(allocator, tag_node); } else { self.advance(1); } } return error.UnclosedBlock; } fn parseFilterBlock(self: *Parser, allocator: std.mem.Allocator, filters_raw: []const u8, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { try body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { try body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { const tag_name = tag_node.tag.?.name; if (std.mem.eql(u8, tag_name, "filter")) { depth += 1; try body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "endfilter")) { depth -= 1; const raw_close = tag_node.tag.?.raw; allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); if (depth == 0) { // const filters = try allocator.dupe(u8, filters_raw); return Node{ .type = .filter_block, .filter_block = .{ .filters = filters_raw, // já duped .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } try body.append(allocator, tag_node); continue; } try body.append(allocator, tag_node); } else { self.advance(1); } } return error.UnclosedBlock; } fn parseBlockBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { try body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { if (node.variable) |v| { if (std.mem.eql(u8, v.expr, "block.super")) { try body.append(allocator, Node{ .type = .super, .super = true }); allocator.free(v.expr); continue; } } try body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { const tag_name = tag_node.tag.?.name; if (std.mem.eql(u8, tag_name, "block")) { depth += 1; try body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "endblock")) { depth -= 1; const raw_close = tag_node.tag.?.raw; allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); if (depth == 0) { return Node{ .type = .block, .block = .{ .name = name, .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } try body.append(allocator, tag_node); continue; } try body.append(allocator, tag_node); } else { self.advance(1); } } return error.UnclosedBlock; } fn parseWithBlock(self: *Parser, allocator: std.mem.Allocator, assignments: []const Assignment, raw_open: []const u8) !Node { std.debug.print("Vou verificar se sou bloco with\n", .{}); var body = std.ArrayList(Node){}; defer body.deinit(allocator); var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); try body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); try body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { const tag_name = tag_node.tag.?.name; if (std.mem.eql(u8, tag_name, "with")) { depth += 1; try body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "endwith")) { depth -= 1; const raw_close = tag_node.tag.?.raw; allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); if (depth == 0) { // para fins de debug std.debug.print("2.4 - Encontrei um bloco with:\n - assignments: {any}\n - body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ assignments, body.items, raw_open, raw_close, }); // fim para fins de debug return Node{ .type = .with_block, .with = .{ .assignments = assignments, .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } try body.append(allocator, tag_node); continue; } try body.append(allocator, tag_node); } else { self.advance(1); } } return error.UnclosedBlock; } fn parseComment(self: *Parser) !void { // Consome a tag open {% comment %} // Já estamos após o %}, então só avançamos var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "{%")) { self.advance(2); self.skipWhitespace(); const content_start = self.pos; while (self.pos < self.template.len) : (self.advance(1)) { if (self.peek(2)) |closing| { if (std.mem.eql(u8, closing, "%}")) break; } } if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "%}")) { return error.UnclosedTag; } const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n"); if (std.mem.eql(u8, inner, "comment")) { depth += 1; } else if (std.mem.eql(u8, inner, "endcomment")) { depth -= 1; if (depth == 0) { self.advance(2); // consome %} return; } } self.advance(2); continue; } } self.advance(1); } return error.UnclosedComment; } fn parseText(self: *Parser, allocator: std.mem.Allocator) !?Node { std.debug.print("2.0 - Vou verificar se sou texto\n", .{}); const start = self.pos; std.debug.print("2.1 - meu start é {d}\n", .{start}); while (self.pos < self.template.len) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "{{") or std.mem.eql(u8, p, "{%")) { std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei {{{{ ou {{%, então parei\n", .{}); break; } } self.advance(1); } if (self.pos == start) return null; const content = try allocator.dupe(u8, self.template[start..self.pos]); std.debug.print("2.2 - meu content é \'{s}\'\n", .{content}); return Node{ .type = .text, .text = .{ .content = content }, }; } fn parseVariable(self: *Parser, allocator: std.mem.Allocator) !?Node { std.debug.print("2.0 - Vou verificar se sou variável\n", .{}); std.debug.print("2.1 - meu start é {d}\n", .{self.pos}); if (self.peek(2)) |p| { if (!std.mem.eql(u8, p, "{{")) return null; } else return null; self.advance(2); self.skipWhitespace(); const expr_start = self.pos; while (self.pos < self.template.len) : (self.advance(1)) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "}}")) break; std.debug.print("2.1 - fiz o peek de 2 em 2 até que achei {{{{\n", .{}); } } std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei }}}}\n", .{}); if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "}}")) { std.debug.print("2.3 - deu ruim achei uma variável que não fecha!\n", .{}); return error.UnclosedVariable; } const full_expr = std.mem.trim(u8, self.template[expr_start..self.pos], " \t\r\n"); self.advance(2); // Separar expr e filtros var filters = std.ArrayList(Filter){}; defer filters.deinit(allocator); var expr_end = full_expr.len; var pipe_pos = std.mem.lastIndexOfScalar(u8, full_expr, '|'); while (pipe_pos) |pos| { const filter_part = std.mem.trim(u8, full_expr[pos + 1 .. expr_end], " \t\r\n"); expr_end = pos; const colon_pos = std.mem.indexOfScalar(u8, filter_part, ':'); const filter_name = if (colon_pos) |cp| std.mem.trim(u8, filter_part[0..cp], " \t\r\n") else filter_part; const filter_arg = if (colon_pos) |cp| std.mem.trim(u8, filter_part[cp + 1 ..], " \"") else null; try filters.append(allocator, .{ .name = try allocator.dupe(u8, filter_name), .arg = if (filter_arg) |a| try allocator.dupe(u8, a) else null, }); pipe_pos = std.mem.lastIndexOfScalar(u8, full_expr[0..expr_end], '|'); } const base_expr = std.mem.trim(u8, full_expr[0..expr_end], " \t\r\n"); const duped_expr = try allocator.dupe(u8, base_expr); // Inverte os filters (porque usamos lastIndexOf) std.mem.reverse(Filter, filters.items); std.debug.print("2.3 - meu conteúdo:\n - expr: \'{s}\' \n - filters: {any}\n", .{ duped_expr, filters }); return Node{ .type = .variable, .variable = .{ .expr = duped_expr, .filters = try filters.toOwnedSlice(allocator), }, }; } fn parseTag(self: *Parser, allocator: std.mem.Allocator) !?Node { std.debug.print("2.0 - Vou verificar se sou uma tag\n", .{}); std.debug.print("2.1 - meu start é {d}\n", .{self.pos}); if (self.peek(2)) |p| { if (!std.mem.eql(u8, p, "{%")) return null; std.debug.print("2.1 - fiz o peek de 2 em 2 até que achei {{%\n", .{}); } else return null; const raw_start = self.pos; self.advance(2); self.skipWhitespace(); const content_start = self.pos; while (self.pos < self.template.len) : (self.advance(1)) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "%}")) break; } } std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei %}}\n", .{}); if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "%}")) { return error.UnclosedTag; } const raw_slice = self.template[raw_start .. self.pos + 2]; const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n"); const space_idx = std.mem.indexOfScalar(u8, inner, ' ') orelse inner.len; const name_raw = inner[0..space_idx]; const args_raw = if (space_idx < inner.len) std.mem.trim(u8, inner[space_idx + 1 ..], " \t\r\n") else ""; const name = try allocator.dupe(u8, name_raw); const args = try allocator.dupe(u8, args_raw); std.debug.print("2.3 - meu node:\n - nome: {s}\n - args: {s}\n - raw: {s}\n", .{ name, args, raw_slice }); self.advance(2); return Node{ .type = .tag, .tag = .{ .name = name, .args = args, .raw = raw_slice, // slice original, sem dupe }, }; } fn parseIfBlock(self: *Parser, allocator: std.mem.Allocator, condition: []const u8, raw_open: []const u8) !Node { std.debug.print("Vou verificar se sou bloco\n", .{}); var true_body = std.ArrayList(Node){}; defer true_body.deinit(allocator); var false_body = std.ArrayList(Node){}; defer false_body.deinit(allocator); var current_body = &true_body; var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); try current_body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); try current_body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { const tag_name = tag_node.tag.?.name; if (std.mem.eql(u8, tag_name, "if")) { depth += 1; try current_body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "endif")) { depth -= 1; const raw_close = tag_node.tag.?.raw; // Libera name e args da tag endif allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); if (depth == 0) { std.debug.print("2.4 - Encontrei um bloco if:\n - condition: {s}\n - true_body: {any}\n - false_body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ condition, true_body.items, false_body.items, raw_open, raw_close, }); return Node{ .type = .if_block, .@"if" = .{ .condition = condition, .true_body = try true_body.toOwnedSlice(allocator), .false_body = try false_body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } // Se depth > 0, é endif aninhado — adiciona como tag normal try current_body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "else") and depth == 1) { current_body = &false_body; allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); continue; } // Qualquer outra tag try current_body.append(allocator, tag_node); } else { self.advance(1); } } return error.UnclosedBlock; } fn parseForBlock(self: *Parser, allocator: std.mem.Allocator, loop_var: []const u8, iterable: []const u8, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); var empty_body = std.ArrayList(Node){}; defer empty_body.deinit(allocator); var current_body = &body; var depth: usize = 1; while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); try current_body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); try current_body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { const tag_name = tag_node.tag.?.name; if (std.mem.eql(u8, tag_name, "for")) { depth += 1; try current_body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "endfor")) { depth -= 1; const raw_close = tag_node.tag.?.raw; if (depth == 0) { // Libera name e args — essa é a tag de fechamento final allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); std.debug.print("2.4 - Encontrei um bloco for:\n - loop_var: {s}\n - iterable: {s}\n - body: {any}\n - empty_body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ loop_var, iterable, body.items, empty_body.items, raw_open, raw_close, }); return Node{ .type = .for_block, .@"for" = .{ .loop_var = loop_var, .iterable = iterable, .body = try body.toOwnedSlice(allocator), .empty_body = try empty_body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } // depth > 0: endfor aninhado — adiciona como tag normal try current_body.append(allocator, tag_node); continue; } if (std.mem.eql(u8, tag_name, "empty") and depth == 1) { current_body = &empty_body; allocator.free(tag_node.tag.?.name); allocator.free(tag_node.tag.?.args); continue; } try current_body.append(allocator, tag_node); } else { self.advance(1); } } return error.UnclosedBlock; } pub fn parse(self: *Parser, allocator: std.mem.Allocator) ![]Node { var list = std.ArrayList(Node){}; defer list.deinit(allocator); std.debug.print("O template recebido é:\n\n{s}\n\n", .{self.template}); while (self.pos < self.template.len) { std.debug.print("1.0 - minha posição ainda é menor que o tamanho do template\n", .{}); if (try self.parseTag(allocator)) |node| { std.debug.print("3.0 - na real sou uma tag\n", .{}); const tag_name = node.tag.?.name; std.debug.print("3.1 - meu tag name é: {s}\n", .{tag_name}); if (std.mem.eql(u8, tag_name, "autoescape")) { const args = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); const raw_open = node.tag.?.raw; const enabled = if (std.mem.eql(u8, args, "on")) true else if (std.mem.eql(u8, args, "off")) false else return error.InvalidAutoescapeArgument; allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou um autoescape\n", .{}); std.debug.print("===================================\n", .{}); const ae_node = try self.parseAutoescapeBlock(allocator, enabled, raw_open); try list.append(allocator, ae_node); continue; } if (std.mem.eql(u8, tag_name, "spaceless")) { const raw_open = node.tag.?.raw; allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou um spaceless\n", .{}); std.debug.print("===================================\n", .{}); const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open); try list.append(allocator, spaceless_node); continue; } if (std.mem.eql(u8, tag_name, "comment")) { std.debug.print("3.0 - na real sou um comentário\n", .{}); std.debug.print("===================================\n", .{}); allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); try self.parseComment(); continue; } if (std.mem.eql(u8, tag_name, "filter")) { const filters_raw = node.tag.?.args; const raw_open = node.tag.?.raw; // DUPE O FILTERS IMEDIATAMENTE const duped_filters = try allocator.dupe(u8, filters_raw); // Agora libera a tag open allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou um filter\n", .{}); std.debug.print("===================================\n", .{}); const filter_node = try self.parseFilterBlock(allocator, duped_filters, raw_open); try list.append(allocator, filter_node); continue; } if (std.mem.eql(u8, tag_name, "extends")) { const parent = std.mem.trim(u8, node.tag.?.args, " \t\""); const duped = try allocator.dupe(u8, parent); allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou um extends\n", .{}); std.debug.print("===================================\n", .{}); try list.append(allocator, Node{ .type = .extends, .extends = .{ .parent_name = duped }, }); continue; } if (std.mem.eql(u8, tag_name, "block")) { const block_name_raw = node.tag.?.args; const raw_open = node.tag.?.raw; const block_name = std.mem.trim(u8, block_name_raw, " \t\r\n"); // DUPE O NOME ANTES DE LIBERAR A TAG const duped_name = try allocator.dupe(u8, block_name); // Agora libera a tag open allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou um block\n", .{}); std.debug.print("===================================\n", .{}); const block_node = try self.parseBlockBlock(allocator, duped_name, raw_open); try list.append(allocator, block_node); continue; } if (std.mem.eql(u8, tag_name, "now")) { const args = node.tag.?.args; // Remove aspas se existirem var format_str = args; if (format_str.len >= 2 and format_str[0] == '"' and format_str[format_str.len - 1] == '"') { format_str = format_str[1 .. format_str.len - 1]; } const duped_format = try allocator.dupe(u8, format_str); // Libera a tag now allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou uma tag now\n", .{}); std.debug.print("===================================\n", .{}); try list.append(allocator, Node{ .type = .now, .now = .{ .format = duped_format }, }); continue; } if (std.mem.eql(u8, tag_name, "with")) { const args = node.tag.?.args; const raw_open = node.tag.?.raw; const assignments = try self.parseAssignments(allocator, args); allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); const with_node = try self.parseWithBlock(allocator, assignments, raw_open); std.debug.print("3.0 - na real sou um bloco with\n", .{}); std.debug.print("===================================\n", .{}); try list.append(allocator, with_node); continue; } if (std.mem.eql(u8, tag_name, "include")) { const args = node.tag.?.args; // Remove aspas se existirem var template_name = args; if (template_name.len >= 2 and template_name[0] == '"' and template_name[template_name.len - 1] == '"') { template_name = template_name[1 .. template_name.len - 1]; } const duped_name = try allocator.dupe(u8, template_name); // Libera a tag include allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou um include\n", .{}); std.debug.print("===================================\n", .{}); try list.append(allocator, Node{ .type = .include, .include = .{ .template_name = duped_name }, }); continue; } if (std.mem.eql(u8, tag_name, "if")) { const condition_raw = node.tag.?.args; const raw_open = node.tag.?.raw; const condition = try allocator.dupe(u8, condition_raw); // Libera apenas name e args da tag open allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); // NÃO chame node.deinit aqui — raw_open ainda é usado const if_node = try self.parseIfBlock(allocator, condition, raw_open); try list.append(allocator, if_node); std.debug.print("===================================\n", .{}); continue; } if (std.mem.eql(u8, tag_name, "for")) { const args = node.tag.?.args; const raw_open = node.tag.?.raw; const in_pos = std.mem.indexOf(u8, args, " in ") orelse return error.InvalidForSyntax; const loop_var_raw = std.mem.trim(u8, args[0..in_pos], " \t"); const iterable_raw = std.mem.trim(u8, args[in_pos + 4 ..], " \t"); // DUPE ANTES DE LIBERAR! const loop_var = try allocator.dupe(u8, loop_var_raw); const iterable = try allocator.dupe(u8, iterable_raw); // Agora sim, libera a tag open allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); const for_node = try self.parseForBlock(allocator, loop_var, iterable, raw_open); std.debug.print("===================================\n", .{}); try list.append(allocator, for_node); continue; } if (std.mem.eql(u8, tag_name, "verbatim")) { allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou um verbatim\n", .{}); std.debug.print("===================================\n", .{}); const verbatim_node = try self.parseVerbatim(allocator); try list.append(allocator, verbatim_node); continue; } if (std.mem.eql(u8, tag_name, "url")) { const args = node.tag.?.args; var arg_list = std.ArrayList([]const u8){}; defer arg_list.deinit(allocator); var i: usize = 0; // Pula o nome da view (entre aspas) while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} if (i >= args.len or args[i] != '\'') return error.InvalidUrlSyntax; i += 1; const view_start = i; while (i < args.len and args[i] != '\'') : (i += 1) {} if (i >= args.len or args[i] != '\'') return error.InvalidUrlSyntax; const view_name = args[view_start..i]; i += 1; const duped_view = try allocator.dupe(u8, view_name); // Agora os argumentos while (i < args.len) { while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} if (i >= args.len) break; const arg_start = i; if (args[i] == '"' or args[i] == '\'') { const quote = args[i]; i += 1; while (i < args.len and args[i] != quote) : (i += 1) {} if (i >= args.len) return error.UnclosedQuoteInUrl; i += 1; } else { while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} } const arg = args[arg_start..i]; try arg_list.append(allocator, try allocator.dupe(u8, arg)); } allocator.free(node.tag.?.name); allocator.free(node.tag.?.args); std.debug.print("3.0 - na real sou uma url\n", .{}); std.debug.print("===================================\n", .{}); try list.append(allocator, Node{ .type = .url, .url = .{ .name = duped_view, .args = try arg_list.toOwnedSlice(allocator), }, }); continue; } // Para tags normais std.debug.print("===================================\n", .{}); try list.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { std.debug.print("3.0 - na real sou variável\n", .{}); std.debug.print("4.0 - content: \'{s}\'\n", .{node.variable.?.expr}); std.debug.print("4.1 - filters: \'{any}\'\n", .{node.variable.?.filters}); std.debug.print("===================================\n", .{}); try list.append(allocator, node); continue; } if (try self.parseText(allocator)) |node| { std.debug.print("3.0 - na real sou texto\n", .{}); std.debug.print("4.0 - content: \'{s}\'\n", .{node.text.?.content}); std.debug.print("===================================\n", .{}); try list.append(allocator, node); continue; } self.advance(1); } std.debug.print("\nO resultado disso foi esse:\n", .{}); for (list.items) |item| { std.debug.print(" -> type: {s}\n", .{@tagName(item.type)}); } std.debug.print("\n", .{}); return try list.toOwnedSlice(allocator); } }; pub fn parse(allocator: std.mem.Allocator, template: []const u8) ![]Node { var p = Parser.init(template); return try p.parse(allocator); }